feat(equipment): 实现设备维修记录图片管理和报警处理功能
-重构文件哈希工具类路径并优化上传逻辑,支持秒传 - 新增维修记录图片ID列表字段及删除旧图片逻辑- 设备维修记录查询增加设备名称模糊搜索条件 -日期查询条件添加格式化注解支持 yyyy-MM-dd- MQTT规则中新增SOS与静止报警处理机制 - 实现报警生命周期管理(开始/结束)及Redis缓存控制 - 添加报警信息入库和位置解析功能 - 优化设备状态数据解析与经纬度异步存储逻辑
This commit is contained in:
@ -61,5 +61,10 @@ public class MqttXinghanJson {
|
||||
*/
|
||||
@JsonProperty("sta_latitude")
|
||||
public String stalatitude;
|
||||
/**
|
||||
* 第十二键值对,系统现状,0关机,1仅充电,2开机未充电,3,开机且充电
|
||||
*/
|
||||
@JsonProperty("sta_system")
|
||||
public String stasystem;
|
||||
|
||||
}
|
||||
|
||||
@ -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)
|
||||
*
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -1,28 +0,0 @@
|
||||
package com.fuyuanshen.web.util;
|
||||
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.HexFormat;
|
||||
|
||||
/**
|
||||
* 文件哈希工具类
|
||||
*/
|
||||
public class FileHashUtil {
|
||||
private static final String ALGORITHM = "SHA-256";
|
||||
|
||||
public static String hash(MultipartFile file) throws IOException {
|
||||
MessageDigest digest = DigestUtils.getDigest(ALGORITHM);
|
||||
try (InputStream in = file.getInputStream()) {
|
||||
byte[] buf = new byte[8192];
|
||||
int len;
|
||||
while ((len = in.read(buf)) != -1) {
|
||||
digest.update(buf, 0, len);
|
||||
}
|
||||
}
|
||||
return HexFormat.of().formatHex(digest.digest());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user