feat(device): 新增发送紧急通知功能

- 在 AppDeviceXinghanController 中添加 sendAlarmMessage接口
- 在 DeviceXinghanBizService 中实现 sendAlarmMessage 方法
- 新增 XinghanSendAlarmMessageRule 类用于处理紧急通知发送逻辑
- 在 DeviceRedisKeyConstants 中添加 DEVICE_ALARM_MESSAGE_KEY_PREFIX 常量
- 修改 XinghanDeviceDataRule 和 XinghanSendMsgRule 中的相关逻辑
This commit is contained in:
2025-08-25 14:18:54 +08:00
parent 74cefe9cc3
commit f839883f82
6 changed files with 199 additions and 4 deletions

View File

@ -6,7 +6,9 @@ import com.fuyuanshen.app.domain.dto.DeviceInstructDto;
import com.fuyuanshen.common.core.domain.R; import com.fuyuanshen.common.core.domain.R;
import com.fuyuanshen.common.core.validate.AddGroup; import com.fuyuanshen.common.core.validate.AddGroup;
import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessAnnotation; import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessAnnotation;
import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessBatcAnnotation;
import com.fuyuanshen.common.web.core.BaseController; import com.fuyuanshen.common.web.core.BaseController;
import com.fuyuanshen.equipment.domain.dto.AppDeviceSendMsgBo;
import com.fuyuanshen.web.service.device.DeviceBJQBizService; import com.fuyuanshen.web.service.device.DeviceBJQBizService;
import com.fuyuanshen.web.service.device.DeviceXinghanBizService; import com.fuyuanshen.web.service.device.DeviceXinghanBizService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -33,6 +35,15 @@ public class AppDeviceXinghanController extends BaseController {
return toAjax(appDeviceService.registerPersonInfo(bo)); return toAjax(appDeviceService.registerPersonInfo(bo));
} }
/**
* 发送紧急通知
*/
@PostMapping(value = "/sendAlarmMessage")
@FunctionAccessBatcAnnotation(value = "sendAlarmMessage", timeOut = 5, batchMaxTimeOut = 10)
public R<Void> sendAlarmMessage(@RequestBody AppDeviceSendMsgBo bo) {
return toAjax(appDeviceService.sendAlarmMessage(bo));
}
/** /**
* 上传设备logo图片 * 上传设备logo图片
*/ */

View File

@ -47,4 +47,9 @@ public class DeviceRedisKeyConstants {
* 告警 * 告警
*/ */
public static final String DEVICE_ALARM_KEY_PREFIX = ":alarm"; public static final String DEVICE_ALARM_KEY_PREFIX = ":alarm";
/**
* 告警信息
*/
public static final String DEVICE_ALARM_MESSAGE_KEY_PREFIX = ":alarmMessage";
} }

View File

@ -14,11 +14,13 @@ import com.fuyuanshen.global.mqtt.base.MqttXinghanJson;
import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants; import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants;
import com.fuyuanshen.global.mqtt.constants.LightingCommandTypeConstants; import com.fuyuanshen.global.mqtt.constants.LightingCommandTypeConstants;
import com.fuyuanshen.global.mqtt.constants.XingHanCommandTypeConstants; import com.fuyuanshen.global.mqtt.constants.XingHanCommandTypeConstants;
import com.fuyuanshen.global.mqtt.listener.domain.FunctionAccessStatus;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@ -58,6 +60,7 @@ public class XinghanDeviceDataRule implements MqttMessageRule {
@Override @Override
public void execute(MqttRuleContext context) { public void execute(MqttRuleContext context) {
String functionAccess = FUNCTION_ACCESS_KEY + context.getDeviceImei();
try { try {
// Latitude, longitude // Latitude, longitude
//主灯档位,激光灯档位,电量百分比,充电状态,电池剩余续航时间 //主灯档位,激光灯档位,电量百分比,充电状态,电池剩余续航时间
@ -65,8 +68,10 @@ public class XinghanDeviceDataRule implements MqttMessageRule {
// 发送设备状态和位置信息到Redis // 发送设备状态和位置信息到Redis
asyncSendDeviceDataToRedisWithFuture(context.getDeviceImei(),deviceStatus); asyncSendDeviceDataToRedisWithFuture(context.getDeviceImei(),deviceStatus);
RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.OK.getCode(), Duration.ofSeconds(20));
} catch (Exception e) { } catch (Exception e) {
log.error("处理上报数据命令时出错", e); log.error("处理上报数据命令时出错", e);
RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.FAILED.getCode(), Duration.ofSeconds(20));
} }
} }

View File

@ -0,0 +1,118 @@
package com.fuyuanshen.global.mqtt.rule.xinghan;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fuyuanshen.common.json.utils.JsonUtils;
import com.fuyuanshen.common.redis.utils.RedisUtils;
import com.fuyuanshen.global.mqtt.base.MqttMessageRule;
import com.fuyuanshen.global.mqtt.base.MqttRuleContext;
import com.fuyuanshen.global.mqtt.config.MqttGateway;
import com.fuyuanshen.global.mqtt.constants.MqttConstants;
import com.fuyuanshen.global.mqtt.constants.XingHanCommandTypeConstants;
import com.fuyuanshen.global.mqtt.listener.domain.FunctionAccessStatus;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.nio.charset.Charset;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.fuyuanshen.common.core.constant.GlobalConstants.FUNCTION_ACCESS_KEY;
import static com.fuyuanshen.common.core.constant.GlobalConstants.GLOBAL_REDIS_KEY;
import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_ALARM_MESSAGE_KEY_PREFIX;
import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX;
/**
* 星汉设备发送紧急通知 下发规则:
* <p>
* 1. 设备上行 sta_BreakNews=cover! => 仅标记成功<br>
* 2. 设备上行 sta_BreakNews=数字 => GBK编码每行文字为一包一共4包第一字节为包序号
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class XinghanSendAlarmMessageRule implements MqttMessageRule {
private final MqttGateway mqttGateway;
private final ObjectMapper objectMapper;
@Override
public String getCommandType() {
return XingHanCommandTypeConstants.XingHan_BREAK_NEWS;
}
@Override
public void execute(MqttRuleContext ctx) {
String functionAccess = FUNCTION_ACCESS_KEY + ctx.getDeviceImei();
try {
XinghanSendAlarmMessageRule.MqttXinghanAlarmMsgJson payload = objectMapper.convertValue(
ctx.getPayloadDict(), XinghanSendAlarmMessageRule.MqttXinghanAlarmMsgJson.class);
String respText = payload.getStaBreakNews();
log.info("设备上报紧急通知握手: {} ", respText);
// 1. cover! —— 成功标记
if ("cover!".equalsIgnoreCase(respText)) {
RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.OK.getCode(), Duration.ofSeconds(20));
log.info("设备 {} 发送紧急通知完成", ctx.getDeviceImei());
return;
}
// 2. 数字 —— 下发数据块
int blockIndex;
try {
blockIndex = Integer.parseInt(respText);
} catch (NumberFormatException ex) {
log.warn("设备 {} 紧急通知上报非法块号:{}", ctx.getDeviceImei(), respText);
return;
}
// 将发送的信息原文本以List<String>形式存储在Redis中
String data = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + ctx.getDeviceImei() + DEVICE_ALARM_MESSAGE_KEY_PREFIX);
if (data.isEmpty()) {
return;
}
//
ArrayList<Integer> intData = new ArrayList<>();
intData.add(blockIndex);
// 获取块原内容 转成GBK 再转成无符号十进制整数
String blockTxt = data.split(",")[blockIndex-1];
// 再按 GBK 编码把字符串转成字节数组,并逐个转为无符号十进制整数
for (byte b : blockTxt.getBytes(GBK)) {
intData.add(b & 0xFF); // b & 0xFF 得到 0~255 的整数
}
Map<String, Object> map = new HashMap<>();
map.put("ins_BreakNews", intData);
String topic = MqttConstants.GLOBAL_PUB_KEY + ctx.getDeviceImei();
String json = JsonUtils.toJsonString(map);
mqttGateway.sendMsgToMqtt(topic, 1, json);
log.info("发送设备紧急通知=>topic:{},payload:{}",
MqttConstants.GLOBAL_PUB_KEY + ctx.getDeviceImei(),
JsonUtils.toJsonString(map));
} catch (Exception e) {
log.error("处理发送设备紧急通知时出错", e);
RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.FAILED.getCode(), Duration.ofSeconds(20));
}
}
private static final Charset GBK = Charset.forName("GBK");
/* ---------- DTO ---------- */
@Data
private static class MqttXinghanAlarmMsgJson {
/**
* 设备上行:
* 数字 -> 请求对应块号
* cover! -> 写入成功
*/
@JsonProperty("sta_BreakNews")
private String staBreakNews;
}
}

View File

@ -57,7 +57,7 @@ public class XinghanSendMsgRule implements MqttMessageRule {
// 1. genius! —— 成功标记 // 1. genius! —— 成功标记
if ("genius!".equalsIgnoreCase(respText)) { if ("genius!".equalsIgnoreCase(respText)) {
RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.FAILED.getCode(), Duration.ofSeconds(20)); RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.OK.getCode(), Duration.ofSeconds(20));
log.info("设备 {} 发送消息完成", ctx.getDeviceImei()); log.info("设备 {} 发送消息完成", ctx.getDeviceImei());
return; return;
} }

View File

@ -39,8 +39,7 @@ import static com.fuyuanshen.common.core.constant.GlobalConstants.GLOBAL_REDIS_K
import static com.fuyuanshen.common.core.utils.Bitmap80x12Generator.buildArr; import static com.fuyuanshen.common.core.utils.Bitmap80x12Generator.buildArr;
import static com.fuyuanshen.common.core.utils.Bitmap80x12Generator.generateFixedBitmapData; import static com.fuyuanshen.common.core.utils.Bitmap80x12Generator.generateFixedBitmapData;
import static com.fuyuanshen.common.core.utils.ImageToCArrayConverter.convertHexToDecimal; import static com.fuyuanshen.common.core.utils.ImageToCArrayConverter.convertHexToDecimal;
import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_BOOT_LOGO_KEY_PREFIX; import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.*;
import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX;
@Slf4j @Slf4j
@Service @Service
@ -62,7 +61,7 @@ public class DeviceXinghanBizService {
"ins_DetectGrade", Map.of(1, "低档", 2, "中档", 3, "高档"), "ins_DetectGrade", Map.of(1, "低档", 2, "中档", 3, "高档"),
"ins_LightGrade", Map.of(1, "强光", 2, "弱光"), "ins_LightGrade", Map.of(1, "强光", 2, "弱光"),
"ins_SOSGrade", Map.of(1, "爆闪模式", 2, "红蓝模式"), "ins_SOSGrade", Map.of(1, "爆闪模式", 2, "红蓝模式"),
"ins_ShakeBit", Map.of(0, "未静止报警", 1, "正在静止报警") "ins_ShakeBit", Map.of(1, "开启报警")
// 再加 4、5、6…… 档,直接往 Map 里塞即可 // 再加 4、5、6…… 档,直接往 Map 里塞即可
); );
@ -166,6 +165,7 @@ public class DeviceXinghanBizService {
list.add(bo.getPosition()); list.add(bo.getPosition());
list.add(bo.getCode()); list.add(bo.getCode());
RedisUtils.setCacheList(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + deviceObj.getDeviceImei() + ":app_send_message_data", list); RedisUtils.setCacheList(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + deviceObj.getDeviceImei() + ":app_send_message_data", list);
RedisUtils.expire(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + deviceObj.getDeviceImei() + ":app_send_message_data", Duration.ofSeconds(5 * 60L));
Map<String, Object> payload = Map.of("ins_TexTrans", Map<String, Object> payload = Map.of("ins_TexTrans",
Collections.singletonList(0)); Collections.singletonList(0));
@ -195,6 +195,62 @@ public class DeviceXinghanBizService {
} }
} }
/**
* 发送报警信息
* @param bo
* @return
*/
public int sendAlarmMessage(AppDeviceSendMsgBo bo) {
try {
List<Long> deviceIds = bo.getDeviceIds();
if (deviceIds == null || deviceIds.isEmpty()) {
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 {
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();
throw new ServiceException("发送告警信息指令失败");
}
return 1;
}
/* ---------------------------------- 私有通用方法 ---------------------------------- */ /* ---------------------------------- 私有通用方法 ---------------------------------- */
private void sendCommand(DeviceInstructDto dto, private void sendCommand(DeviceInstructDto dto,