419 lines
13 KiB
C
419 lines
13 KiB
C
#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;
|
||
} |