From 49e9066033a27176695efacf54a3d17bc7209107 Mon Sep 17 00:00:00 2001 From: DragonWenLong <552045633@qq.com> Date: Tue, 23 Sep 2025 11:31:11 +0800 Subject: [PATCH] =?UTF-8?q?feat(equipment):=20=E5=AE=9E=E7=8E=B0=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E7=BB=B4=E4=BF=AE=E8=AE=B0=E5=BD=95=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=92=8C=E6=8A=A5=E8=AD=A6=E5=A4=84=E7=90=86?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -重构文件哈希工具类路径并优化上传逻辑,支持秒传 - 新增维修记录图片ID列表字段及删除旧图片逻辑- 设备维修记录查询增加设备名称模糊搜索条件 -日期查询条件添加格式化注解支持 yyyy-MM-dd- MQTT规则中新增SOS与静止报警处理机制 - 实现报警生命周期管理(开始/结束)及Redis缓存控制 - 添加报警信息入库和位置解析功能 - 优化设备状态数据解析与经纬度异步存储逻辑 --- .../global/mqtt/base/MqttXinghanJson.java | 5 + .../rule/xinghan/XinghanDeviceDataRule.java | 132 +++++++++++++++++- .../service/device/DeviceDebugService.java | 5 +- .../domain/bo/DeviceRepairRecordsBo.java | 4 + .../DeviceRepairRecordsQueryCriteria.java | 3 + .../impl/DeviceRepairRecordsServiceImpl.java | 26 +++- .../equipment/utils}/FileHashUtil.java | 2 +- .../equipment/DeviceRepairRecordsMapper.xml | 3 + 8 files changed, 168 insertions(+), 12 deletions(-) rename {fys-admin/src/main/java/com/fuyuanshen/web/util => fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/utils}/FileHashUtil.java (95%) 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 3e0737f..4d9513f 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 @@ -61,5 +61,10 @@ public class MqttXinghanJson { */ @JsonProperty("sta_latitude") public String stalatitude; + /** + * 第十二键值对,系统现状,0关机,1仅充电,2开机未充电,3,开机且充电 + */ + @JsonProperty("sta_system") + public String stasystem; } 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 index cf4fbd0..17dce0e 100644 --- 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 @@ -1,33 +1,41 @@ package com.fuyuanshen.global.mqtt.rule.xinghan; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.StrUtil; 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.core.utils.date.DurationUtils; import com.fuyuanshen.common.json.utils.JsonUtils; import com.fuyuanshen.common.redis.utils.RedisUtils; +import com.fuyuanshen.equipment.domain.Device; +import com.fuyuanshen.equipment.domain.bo.DeviceAlarmBo; +import com.fuyuanshen.equipment.domain.vo.DeviceAlarmVo; +import com.fuyuanshen.equipment.mapper.DeviceAlarmMapper; +import com.fuyuanshen.equipment.service.DeviceService; +import com.fuyuanshen.equipment.service.IDeviceAlarmService; 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 com.fuyuanshen.global.mqtt.listener.domain.FunctionAccessStatus; +import lombok.AllArgsConstructor; +import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.beans.factory.annotation.Autowired; import java.time.Duration; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; +import java.util.*; import java.util.concurrent.CompletableFuture; 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.*; import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX; @@ -57,6 +65,9 @@ public class XinghanDeviceDataRule implements MqttMessageRule { @Autowired private ObjectMapper objectMapper; + private final IDeviceAlarmService deviceAlarmService; + private final DeviceService deviceService; + private final DeviceAlarmMapper deviceAlarmMapper; @Override public void execute(MqttRuleContext context) { @@ -99,12 +110,123 @@ public class XinghanDeviceDataRule implements MqttMessageRule { // 异步发送经纬度到Redis asyncSendLocationToRedisWithFuture(deviceImei, deviceStatus.getStalatitude(), deviceStatus.getStalongitude()); + // 保存报警信息 + saveAlarm(deviceImei,deviceStatus); + } catch (Exception e) { log.error("异步发送设备信息到Redis时出错: device={}, error={}", deviceImei, e.getMessage(), e); } }); } + /** + * 入口:保存报警(SOS 与 Shake 完全独立) + */ + public void saveAlarm(String deviceImei, MqttXinghanJson status) { + int sos = Optional.ofNullable(status.getStaSOSGrade()).orElse(0); + // 1. 处理 SOS 报警 + handleSingleAlarm(deviceImei, + sos > 0, + AlarmType.SOS); + int shake = Optional.ofNullable(status.getStaShakeBit()).orElse(0); + // 2. 处理 Shake 报警 + handleSingleAlarm(deviceImei, + shake > 0, + AlarmType.SHAKE); + } + + /** + * 通用:对单个报警源的“开始/结束”生命周期管理 + */ + private void handleSingleAlarm(String deviceImei, boolean nowAlarming, AlarmType type) { + String redisKey = buildAlarmRedisKey(deviceImei, type); + + Long alarmId = RedisUtils.getCacheObject(redisKey); + + // ---------- 情况 1:当前正在报警 ---------- + if (nowAlarming) { + // 已存在未结束报警 -> 什么都不做(同一条报警) + if (alarmId != null) { + // key 还在 -> 同一条报警,只续期 + RedisUtils.setCacheObject(redisKey, alarmId, Duration.ofMinutes(10)); + return; + } + // 不存在 -> 新建 + DeviceAlarmBo bo = createAlarmBo(deviceImei, type); + deviceAlarmService.insertByBo(bo); + RedisUtils.setCacheObject(redisKey, bo.getId(), Duration.ofMinutes(10)); // 5分钟后结束过期 + return; + } + + // ---------- 情况 2:当前不报警 ---------- + if (alarmId != null) { + // 结束它 + finishAlarm(alarmId); + RedisUtils.deleteObject(redisKey); + } + } + + /** + * 结束报警:写库 + */ + private void finishAlarm(Long alarmId) { + DeviceAlarmVo vo = deviceAlarmService.queryById(alarmId); + if (vo == null || vo.getTreatmentState() == 0) { + return; // 已处理或已被删 + } + DeviceAlarmBo bo = BeanUtil.toBean(vo, DeviceAlarmBo.class); + bo.setFinishTime(new Date()); + bo.setDurationTime(DurationUtils.getDurationBetween(vo.getStartTime(), bo.getFinishTime())); + bo.setTreatmentState(0); // 已处理 + deviceAlarmService.updateByBo(bo); + } + + /** + * 新建报警 Bo + */ + private DeviceAlarmBo createAlarmBo(String deviceImei, AlarmType type) { + Device device = deviceService.selectDeviceByImei(deviceImei); + DeviceAlarmBo bo = new DeviceAlarmBo(); + bo.setDeviceId(device.getId()); + bo.setDeviceImei(deviceImei); + bo.setDeviceAction(2); // 自动报警 + bo.setStartTime(new Date()); + bo.setTreatmentState(1); // 未处理 + bo.setTenantId(device.getTenantId()); + + // 报警内容 + bo.setContent("自动报警:" + type.getDesc()); + + // 位置 + String location = RedisUtils.getCacheObject( + GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + deviceImei + DEVICE_LOCATION_KEY_PREFIX); + if (StrUtil.isNotBlank(location)) { + bo.setLocation(JSONObject.parseObject(location).getString("address")); + } + return bo; + } + + /** + * key 构建 + */ + private String buildAlarmRedisKey(String deviceImei, AlarmType type) { + return StrUtil.format("{}{}{}{}:alarm_id", + GLOBAL_REDIS_KEY, DEVICE_KEY_PREFIX, deviceImei, type.getSuffix()); + } + + /** + * 报警类型枚举 + */ + @AllArgsConstructor + @Getter + enum AlarmType { + SOS("_sos", "SOS报警"), + SHAKE("_shake", "静止报警"); + + private final String suffix; + private final String desc; + } + /** * 异步发送位置信息到Redis(使用CompletableFuture) * diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceDebugService.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceDebugService.java index 2f2e6b8..d4b875a 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceDebugService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceDebugService.java @@ -10,18 +10,15 @@ import com.fuyuanshen.app.service.IAppBusinessFileService; import com.fuyuanshen.app.service.IAppOperationVideoService; import com.fuyuanshen.common.core.exception.ServiceException; import com.fuyuanshen.common.satoken.utils.AppLoginHelper; -import com.fuyuanshen.equipment.mapper.DeviceMapper; import com.fuyuanshen.equipment.service.DeviceService; import com.fuyuanshen.system.domain.vo.SysOssVo; import com.fuyuanshen.system.service.ISysOssService; import com.fuyuanshen.web.domain.vo.DeviceInfoVo; -import com.fuyuanshen.web.util.FileHashUtil; -import io.swagger.v3.oas.annotations.Operation; +import com.fuyuanshen.equipment.utils.FileHashUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceRepairRecordsBo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceRepairRecordsBo.java index e134543..e463172 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceRepairRecordsBo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceRepairRecordsBo.java @@ -11,6 +11,8 @@ import lombok.Data; import lombok.EqualsAndHashCode; import jakarta.validation.constraints.*; import java.util.Date; +import java.util.List; + import com.fasterxml.jackson.annotation.JsonFormat; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.web.multipart.MultipartFile; @@ -70,4 +72,6 @@ public class DeviceRepairRecordsBo extends BaseEntity { @Schema(title = "维修后图片") @JsonIgnore private MultipartFile afterFile; + + private List imageIds; } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/DeviceRepairRecordsQueryCriteria.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/DeviceRepairRecordsQueryCriteria.java index 4df5ec6..bf9ba0f 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/DeviceRepairRecordsQueryCriteria.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/DeviceRepairRecordsQueryCriteria.java @@ -7,6 +7,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; import org.springframework.web.multipart.MultipartFile; import java.util.Date; @@ -51,8 +52,10 @@ public class DeviceRepairRecordsQueryCriteria extends BaseEntity { private String repairPerson; @Schema(title = "维修开始时间") + @DateTimeFormat(pattern = "yyyy-MM-dd") private Date repairBeginTime; @Schema(title = "维修结束时间") + @DateTimeFormat(pattern = "yyyy-MM-dd") private Date repairEndTime; @Schema(title = "所属客户") diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceRepairRecordsServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceRepairRecordsServiceImpl.java index 3b838d8..80ff0fe 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceRepairRecordsServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceRepairRecordsServiceImpl.java @@ -18,6 +18,7 @@ import com.fuyuanshen.equipment.domain.vo.DeviceRepairImagesVo; import com.fuyuanshen.equipment.enums.RepairImageType; import com.fuyuanshen.equipment.mapper.DeviceMapper; import com.fuyuanshen.equipment.mapper.DeviceRepairImagesMapper; +import com.fuyuanshen.equipment.utils.FileHashUtil; import com.fuyuanshen.system.domain.vo.SysOssVo; import com.fuyuanshen.system.service.ISysOssService; import lombok.RequiredArgsConstructor; @@ -32,6 +33,7 @@ import com.fuyuanshen.equipment.service.IDeviceRepairRecordsService; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; import java.util.*; /** @@ -178,6 +180,10 @@ public class DeviceRepairRecordsServiceImpl extends ServiceImpl images = new ArrayList<>(2); @@ -200,12 +206,28 @@ public class DeviceRepairRecordsServiceImpl extends ServiceImpl + + and d.device_name like concat('%', TRIM(#{criteria.searchValue}), '%') + and dr.device_id = #{criteria.deviceId}