AP05/redis_sn/redis_sn_client.c

419 lines
13 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "redis_sn_client.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <hiredis/hiredis.h>
// Redis SN获取客户端模块
// 用于从Redis服务器获取唯一的设备SN
/**
* 连接到Redis并进行认证
* @param context Redis连接上下文指针的指针
* @return 0成功-1失败
*/
int connect_to_redis_with_auth(redisContext **context) {
struct timeval timeout = { REDIS_CONNECTION_TIMEOUT, 0 }; // 设置超时
// 使用超时连接到Redis服务器
*context = redisConnectWithTimeout(REDIS_HOST, REDIS_PORT, timeout);
if (*context == NULL || (*context)->err) {
if (*context) {
printf("Redis连接错误: %s\n", (*context)->errstr);
redisFree(*context);
} else {
printf("无法分配Redis上下文\n");
}
return -1;
}
// 如果设置了密码,进行认证
if (strlen(REDIS_PASSWORD) > 0) {
redisReply *reply = redisCommand(*context, "AUTH %s", REDIS_PASSWORD);
if (reply == NULL || reply->type == REDIS_REPLY_ERROR) {
printf("Redis认证失败: %s\n", reply ? reply->str : "未知错误");
if (reply) freeReplyObject(reply);
redisFree(*context);
*context = NULL;
return -1;
}
freeReplyObject(reply);
}
return 0;
}
/**
* 从Redis SN池中获取一个唯一的设备SN
* @param sn_buffer 用于存储获取到的SN的缓冲区
* @param buffer_size 缓冲区大小
* @return 0成功-1失败
*/
int get_device_sn_from_redis(char *sn_buffer, size_t buffer_size) {
redisContext *context = NULL;
redisReply *reply = NULL;
int result = -1;
if (sn_buffer == NULL || buffer_size == 0) {
printf("无效的缓冲区参数\n");
return -1;
}
// 连接到Redis服务器并认证
if (connect_to_redis_with_auth(&context) != 0) {
return -1;
}
// 使用SPOP命令原子性地从SN池中弹出一个SN
reply = redisCommand(context, "SPOP %s", SN_POOL_KEY);
if (reply == NULL) {
printf("Redis命令执行失败\n");
goto cleanup;
}
if (reply->type == REDIS_REPLY_STRING && reply->str != NULL) {
// 成功获取到SN
if (strlen(reply->str) < buffer_size) {
strcpy(sn_buffer, reply->str);
printf("成功获取设备SN: %s\n", sn_buffer);
result = 0;
} else {
printf("SN长度超过缓冲区大小\n");
}
} else if (reply->type == REDIS_REPLY_NIL) {
printf("警告: SN池已空无可用SN\n");
} else {
printf("Redis返回意外的响应类型: %d\n", reply->type);
}
cleanup:
if (reply) freeReplyObject(reply);
if (context) redisFree(context);
return result;
}
/**
* 从Redis SN池中获取设备SN和MAC地址
* @param sn_buffer 用于存储获取到的SN的缓冲区
* @param sn_buffer_size SN缓冲区大小
* @param mac_buffer 用于存储获取到的MAC地址的缓冲区
* @param mac_buffer_size MAC缓冲区大小
* @return 0成功-1失败
*/
int get_device_sn_and_mac_from_redis(char *sn_buffer, size_t sn_buffer_size, char *mac_buffer, size_t mac_buffer_size) {
redisContext *context = NULL;
redisReply *reply = NULL;
int result = -1;
if (sn_buffer == NULL || sn_buffer_size == 0 || mac_buffer == NULL || mac_buffer_size == 0) {
printf("无效的缓冲区参数\n");
return -1;
}
// 连接到Redis服务器并认证
if (connect_to_redis_with_auth(&context) != 0) {
return -1;
}
// 使用SPOP命令原子性地从SN池中弹出一个SN:MAC条目
reply = redisCommand(context, "SPOP %s", SN_POOL_KEY);
if (reply == NULL) {
printf("Redis命令执行失败\n");
goto cleanup;
}
if (reply->type == REDIS_REPLY_STRING && reply->str != NULL) {
// 解析SN:MAC格式的数据
char *colon_pos = strchr(reply->str, ':');
if (colon_pos != NULL) {
// 分离SN和MAC地址
size_t sn_len = colon_pos - reply->str;
if (sn_len < sn_buffer_size && strlen(colon_pos + 1) < mac_buffer_size) {
// 复制SN部分
strncpy(sn_buffer, reply->str, sn_len);
sn_buffer[sn_len] = '\0';
// 复制MAC地址部分
strcpy(mac_buffer, colon_pos + 1);
printf("成功获取设备SN: %s, MAC: %s\n", sn_buffer, mac_buffer);
result = 0;
} else {
printf("SN或MAC地址长度超过缓冲区大小\n");
}
} else {
// 没有找到冒号可能是旧格式只有SN
if (strlen(reply->str) < sn_buffer_size) {
strcpy(sn_buffer, reply->str);
mac_buffer[0] = '\0'; // MAC地址为空
printf("成功获取设备SN: %s (无MAC地址)\n", sn_buffer);
result = 0;
} else {
printf("SN长度超过缓冲区大小\n");
}
}
} else if (reply->type == REDIS_REPLY_NIL) {
printf("警告: SN池已空无可用SN\n");
} else {
printf("Redis返回意外的响应类型: %d\n", reply->type);
}
cleanup:
if (reply) freeReplyObject(reply);
if (context) redisFree(context);
return result;
}
/**
* 格式化MAC地址添加冒号分隔符
* @param raw_mac 原始MAC地址如90A9F73005FB
* @param formatted_mac 格式化后的MAC地址缓冲区如90:A9:F7:30:05:FB
* @param buffer_size 缓冲区大小
* @return 0成功-1失败
*/
int format_mac_address(const char *raw_mac, char *formatted_mac, size_t buffer_size) {
if (raw_mac == NULL || formatted_mac == NULL || buffer_size < 18) {
return -1;
}
// 检查原始MAC地址长度应该是12个字符
if (strlen(raw_mac) != 12) {
return -1;
}
// 格式化MAC地址90A9F73005FB -> 90:A9:F7:30:05:FB
snprintf(formatted_mac, buffer_size, "%c%c:%c%c:%c%c:%c%c:%c%c:%c%c",
raw_mac[0], raw_mac[1], raw_mac[2], raw_mac[3],
raw_mac[4], raw_mac[5], raw_mac[6], raw_mac[7],
raw_mac[8], raw_mac[9], raw_mac[10], raw_mac[11]);
return 0;
}
/**
* 根据生产批次号码从Redis中查找对应的SN和MAC地址
* @param batch_number 生产批次号码如D20250422006504
* @param sn_buffer 用于存储获取到的SN的缓冲区
* @param sn_buffer_size SN缓冲区大小
* @param mac_buffer 用于存储获取到的MAC地址的缓冲区
* @param mac_buffer_size MAC缓冲区大小
* @return 0成功-1失败
*/
int get_sn_mac_by_batch_number(const char *batch_number, char *sn_buffer, size_t sn_buffer_size, char *mac_buffer, size_t mac_buffer_size) {
redisContext *context = NULL;
redisReply *reply = NULL;
int result = -1;
if (batch_number == NULL || sn_buffer == NULL || sn_buffer_size == 0 || mac_buffer == NULL || mac_buffer_size == 0) {
printf("无效的参数\n");
return -1;
}
// 连接到Redis服务器并认证
if (connect_to_redis_with_auth(&context) != 0) {
return -1;
}
// 使用HGET命令从批次号码映射表中获取对应的SN:MAC
reply = redisCommand(context, "HGET batch_sn_mapping %s", batch_number);
if (reply == NULL) {
printf("Redis命令执行失败\n");
goto cleanup;
}
if (reply->type == REDIS_REPLY_STRING && reply->str != NULL) {
// 解析SN:MAC格式的数据
char *colon_pos = strchr(reply->str, ':');
if (colon_pos != NULL) {
// 分离SN和MAC地址
size_t sn_len = colon_pos - reply->str;
if (sn_len < sn_buffer_size && strlen(colon_pos + 1) < mac_buffer_size) {
// 复制SN部分
strncpy(sn_buffer, reply->str, sn_len);
sn_buffer[sn_len] = '\0';
// 复制MAC地址部分
strcpy(mac_buffer, colon_pos + 1);
printf("根据批次号码 %s 成功获取设备SN: %s, MAC: %s\n", batch_number, sn_buffer, mac_buffer);
result = 0;
} else {
printf("SN或MAC地址长度超过缓冲区大小\n");
}
} else {
printf("Redis中的数据格式不正确缺少冒号分隔符\n");
}
} else if (reply->type == REDIS_REPLY_NIL) {
printf("警告: 未找到批次号码 %s 对应的SN和MAC\n", batch_number);
} else {
printf("Redis返回意外的响应类型: %d\n", reply->type);
}
cleanup:
if (reply) freeReplyObject(reply);
if (context) redisFree(context);
return result;
}
/**
* 检查SN池中剩余的SN数量
* @return SN数量-1表示失败
*/
int check_sn_pool_count() {
redisContext *context = NULL;
redisReply *reply = NULL;
int count = -1;
if (connect_to_redis_with_auth(&context) != 0) {
return -1;
}
reply = redisCommand(context, "SCARD %s", SN_POOL_KEY);
if (reply && reply->type == REDIS_REPLY_INTEGER) {
count = reply->integer;
} else {
printf("获取SN池数量失败\n");
}
if (reply) freeReplyObject(reply);
if (context) redisFree(context);
return count;
}
/**
* 测试Redis连接
* @return 0成功-1失败
*/
int test_redis_connection() {
redisContext *context = NULL;
redisReply *reply = NULL;
int result = -1;
printf("测试Redis连接...\n");
printf("连接地址: %s:%d\n", REDIS_HOST, REDIS_PORT);
if (connect_to_redis_with_auth(&context) != 0) {
return -1;
}
// 发送PING命令测试连接
reply = redisCommand(context, "PING");
if (reply && reply->type == REDIS_REPLY_STATUS &&
strcmp(reply->str, "PONG") == 0) {
printf("Redis连接测试成功\n");
result = 0;
} else {
printf("Redis连接测试失败\n");
}
if (reply) freeReplyObject(reply);
if (context) redisFree(context);
return result;
}
/**
* 初始化Redis SN客户端
* @return 0成功-1失败
*/
int init_redis_sn_client() {
printf("初始化Redis SN客户端...\n");
// 测试连接
if (test_redis_connection() != 0) {
printf("Redis SN客户端初始化失败\n");
return -1;
}
// 检查SN池状态
int pool_count = check_sn_pool_count();
if (pool_count >= 0) {
printf("SN池中当前有 %d 个可用SN\n", pool_count);
}
printf("Redis SN客户端初始化完成\n");
return 0;
}
/**
* 清理Redis SN客户端资源
*/
void cleanup_redis_sn_client() {
// 目前无需特殊清理操作
printf("Redis SN客户端资源清理完成\n");
}
/**
* 发送审计日志到Redis
* @param batch 批次号
* @param mac MAC地址
* @param note 备注可为NULL
* @return 0成功-1失败
*/
int send_audit_to_redis(const char *batch, const char *mac, const char *note) {
if (!batch || !*batch || !mac || !*mac) {
printf("[redis-audit] 无效的批次号或MAC地址\n");
return -1;
}
redisContext *context = NULL;
redisReply *reply = NULL;
int result = -1;
const char *audit_key = "mac_batch_audit_tx";
// 连接到Redis服务器并认证
if (connect_to_redis_with_auth(&context) != 0) {
return -1;
}
// 获取Redis服务器时间避免设备本地时间不准
long long sv_secs = -1;
reply = redisCommand(context, "TIME");
if (reply && reply->type == REDIS_REPLY_ARRAY && reply->elements >= 2 &&
reply->element[0] && reply->element[0]->str) {
sv_secs = atoll(reply->element[0]->str);
} else {
printf("[redis-audit] 获取服务器时间失败,使用本地时间\n");
}
if (reply) freeReplyObject(reply);
// 计算中国时区时间(+08:00
time_t base = (sv_secs > 0) ? (time_t)sv_secs : time(NULL);
time_t base_cn = base + 8 * 3600;
struct tm tm_cn;
gmtime_r(&base_cn, &tm_cn);
char ts_cn[24];
strftime(ts_cn, sizeof(ts_cn), "%Y-%m-%d %H:%M:%S", &tm_cn);
// 构建审计记录
char val[320];
snprintf(val, sizeof(val), "ts_cn=%s batch=%s mac=%s%s%s", ts_cn, batch, mac,
(note && *note) ? " sn=" : "",
(note && *note) ? note : "");
// 写入总审计列表
reply = redisCommand(context, "LPUSH %s %s", audit_key, val);
if (!reply || reply->type == REDIS_REPLY_ERROR) {
printf("[redis-audit] LPUSH %s 失败: %s\n", audit_key, reply && reply->str ? reply->str : "no-reply");
if (reply) freeReplyObject(reply);
redisFree(context);
return -1;
}
freeReplyObject(reply);
// 同时按批次维度记录
char batch_key[128];
snprintf(batch_key, sizeof(batch_key), "%s:%s", audit_key, batch);
reply = redisCommand(context, "LPUSH %s %s", batch_key, val);
if (!reply || reply->type == REDIS_REPLY_ERROR) {
printf("[redis-audit] LPUSH %s 失败: %s\n", batch_key, reply && reply->str ? reply->str : "no-reply");
if (reply) freeReplyObject(reply);
redisFree(context);
return -1;
}
freeReplyObject(reply);
redisFree(context);
printf("[redis-audit] 审计记录已发送: batch=%s mac=%s\n", batch, mac);
return 0;
}