#include "redis_sn_client.h" #include #include #include #include #include // 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; }