From e6d0e883fb0df0b90a9bf048fc5325e5b07a7d94 Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Thu, 21 Aug 2025 16:51:17 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=91=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/query/DeviceQueryCriteria.java | 5 +++ .../domain/query/DeviceTypeQueryCriteria.java | 6 ++++ .../service/impl/DeviceServiceImpl.java | 7 +++++ .../service/impl/DeviceTypeServiceImpl.java | 20 +++++++++--- .../mapper/equipment/DeviceMapper.xml | 31 +++++++++++-------- 5 files changed, 52 insertions(+), 17 deletions(-) diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/DeviceQueryCriteria.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/DeviceQueryCriteria.java index 09f9815f..d8a9505e 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/DeviceQueryCriteria.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/DeviceQueryCriteria.java @@ -65,4 +65,9 @@ public class DeviceQueryCriteria extends BaseEntity { /* app绑定用户id */ private Long bindingUserId; + + + /* 是否为管理员 */ + private Boolean isAdmin = false; + } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/DeviceTypeQueryCriteria.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/DeviceTypeQueryCriteria.java index a2a0ad09..2beea69a 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/DeviceTypeQueryCriteria.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/DeviceTypeQueryCriteria.java @@ -35,4 +35,10 @@ public class DeviceTypeQueryCriteria extends BaseEntity implements Serializable @Schema(name = "每页数据量", example = "10") private Integer pageSize = 10; + + + /* 是否为管理员 */ + private Boolean isAdmin = false; + + } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java index 92ba94ac..357a82d1 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java @@ -106,6 +106,13 @@ public class DeviceServiceImpl extends ServiceImpl impleme criteria.setDeviceType(deviceTypeGrant.getDeviceTypeId()); } } + + // 管理员 + String username = LoginHelper.getUsername(); + if (username.equals("admin")) { + criteria.setIsAdmin(true); + } + IPage devices = deviceMapper.findAll(criteria, page); List records = devices.getRecords(); diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceTypeServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceTypeServiceImpl.java index 8f9d7f7a..c069ddf6 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceTypeServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceTypeServiceImpl.java @@ -53,8 +53,12 @@ public class DeviceTypeServiceImpl extends ServiceImpl queryAll(DeviceTypeQueryCriteria criteria, Page page) { - criteria.setCustomerId(LoginHelper.getUserId()); - // return + // 管理员 + String username = LoginHelper.getUsername(); + if (!username.equals("admin")) { + criteria.setCustomerId(LoginHelper.getUserId()); + } + IPage deviceTypeIPage = deviceTypeMapper.findAll(criteria, page); return new TableDataInfo(deviceTypeIPage.getRecords(), deviceTypeIPage.getTotal()); } @@ -74,8 +78,16 @@ public class DeviceTypeServiceImpl extends ServiceImpl queryDeviceTypes() { DeviceTypeQueryCriteria criteria = new DeviceTypeQueryCriteria(); - Long userId = LoginHelper.getUserId(); - criteria.setCustomerId(userId); + + // 管理员 + String username = LoginHelper.getUsername(); + if (!username.equals("admin")) { + criteria.setCustomerId(LoginHelper.getUserId()); + + Long userId = LoginHelper.getUserId(); + criteria.setCustomerId(userId); + } + return deviceTypeMapper.findAll(criteria); } diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml index e5c95a31..3439362f 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml @@ -74,8 +74,11 @@ and da.create_time between #{criteria.params.beginTime} and #{criteria.params.endTime} - AND da.assignee_id = #{criteria.currentOwnerId} - AND dg.customer_id = #{criteria.currentOwnerId} + + + AND da.assignee_id = #{criteria.currentOwnerId} + AND dg.customer_id = #{criteria.currentOwnerId} + ) AS ranked WHERE rn = 1 @@ -213,18 +216,20 @@ WHERE original_device_id = #{originalDeviceId} -- 2.43.5 From f1a19f95f5a83057ae267a7a39f3f38dd483670f Mon Sep 17 00:00:00 2001 From: DragonWenLong <552045633@qq.com> Date: Thu, 21 Aug 2025 17:44:06 +0800 Subject: [PATCH 2/3] =?UTF-8?q?feat(mqtt):=20=E6=96=B0=E5=A2=9E=E6=98=9F?= =?UTF-8?q?=E6=B1=89=E8=AE=BE=E5=A4=87=E6=95=B0=E6=8D=AE=E5=A4=84=E7=90=86?= =?UTF-8?q?=E8=A7=84=E5=88=99=E5=92=8C=E8=A7=A3=E6=9E=90=E9=80=BB=E8=BE=91?= =?UTF-8?q?-=20=E6=B7=BB=E5=8A=A0=20MqttXinghanCommandType=20=E6=9E=9A?= =?UTF-8?q?=E4=B8=BE=E7=B1=BB=EF=BC=8C=E7=94=A8=E4=BA=8E=E8=AF=86=E5=88=AB?= =?UTF-8?q?=E5=92=8C=E5=A4=84=E7=90=86=E6=98=9F=E6=B1=89=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E7=9A=84=E5=91=BD=E4=BB=A4=E7=B1=BB=E5=9E=8B=20-=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20MqttXinghanJson=20=E7=B1=BB=EF=BC=8C=E7=94=A8?= =?UTF-8?q?=E4=BA=8E=E8=A7=A3=E6=9E=90=E6=98=9F=E6=B1=89=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E7=9A=84=20JSON=20=E6=95=B0=E6=8D=AE=20-=20=E5=9C=A8=20Receive?= =?UTF-8?q?rMessageHandler=20=E4=B8=AD=E9=9B=86=E6=88=90=E6=98=9F=E6=B1=89?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E6=95=B0=E6=8D=AE=E7=9A=84=E5=A4=84=E7=90=86?= =?UTF-8?q?=E9=80=BB=E8=BE=91=20-=20=E6=B7=BB=E5=8A=A0=20XingHanCommandTyp?= =?UTF-8?q?eConstants=20=E5=B8=B8=E9=87=8F=E7=B1=BB=EF=BC=8C=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E6=98=9F=E6=B1=89=E8=AE=BE=E5=A4=87=E7=9A=84=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E7=B1=BB=E5=9E=8B=E5=B8=B8=E9=87=8F=20-=20=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=20XinghanDeviceDataRule=20=E7=B1=BB=EF=BC=8C=E5=A4=84?= =?UTF-8?q?=E7=90=86=E6=98=9F=E6=B1=89=E8=AE=BE=E5=A4=87=E4=B8=BB=E5=8A=A8?= =?UTF-8?q?=E4=B8=8A=E6=8A=A5=E7=9A=84=E6=95=B0=E6=8D=AE=E5=91=BD=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mqtt/base/MqttXinghanCommandType.java | 56 +++++ .../global/mqtt/base/MqttXinghanJson.java | 65 ++++++ .../XingHanCommandTypeConstants.java | 16 ++ .../mqtt/receiver/ReceiverMessageHandler.java | 15 ++ .../rule/xinghan/XinghanDeviceDataRule.java | 212 ++++++++++++++++++ 5 files changed, 364 insertions(+) create mode 100644 fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/MqttXinghanCommandType.java create mode 100644 fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/MqttXinghanJson.java create mode 100644 fys-admin/src/main/java/com/fuyuanshen/global/mqtt/constants/XingHanCommandTypeConstants.java create mode 100644 fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanDeviceDataRule.java diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/MqttXinghanCommandType.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/MqttXinghanCommandType.java new file mode 100644 index 00000000..6f6a6a81 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/MqttXinghanCommandType.java @@ -0,0 +1,56 @@ +package com.fuyuanshen.global.mqtt.base; + +import cn.hutool.core.lang.Dict; + +import java.util.Map; +import java.util.LinkedHashMap; +import java.util.Collections; +import org.springframework.stereotype.Component; + + +@Component +public final class MqttXinghanCommandType { + private MqttXinghanCommandType() {} + + public enum XinghanCommandTypeEnum { + GRADE_INFO(101), + PIC_TRANS(102), + TEX_TRANS(103), + BREAK_NEWS(104), + UNKNOWN(0); + + private final int value; + XinghanCommandTypeEnum(int value) { this.value = value; } + public int getValue() { return value; } + } + + private static final Map KEY_TO_TYPE; + static { + LinkedHashMap map = new LinkedHashMap<>(); + map.put("sta_DetectGrade", XinghanCommandTypeEnum.GRADE_INFO); + map.put("sta_PowerTime", XinghanCommandTypeEnum.GRADE_INFO); + map.put("sta_longitude", XinghanCommandTypeEnum.GRADE_INFO); + map.put("sta_latitude", XinghanCommandTypeEnum.GRADE_INFO); + map.put("sta_PicTrans", XinghanCommandTypeEnum.PIC_TRANS); + map.put("sta_TexTrans", XinghanCommandTypeEnum.TEX_TRANS); + map.put("sta_BreakNews", XinghanCommandTypeEnum.BREAK_NEWS); + KEY_TO_TYPE = Collections.unmodifiableMap(map); + } + + public static int computeVirtualCommandType(Dict payloadDict) { + if (payloadDict == null) { + return XinghanCommandTypeEnum.UNKNOWN.getValue(); + } + try { + for (String key : KEY_TO_TYPE.keySet()) { + if (payloadDict.containsKey(key)) { + return KEY_TO_TYPE.get(key).getValue(); + } + } + } catch (Exception ex) { + return XinghanCommandTypeEnum.UNKNOWN.getValue(); + } + + return XinghanCommandTypeEnum.UNKNOWN.getValue(); + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/MqttXinghanJson.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/MqttXinghanJson.java new file mode 100644 index 00000000..fde03ac9 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/MqttXinghanJson.java @@ -0,0 +1,65 @@ +package com.fuyuanshen.global.mqtt.base; + +import lombok.Data; +import com.fasterxml.jackson.annotation.JsonProperty; + +@Data +public class MqttXinghanJson { + + /** + * 第一键值对,静电预警档位:3,2,1,0,分别表示高档/中档/低挡/关闭. + */ + @JsonProperty("sta_DetectGrade") + private Integer staDetectGrade; + /** + * 第二键值对,照明档位,2,1,0,分别表示弱光/强光/关闭 + */ + @JsonProperty("sta_LightGrade") + private Integer staLightGrade; + /** + * 第三键值对,SOS档位,2,1,0, 分别表示红蓝模式/爆闪模式/关闭 + */ + @JsonProperty("sta_SOSGrade") + public int staSOSGrade; + /** + * 第四键值对,剩余照明时间,0-5999,单位分钟。 + */ + @JsonProperty("sta_PowerTime") + public int staPowerTime; + /** + * 第五键值对,剩余电量百分比,0-100 + */ + @JsonProperty("sta_PowerPercent") + public int staPowerPercent; + /** + * 第六键值对, 近电预警级别, 0-无预警,1-弱预警,2-中预警,3-强预警,4-非常强预警。 + */ + @JsonProperty("sta_DetectResult") + public int staDetectResult; + /** + * 第七键值对, 静止报警状态,0-未静止报警,1-正在静止报警。 + */ + @JsonProperty("staShakeBit") + public int sta_ShakeBit; + /** + * 第八键值对, 4G信号强度,0-32,数值越大,信号越强。 + */ + @JsonProperty("sta_4gSinal") + public int sta4gSinal; + /** + * 第九键值对,IMIE卡号 + */ + @JsonProperty("sta_imei") + public int staimei; + /** + * 第十键值对,经度 + */ + @JsonProperty("sta_longitude") + public String stalongitude; + /** + * 第十一键值对,纬度 + */ + @JsonProperty("sta_latitude") + public String stalatitude; + +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/constants/XingHanCommandTypeConstants.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/constants/XingHanCommandTypeConstants.java new file mode 100644 index 00000000..a7f4ba28 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/constants/XingHanCommandTypeConstants.java @@ -0,0 +1,16 @@ +package com.fuyuanshen.global.mqtt.constants; + +public class XingHanCommandTypeConstants { + /** + * 星汉设备主动上报数据 (XingHan Device Data) + */ + public static final String XingHan_DEVICE_DATA = "Light_101"; + /** + * 星汉开机LOGO (XingHan Boot Logo) + */ + public static final String XingHan_BOOT_LOGO = "Light_102"; + /** + * 星汉设备发送消息 (XingHan send msg) + */ + public static final String XingHan_ESEND_MSG = "Light_103"; +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java index 811acd54..4259cd18 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java @@ -8,6 +8,7 @@ import com.fuyuanshen.common.json.utils.JsonUtils; import com.fuyuanshen.common.redis.utils.RedisUtils; import com.fuyuanshen.global.mqtt.base.MqttRuleContext; import com.fuyuanshen.global.mqtt.base.MqttRuleEngine; +import com.fuyuanshen.global.mqtt.base.MqttXinghanCommandType; import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -69,5 +70,19 @@ public class ReceiverMessageHandler implements MessageHandler { log.warn("未找到匹配的规则来处理命令类型: {}", val1); } } + + /* ===== 追加:根据报文内容识别格式并统一解析 ===== */ + int intType = MqttXinghanCommandType.computeVirtualCommandType(payloadDict); + if (intType > 0) { + MqttRuleContext newCtx = new MqttRuleContext(); + newCtx.setCommandType((byte) intType); + newCtx.setDeviceImei(deviceImei); + newCtx.setPayloadDict(payloadDict); + + boolean ok = ruleEngine.executeRule(newCtx); + if (!ok) { + log.warn("新规则引擎未命中, imei={}", deviceImei); + } + } } } diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanDeviceDataRule.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanDeviceDataRule.java new file mode 100644 index 00000000..e12c0665 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanDeviceDataRule.java @@ -0,0 +1,212 @@ +package com.fuyuanshen.global.mqtt.rule.xinghan; + +import com.alibaba.fastjson2.JSONObject; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fuyuanshen.common.core.constant.GlobalConstants; +import com.fuyuanshen.common.core.utils.StringUtils; +import com.fuyuanshen.common.json.utils.JsonUtils; +import com.fuyuanshen.common.redis.utils.RedisUtils; +import com.fuyuanshen.equipment.utils.map.GetAddressFromLatUtil; +import com.fuyuanshen.equipment.utils.map.LngLonUtil; +import com.fuyuanshen.global.mqtt.base.MqttMessageRule; +import com.fuyuanshen.global.mqtt.base.MqttRuleContext; +import com.fuyuanshen.global.mqtt.base.MqttXinghanJson; +import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants; +import com.fuyuanshen.global.mqtt.constants.LightingCommandTypeConstants; +import com.fuyuanshen.global.mqtt.constants.XingHanCommandTypeConstants; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import static com.fuyuanshen.common.core.constant.GlobalConstants.FUNCTION_ACCESS_KEY; +import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.*; +import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX; + +/** + * 主动上报设备数据命令处理 + * 第一键值对,静电预警档位:3,2,1,0,分别表示高档/中档/低挡/关闭. + * 第二键值对,照明档位,2,1,0,分别表示弱光/强光/关闭 + * 第三键值对,SOS档位,2,1,0, 分别表示红蓝模式/爆闪模式/关闭 + * 第四键值对,剩余照明时间,0-5999,单位分钟。 + * 第五键值对, 剩余电量百分比,0-100。 + * 第六键值对, 近电预警级别, 0-无预警,1-弱预警,2-中预警,3-强预警,4-非常强预警。 + * 第七键值对, 静止报警状态,0-未静止报警,1-正在静止报警。 + * 第八键值对, 4G信号强度,0-32,数值越大,信号越强。 + * 第九键值对,IMIE卡号 + * 第十键值对,经度 + * 第十一键值对,纬度 + */ +@Component +@RequiredArgsConstructor +@Slf4j +public class XinghanDeviceDataRule implements MqttMessageRule { + + @Override + public String getCommandType() { + return XingHanCommandTypeConstants.XingHan_DEVICE_DATA; + } + + @Autowired + private ObjectMapper objectMapper; + + @Override + public void execute(MqttRuleContext context) { + try { + // Latitude, longitude + //主灯档位,激光灯档位,电量百分比,充电状态,电池剩余续航时间 + MqttXinghanJson deviceStatus = objectMapper.convertValue(context.getPayloadDict(), MqttXinghanJson.class); + + // 发送设备状态和位置信息到Redis + asyncSendDeviceDataToRedisWithFuture(context.getDeviceImei(),deviceStatus); + } catch (Exception e) { + log.error("处理上报数据命令时出错", e); + } + } + + /** + * 发送设备状态信息和位置信息到Redis + * + * @param deviceImei 设备IMEI + * @param deviceStatus 机器主动上报的状态信息 + */ + public void asyncSendDeviceDataToRedisWithFuture(String deviceImei, MqttXinghanJson deviceStatus) { + CompletableFuture.runAsync(() -> { + try { + + // 将设备状态信息存储到Redis中 + String deviceRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DeviceRedisKeyConstants.DEVICE_KEY_PREFIX + deviceImei + DEVICE_STATUS_KEY_PREFIX; + String deviceInfoJson = JsonUtils.toJsonString(deviceStatus); + // 存储到Redis + RedisUtils.setCacheObject(deviceRedisKey, deviceInfoJson); + + log.info("设备状态信息已异步发送到Redis: device={}, deviceInfoJson={}", + deviceImei, deviceInfoJson); + + //设备坐标缓存KEY + String functionAccess = FUNCTION_ACCESS_KEY + deviceImei; + // 异步发送经纬度到Redis + asyncSendLocationToRedisWithFuture(deviceImei, deviceStatus.getStalatitude(), deviceStatus.getStalongitude()); + + } catch (Exception e) { + log.error("异步发送设备信息到Redis时出错: device={}, error={}", deviceImei, e.getMessage(), e); + } + }); + } + + /** + * 异步发送位置信息到Redis(使用CompletableFuture) + * + * @param deviceImei 设备IMEI + * @param latitude 纬度 + * @param longitude 经度 + */ + public void asyncSendLocationToRedisWithFuture(String deviceImei, String latitude, String longitude) { + CompletableFuture.runAsync(() -> { + try { + if(StringUtils.isBlank(latitude) || StringUtils.isBlank(longitude)){ + return; + } + String[] latArr = latitude.split("\\."); + String[] lonArr = longitude.split("\\."); + // 将位置信息存储到Redis中 + String redisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ deviceImei + DEVICE_LOCATION_KEY_PREFIX; + String redisObj = RedisUtils.getCacheObject(redisKey); + JSONObject jsonOBj = JSONObject.parseObject(redisObj); + if(jsonOBj != null){ + String str1 = latArr[0] +"."+ latArr[1].substring(0,4); + String str2 = lonArr[0] +"."+ lonArr[1].substring(0,4); + + String cacheLatitude = jsonOBj.getString("wgs84_latitude"); + String cacheLongitude = jsonOBj.getString("wgs84_longitude"); + String[] latArr1 = cacheLatitude.split("\\."); + String[] lonArr1 = cacheLongitude.split("\\."); + + String cacheStr1 = latArr1[0] +"."+ latArr1[1].substring(0,4); + String cacheStr2 = lonArr1[0] +"."+ lonArr1[1].substring(0,4); + if(str1.equals(cacheStr1) && str2.equals(cacheStr2)){ + log.info("位置信息未发生变化: device={}, lat={}, lon={}", deviceImei, latitude, longitude); + return; + } + } + + // 构造位置信息对象 + Map locationInfo = new LinkedHashMap<>(); + double[] doubles = LngLonUtil.gps84_To_Gcj02(Double.parseDouble(latitude), Double.parseDouble(longitude)); + locationInfo.put("deviceImei", deviceImei); + locationInfo.put("latitude", doubles[0]); + locationInfo.put("longitude", doubles[1]); + locationInfo.put("wgs84_latitude", latitude); + locationInfo.put("wgs84_longitude", longitude); + String address = GetAddressFromLatUtil.getAdd(String.valueOf(doubles[1]), String.valueOf(doubles[0])); + locationInfo.put("address", address); + locationInfo.put("timestamp", System.currentTimeMillis()); + + + + String locationJson = JsonUtils.toJsonString(locationInfo); + + // 存储到Redis + RedisUtils.setCacheObject(redisKey, locationJson); + + // 存储到一个列表中,保留历史位置信息 +// String locationHistoryKey = GlobalConstants.GLOBAL_REDIS_KEY+DeviceRedisKeyConstants.DEVICE_LOCATION_HISTORY_KEY_PREFIX + deviceImei; +// RedisUtils.addCacheList(locationHistoryKey, locationJson); +// RedisUtils.expire(locationHistoryKey, Duration.ofDays(90)); + storeDeviceTrajectoryWithSortedSet(deviceImei, locationJson); + log.info("位置信息已异步发送到Redis: device={}, lat={}, lon={}", deviceImei, latitude, longitude); + } catch (Exception e) { + log.error("异步发送位置信息到Redis时出错: device={}, error={}", deviceImei, e.getMessage(), e); + } + }); + } + + /** + * 存储设备30天历史轨迹到Redis (使用Sorted Set) + */ + public void storeDeviceTrajectoryWithSortedSet(String deviceImei, String locationJson) { + try { + String trajectoryKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + deviceImei + DeviceRedisKeyConstants.DEVICE_LOCATION_HISTORY_KEY_PREFIX; +// String trajectoryKey = "device:trajectory:zset:" + deviceImei; +// String locationJson = JsonUtils.toJsonString(locationInfo); + long timestamp = System.currentTimeMillis(); + + // 添加到Sorted Set,使用时间戳作为score + RedisUtils.zAdd(trajectoryKey, locationJson, timestamp); + +// // 设置30天过期时间 +// RedisUtils.expire(trajectoryKey, Duration.ofDays(30)); + + // 清理30天前的数据(冗余保护) + long thirtyDaysAgo = System.currentTimeMillis() - (7L * 24 * 60 * 60 * 1000); + RedisUtils.zRemoveRangeByScore(trajectoryKey, 0, thirtyDaysAgo); + } catch (Exception e) { + log.error("存储设备轨迹到Redis(ZSet)失败: device={}, error={}", deviceImei, e.getMessage(), e); + } + } + + private Map buildLocationDataMap(String latitude, String longitude) { + String[] latArr = latitude.split("\\."); + String[] lonArr = longitude.split("\\."); + + ArrayList intData = new ArrayList<>(); + intData.add(11); + intData.add(Integer.parseInt(latArr[0])); + String str1 = latArr[1]; + intData.add(Integer.parseInt(str1.substring(0,4))); + String str2 = lonArr[1]; + intData.add(Integer.parseInt(lonArr[0])); + intData.add(Integer.parseInt(str2.substring(0,4))); + + Map map = new HashMap<>(); + map.put("instruct", intData); + return map; + } + +} -- 2.43.5 From 95aa01e1c253777166836659704963415617abe8 Mon Sep 17 00:00:00 2001 From: DragonWenLong <552045633@qq.com> Date: Fri, 22 Aug 2025 18:09:08 +0800 Subject: [PATCH 3/3] =?UTF-8?q?feat(device):=20=E6=96=B0=E5=A2=9E=E6=98=9F?= =?UTF-8?q?=E6=B1=89=E8=AE=BE=E5=A4=87=E6=8E=A7=E5=88=B6=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加星汉设备控制器 AppDeviceXinghanController- 实现星汉设备业务逻辑 DeviceXinghanBizService - 增加开机 LOGO 下发规则 XinghanBootLogoRule - 添加设备发送消息规则 XinghanSendMsgRule - 更新 MQTT 命令类型常量 XingHanCommandTypeConstants - 修改设备状态 JSON 结构 MqttXinghanJson --- .../device/AppDeviceXinghanController.java | 96 +++++++ .../mqtt/base/MqttXinghanCommandType.java | 9 + .../global/mqtt/base/MqttXinghanJson.java | 14 +- .../XingHanCommandTypeConstants.java | 4 + .../rule/xinghan/XinghanBootLogoRule.java | 143 ++++++++++ .../mqtt/rule/xinghan/XinghanSendMsgRule.java | 117 ++++++++ .../device/DeviceXinghanBizService.java | 269 ++++++++++++++++++ 7 files changed, 645 insertions(+), 7 deletions(-) create mode 100644 fys-admin/src/main/java/com/fuyuanshen/app/controller/device/AppDeviceXinghanController.java create mode 100644 fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanBootLogoRule.java create mode 100644 fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanSendMsgRule.java create mode 100644 fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceXinghanBizService.java diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/controller/device/AppDeviceXinghanController.java b/fys-admin/src/main/java/com/fuyuanshen/app/controller/device/AppDeviceXinghanController.java new file mode 100644 index 00000000..b4f53c51 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/app/controller/device/AppDeviceXinghanController.java @@ -0,0 +1,96 @@ +package com.fuyuanshen.app.controller.device; + +import com.fuyuanshen.app.domain.bo.AppPersonnelInfoBo; +import com.fuyuanshen.app.domain.dto.AppDeviceLogoUploadDto; +import com.fuyuanshen.app.domain.dto.DeviceInstructDto; +import com.fuyuanshen.common.core.domain.R; +import com.fuyuanshen.common.core.validate.AddGroup; +import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessAnnotation; +import com.fuyuanshen.common.web.core.BaseController; +import com.fuyuanshen.web.service.device.DeviceBJQBizService; +import com.fuyuanshen.web.service.device.DeviceXinghanBizService; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +/** + * HBY670设备控制类 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/app/xinghan/device") +public class AppDeviceXinghanController extends BaseController { + + private final DeviceXinghanBizService appDeviceService; + /** + * 人员信息登记 + */ + @PostMapping(value = "/registerPersonInfo") +// @FunctionAccessAnnotation("registerPersonInfo") + public R registerPersonInfo(@Validated(AddGroup.class) @RequestBody AppPersonnelInfoBo bo) { + return toAjax(appDeviceService.registerPersonInfo(bo)); + } + + /** + * 上传设备logo图片 + */ + @PostMapping("/uploadLogo") + @FunctionAccessAnnotation("uploadLogo") + public R upload(@Validated @ModelAttribute AppDeviceLogoUploadDto bo) { + + MultipartFile file = bo.getFile(); + if(file.getSize()>1024*1024*2){ + return R.warn("图片不能大于2M"); + } + appDeviceService.uploadDeviceLogo(bo); + + return R.ok(); + } + + /** + * 静电预警档位 + * 3,2,1,0,分别表示高档/中档/低挡/关闭 + */ + @PostMapping("/DetectGradeSettings") + public R DetectGradeSettings(@RequestBody DeviceInstructDto params) { + // params 转 JSONObject + appDeviceService.upDetectGradeSettings(params); + return R.ok(); + } + + /** + * 照明档位 + * 照明档位,2,1,0,分别表示弱光/强光/关闭 + */ + @PostMapping("/LightGradeSettings") + public R LightGradeSettings(@RequestBody DeviceInstructDto params) { + // params 转 JSONObject + appDeviceService.upLightGradeSettings(params); + return R.ok(); + } + + /** + * SOS档位 + * SOS档位,2,1,0, 分别表示红蓝模式/爆闪模式/关闭 + */ + @PostMapping("/SOSGradeSettings") + public R SOSGradeSettings(@RequestBody DeviceInstructDto params) { + // params 转 JSONObject + appDeviceService.upSOSGradeSettings(params); + return R.ok(); + } + + /** + * 静止报警状态 + * 静止报警状态,0-未静止报警,1-正在静止报警。 + */ + @PostMapping("/ShakeBitSettings") + public R ShakeBitSettings(@RequestBody DeviceInstructDto params) { + // params 转 JSONObject + appDeviceService.upShakeBitSettings(params); + return R.ok(); + } + +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/MqttXinghanCommandType.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/MqttXinghanCommandType.java index 6f6a6a81..99e7bc78 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/MqttXinghanCommandType.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/MqttXinghanCommandType.java @@ -13,8 +13,17 @@ public final class MqttXinghanCommandType { private MqttXinghanCommandType() {} public enum XinghanCommandTypeEnum { + /** + * 星汉设备主动上报数据 + */ GRADE_INFO(101), + /** + * 星汉开机LOGO + */ PIC_TRANS(102), + /** + * 星汉设备发送消息 (XingHan send msg) + */ TEX_TRANS(103), BREAK_NEWS(104), UNKNOWN(0); diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/MqttXinghanJson.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/MqttXinghanJson.java index fde03ac9..2d49b468 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/MqttXinghanJson.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/MqttXinghanJson.java @@ -20,37 +20,37 @@ public class MqttXinghanJson { * 第三键值对,SOS档位,2,1,0, 分别表示红蓝模式/爆闪模式/关闭 */ @JsonProperty("sta_SOSGrade") - public int staSOSGrade; + public Integer staSOSGrade; /** * 第四键值对,剩余照明时间,0-5999,单位分钟。 */ @JsonProperty("sta_PowerTime") - public int staPowerTime; + public Integer staPowerTime; /** * 第五键值对,剩余电量百分比,0-100 */ @JsonProperty("sta_PowerPercent") - public int staPowerPercent; + public Integer staPowerPercent; /** * 第六键值对, 近电预警级别, 0-无预警,1-弱预警,2-中预警,3-强预警,4-非常强预警。 */ @JsonProperty("sta_DetectResult") - public int staDetectResult; + public Integer staDetectResult; /** * 第七键值对, 静止报警状态,0-未静止报警,1-正在静止报警。 */ @JsonProperty("staShakeBit") - public int sta_ShakeBit; + public Integer sta_ShakeBit; /** * 第八键值对, 4G信号强度,0-32,数值越大,信号越强。 */ @JsonProperty("sta_4gSinal") - public int sta4gSinal; + public Integer sta4gSinal; /** * 第九键值对,IMIE卡号 */ @JsonProperty("sta_imei") - public int staimei; + public String staimei; /** * 第十键值对,经度 */ diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/constants/XingHanCommandTypeConstants.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/constants/XingHanCommandTypeConstants.java index a7f4ba28..0b6f3d7c 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/constants/XingHanCommandTypeConstants.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/constants/XingHanCommandTypeConstants.java @@ -13,4 +13,8 @@ public class XingHanCommandTypeConstants { * 星汉设备发送消息 (XingHan send msg) */ public static final String XingHan_ESEND_MSG = "Light_103"; + /** + * 星汉设备发送紧急通知 (XingHan break news) + */ + public static final String XingHan_BREAK_NEWS = "Light_104"; } diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanBootLogoRule.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanBootLogoRule.java new file mode 100644 index 00000000..96faf308 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanBootLogoRule.java @@ -0,0 +1,143 @@ +package com.fuyuanshen.global.mqtt.rule.xinghan; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fuyuanshen.common.core.utils.ImageToCArrayConverter; +import com.fuyuanshen.common.core.utils.StringUtils; +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.LightingCommandTypeConstants; +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.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.time.Duration; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.zip.CRC32; + +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.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.DEVICE_KEY_PREFIX; + +/** + * 星汉设备开机 LOGO 下发规则: + *

+ * 1. 设备上行 sta_PicTarns=great! => 仅标记成功
+ * 2. 设备上行 sta_PicTarns=数字 => 下发第 N 块数据(256B/块,带 CRC32) + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class XinghanBootLogoRule implements MqttMessageRule { + + private final MqttGateway mqttGateway; + private final ObjectMapper objectMapper; + + @Override + public String getCommandType() { + return XingHanCommandTypeConstants.XingHan_BOOT_LOGO; + } + + @Override + public void execute(MqttRuleContext ctx) { + final String functionAccessKey = FUNCTION_ACCESS_KEY + ctx.getDeviceImei(); + try { + MqttXinghanLogoJson payload = objectMapper.convertValue( + ctx.getPayloadDict(), MqttXinghanLogoJson.class); + + String respText = payload.getStaPicTrans(); + log.warn("设备上报LOGO:{}", respText); + + // 1. great! —— 成功标记 + if ("great!".equalsIgnoreCase(respText)) { + RedisUtils.setCacheObject(functionAccessKey, + FunctionAccessStatus.OK.getCode(), Duration.ofSeconds(20)); + log.info("设备 {} 开机 LOGO 写入成功", ctx.getDeviceImei()); + return; + } + + // 2. 数字 —— 下发数据块 + int blockIndex; + try { + blockIndex = Integer.parseInt(respText); + } catch (NumberFormatException ex) { + log.warn("设备 {} LOGO 上报非法块号:{}", ctx.getDeviceImei(), respText); + return; + } + String hexImage = RedisUtils.getCacheObject( + GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + ctx.getDeviceImei() + DEVICE_BOOT_LOGO_KEY_PREFIX); + if (StringUtils.isEmpty(hexImage)) { + return; + } + + byte[] fullBin = ImageToCArrayConverter.convertStringToByteArray(hexImage); + byte[] chunk = ImageToCArrayConverter.getChunk(fullBin, blockIndex - 1, CHUNK_SIZE); + log.info("设备 {} 第 {} 块数据长度: {} bytes", ctx.getDeviceImei(), blockIndex, chunk.length); + + // 组装下发数据 + ArrayList dataFrame = new ArrayList<>(); + dataFrame.add(blockIndex); // 块号 + ImageToCArrayConverter.buildArr(convertHexToDecimal(chunk), dataFrame); + dataFrame.addAll(crc32AsList(chunk)); // CRC32 + + Map pub = new HashMap<>(); + pub.put("ins_PicTrans", dataFrame); + + String topic = MqttConstants.GLOBAL_PUB_KEY + ctx.getDeviceImei(); + String json = JsonUtils.toJsonString(pub); + mqttGateway.sendMsgToMqtt(topic, 1, json); + + log.info("下发开机 LOGO 数据 => topic:{}, payload:{}", topic, json); + + } catch (Exception e) { + log.error("处理设备 {} 开机 LOGO 失败", ctx.getDeviceImei(), e); + RedisUtils.setCacheObject(functionAccessKey, + FunctionAccessStatus.FAILED.getCode(), Duration.ofSeconds(20)); + } + } + + /* ---------- 内部工具 ---------- */ + + private static final int CHUNK_SIZE = 256; + + private static ArrayList crc32AsList(byte[] data) { + CRC32 crc = new CRC32(); + crc.update(data); + byte[] crcBytes = ByteBuffer.allocate(4) + .order(ByteOrder.BIG_ENDIAN) + .putInt((int) crc.getValue()) + .array(); + ArrayList list = new ArrayList<>(4); + for (byte b : crcBytes) { + list.add(Byte.toUnsignedInt(b)); + } + return list; + } + + /* ---------- DTO ---------- */ + + @Data + private static class MqttXinghanLogoJson { + /** + * 设备上行: + * 数字 -> 请求对应块号 + * great! -> 写入成功 + */ + @JsonProperty("sta_PicTrans") + private String staPicTrans; + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanSendMsgRule.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanSendMsgRule.java new file mode 100644 index 00000000..68acb099 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanSendMsgRule.java @@ -0,0 +1,117 @@ +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_KEY_PREFIX; + +/** + * 星汉设备发送消息 下发规则: + *

+ * 1. 设备上行 sta_TexTarns=genius! => 仅标记成功
+ * 2. 设备上行 sta_TexTarns=数字 => GBK编码,每行文字为一包,一共4包,第一字节为包序号 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class XinghanSendMsgRule implements MqttMessageRule { + + private final MqttGateway mqttGateway; + private final ObjectMapper objectMapper; + + @Override + public String getCommandType() { + return XingHanCommandTypeConstants.XingHan_ESEND_MSG; + } + + @Override + public void execute(MqttRuleContext ctx) { + String functionAccess = FUNCTION_ACCESS_KEY + ctx.getDeviceImei(); + try { + XinghanSendMsgRule.MqttXinghanMsgJson payload = objectMapper.convertValue( + ctx.getPayloadDict(), XinghanSendMsgRule.MqttXinghanMsgJson.class); + + String respText = payload.getStaTexTrans(); + log.info("设备上报人员信息: {} ", respText); + + // 1. genius! —— 成功标记 + if ("genius!".equalsIgnoreCase(respText)) { + RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.FAILED.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形式存储在Redis中 + List data = RedisUtils.getCacheList(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + ctx.getDeviceImei() + ":app_send_message_data"); + if (data.isEmpty()) { + return; + } + // + ArrayList intData = new ArrayList<>(); + intData.add(blockIndex); + // 获取块原内容 转成GBK 再转成无符号十进制整数 + String blockTxt = data.get(blockIndex-1); + // 再按 GBK 编码把字符串转成字节数组,并逐个转为无符号十进制整数 + for (byte b : blockTxt.getBytes(GBK)) { + intData.add(b & 0xFF); // b & 0xFF 得到 0~255 的整数 + } + + Map map = new HashMap<>(); + map.put("ins_TexTrans", 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 MqttXinghanMsgJson { + /** + * 设备上行: + * 数字 -> 请求对应块号 + * genius! -> 写入成功 + */ + @JsonProperty("sta_TexTrans") + private String staTexTrans; + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceXinghanBizService.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceXinghanBizService.java new file mode 100644 index 00000000..40c2d7af --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceXinghanBizService.java @@ -0,0 +1,269 @@ +package com.fuyuanshen.web.service.device; + +import com.alibaba.fastjson2.JSON; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.fuyuanshen.app.domain.AppPersonnelInfo; +import com.fuyuanshen.app.domain.bo.AppPersonnelInfoBo; +import com.fuyuanshen.app.domain.dto.AppDeviceLogoUploadDto; +import com.fuyuanshen.app.domain.dto.DeviceInstructDto; +import com.fuyuanshen.app.domain.vo.AppPersonnelInfoVo; +import com.fuyuanshen.app.mapper.AppPersonnelInfoMapper; +import com.fuyuanshen.common.core.constant.GlobalConstants; +import com.fuyuanshen.common.core.exception.ServiceException; +import com.fuyuanshen.common.core.utils.ImageToCArrayConverter; +import com.fuyuanshen.common.core.utils.MapstructUtils; +import com.fuyuanshen.common.core.utils.ObjectUtils; +import com.fuyuanshen.common.core.utils.StringUtils; +import com.fuyuanshen.common.json.utils.JsonUtils; +import com.fuyuanshen.common.redis.utils.RedisUtils; +import com.fuyuanshen.common.satoken.utils.AppLoginHelper; +import com.fuyuanshen.equipment.domain.Device; +import com.fuyuanshen.equipment.domain.dto.AppDeviceSendMsgBo; +import com.fuyuanshen.equipment.enums.LightModeEnum; +import com.fuyuanshen.equipment.mapper.DeviceLogMapper; +import com.fuyuanshen.equipment.mapper.DeviceMapper; +import com.fuyuanshen.equipment.mapper.DeviceTypeMapper; +import com.fuyuanshen.global.mqtt.config.MqttGateway; +import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants; +import com.fuyuanshen.global.mqtt.constants.MqttConstants; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.time.Duration; +import java.util.*; + +import static com.fuyuanshen.common.core.constant.GlobalConstants.GLOBAL_REDIS_KEY; +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.ImageToCArrayConverter.convertHexToDecimal; +import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_BOOT_LOGO_KEY_PREFIX; +import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX; + +@Slf4j +@Service +@RequiredArgsConstructor +public class DeviceXinghanBizService { + + private final DeviceMapper deviceMapper; + private final AppPersonnelInfoMapper appPersonnelInfoMapper; + private final DeviceTypeMapper deviceTypeMapper; + private final MqttGateway mqttGateway; + private final DeviceLogMapper deviceLogMapper; + + /** + * 所有档位的描述表 + * key : 指令类型,如 "ins_DetectGrade"、"ins_LightGrade" …… + * value : Map 值 -> 描述 + */ + private static final Map> GRADE_DESC = Map.of( + "ins_DetectGrade", Map.of(1, "低档", 2, "中档", 3, "高档"), + "ins_LightGrade", Map.of(1, "强光", 2, "弱光"), + "ins_SOSGrade", Map.of(1, "爆闪模式", 2, "红蓝模式"), + "ins_ShakeBit", Map.of(0, "未静止报警", 1, "正在静止报警") + // 再加 4、5、6…… 档,直接往 Map 里塞即可 + ); + + /** + * 根据指令类型和值,返回中文描述 + */ + private static String resolveGradeDesc(String type, int value) { + return GRADE_DESC.getOrDefault(type, Map.of()) + .getOrDefault(value, "关闭"); + } + + /** + * 设置静电预警档位 + */ + public void upDetectGradeSettings(DeviceInstructDto dto) { + sendCommand(dto, "ins_DetectGrade","静电预警档位"); + } + + /** + * 设置照明档位 + */ + public void upLightGradeSettings(DeviceInstructDto dto) { + sendCommand(dto, "ins_LightGrade","照明档位"); + } + + /** + * 设置SOS档位 + */ + public void upSOSGradeSettings(DeviceInstructDto dto) { + sendCommand(dto, "ins_SOSGrade","SOS档位"); + } + + /** + * 设置强制报警 + */ + public void upShakeBitSettings(DeviceInstructDto dto) { + sendCommand(dto, "ins_ShakeBit","强制报警"); + } + + /** + * 上传设备logo + */ + public void uploadDeviceLogo(AppDeviceLogoUploadDto bo) { + try { + Device device = deviceMapper.selectById(bo.getDeviceId()); + if (device == null) { + throw new ServiceException("设备不存在"); + } + if (isDeviceOffline(device.getDeviceImei())) { + throw new ServiceException("设备已断开连接:" + device.getDeviceName()); + } + MultipartFile file = bo.getFile(); + + byte[] largeData = ImageToCArrayConverter.convertImageToCArray(file.getInputStream(), 160, 80, 25600); + log.info("长度:" + largeData.length); + + log.info("原始数据大小: {} 字节", largeData.length); + + int[] ints = convertHexToDecimal(largeData); + RedisUtils.setCacheObject(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + device.getDeviceImei() +DEVICE_BOOT_LOGO_KEY_PREFIX, Arrays.toString(ints), Duration.ofSeconds(5 * 60L)); + + Map payload = Map.of("ins_PicTrans", + 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("上传LOGO失败:" + e.getMessage()); + } + log.info("发送上传开机画面到设备消息=>topic:{},payload:{}", MqttConstants.GLOBAL_PUB_KEY+device.getDeviceImei(),json); + + recordDeviceLog(device.getId(), device.getDeviceName(), "上传开机画面", "上传开机画面", AppLoginHelper.getUserId()); + } catch (Exception e){ + throw new ServiceException("发送指令失败"); + } + } + + /** + * 人员登记 + * @param bo + */ + public boolean registerPersonInfo(AppPersonnelInfoBo bo) { + Long deviceId = bo.getDeviceId(); + Device deviceObj = deviceMapper.selectById(deviceId); + if (deviceObj == null) { + throw new RuntimeException("请先将设备入库!!!"); + } + if (isDeviceOffline(deviceObj.getDeviceImei())) { + throw new ServiceException("设备已断开连接:" + deviceObj.getDeviceName()); + } + QueryWrapper qw = new QueryWrapper() + .eq("device_id", deviceId); + List appPersonnelInfoVos = appPersonnelInfoMapper.selectVoList(qw); + + List list = new ArrayList<>(); + list.add(bo.getUnitName()); + list.add(bo.getName()); + list.add(bo.getPosition()); + list.add(bo.getCode()); + RedisUtils.setCacheList(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + deviceObj.getDeviceImei() + ":app_send_message_data", list); + + Map payload = Map.of("ins_TexTrans", + Collections.singletonList(0)); + String topic = MqttConstants.GLOBAL_PUB_KEY + deviceObj.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 + deviceObj.getDeviceImei(), bo); + + recordDeviceLog(deviceId, deviceObj.getDeviceName(), "人员信息登记", JSON.toJSONString(bo), AppLoginHelper.getUserId()); + if (ObjectUtils.length(appPersonnelInfoVos) == 0) { + AppPersonnelInfo appPersonnelInfo = MapstructUtils.convert(bo, AppPersonnelInfo.class); + return appPersonnelInfoMapper.insertOrUpdate(appPersonnelInfo); + } else { + UpdateWrapper uw = new UpdateWrapper<>(); + uw.eq("device_id", deviceId) + .set("name", bo.getName()) + .set("position", bo.getPosition()) + .set("unit_name", bo.getUnitName()) + .set("code", bo.getCode()); + return appPersonnelInfoMapper.update(null, uw) > 0; + } + } + + /* ---------------------------------- 私有通用方法 ---------------------------------- */ + + 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()); + } + + Integer value = Integer.parseInt(dto.getInstructValue()); + + Map payload = Map.of(payloadKey, + Collections.singletonList(value)); + + 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:{}", topic, json); + String content = resolveGradeDesc("ins_DetectGrade", value); + recordDeviceLog(device.getId(), + device.getDeviceName(), + deviceAction, + content, + AppLoginHelper.getUserId()); + } + + private boolean isDeviceOffline(String imei) { + // 原方法名语义相反,这里取反,使含义更清晰 + return getDeviceStatus(imei); + } + + /** + * 记录设备操作日志 + * @param deviceId 设备ID + * @param content 日志内容 + * @param operator 操作人 + */ + private void recordDeviceLog(Long deviceId,String deviceName, String deviceAction, String content, Long operator) { + try { + // 创建设备日志实体 + com.fuyuanshen.equipment.domain.DeviceLog deviceLog = new com.fuyuanshen.equipment.domain.DeviceLog(); + deviceLog.setDeviceId(deviceId); + deviceLog.setDeviceAction(deviceAction); + deviceLog.setContent(content); + deviceLog.setCreateBy(operator); + deviceLog.setDeviceName(deviceName); + deviceLog.setCreateTime(new Date()); + + // 插入日志记录 + deviceLogMapper.insert(deviceLog); + } catch (Exception e) { + log.error("记录设备操作日志失败: {}", e.getMessage(), e); + } + } + + private boolean getDeviceStatus(String deviceImei) { + String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ deviceImei + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ; + return StringUtils.isBlank(deviceOnlineStatusRedisKey); + } + +} -- 2.43.5