Merge branch 'main' into dyf-device

This commit is contained in:
2025-09-26 08:36:00 +08:00
14 changed files with 287 additions and 37 deletions

View File

@ -263,18 +263,21 @@ public class AppAuthController {
@GetMapping("/version") @GetMapping("/version")
public R<List<SysDictDataVo>> getAppVersion() { public R<List<SysDictDataVo>> getAppVersion() {
List<SysDictDataVo> list = dictTypeService.selectDictDataByType("app_version"); List<SysDictDataVo> list = dictTypeService.selectDictDataByType("app_version");
list.forEach(d -> { list.forEach(d -> {
String[] arr = d.getRemark().split("\\|"); // 1. 安全拆分
d.setDictLabel(d.getDictLabel()); // ios/android String[] arr = d.getRemark() == null ? new String[0] : d.getRemark().split("\\|");
d.setDictValue(arr[0]); // 版本号 if (arr.length < 2) { // 格式不对
d.setRemark(arr[1]); // 下载地址 log.warn("字典数据 app_version 格式非法dictLabel={}, remark={}", d.getDictLabel(), d.getRemark());
d.setDictValue(""); // 或者 d.setDictValue(d.getDictValue());
d.setRemark(""); // 下载地址留空
return; // 跳过
}
// 2. 正常赋值
d.setDictValue(arr[0].trim()); // 版本号
d.setRemark(arr[1].trim()); // 下载地址
}); });
// 只保留方法体:筛选 label=ios 且版本号 ≥ 2.5.0 的列表
// List<SysDictDataVo> result = list.stream()
// .peek(d -> { String[] a = d.getRemark().split("\\|"); d.setDictValue(a[0]); d.setRemark(a[1]); })
// .filter(d -> "ios".equalsIgnoreCase(d.getDictLabel()))
// .filter(d -> VersionComparator.INSTANCE.compare(d.getDictValue(), "2.5.0") >= 0)
// .toList();
return R.ok(list); return R.ok(list);
} }

View File

@ -10,6 +10,7 @@ import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessAnnotation;
import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessBatcAnnotation; 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.equipment.domain.dto.AppDeviceSendMsgBo;
import com.fuyuanshen.web.domain.Dto.DeviceXinghanInstructDto;
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;
@ -70,7 +71,7 @@ public class AppDeviceXinghanController extends BaseController {
*/ */
@Log(title = "xinghan指令-静电预警档位") @Log(title = "xinghan指令-静电预警档位")
@PostMapping("/DetectGradeSettings") @PostMapping("/DetectGradeSettings")
public R<Void> DetectGradeSettings(@RequestBody DeviceInstructDto params) { public R<Void> DetectGradeSettings(@RequestBody DeviceXinghanInstructDto params) {
// params 转 JSONObject // params 转 JSONObject
appDeviceService.upDetectGradeSettings(params); appDeviceService.upDetectGradeSettings(params);
return R.ok(); return R.ok();
@ -82,7 +83,7 @@ public class AppDeviceXinghanController extends BaseController {
*/ */
@Log(title = "xinghan指令-照明档位") @Log(title = "xinghan指令-照明档位")
@PostMapping("/LightGradeSettings") @PostMapping("/LightGradeSettings")
public R<Void> LightGradeSettings(@RequestBody DeviceInstructDto params) { public R<Void> LightGradeSettings(@RequestBody DeviceXinghanInstructDto params) {
// params 转 JSONObject // params 转 JSONObject
appDeviceService.upLightGradeSettings(params); appDeviceService.upLightGradeSettings(params);
return R.ok(); return R.ok();
@ -94,7 +95,7 @@ public class AppDeviceXinghanController extends BaseController {
*/ */
@Log(title = "xinghan指令-SOS档位s") @Log(title = "xinghan指令-SOS档位s")
@PostMapping("/SOSGradeSettings") @PostMapping("/SOSGradeSettings")
public R<Void> SOSGradeSettings(@RequestBody DeviceInstructDto params) { public R<Void> SOSGradeSettings(@RequestBody DeviceXinghanInstructDto params) {
// params 转 JSONObject // params 转 JSONObject
appDeviceService.upSOSGradeSettings(params); appDeviceService.upSOSGradeSettings(params);
return R.ok(); return R.ok();
@ -106,7 +107,7 @@ public class AppDeviceXinghanController extends BaseController {
*/ */
@Log(title = "xinghan指令-静止报警状态") @Log(title = "xinghan指令-静止报警状态")
@PostMapping("/ShakeBitSettings") @PostMapping("/ShakeBitSettings")
public R<Void> ShakeBitSettings(@RequestBody DeviceInstructDto params) { public R<Void> ShakeBitSettings(@RequestBody DeviceXinghanInstructDto params) {
// params 转 JSONObject // params 转 JSONObject
appDeviceService.upShakeBitSettings(params); appDeviceService.upShakeBitSettings(params);
return R.ok(); return R.ok();

View File

@ -61,5 +61,14 @@ public class MqttXinghanJson {
*/ */
@JsonProperty("sta_latitude") @JsonProperty("sta_latitude")
public String stalatitude; public String stalatitude;
/**
* 第十二键值对系统现状0关机1仅充电2开机未充电,3开机且充电
*/
@JsonProperty("sta_system")
public String stasystem;
/**
* 电量百分比(适配控制列表显示)
*/
public String batteryPercentage;
} }

View File

@ -1,33 +1,42 @@
package com.fuyuanshen.global.mqtt.rule.xinghan; 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.alibaba.fastjson2.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fuyuanshen.common.core.constant.GlobalConstants; import com.fuyuanshen.common.core.constant.GlobalConstants;
import com.fuyuanshen.common.core.utils.StringUtils; 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.json.utils.JsonUtils;
import com.fuyuanshen.common.redis.utils.RedisUtils; 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.GetAddressFromLatUtil;
import com.fuyuanshen.equipment.utils.map.LngLonUtil; import com.fuyuanshen.equipment.utils.map.LngLonUtil;
import com.fuyuanshen.global.mqtt.base.MqttMessageRule; import com.fuyuanshen.global.mqtt.base.MqttMessageRule;
import com.fuyuanshen.global.mqtt.base.MqttRuleContext; import com.fuyuanshen.global.mqtt.base.MqttRuleContext;
import com.fuyuanshen.global.mqtt.base.MqttXinghanJson; 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.XingHanCommandTypeConstants; import com.fuyuanshen.global.mqtt.constants.XingHanCommandTypeConstants;
import com.fuyuanshen.global.mqtt.listener.domain.FunctionAccessStatus; import com.fuyuanshen.global.mqtt.listener.domain.FunctionAccessStatus;
import com.fuyuanshen.web.enums.AlarmTypeEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
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.time.Duration;
import java.util.ArrayList; import java.util.*;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import static com.fuyuanshen.common.core.constant.GlobalConstants.FUNCTION_ACCESS_KEY; 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.*;
import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX; import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX;
@ -57,6 +66,9 @@ public class XinghanDeviceDataRule implements MqttMessageRule {
@Autowired @Autowired
private ObjectMapper objectMapper; private ObjectMapper objectMapper;
private final IDeviceAlarmService deviceAlarmService;
private final DeviceService deviceService;
private final DeviceAlarmMapper deviceAlarmMapper;
@Override @Override
public void execute(MqttRuleContext context) { public void execute(MqttRuleContext context) {
@ -65,7 +77,7 @@ public class XinghanDeviceDataRule implements MqttMessageRule {
// Latitude, longitude // Latitude, longitude
//主灯档位,激光灯档位,电量百分比,充电状态,电池剩余续航时间 //主灯档位,激光灯档位,电量百分比,充电状态,电池剩余续航时间
MqttXinghanJson deviceStatus = objectMapper.convertValue(context.getPayloadDict(), MqttXinghanJson.class); MqttXinghanJson deviceStatus = objectMapper.convertValue(context.getPayloadDict(), MqttXinghanJson.class);
deviceStatus.setBatteryPercentage(deviceStatus.getStaPowerPercent().toString());
// 发送设备状态和位置信息到Redis // 发送设备状态和位置信息到Redis
asyncSendDeviceDataToRedisWithFuture(context.getDeviceImei(),deviceStatus); asyncSendDeviceDataToRedisWithFuture(context.getDeviceImei(),deviceStatus);
RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.OK.getCode(), Duration.ofSeconds(20)); RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.OK.getCode(), Duration.ofSeconds(20));
@ -99,12 +111,113 @@ public class XinghanDeviceDataRule implements MqttMessageRule {
// 异步发送经纬度到Redis // 异步发送经纬度到Redis
asyncSendLocationToRedisWithFuture(deviceImei, deviceStatus.getStalatitude(), deviceStatus.getStalongitude()); asyncSendLocationToRedisWithFuture(deviceImei, deviceStatus.getStalatitude(), deviceStatus.getStalongitude());
// 保存报警信息
saveAlarm(deviceImei,deviceStatus);
} catch (Exception e) { } catch (Exception e) {
log.error("异步发送设备信息到Redis时出错: device={}, error={}", deviceImei, e.getMessage(), 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,
AlarmTypeEnum.SOS);
int shake = Optional.ofNullable(status.getStaShakeBit()).orElse(0);
// 2. 处理 Shake 报警
handleSingleAlarm(deviceImei,
shake > 0,
AlarmTypeEnum.SHAKE);
}
/**
* 通用:对单个报警源的“开始/结束”生命周期管理
*/
private void handleSingleAlarm(String deviceImei, boolean nowAlarming, AlarmTypeEnum 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, AlarmTypeEnum type) {
Device device = deviceService.selectDeviceByImei(deviceImei);
if (device == null) {
return null;
}
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, AlarmTypeEnum type) {
return StrUtil.format("{}{}{}{}:alarm_id",
GLOBAL_REDIS_KEY, DEVICE_KEY_PREFIX, deviceImei, type.getSuffix());
}
/** /**
* 异步发送位置信息到Redis使用CompletableFuture * 异步发送位置信息到Redis使用CompletableFuture
* *

View File

@ -10,6 +10,7 @@ import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessAnnotation;
import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessBatcAnnotation; 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.equipment.domain.dto.AppDeviceSendMsgBo;
import com.fuyuanshen.web.domain.Dto.DeviceXinghanInstructDto;
import com.fuyuanshen.web.domain.vo.DeviceXinghanDetailVo; import com.fuyuanshen.web.domain.vo.DeviceXinghanDetailVo;
import com.fuyuanshen.web.service.device.DeviceXinghanBizService; import com.fuyuanshen.web.service.device.DeviceXinghanBizService;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
@ -80,7 +81,7 @@ public class DeviceXinghanController extends BaseController {
* 3,2,1,0,分别表示高档/中档/低挡/关闭 * 3,2,1,0,分别表示高档/中档/低挡/关闭
*/ */
@PostMapping("/DetectGradeSettings") @PostMapping("/DetectGradeSettings")
public R<Void> DetectGradeSettings(@RequestBody DeviceInstructDto params) { public R<Void> DetectGradeSettings(@RequestBody DeviceXinghanInstructDto params) {
// params 转 JSONObject // params 转 JSONObject
deviceXinghanBizService.upDetectGradeSettings(params); deviceXinghanBizService.upDetectGradeSettings(params);
return R.ok(); return R.ok();
@ -91,7 +92,7 @@ public class DeviceXinghanController extends BaseController {
* 照明档位2,1,0,分别表示弱光/强光/关闭 * 照明档位2,1,0,分别表示弱光/强光/关闭
*/ */
@PostMapping("/LightGradeSettings") @PostMapping("/LightGradeSettings")
public R<Void> LightGradeSettings(@RequestBody DeviceInstructDto params) { public R<Void> LightGradeSettings(@RequestBody DeviceXinghanInstructDto params) {
// params 转 JSONObject // params 转 JSONObject
deviceXinghanBizService.upLightGradeSettings(params); deviceXinghanBizService.upLightGradeSettings(params);
return R.ok(); return R.ok();
@ -102,7 +103,7 @@ public class DeviceXinghanController extends BaseController {
* SOS档位2,1,0, 分别表示红蓝模式/爆闪模式/关闭 * SOS档位2,1,0, 分别表示红蓝模式/爆闪模式/关闭
*/ */
@PostMapping("/SOSGradeSettings") @PostMapping("/SOSGradeSettings")
public R<Void> SOSGradeSettings(@RequestBody DeviceInstructDto params) { public R<Void> SOSGradeSettings(@RequestBody DeviceXinghanInstructDto params) {
// params 转 JSONObject // params 转 JSONObject
deviceXinghanBizService.upSOSGradeSettings(params); deviceXinghanBizService.upSOSGradeSettings(params);
return R.ok(); return R.ok();
@ -113,7 +114,7 @@ public class DeviceXinghanController extends BaseController {
* 静止报警状态0-未静止报警1-正在静止报警。 * 静止报警状态0-未静止报警1-正在静止报警。
*/ */
@PostMapping("/ShakeBitSettings") @PostMapping("/ShakeBitSettings")
public R<Void> ShakeBitSettings(@RequestBody DeviceInstructDto params) { public R<Void> ShakeBitSettings(@RequestBody DeviceXinghanInstructDto params) {
// params 转 JSONObject // params 转 JSONObject
deviceXinghanBizService.upShakeBitSettings(params); deviceXinghanBizService.upShakeBitSettings(params);
return R.ok(); return R.ok();

View File

@ -0,0 +1,15 @@
package com.fuyuanshen.web.domain.Dto;
import lombok.Data;
@Data
public class DeviceXinghanInstructDto {
private Long deviceId;
private String deviceImei;
/**
* 下发指令
*/
private String instructValue;
private Boolean isBluetooth = false;
}

View File

@ -0,0 +1,17 @@
package com.fuyuanshen.web.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 报警类型枚举
*/
@AllArgsConstructor
@Getter
public enum AlarmTypeEnum {
SOS("_sos", "SOS报警"),
SHAKE("_shake", "静止报警");
private final String suffix;
private final String desc;
}

View File

@ -10,18 +10,15 @@ import com.fuyuanshen.app.service.IAppBusinessFileService;
import com.fuyuanshen.app.service.IAppOperationVideoService; import com.fuyuanshen.app.service.IAppOperationVideoService;
import com.fuyuanshen.common.core.exception.ServiceException; import com.fuyuanshen.common.core.exception.ServiceException;
import com.fuyuanshen.common.satoken.utils.AppLoginHelper; import com.fuyuanshen.common.satoken.utils.AppLoginHelper;
import com.fuyuanshen.equipment.mapper.DeviceMapper;
import com.fuyuanshen.equipment.service.DeviceService; import com.fuyuanshen.equipment.service.DeviceService;
import com.fuyuanshen.system.domain.vo.SysOssVo; import com.fuyuanshen.system.domain.vo.SysOssVo;
import com.fuyuanshen.system.service.ISysOssService; import com.fuyuanshen.system.service.ISysOssService;
import com.fuyuanshen.web.domain.vo.DeviceInfoVo; import com.fuyuanshen.web.domain.vo.DeviceInfoVo;
import com.fuyuanshen.web.util.FileHashUtil; import com.fuyuanshen.equipment.utils.FileHashUtil;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.io.IOException; import java.io.IOException;

View File

@ -1,6 +1,7 @@
package com.fuyuanshen.web.service.device; package com.fuyuanshen.web.service.device;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
@ -28,17 +29,21 @@ import com.fuyuanshen.common.redis.utils.RedisUtils;
import com.fuyuanshen.common.satoken.utils.AppLoginHelper; import com.fuyuanshen.common.satoken.utils.AppLoginHelper;
import com.fuyuanshen.equipment.domain.Device; import com.fuyuanshen.equipment.domain.Device;
import com.fuyuanshen.equipment.domain.DeviceType; import com.fuyuanshen.equipment.domain.DeviceType;
import com.fuyuanshen.equipment.domain.bo.DeviceAlarmBo;
import com.fuyuanshen.equipment.domain.dto.AppDeviceSendMsgBo; import com.fuyuanshen.equipment.domain.dto.AppDeviceSendMsgBo;
import com.fuyuanshen.equipment.enums.LightModeEnum; import com.fuyuanshen.equipment.enums.LightModeEnum;
import com.fuyuanshen.equipment.mapper.DeviceLogMapper; import com.fuyuanshen.equipment.mapper.DeviceLogMapper;
import com.fuyuanshen.equipment.mapper.DeviceMapper; import com.fuyuanshen.equipment.mapper.DeviceMapper;
import com.fuyuanshen.equipment.mapper.DeviceTypeMapper; import com.fuyuanshen.equipment.mapper.DeviceTypeMapper;
import com.fuyuanshen.equipment.service.IDeviceAlarmService;
import com.fuyuanshen.global.mqtt.base.MqttXinghanJson; import com.fuyuanshen.global.mqtt.base.MqttXinghanJson;
import com.fuyuanshen.global.mqtt.config.MqttGateway; import com.fuyuanshen.global.mqtt.config.MqttGateway;
import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants; import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants;
import com.fuyuanshen.global.mqtt.constants.MqttConstants; import com.fuyuanshen.global.mqtt.constants.MqttConstants;
import com.fuyuanshen.web.domain.Dto.DeviceDebugLogoUploadDto; import com.fuyuanshen.web.domain.Dto.DeviceDebugLogoUploadDto;
import com.fuyuanshen.web.domain.Dto.DeviceXinghanInstructDto;
import com.fuyuanshen.web.domain.vo.DeviceXinghanDetailVo; import com.fuyuanshen.web.domain.vo.DeviceXinghanDetailVo;
import com.fuyuanshen.web.enums.AlarmTypeEnum;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -68,6 +73,7 @@ public class DeviceXinghanBizService {
private final MqttGateway mqttGateway; private final MqttGateway mqttGateway;
private final DeviceLogMapper deviceLogMapper; private final DeviceLogMapper deviceLogMapper;
private final AppPersonnelInfoRecordsMapper appPersonnelInfoRecordsMapper; private final AppPersonnelInfoRecordsMapper appPersonnelInfoRecordsMapper;
private final IDeviceAlarmService deviceAlarmService;
@Autowired @Autowired
private ObjectMapper objectMapper; private ObjectMapper objectMapper;
@ -95,28 +101,39 @@ public class DeviceXinghanBizService {
/** /**
* 设置静电预警档位 * 设置静电预警档位
*/ */
public void upDetectGradeSettings(DeviceInstructDto dto) { public void upDetectGradeSettings(DeviceXinghanInstructDto dto) {
sendCommand(dto, "ins_DetectGrade","静电预警档位"); sendCommand(dto, "ins_DetectGrade","静电预警档位");
} }
/** /**
* 设置照明档位 * 设置照明档位
*/ */
public void upLightGradeSettings(DeviceInstructDto dto) { public void upLightGradeSettings(DeviceXinghanInstructDto dto) {
sendCommand(dto, "ins_LightGrade","照明档位"); sendCommand(dto, "ins_LightGrade","照明档位");
} }
/** /**
* 设置SOS档位 * 设置SOS档位
*/ */
public void upSOSGradeSettings(DeviceInstructDto dto) { public void upSOSGradeSettings(DeviceXinghanInstructDto dto) {
if(dto.getIsBluetooth()){
long deviceId = dto.getDeviceId();
// 1. 使用Optional简化空值检查使代码更简洁
Device device = Optional.ofNullable(deviceMapper.selectById(deviceId))
.orElseThrow(() -> new ServiceException("设备不存在"));
int sosGrade = Integer.parseInt(dto.getInstructValue());
// 6. 新建报警信息
createAlarm(device.getId(),device.getDeviceImei(),"ins_SOSGrade",sosGrade);
}else {
sendCommand(dto, "ins_SOSGrade","SOS档位"); sendCommand(dto, "ins_SOSGrade","SOS档位");
} }
}
/** /**
* 设置强制报警 * 设置强制报警
*/ */
public void upShakeBitSettings(DeviceInstructDto dto) { public void upShakeBitSettings(DeviceXinghanInstructDto dto) {
sendCommand(dto, "ins_ShakeBit","强制报警"); sendCommand(dto, "ins_ShakeBit","强制报警");
} }
@ -465,7 +482,7 @@ public class DeviceXinghanBizService {
* @param payloadKey 指令负载数据的键名 * @param payloadKey 指令负载数据的键名
* @param deviceAction 设备操作类型描述 * @param deviceAction 设备操作类型描述
*/ */
private void sendCommand(DeviceInstructDto dto, String payloadKey, String deviceAction) { private void sendCommand(DeviceXinghanInstructDto dto, String payloadKey, String deviceAction) {
long deviceId = dto.getDeviceId(); long deviceId = dto.getDeviceId();
// 1. 使用Optional简化空值检查使代码更简洁 // 1. 使用Optional简化空值检查使代码更简洁
@ -509,6 +526,9 @@ public class DeviceXinghanBizService {
deviceAction, deviceAction,
content, content,
AppLoginHelper.getUserId()); AppLoginHelper.getUserId());
// 6. 新建报警信息
createAlarm(device.getId(),deviceImei,payloadKey,value);
} }
// private boolean isDeviceOffline(String imei) { // private boolean isDeviceOffline(String imei) {
@ -516,6 +536,48 @@ public class DeviceXinghanBizService {
// return getDeviceStatus(imei); // return getDeviceStatus(imei);
// } // }
/**
* 新建报警 Bo
*/
private void createAlarm(Long deviceId, String deviceImei,
String payloadKey, int value) {
// 这里直接放你原来的 createAlarmBo 全部逻辑
if (!"ins_SOSGrade".equals(payloadKey) || value == 0) {
return;
}
AlarmTypeEnum type = value == 1 ? AlarmTypeEnum.SOS : AlarmTypeEnum.SHAKE;
String redisKey = buildAlarmRedisKey(deviceImei, type);
Long alarmId = RedisUtils.getCacheObject(redisKey);
// 已存在未结束报警 -> 什么都不做(同一条报警)
if (alarmId != null) {
// key 还在 -> 同一条报警,只续期
RedisUtils.setCacheObject(redisKey, alarmId, Duration.ofMinutes(10));
return;
}
// 不存在 -> 新建
DeviceAlarmBo bo = new DeviceAlarmBo();
bo.setDeviceId(deviceId);
bo.setDeviceImei(deviceImei);
bo.setDeviceAction(0); // 强制报警
bo.setStartTime(new Date());
bo.setTreatmentState(1); // 未处理
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"));
}
deviceAlarmService.insertByBo(bo);
RedisUtils.setCacheObject(redisKey, bo.getId(), Duration.ofMinutes(10));
}
/**
* key 构建
*/
private String buildAlarmRedisKey(String deviceImei, AlarmTypeEnum type) {
return StrUtil.format("{}{}{}{}:alarm_id",
GLOBAL_REDIS_KEY, DEVICE_KEY_PREFIX, deviceImei, type.getSuffix());
}
/** /**
* 记录设备操作日志 * 记录设备操作日志
* @param deviceId 设备ID * @param deviceId 设备ID

View File

@ -11,6 +11,8 @@ import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import java.util.Date; import java.util.Date;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
@ -70,4 +72,6 @@ public class DeviceRepairRecordsBo extends BaseEntity {
@Schema(title = "维修后图片") @Schema(title = "维修后图片")
@JsonIgnore @JsonIgnore
private MultipartFile afterFile; private MultipartFile afterFile;
private List<Long> imageIds;
} }

View File

@ -7,6 +7,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.util.Date; import java.util.Date;
@ -51,8 +52,10 @@ public class DeviceRepairRecordsQueryCriteria extends BaseEntity {
private String repairPerson; private String repairPerson;
@Schema(title = "维修开始时间") @Schema(title = "维修开始时间")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date repairBeginTime; private Date repairBeginTime;
@Schema(title = "维修结束时间") @Schema(title = "维修结束时间")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date repairEndTime; private Date repairEndTime;
@Schema(title = "所属客户") @Schema(title = "所属客户")

View File

@ -18,6 +18,7 @@ import com.fuyuanshen.equipment.domain.vo.DeviceRepairImagesVo;
import com.fuyuanshen.equipment.enums.RepairImageType; import com.fuyuanshen.equipment.enums.RepairImageType;
import com.fuyuanshen.equipment.mapper.DeviceMapper; import com.fuyuanshen.equipment.mapper.DeviceMapper;
import com.fuyuanshen.equipment.mapper.DeviceRepairImagesMapper; import com.fuyuanshen.equipment.mapper.DeviceRepairImagesMapper;
import com.fuyuanshen.equipment.utils.FileHashUtil;
import com.fuyuanshen.system.domain.vo.SysOssVo; import com.fuyuanshen.system.domain.vo.SysOssVo;
import com.fuyuanshen.system.service.ISysOssService; import com.fuyuanshen.system.service.ISysOssService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -32,6 +33,7 @@ import com.fuyuanshen.equipment.service.IDeviceRepairRecordsService;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.*; import java.util.*;
/** /**
@ -178,6 +180,10 @@ public class DeviceRepairRecordsServiceImpl extends ServiceImpl<DeviceRepairReco
if (!updated) { if (!updated) {
return false; return false;
} }
// 3. 删除旧图片
if(bo.getImageIds() != null){
imagesMapper.deleteByIds(bo.getImageIds());
}
// 3. 收集需要保存的图片 // 3. 收集需要保存的图片
List<DeviceRepairImages> images = new ArrayList<>(2); List<DeviceRepairImages> images = new ArrayList<>(2);
@ -200,12 +206,28 @@ public class DeviceRepairRecordsServiceImpl extends ServiceImpl<DeviceRepairReco
if (file == null || file.isEmpty()) { if (file == null || file.isEmpty()) {
return; return;
} }
SysOssVo ossVo = ossService.upload(file);
// 1. 计算文件哈希
String hash = null;
try {
hash = FileHashUtil.hash(file);
} catch (IOException e) {
throw new RuntimeException(e);
}
// 2. 先根据 hash 查库(秒传)
SysOssVo exist = ossService.selectByHash(hash);
if (exist == null) {
// 2.2 不存在,真正上传
exist = ossService.upload(file);
// 2.3 把 hash 写回记录(供下次去重)
ossService.updateHashById(exist.getOssId(), hash);
}
DeviceRepairImages image = new DeviceRepairImages(); DeviceRepairImages image = new DeviceRepairImages();
image.setRecordId(recordId); image.setRecordId(recordId);
image.setImageType(imageType); image.setImageType(imageType);
image.setImageUrl(ossVo.getUrl()); image.setImageUrl(exist.getUrl());
list.add(image); list.add(image);
} }

View File

@ -1,4 +1,4 @@
package com.fuyuanshen.web.util; package com.fuyuanshen.equipment.utils;
import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;

View File

@ -23,6 +23,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
FROM device_repair_records dr FROM device_repair_records dr
JOIN device d ON dr.device_id = d.id JOIN device d ON dr.device_id = d.id
<where> <where>
<if test="criteria.searchValue != null">
and d.device_name like concat('%', TRIM(#{criteria.searchValue}), '%')
</if>
<if test="criteria.deviceId != null"> <if test="criteria.deviceId != null">
and dr.device_id = #{criteria.deviceId} and dr.device_id = #{criteria.deviceId}
</if> </if>