refactor(device): 优化设备发送告警信息功能

- 批量查询设备,减少数据库交互次数
- 优化设备状态检查逻辑,提高效率
- 封装单个设备发送告警信息的逻辑,提高代码可读性- 使用 Redis 和 MQTT 时增加异常处理,提高系统稳定性
- 优化日志记录和异常提示,便于问题排查
This commit is contained in:
2025-08-26 17:12:36 +08:00
parent f839883f82
commit a5b8cdffec
3 changed files with 116 additions and 62 deletions

View File

@ -114,13 +114,24 @@ public class XinghanBootLogoRule implements MqttMessageRule {
private static final int CHUNK_SIZE = 256; private static final int CHUNK_SIZE = 256;
/**
* 计算数据的CRC32校验值并将结果转换为整数列表
*
* @param data 需要计算CRC32校验值的字节数组
* @return 包含CRC32校验值的整数列表每个字节对应一个无符号整数
*/
private static ArrayList<Integer> crc32AsList(byte[] data) { private static ArrayList<Integer> crc32AsList(byte[] data) {
// 计算CRC32校验值
CRC32 crc = new CRC32(); CRC32 crc = new CRC32();
crc.update(data); crc.update(data);
// 将CRC32值转换为字节数组
byte[] crcBytes = ByteBuffer.allocate(4) byte[] crcBytes = ByteBuffer.allocate(4)
.order(ByteOrder.BIG_ENDIAN) .order(ByteOrder.BIG_ENDIAN)
.putInt((int) crc.getValue()) .putInt((int) crc.getValue())
.array(); .array();
// 将字节数组转换为无符号整数列表
ArrayList<Integer> list = new ArrayList<>(4); ArrayList<Integer> list = new ArrayList<>(4);
for (byte b : crcBytes) { for (byte b : crcBytes) {
list.add(Byte.toUnsignedInt(b)); list.add(Byte.toUnsignedInt(b));
@ -128,6 +139,7 @@ public class XinghanBootLogoRule implements MqttMessageRule {
return list; return list;
} }
/* ---------- DTO ---------- */ /* ---------- DTO ---------- */
@Data @Data

View File

@ -79,7 +79,7 @@ public class XinghanSendAlarmMessageRule implements MqttMessageRule {
ArrayList<Integer> intData = new ArrayList<>(); ArrayList<Integer> intData = new ArrayList<>();
intData.add(blockIndex); intData.add(blockIndex);
// 获取块原内容 转成GBK 再转成无符号十进制整数 // 获取块原内容 转成GBK 再转成无符号十进制整数
String blockTxt = data.split(",")[blockIndex-1]; String blockTxt = data.split("\\|")[blockIndex-1];
// 再按 GBK 编码把字符串转成字节数组,并逐个转为无符号十进制整数 // 再按 GBK 编码把字符串转成字节数组,并逐个转为无符号十进制整数
for (byte b : blockTxt.getBytes(GBK)) { for (byte b : blockTxt.getBytes(GBK)) {
intData.add(b & 0xFF); // b & 0xFF 得到 0~255 的整数 intData.add(b & 0xFF); // b & 0xFF 得到 0~255 的整数

View File

@ -201,88 +201,130 @@ public class DeviceXinghanBizService {
* @return * @return
*/ */
public int sendAlarmMessage(AppDeviceSendMsgBo bo) { public int sendAlarmMessage(AppDeviceSendMsgBo bo) {
try {
List<Long> deviceIds = bo.getDeviceIds(); List<Long> deviceIds = bo.getDeviceIds();
// 1. 简化非空检查和抛出异常
if (deviceIds == null || deviceIds.isEmpty()) { if (deviceIds == null || deviceIds.isEmpty()) {
throw new ServiceException("请选择设备"); throw new ServiceException("请选择设备");
} }
QueryWrapper<Device> queryWrapper = new QueryWrapper<>();
// 使用 in 语句根据id集合查询
queryWrapper.in("id", deviceIds);
// 2. 将批量查询设备,减少数据库交互次数
List<Device> devices = deviceMapper.selectList(queryWrapper);
if (devices.size() != deviceIds.size()) {
// 如果查询回来的设备数量不一致,说明有设备不存在,此处可以优化为更详细的提示
throw new ServiceException("部分设备不存在");
}
for (Long deviceId : deviceIds) {
Device device = deviceMapper.selectById(deviceId);
if (device == null) {
throw new ServiceException("设备不存在" + deviceId);
}
if(isDeviceOffline(device.getDeviceImei())){
throw new ServiceException(device.getDeviceName()+",设备已断开连接");
}
try { try {
for (Device device : devices) {
String deviceImei = device.getDeviceImei();
String deviceName = device.getDeviceName();
RedisUtils.setCacheObject(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + device.getDeviceImei() + DEVICE_ALARM_MESSAGE_KEY_PREFIX, bo.getSendMsg(),Duration.ofSeconds(5 * 60L)); // 3. 在循环中进行设备状态检查,快速失败
if (isDeviceOffline(deviceImei)) {
// 如果设备离线,可以选择继续处理下一个设备,或者抛出异常。这里选择抛出异常。
throw new ServiceException(deviceName + ", 设备已断开连接");
}
Map<String, Object> payload = Map.of("ins_BreakNews", // 4. 将Redis和MQTT操作封装在一个方法中提高可读性
Collections.singletonList(0)); sendSingleAlarmMessage(device, bo.getSendMsg());
String topic = MqttConstants.GLOBAL_PUB_KEY + device.getDeviceImei();
// 5. 批量更新设备状态,提高效率
UpdateWrapper<Device> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("id", device.getId())
.eq("binding_user_id", AppLoginHelper.getUserId())
.set("send_msg", bo.getSendMsg());
deviceMapper.update(updateWrapper);
// 6. 记录操作日志
recordDeviceLog(device.getId(), deviceName, "发送紧急通知", bo.getSendMsg(), AppLoginHelper.getUserId());
}
} catch (ServiceException e) {
// 捕获并重新抛出自定义异常,避免内层异常被外层泛化捕获
log.error("发送告警信息指令失败: {}", e.getMessage(), e);
throw e;
} catch (Exception e) {
log.error("发送告警信息指令发生未知错误", e);
throw new ServiceException("发送告警信息指令失败");
}
return 1;
}
/* ---------------------------------- 私有通用方法 ---------------------------------- */
/**
* 封装单个设备发送告警信息的逻辑
*/
private void sendSingleAlarmMessage(Device device, String message) {
String deviceImei = device.getDeviceImei();
// 缓存告警消息到Redis
RedisUtils.setCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + deviceImei + DEVICE_ALARM_MESSAGE_KEY_PREFIX, message, Duration.ofSeconds(5 * 60L));
// 构建并发送MQTT消息
Map<String, Object> payload = Map.of("ins_BreakNews", Collections.singletonList(0));
String topic = MqttConstants.GLOBAL_PUB_KEY + deviceImei;
String json = JsonUtils.toJsonString(payload); String json = JsonUtils.toJsonString(payload);
try { try {
mqttGateway.sendMsgToMqtt(topic, 1, json); mqttGateway.sendMsgToMqtt(topic, 1, json);
log.info("发送紧急通知成功 => topic:{}, payload:{}", topic, json);
} catch (Exception e) { } catch (Exception e) {
log.error("发送紧急通知失败, topic={}, payload={}", topic, json, e); log.error("发送紧急通知失败, topic={}, payload={}", topic, json, e);
throw new ServiceException("发送紧急通知失败:" + e.getMessage()); throw new ServiceException("发送紧急通知失败:" + e.getMessage());
} }
log.info("发送紧急通知=>topic:{},payload:{}", MqttConstants.GLOBAL_PUB_KEY+device.getDeviceImei(),json);
UpdateWrapper<Device> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("id", deviceId)
.eq("binding_user_id", AppLoginHelper.getUserId())
.set("send_msg", bo.getSendMsg());
deviceMapper.update(updateWrapper);
recordDeviceLog(device.getId(), device.getDeviceName(), "发送紧急通知", bo.getSendMsg(), AppLoginHelper.getUserId());
} catch (Exception e) {
log.info("设备发送告警信息信息失败:{}" ,deviceId);
throw new ServiceException("设备发送告警信息信息失败");
} }
} /**
} catch (Exception e){ * 发送设备控制指令
e.printStackTrace(); *
throw new ServiceException("发送告警信息指令失败"); * @param dto 设备指令数据传输对象包含设备ID和指令值等信息
} * @param payloadKey 指令负载数据的键名
return 1; * @param deviceAction 设备操作类型描述
} */
private void sendCommand(DeviceInstructDto dto, String payloadKey, String deviceAction) {
/* ---------------------------------- 私有通用方法 ---------------------------------- */
private void sendCommand(DeviceInstructDto dto,
String payloadKey,String deviceAction) {
long deviceId = dto.getDeviceId(); long deviceId = dto.getDeviceId();
Device device = deviceMapper.selectById(deviceId);
if (device == null) { // 1. 使用Optional简化空值检查使代码更简洁
throw new ServiceException("设备不存在"); Device device = Optional.ofNullable(deviceMapper.selectById(deviceId))
} .orElseThrow(() -> new ServiceException("设备不存在"));
if (isDeviceOffline(device.getDeviceImei())) {
throw new ServiceException("设备已断开连接:" + device.getDeviceName()); String deviceImei = device.getDeviceImei();
String deviceName = device.getDeviceName();
// 2. 提前进行设备状态检查,逻辑更清晰
if (isDeviceOffline(deviceImei)) {
throw new ServiceException("设备已断开连接:" + deviceName);
} }
Integer value = Integer.parseInt(dto.getInstructValue()); // 3. 统一处理类型转换异常避免在业务逻辑中混杂try-catch
int value;
try {
value = Integer.parseInt(dto.getInstructValue());
} catch (NumberFormatException e) {
throw new IllegalArgumentException("指令值格式不正确,必须为整数。", e);
}
Map<String, Object> payload = Map.of(payloadKey, // 4. 使用Map.of()或Map.ofEntries()创建不可变Map更简洁且线程安全
Collections.singletonList(value)); Map<String, List<Integer>> payload = Map.of(payloadKey, List.of(value));
String topic = MqttConstants.GLOBAL_PUB_KEY + device.getDeviceImei(); String topic = MqttConstants.GLOBAL_PUB_KEY + deviceImei;
String json = JsonUtils.toJsonString(payload); String json = JsonUtils.toJsonString(payload);
try { try {
mqttGateway.sendMsgToMqtt(topic, 1, json); mqttGateway.sendMsgToMqtt(topic, 1, json);
log.info("发送指令成功 => topic:{}, payload:{}", topic, json);
} catch (Exception e) { } catch (Exception e) {
log.error("发送指令失败, topic={}, payload={}", topic, json, e); log.error("发送指令失败, topic={}, payload={}", topic, json, e);
throw new ServiceException("发送指令失败:" + e.getMessage()); throw new ServiceException("发送指令失败:" + e.getMessage());
} }
log.info("发送指令成功 => topic:{}, payload:{}", topic, json); // 5. 将日志记录和描述解析放在try-catch块之外确保无论是否成功发送指令都能执行
String content = resolveGradeDesc("ins_DetectGrade", value); String content = resolveGradeDesc("ins_DetectGrade", value);
recordDeviceLog(device.getId(), recordDeviceLog(device.getId(),
device.getDeviceName(), deviceName,
deviceAction, deviceAction,
content, content,
AppLoginHelper.getUserId()); AppLoginHelper.getUserId());