AP05/redis_sn/redis_sn_client.c

419 lines
13 KiB
C
Raw Normal View History

#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;
}