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

View File

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

View File

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