fix(ops): 统一 Redis 序列化为 StringRedisTemplate,修复跨模块/跨路径数据不兼容
Some checks failed
Java CI with Maven / build (11) (push) Has been cancelled
Java CI with Maven / build (17) (push) Has been cancelled
Java CI with Maven / build (8) (push) Has been cancelled

BadgeDeviceStatusServiceImpl 和 RedisOrderQueueServiceImpl 原使用
RedisTemplate<String, Object>(Jackson JSON 序列化),但 Pipeline、
Lua 脚本写入的裸字符串与 Jackson 格式不兼容,导致:
- IoT 模块 StringRedisTemplate 读取工单状态比对失败(按键无法确认)
- 队列 entries() 反序列化失败(REMOVED 记录无法清理,持续报错)

修改内容:
- BadgeDeviceStatusServiceImpl: RedisTemplate → StringRedisTemplate
- RedisOrderQueueServiceImpl: RedisTemplate → StringRedisTemplate
- 所有写入值显式 String.valueOf(),读取用 parse 替代强转
- 修复 null 值写入 StringRedisSerializer 导致 NPE 的隐患
- application.yaml: TTS 播报间隔 6000ms → 3000ms

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
lzh
2026-02-27 10:50:03 +08:00
parent c21c77c758
commit 7c22fe998e
3 changed files with 124 additions and 101 deletions

View File

@@ -1,12 +1,9 @@
package com.viewsh.module.ops.service.queue;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.viewsh.module.ops.api.queue.OrderQueueDTO;
import com.viewsh.module.ops.enums.PriorityEnum;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
@@ -28,10 +25,7 @@ import java.util.stream.Collectors;
public class RedisOrderQueueServiceImpl implements RedisOrderQueueService {
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Resource
private ObjectMapper objectMapper;
private StringRedisTemplate stringRedisTemplate;
/**
* Score 计算公式:优先级分数 + 时间戳
@@ -57,12 +51,12 @@ public class RedisOrderQueueServiceImpl implements RedisOrderQueueService {
dto.setQueueScore(score);
// 2. 添加到 Sorted Set使用 queueId 作为 member而非 JSON
redisTemplate.opsForZSet().add(queueKey, dto.getId().toString(), score);
stringRedisTemplate.opsForZSet().add(queueKey, dto.getId().toString(), score);
// 3. 存储详细信息到 Hash
Map<String, Object> infoMap = convertToMap(dto);
redisTemplate.opsForHash().putAll(infoKey, infoMap);
redisTemplate.expire(infoKey, 24, TimeUnit.HOURS);
Map<String, String> infoMap = convertToMap(dto);
stringRedisTemplate.opsForHash().putAll(infoKey, infoMap);
stringRedisTemplate.expire(infoKey, 24, TimeUnit.HOURS);
log.debug("Redis 入队成功: queueId={}, orderId={}, score={}",
dto.getId(), dto.getOpsOrderId(), score);
@@ -83,7 +77,7 @@ public class RedisOrderQueueServiceImpl implements RedisOrderQueueService {
try {
// 使用 Pipeline 批量操作
redisTemplate.executePipelined((org.springframework.data.redis.core.RedisCallback<Object>) connection -> {
stringRedisTemplate.executePipelined((org.springframework.data.redis.core.RedisCallback<Object>) connection -> {
dtos.forEach(dto -> {
try {
byte[] queueKey = (QUEUE_KEY_PREFIX + dto.getUserId()).getBytes();
@@ -125,25 +119,25 @@ public class RedisOrderQueueServiceImpl implements RedisOrderQueueService {
String queueKey = QUEUE_KEY_PREFIX + cleanerId;
// 使用 ZPOPMIN 原子性出队(获取并移除最高优先级任务)
// popMin 返回单个 TypedTuple<Object>,值为 queueId
org.springframework.data.redis.core.ZSetOperations.TypedTuple<Object> typedTuple =
redisTemplate.opsForZSet().popMin(queueKey);
// popMin 返回单个 TypedTuple<String>,值为 queueId
ZSetOperations.TypedTuple<String> typedTuple =
stringRedisTemplate.opsForZSet().popMin(queueKey);
if (typedTuple == null) {
return null;
}
// 从 TypedTuple 中获取 queueId
Object queueIdObj = typedTuple.getValue();
if (queueIdObj == null) {
String queueIdStr = typedTuple.getValue();
if (queueIdStr == null) {
return null;
}
Long queueId = Long.parseLong(queueIdObj.toString());
Long queueId = Long.parseLong(queueIdStr);
// 从 Hash 获取详细信息
String infoKey = INFO_KEY_PREFIX + queueId;
Map<Object, Object> infoMap = redisTemplate.opsForHash().entries(infoKey);
Map<Object, Object> infoMap = stringRedisTemplate.opsForHash().entries(infoKey);
if (infoMap == null || infoMap.isEmpty()) {
log.warn("Redis 出队时队列记录不存在: queueId={}", queueId);
@@ -153,7 +147,7 @@ public class RedisOrderQueueServiceImpl implements RedisOrderQueueService {
OrderQueueDTO dto = mapToDto(infoMap);
// 删除 Hash 中的详细信息
redisTemplate.delete(infoKey);
stringRedisTemplate.delete(infoKey);
log.debug("Redis 出队成功: queueId={}, orderId={}, cleanerId={}",
queueId, dto != null ? dto.getOpsOrderId() : null, cleanerId);
@@ -172,7 +166,7 @@ public class RedisOrderQueueServiceImpl implements RedisOrderQueueService {
String queueKey = QUEUE_KEY_PREFIX + cleanerId;
// 1. 查询前 N 个 queueId按 score 排序)
Set<Object> queueIds = redisTemplate.opsForZSet().range(queueKey, 0, count - 1);
Set<String> queueIds = stringRedisTemplate.opsForZSet().range(queueKey, 0, count - 1);
if (queueIds == null || queueIds.isEmpty()) {
return Collections.emptyList();
@@ -184,7 +178,7 @@ public class RedisOrderQueueServiceImpl implements RedisOrderQueueService {
try {
String idStr = queueId.toString();
String infoKey = INFO_KEY_PREFIX + idStr;
Map<Object, Object> infoMap = redisTemplate.opsForHash().entries(infoKey);
Map<Object, Object> infoMap = stringRedisTemplate.opsForHash().entries(infoKey);
if (infoMap != null && !infoMap.isEmpty()) {
return mapToDto(infoMap);
}
@@ -208,7 +202,7 @@ public class RedisOrderQueueServiceImpl implements RedisOrderQueueService {
public long getQueueSize(Long cleanerId) {
try {
String queueKey = QUEUE_KEY_PREFIX + cleanerId;
Long size = redisTemplate.opsForZSet().size(queueKey);
Long size = stringRedisTemplate.opsForZSet().size(queueKey);
return size != null ? size : 0;
} catch (Exception e) {
log.error("Redis 查询队列长度失败: cleanerId={}", cleanerId, e);
@@ -220,7 +214,7 @@ public class RedisOrderQueueServiceImpl implements RedisOrderQueueService {
public long clearQueue(Long cleanerId) {
try {
String queueKey = QUEUE_KEY_PREFIX + cleanerId;
redisTemplate.delete(queueKey);
stringRedisTemplate.delete(queueKey);
log.info("Redis 清空队列成功: cleanerId={}", cleanerId);
return 0; // Redis 不返回删除数量
} catch (Exception e) {
@@ -235,7 +229,7 @@ public class RedisOrderQueueServiceImpl implements RedisOrderQueueService {
String infoKey = INFO_KEY_PREFIX + queueId;
// 先从 Hash 获取 userId
Map<Object, Object> infoMap = redisTemplate.opsForHash().entries(infoKey);
Map<Object, Object> infoMap = stringRedisTemplate.opsForHash().entries(infoKey);
if (infoMap == null || infoMap.isEmpty()) {
log.warn("队列记录不存在: queueId={}", queueId);
return false;
@@ -260,7 +254,7 @@ public class RedisOrderQueueServiceImpl implements RedisOrderQueueService {
"redis.call('DEL', infoKey) " +
"return 1";
redisTemplate.execute(
stringRedisTemplate.execute(
new DefaultRedisScript<>(removeScript, Long.class),
Arrays.asList(infoKey, queueKey),
queueId.toString()
@@ -292,7 +286,7 @@ public class RedisOrderQueueServiceImpl implements RedisOrderQueueService {
"return 1";
Long result = redisTemplate.execute(
Long result = stringRedisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Arrays.asList(infoKey, queueKey),
newStatus, queueId.toString()
@@ -318,7 +312,7 @@ public class RedisOrderQueueServiceImpl implements RedisOrderQueueService {
String infoKey = INFO_KEY_PREFIX + queueId;
// 从 Hash 中获取数据
Map<Object, Object> infoMap = redisTemplate.opsForHash().entries(infoKey);
Map<Object, Object> infoMap = stringRedisTemplate.opsForHash().entries(infoKey);
if (infoMap.isEmpty()) {
log.warn("队列记录不存在: queueId={}", queueId);
return false;
@@ -353,7 +347,7 @@ public class RedisOrderQueueServiceImpl implements RedisOrderQueueService {
"redis.call('EXPIRE', infoKey, 86400) " +
"return 1";
Long result = redisTemplate.execute(
Long result = stringRedisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Arrays.asList(infoKey, queueKey),
queueId.toString(), String.valueOf(newScore), String.valueOf(newPriority)
@@ -379,7 +373,7 @@ public class RedisOrderQueueServiceImpl implements RedisOrderQueueService {
String infoKey = INFO_KEY_PREFIX + queueId;
// 从 Hash 中获取 userId
Map<Object, Object> infoMap = redisTemplate.opsForHash().entries(infoKey);
Map<Object, Object> infoMap = stringRedisTemplate.opsForHash().entries(infoKey);
if (infoMap.isEmpty()) {
return false;
}
@@ -401,7 +395,7 @@ public class RedisOrderQueueServiceImpl implements RedisOrderQueueService {
"local deletedHash = redis.call('DEL', infoKey) " +
"return (removedFromZSet > 0 or deletedHash > 0) and 1 or 0";
Long result = redisTemplate.execute(
Long result = stringRedisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Arrays.asList(queueKey, infoKey),
queueId.toString()
@@ -427,7 +421,7 @@ public class RedisOrderQueueServiceImpl implements RedisOrderQueueService {
try {
String infoKey = INFO_KEY_PREFIX + queueId;
Map<Object, Object> infoMap = redisTemplate.opsForHash().entries(infoKey);
Map<Object, Object> infoMap = stringRedisTemplate.opsForHash().entries(infoKey);
if (infoMap.isEmpty()) {
return null;
}
@@ -444,14 +438,14 @@ public class RedisOrderQueueServiceImpl implements RedisOrderQueueService {
public OrderQueueDTO getByOrderId(Long orderId) {
try {
// 查询所有 Hash 信息键(效率较低,慎用)
Set<String> keys = redisTemplate.keys(INFO_KEY_PREFIX + "*");
Set<String> keys = stringRedisTemplate.keys(INFO_KEY_PREFIX + "*");
if (keys == null || keys.isEmpty()) {
return null;
}
for (String infoKey : keys) {
try {
Map<Object, Object> infoMap = redisTemplate.opsForHash().entries(infoKey);
Map<Object, Object> infoMap = stringRedisTemplate.opsForHash().entries(infoKey);
if (infoMap != null && !infoMap.isEmpty()) {
Object opsOrderIdObj = infoMap.get("opsOrderId");
if (opsOrderIdObj != null) {
@@ -483,7 +477,7 @@ public class RedisOrderQueueServiceImpl implements RedisOrderQueueService {
try {
String lockKey = LOCK_KEY_PREFIX + cleanerId;
Boolean acquired = redisTemplate.opsForValue()
Boolean acquired = stringRedisTemplate.opsForValue()
.setIfAbsent(lockKey, lockValue, expireTime, TimeUnit.MILLISECONDS);
log.debug("尝试获取锁: cleanerId={}, acquired={}", cleanerId, acquired);
@@ -504,7 +498,7 @@ public class RedisOrderQueueServiceImpl implements RedisOrderQueueService {
// 使用 Lua 脚本确保只删除自己的锁
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Long result = redisTemplate.execute(
Long result = stringRedisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(lockKey),
lockValue
@@ -525,7 +519,7 @@ public class RedisOrderQueueServiceImpl implements RedisOrderQueueService {
public boolean forceUnlock(Long cleanerId) {
try {
String lockKey = LOCK_KEY_PREFIX + cleanerId;
redisTemplate.delete(lockKey);
stringRedisTemplate.delete(lockKey);
log.warn("强制释放分布式锁: cleanerId={}", cleanerId);
return true;
@@ -556,17 +550,17 @@ public class RedisOrderQueueServiceImpl implements RedisOrderQueueService {
}
/**
* 将 DTO 转换为 Map用于 Hash 存储)
* 将 DTO 转换为 Map用于 Hash 存储,所有值显式转 String确保跨路径序列化一致
*/
private Map<String, Object> convertToMap(OrderQueueDTO dto) {
Map<String, Object> map = new HashMap<>();
map.put("id", dto.getId());
map.put("opsOrderId", dto.getOpsOrderId());
map.put("userId", dto.getUserId());
map.put("queueIndex", dto.getQueueIndex());
map.put("priority", dto.getPriority());
private Map<String, String> convertToMap(OrderQueueDTO dto) {
Map<String, String> map = new HashMap<>();
map.put("id", String.valueOf(dto.getId()));
map.put("opsOrderId", String.valueOf(dto.getOpsOrderId()));
map.put("userId", String.valueOf(dto.getUserId()));
map.put("queueIndex", String.valueOf(dto.getQueueIndex()));
map.put("priority", String.valueOf(dto.getPriority()));
map.put("queueStatus", dto.getQueueStatus());
map.put("queueScore", dto.getQueueScore());
map.put("queueScore", String.valueOf(dto.getQueueScore()));
// LocalDateTime 转换为字符串存储
if (dto.getEnqueueTime() != null) {
@@ -598,7 +592,7 @@ public class RedisOrderQueueServiceImpl implements RedisOrderQueueService {
}
/**
* 将 Map 转换回 DTO
* 将 Map 转换回 DTO(所有值均为 String使用 parse 方法转换)
*/
private OrderQueueDTO mapToDto(Map<Object, Object> map) {
try {
@@ -606,8 +600,17 @@ public class RedisOrderQueueServiceImpl implements RedisOrderQueueService {
dto.setId(Long.parseLong(map.get("id").toString()));
dto.setOpsOrderId(Long.parseLong(map.get("opsOrderId").toString()));
dto.setUserId(Long.parseLong(map.get("userId").toString()));
dto.setQueueIndex((Integer) map.get("queueIndex"));
dto.setPriority((Integer) map.get("priority"));
Object queueIndexObj = map.get("queueIndex");
if (queueIndexObj != null) {
dto.setQueueIndex(Integer.parseInt(queueIndexObj.toString()));
}
Object priorityObj = map.get("priority");
if (priorityObj != null) {
dto.setPriority(Integer.parseInt(priorityObj.toString()));
}
dto.setQueueStatus((String) map.get("queueStatus"));
// 读取 queueScore