From f839883f82b3f180bb20033af3bc380242895b4d Mon Sep 17 00:00:00 2001 From: DragonWenLong <552045633@qq.com> Date: Mon, 25 Aug 2025 14:18:54 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat(device):=20=E6=96=B0=E5=A2=9E=E5=8F=91?= =?UTF-8?q?=E9=80=81=E7=B4=A7=E6=80=A5=E9=80=9A=E7=9F=A5=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 AppDeviceXinghanController 中添加 sendAlarmMessage接口 - 在 DeviceXinghanBizService 中实现 sendAlarmMessage 方法 - 新增 XinghanSendAlarmMessageRule 类用于处理紧急通知发送逻辑 - 在 DeviceRedisKeyConstants 中添加 DEVICE_ALARM_MESSAGE_KEY_PREFIX 常量 - 修改 XinghanDeviceDataRule 和 XinghanSendMsgRule 中的相关逻辑 --- .../device/AppDeviceXinghanController.java | 11 ++ .../constants/DeviceRedisKeyConstants.java | 5 + .../rule/xinghan/XinghanDeviceDataRule.java | 5 + .../xinghan/XinghanSendAlarmMessageRule.java | 118 ++++++++++++++++++ .../mqtt/rule/xinghan/XinghanSendMsgRule.java | 2 +- .../device/DeviceXinghanBizService.java | 62 ++++++++- 6 files changed, 199 insertions(+), 4 deletions(-) create mode 100644 fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanSendAlarmMessageRule.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 index b4f53c51..85fb39a0 100644 --- 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 @@ -6,7 +6,9 @@ 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.ratelimiter.annotation.FunctionAccessBatcAnnotation; import com.fuyuanshen.common.web.core.BaseController; +import com.fuyuanshen.equipment.domain.dto.AppDeviceSendMsgBo; import com.fuyuanshen.web.service.device.DeviceBJQBizService; import com.fuyuanshen.web.service.device.DeviceXinghanBizService; import lombok.RequiredArgsConstructor; @@ -33,6 +35,15 @@ public class AppDeviceXinghanController extends BaseController { return toAjax(appDeviceService.registerPersonInfo(bo)); } + /** + * 发送紧急通知 + */ + @PostMapping(value = "/sendAlarmMessage") + @FunctionAccessBatcAnnotation(value = "sendAlarmMessage", timeOut = 5, batchMaxTimeOut = 10) + public R sendAlarmMessage(@RequestBody AppDeviceSendMsgBo bo) { + return toAjax(appDeviceService.sendAlarmMessage(bo)); + } + /** * 上传设备logo图片 */ diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/constants/DeviceRedisKeyConstants.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/constants/DeviceRedisKeyConstants.java index 080debc7..99f9b5d6 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/constants/DeviceRedisKeyConstants.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/constants/DeviceRedisKeyConstants.java @@ -47,4 +47,9 @@ public class DeviceRedisKeyConstants { * 告警 */ public static final String DEVICE_ALARM_KEY_PREFIX = ":alarm"; + + /** + * 告警信息 + */ + public static final String DEVICE_ALARM_MESSAGE_KEY_PREFIX = ":alarmMessage"; } 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 e12c0665..23f92be0 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 @@ -14,11 +14,13 @@ 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.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; @@ -58,6 +60,7 @@ public class XinghanDeviceDataRule implements MqttMessageRule { @Override public void execute(MqttRuleContext context) { + String functionAccess = FUNCTION_ACCESS_KEY + context.getDeviceImei(); try { // Latitude, longitude //主灯档位,激光灯档位,电量百分比,充电状态,电池剩余续航时间 @@ -65,8 +68,10 @@ public class XinghanDeviceDataRule implements MqttMessageRule { // 发送设备状态和位置信息到Redis asyncSendDeviceDataToRedisWithFuture(context.getDeviceImei(),deviceStatus); + RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.OK.getCode(), Duration.ofSeconds(20)); } catch (Exception e) { log.error("处理上报数据命令时出错", e); + RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.FAILED.getCode(), Duration.ofSeconds(20)); } } diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanSendAlarmMessageRule.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanSendAlarmMessageRule.java new file mode 100644 index 00000000..4134e783 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanSendAlarmMessageRule.java @@ -0,0 +1,118 @@ +package com.fuyuanshen.global.mqtt.rule.xinghan; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fuyuanshen.common.json.utils.JsonUtils; +import com.fuyuanshen.common.redis.utils.RedisUtils; +import com.fuyuanshen.global.mqtt.base.MqttMessageRule; +import com.fuyuanshen.global.mqtt.base.MqttRuleContext; +import com.fuyuanshen.global.mqtt.config.MqttGateway; +import com.fuyuanshen.global.mqtt.constants.MqttConstants; +import com.fuyuanshen.global.mqtt.constants.XingHanCommandTypeConstants; +import com.fuyuanshen.global.mqtt.listener.domain.FunctionAccessStatus; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.nio.charset.Charset; +import java.time.Duration; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.fuyuanshen.common.core.constant.GlobalConstants.FUNCTION_ACCESS_KEY; +import static com.fuyuanshen.common.core.constant.GlobalConstants.GLOBAL_REDIS_KEY; +import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_ALARM_MESSAGE_KEY_PREFIX; +import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX; + +/** + * 星汉设备发送紧急通知 下发规则: + *

+ * 1. 设备上行 sta_BreakNews=cover! => 仅标记成功
+ * 2. 设备上行 sta_BreakNews=数字 => GBK编码,每行文字为一包,一共4包,第一字节为包序号 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class XinghanSendAlarmMessageRule implements MqttMessageRule { + + private final MqttGateway mqttGateway; + private final ObjectMapper objectMapper; + + @Override + public String getCommandType() { + return XingHanCommandTypeConstants.XingHan_BREAK_NEWS; + } + + @Override + public void execute(MqttRuleContext ctx) { + String functionAccess = FUNCTION_ACCESS_KEY + ctx.getDeviceImei(); + try { + XinghanSendAlarmMessageRule.MqttXinghanAlarmMsgJson payload = objectMapper.convertValue( + ctx.getPayloadDict(), XinghanSendAlarmMessageRule.MqttXinghanAlarmMsgJson.class); + + String respText = payload.getStaBreakNews(); + log.info("设备上报紧急通知握手: {} ", respText); + + // 1. cover! —— 成功标记 + if ("cover!".equalsIgnoreCase(respText)) { + RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.OK.getCode(), Duration.ofSeconds(20)); + log.info("设备 {} 发送紧急通知完成", ctx.getDeviceImei()); + return; + } + // 2. 数字 —— 下发数据块 + int blockIndex; + try { + blockIndex = Integer.parseInt(respText); + } catch (NumberFormatException ex) { + log.warn("设备 {} 紧急通知上报非法块号:{}", ctx.getDeviceImei(), respText); + return; + } + // 将发送的信息原文本以List形式存储在Redis中 + String data = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + ctx.getDeviceImei() + DEVICE_ALARM_MESSAGE_KEY_PREFIX); + if (data.isEmpty()) { + return; + } + // + ArrayList intData = new ArrayList<>(); + intData.add(blockIndex); + // 获取块原内容 转成GBK 再转成无符号十进制整数 + String blockTxt = data.split(",")[blockIndex-1]; + // 再按 GBK 编码把字符串转成字节数组,并逐个转为无符号十进制整数 + for (byte b : blockTxt.getBytes(GBK)) { + intData.add(b & 0xFF); // b & 0xFF 得到 0~255 的整数 + } + + Map map = new HashMap<>(); + map.put("ins_BreakNews", intData); + + String topic = MqttConstants.GLOBAL_PUB_KEY + ctx.getDeviceImei(); + String json = JsonUtils.toJsonString(map); + mqttGateway.sendMsgToMqtt(topic, 1, json); + log.info("发送设备紧急通知=>topic:{},payload:{}", + MqttConstants.GLOBAL_PUB_KEY + ctx.getDeviceImei(), + JsonUtils.toJsonString(map)); + + } catch (Exception e) { + log.error("处理发送设备紧急通知时出错", e); + RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.FAILED.getCode(), Duration.ofSeconds(20)); + } + } + + private static final Charset GBK = Charset.forName("GBK"); + + /* ---------- DTO ---------- */ + + @Data + private static class MqttXinghanAlarmMsgJson { + /** + * 设备上行: + * 数字 -> 请求对应块号 + * cover! -> 写入成功 + */ + @JsonProperty("sta_BreakNews") + private String staBreakNews; + } +} 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 index 68acb099..d45d5b3c 100644 --- 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 @@ -57,7 +57,7 @@ public class XinghanSendMsgRule implements MqttMessageRule { // 1. genius! —— 成功标记 if ("genius!".equalsIgnoreCase(respText)) { - RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.FAILED.getCode(), Duration.ofSeconds(20)); + RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.OK.getCode(), Duration.ofSeconds(20)); log.info("设备 {} 发送消息完成", ctx.getDeviceImei()); return; } 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 index 40c2d7af..fc69d57b 100644 --- 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 @@ -39,8 +39,7 @@ import static com.fuyuanshen.common.core.constant.GlobalConstants.GLOBAL_REDIS_K import static com.fuyuanshen.common.core.utils.Bitmap80x12Generator.buildArr; import static com.fuyuanshen.common.core.utils.Bitmap80x12Generator.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; +import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.*; @Slf4j @Service @@ -62,7 +61,7 @@ public class DeviceXinghanBizService { "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, "正在静止报警") + "ins_ShakeBit", Map.of(1, "开启报警") // 再加 4、5、6…… 档,直接往 Map 里塞即可 ); @@ -166,6 +165,7 @@ public class DeviceXinghanBizService { list.add(bo.getPosition()); list.add(bo.getCode()); RedisUtils.setCacheList(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + deviceObj.getDeviceImei() + ":app_send_message_data", list); + RedisUtils.expire(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + deviceObj.getDeviceImei() + ":app_send_message_data", Duration.ofSeconds(5 * 60L)); Map payload = Map.of("ins_TexTrans", Collections.singletonList(0)); @@ -195,6 +195,62 @@ public class DeviceXinghanBizService { } } + /** + * 发送报警信息 + * @param bo + * @return + */ + public int sendAlarmMessage(AppDeviceSendMsgBo bo) { + try { + List deviceIds = bo.getDeviceIds(); + if (deviceIds == null || deviceIds.isEmpty()) { + throw new ServiceException("请选择设备"); + } + + for (Long deviceId : deviceIds) { + Device device = deviceMapper.selectById(deviceId); + if (device == null) { + throw new ServiceException("设备不存在" + deviceId); + } + if(isDeviceOffline(device.getDeviceImei())){ + throw new ServiceException(device.getDeviceName()+",设备已断开连接"); + } + try { + + RedisUtils.setCacheObject(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + device.getDeviceImei() + DEVICE_ALARM_MESSAGE_KEY_PREFIX, bo.getSendMsg(),Duration.ofSeconds(5 * 60L)); + + Map payload = Map.of("ins_BreakNews", + Collections.singletonList(0)); + String topic = MqttConstants.GLOBAL_PUB_KEY + device.getDeviceImei(); + String json = JsonUtils.toJsonString(payload); + + try { + mqttGateway.sendMsgToMqtt(topic, 1, json); + } catch (Exception e) { + log.error("发送紧急通知失败, topic={}, payload={}", topic, json, e); + throw new ServiceException("发送紧急通知失败:" + e.getMessage()); + } + log.info("发送紧急通知=>topic:{},payload:{}", MqttConstants.GLOBAL_PUB_KEY+device.getDeviceImei(),json); + + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("id", deviceId) + .eq("binding_user_id", AppLoginHelper.getUserId()) + .set("send_msg", bo.getSendMsg()); + deviceMapper.update(updateWrapper); + recordDeviceLog(device.getId(), device.getDeviceName(), "发送紧急通知", bo.getSendMsg(), AppLoginHelper.getUserId()); + } catch (Exception e) { + log.info("设备发送告警信息信息失败:{}" ,deviceId); + throw new ServiceException("设备发送告警信息信息失败"); + } + + } + } catch (Exception e){ + e.printStackTrace(); + throw new ServiceException("发送告警信息指令失败"); + } + return 1; + } + /* ---------------------------------- 私有通用方法 ---------------------------------- */ private void sendCommand(DeviceInstructDto dto, From a5b8cdffec5b5f014eaa30f7a3d3ca49bc304c65 Mon Sep 17 00:00:00 2001 From: DragonWenLong <552045633@qq.com> Date: Tue, 26 Aug 2025 17:12:36 +0800 Subject: [PATCH 2/5] =?UTF-8?q?refactor(device):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E5=8F=91=E9=80=81=E5=91=8A=E8=AD=A6=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 批量查询设备,减少数据库交互次数 - 优化设备状态检查逻辑,提高效率 - 封装单个设备发送告警信息的逻辑,提高代码可读性- 使用 Redis 和 MQTT 时增加异常处理,提高系统稳定性 - 优化日志记录和异常提示,便于问题排查 --- .../rule/xinghan/XinghanBootLogoRule.java | 12 ++ .../xinghan/XinghanSendAlarmMessageRule.java | 2 +- .../device/DeviceXinghanBizService.java | 164 +++++++++++------- 3 files changed, 116 insertions(+), 62 deletions(-) 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 index 96faf308..9109b942 100644 --- 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 @@ -114,13 +114,24 @@ public class XinghanBootLogoRule implements MqttMessageRule { private static final int CHUNK_SIZE = 256; + /** + * 计算数据的CRC32校验值,并将结果转换为整数列表 + * + * @param data 需要计算CRC32校验值的字节数组 + * @return 包含CRC32校验值的整数列表,每个字节对应一个无符号整数 + */ private static ArrayList crc32AsList(byte[] data) { + // 计算CRC32校验值 CRC32 crc = new CRC32(); crc.update(data); + + // 将CRC32值转换为字节数组 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)); @@ -128,6 +139,7 @@ public class XinghanBootLogoRule implements MqttMessageRule { return list; } + /* ---------- DTO ---------- */ @Data diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanSendAlarmMessageRule.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanSendAlarmMessageRule.java index 4134e783..21f9c124 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanSendAlarmMessageRule.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanSendAlarmMessageRule.java @@ -79,7 +79,7 @@ public class XinghanSendAlarmMessageRule implements MqttMessageRule { ArrayList intData = new ArrayList<>(); intData.add(blockIndex); // 获取块原内容 转成GBK 再转成无符号十进制整数 - String blockTxt = data.split(",")[blockIndex-1]; + String blockTxt = data.split("\\|")[blockIndex-1]; // 再按 GBK 编码把字符串转成字节数组,并逐个转为无符号十进制整数 for (byte b : blockTxt.getBytes(GBK)) { intData.add(b & 0xFF); // b & 0xFF 得到 0~255 的整数 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 index fc69d57b..aadd4148 100644 --- 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 @@ -201,88 +201,130 @@ public class DeviceXinghanBizService { * @return */ public int sendAlarmMessage(AppDeviceSendMsgBo bo) { + List deviceIds = bo.getDeviceIds(); + + // 1. 简化非空检查和抛出异常 + if (deviceIds == null || deviceIds.isEmpty()) { + throw new ServiceException("请选择设备"); + } + QueryWrapper queryWrapper = new QueryWrapper<>(); + // 使用 in 语句根据id集合查询 + queryWrapper.in("id", deviceIds); + // 2. 将批量查询设备,减少数据库交互次数 + List devices = deviceMapper.selectList(queryWrapper); + if (devices.size() != deviceIds.size()) { + // 如果查询回来的设备数量不一致,说明有设备不存在,此处可以优化为更详细的提示 + throw new ServiceException("部分设备不存在"); + } + try { - List deviceIds = bo.getDeviceIds(); - if (deviceIds == null || deviceIds.isEmpty()) { - throw new ServiceException("请选择设备"); + for (Device device : devices) { + String deviceImei = device.getDeviceImei(); + String deviceName = device.getDeviceName(); + + // 3. 在循环中进行设备状态检查,快速失败 + if (isDeviceOffline(deviceImei)) { + // 如果设备离线,可以选择继续处理下一个设备,或者抛出异常。这里选择抛出异常。 + throw new ServiceException(deviceName + ", 设备已断开连接"); + } + + // 4. 将Redis和MQTT操作封装在一个方法中,提高可读性 + sendSingleAlarmMessage(device, bo.getSendMsg()); + + // 5. 批量更新设备状态,提高效率 + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("id", device.getId()) + .eq("binding_user_id", AppLoginHelper.getUserId()) + .set("send_msg", bo.getSendMsg()); + deviceMapper.update(updateWrapper); + + // 6. 记录操作日志 + recordDeviceLog(device.getId(), deviceName, "发送紧急通知", bo.getSendMsg(), AppLoginHelper.getUserId()); } - - for (Long deviceId : deviceIds) { - Device device = deviceMapper.selectById(deviceId); - if (device == null) { - throw new ServiceException("设备不存在" + deviceId); - } - if(isDeviceOffline(device.getDeviceImei())){ - throw new ServiceException(device.getDeviceName()+",设备已断开连接"); - } - try { - - RedisUtils.setCacheObject(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + device.getDeviceImei() + DEVICE_ALARM_MESSAGE_KEY_PREFIX, bo.getSendMsg(),Duration.ofSeconds(5 * 60L)); - - Map payload = Map.of("ins_BreakNews", - Collections.singletonList(0)); - String topic = MqttConstants.GLOBAL_PUB_KEY + device.getDeviceImei(); - String json = JsonUtils.toJsonString(payload); - - try { - mqttGateway.sendMsgToMqtt(topic, 1, json); - } catch (Exception e) { - log.error("发送紧急通知失败, topic={}, payload={}", topic, json, e); - throw new ServiceException("发送紧急通知失败:" + e.getMessage()); - } - log.info("发送紧急通知=>topic:{},payload:{}", MqttConstants.GLOBAL_PUB_KEY+device.getDeviceImei(),json); - - UpdateWrapper updateWrapper = new UpdateWrapper<>(); - updateWrapper.eq("id", deviceId) - .eq("binding_user_id", AppLoginHelper.getUserId()) - .set("send_msg", bo.getSendMsg()); - deviceMapper.update(updateWrapper); - recordDeviceLog(device.getId(), device.getDeviceName(), "发送紧急通知", bo.getSendMsg(), AppLoginHelper.getUserId()); - } catch (Exception e) { - log.info("设备发送告警信息信息失败:{}" ,deviceId); - throw new ServiceException("设备发送告警信息信息失败"); - } - - } - } catch (Exception e){ - e.printStackTrace(); + } catch (ServiceException e) { + // 捕获并重新抛出自定义异常,避免内层异常被外层泛化捕获 + log.error("发送告警信息指令失败: {}", e.getMessage(), e); + throw e; + } catch (Exception e) { + log.error("发送告警信息指令发生未知错误", e); throw new ServiceException("发送告警信息指令失败"); } + return 1; } - /* ---------------------------------- 私有通用方法 ---------------------------------- */ - 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()); - } + /** + * 封装单个设备发送告警信息的逻辑 + */ + private void sendSingleAlarmMessage(Device device, String message) { + String deviceImei = device.getDeviceImei(); - Integer value = Integer.parseInt(dto.getInstructValue()); + // 缓存告警消息到Redis + RedisUtils.setCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + deviceImei + DEVICE_ALARM_MESSAGE_KEY_PREFIX, message, Duration.ofSeconds(5 * 60L)); - Map payload = Map.of(payloadKey, - Collections.singletonList(value)); - - String topic = MqttConstants.GLOBAL_PUB_KEY + device.getDeviceImei(); + // 构建并发送MQTT消息 + Map payload = Map.of("ins_BreakNews", Collections.singletonList(0)); + String topic = MqttConstants.GLOBAL_PUB_KEY + deviceImei; String json = JsonUtils.toJsonString(payload); try { mqttGateway.sendMsgToMqtt(topic, 1, json); + log.info("发送紧急通知成功 => topic:{}, payload:{}", topic, json); + } catch (Exception e) { + log.error("发送紧急通知失败, topic={}, payload={}", topic, json, e); + throw new ServiceException("发送紧急通知失败:" + e.getMessage()); + } + } + + /** + * 发送设备控制指令 + * + * @param dto 设备指令数据传输对象,包含设备ID和指令值等信息 + * @param payloadKey 指令负载数据的键名 + * @param deviceAction 设备操作类型描述 + */ + private void sendCommand(DeviceInstructDto dto, String payloadKey, String deviceAction) { + long deviceId = dto.getDeviceId(); + + // 1. 使用Optional简化空值检查,使代码更简洁 + Device device = Optional.ofNullable(deviceMapper.selectById(deviceId)) + .orElseThrow(() -> new ServiceException("设备不存在")); + + String deviceImei = device.getDeviceImei(); + String deviceName = device.getDeviceName(); + + // 2. 提前进行设备状态检查,逻辑更清晰 + if (isDeviceOffline(deviceImei)) { + throw new ServiceException("设备已断开连接:" + deviceName); + } + + // 3. 统一处理类型转换异常,避免在业务逻辑中混杂try-catch + int value; + try { + value = Integer.parseInt(dto.getInstructValue()); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("指令值格式不正确,必须为整数。", e); + } + + // 4. 使用Map.of()或Map.ofEntries()创建不可变Map,更简洁且线程安全 + Map> payload = Map.of(payloadKey, List.of(value)); + + String topic = MqttConstants.GLOBAL_PUB_KEY + deviceImei; + String json = JsonUtils.toJsonString(payload); + + try { + mqttGateway.sendMsgToMqtt(topic, 1, json); + log.info("发送指令成功 => topic:{}, payload:{}", topic, json); } catch (Exception e) { log.error("发送指令失败, topic={}, payload={}", topic, json, e); throw new ServiceException("发送指令失败:" + e.getMessage()); } - log.info("发送指令成功 => topic:{}, payload:{}", topic, json); + // 5. 将日志记录和描述解析放在try-catch块之外,确保无论是否成功发送指令都能执行 String content = resolveGradeDesc("ins_DetectGrade", value); recordDeviceLog(device.getId(), - device.getDeviceName(), + deviceName, deviceAction, content, AppLoginHelper.getUserId()); From 9bbed77170f8f1104b815251873ff1be2dceadcf Mon Sep 17 00:00:00 2001 From: DragonWenLong <552045633@qq.com> Date: Wed, 27 Aug 2025 15:27:19 +0800 Subject: [PATCH 3/5] =?UTF-8?q?refactor(fys-demo):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=A7=86=E9=A2=91=E4=B8=8A=E4=BC=A0=E6=8E=A5=E5=8F=A3=E5=B9=B6?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20MQTT=20=E6=8A=A5=E8=AD=A6=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=E5=8F=91=E9=80=81=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除了 VideoUploadController 中的 @SaIgnore 注解,恢复权限控制- 优化了 XinghanSendAlarmMessageRule 中的 Redis 数据检查逻辑 --- .../global/mqtt/rule/xinghan/XinghanSendAlarmMessageRule.java | 2 +- .../com/fuyuanshen/demo/controller/VideoUploadController.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanSendAlarmMessageRule.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanSendAlarmMessageRule.java index 21f9c124..49f82c97 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanSendAlarmMessageRule.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanSendAlarmMessageRule.java @@ -72,7 +72,7 @@ public class XinghanSendAlarmMessageRule implements MqttMessageRule { } // 将发送的信息原文本以List形式存储在Redis中 String data = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + ctx.getDeviceImei() + DEVICE_ALARM_MESSAGE_KEY_PREFIX); - if (data.isEmpty()) { + if (data == null || data.isEmpty()) { return; } // diff --git a/fys-modules/fys-demo/src/main/java/com/fuyuanshen/demo/controller/VideoUploadController.java b/fys-modules/fys-demo/src/main/java/com/fuyuanshen/demo/controller/VideoUploadController.java index ddbaefff..e1570f1b 100644 --- a/fys-modules/fys-demo/src/main/java/com/fuyuanshen/demo/controller/VideoUploadController.java +++ b/fys-modules/fys-demo/src/main/java/com/fuyuanshen/demo/controller/VideoUploadController.java @@ -36,7 +36,6 @@ public class VideoUploadController { private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - @SaIgnore public R> upload(@RequestParam("file") MultipartFile file) { if (file == null || file.isEmpty()) { return R.fail("上传文件不能为空"); From 7aa02635f292a37e0a78470c065bae974dc3df68 Mon Sep 17 00:00:00 2001 From: DragonWenLong <552045633@qq.com> Date: Wed, 27 Aug 2025 17:30:00 +0800 Subject: [PATCH 4/5] =?UTF-8?q?feat(device):=20=E4=BC=98=E5=8C=96=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E4=BA=BA=E5=91=98=E4=BF=A1=E6=81=AF=E5=8F=91=E9=80=81?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 调整人员信息的顺序,符合兴汉设备接收数据的要求 - 添加设备上报人员登记信息的日志,方便问题追踪 --- .../fuyuanshen/global/mqtt/rule/xinghan/XinghanSendMsgRule.java | 1 + .../fuyuanshen/web/service/device/DeviceXinghanBizService.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) 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 index d45d5b3c..6568b57c 100644 --- 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 @@ -79,6 +79,7 @@ public class XinghanSendMsgRule implements MqttMessageRule { intData.add(blockIndex); // 获取块原内容 转成GBK 再转成无符号十进制整数 String blockTxt = data.get(blockIndex-1); + log.warn("设备上报人员登记信息:{}", blockTxt); // 再按 GBK 编码把字符串转成字节数组,并逐个转为无符号十进制整数 for (byte b : blockTxt.getBytes(GBK)) { intData.add(b & 0xFF); // b & 0xFF 得到 0~255 的整数 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 index aadd4148..29b8374b 100644 --- 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 @@ -160,9 +160,9 @@ public class DeviceXinghanBizService { List appPersonnelInfoVos = appPersonnelInfoMapper.selectVoList(qw); List list = new ArrayList<>(); - list.add(bo.getUnitName()); list.add(bo.getName()); list.add(bo.getPosition()); + list.add(bo.getUnitName()); list.add(bo.getCode()); RedisUtils.setCacheList(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + deviceObj.getDeviceImei() + ":app_send_message_data", list); RedisUtils.expire(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + deviceObj.getDeviceImei() + ":app_send_message_data", Duration.ofSeconds(5 * 60L)); From bdbbd5a12fff7378b09aa64f18c151e22fbaf029 Mon Sep 17 00:00:00 2001 From: DragonWenLong <552045633@qq.com> Date: Wed, 3 Sep 2025 15:17:39 +0800 Subject: [PATCH 5/5] =?UTF-8?q?feat(equipment):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E7=BB=B4=E4=BF=AE=E5=9B=BE=E7=89=87=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E5=B9=B6=E4=BC=98=E5=8C=96=E7=BB=B4=E4=BF=AE=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E7=9B=B8=E5=85=B3=E6=8E=A5=E5=8F=A3-=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20DeviceRepairImages=20=E7=9B=B8=E5=85=B3=E5=AE=9E?= =?UTF-8?q?=E4=BD=93=E3=80=81Mapper=20=E5=92=8C=20XML=20=E6=96=87=E4=BB=B6?= =?UTF-8?q?-=20=E9=87=8D=E6=9E=84=20DeviceRepairRecords=20=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E6=8E=A5=E5=8F=A3=E5=92=8C=E5=AE=9E=E7=8E=B0=E7=B1=BB?= =?UTF-8?q?=EF=BC=8C=E6=94=AF=E6=8C=81=E5=9B=BE=E7=89=87=E4=B8=8A=E4=BC=A0?= =?UTF-8?q?=20-=20=E6=96=B0=E5=A2=9E=20RepairImageType=20=E6=9E=9A?= =?UTF-8?q?=E4=B8=BE=E7=B1=BB=20-=20=E4=BC=98=E5=8C=96=20DeviceRepairRecor?= =?UTF-8?q?dsVo=EF=BC=8C=E5=A2=9E=E5=8A=A0=E5=9B=BE=E7=89=87=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E5=92=8C=E8=AE=BE=E5=A4=87=E5=90=8D=E7=A7=B0=E5=AD=97?= =?UTF-8?q?=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DeviceRepairRecordsController.java | 21 ++- .../equipment/domain/DeviceRepairImages.java | 41 +++++ .../equipment/domain/DeviceRepairRecords.java | 3 +- .../domain/bo/DeviceRepairRecordsBo.java | 11 ++ .../DeviceRepairRecordsQueryCriteria.java | 72 +++++++++ .../domain/vo/DeviceRepairImagesVo.java | 48 ++++++ .../domain/vo/DeviceRepairRecordsVo.java | 11 +- .../equipment/enums/RepairImageType.java | 23 +++ .../mapper/DeviceRepairImagesMapper.java | 8 + .../mapper/DeviceRepairRecordsMapper.java | 24 ++- .../service/IDeviceRepairRecordsService.java | 11 +- .../impl/DeviceRepairRecordsServiceImpl.java | 149 +++++++++++++++--- .../equipment/DeviceRepairImagesMapper.xml | 7 + .../equipment/DeviceRepairRecordsMapper.xml | 36 +++++ 14 files changed, 427 insertions(+), 38 deletions(-) create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceRepairImages.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/DeviceRepairRecordsQueryCriteria.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceRepairImagesVo.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/enums/RepairImageType.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceRepairImagesMapper.java create mode 100644 fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceRepairImagesMapper.xml diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceRepairRecordsController.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceRepairRecordsController.java index 1304daa6..03219d60 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceRepairRecordsController.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceRepairRecordsController.java @@ -2,6 +2,11 @@ package com.fuyuanshen.equipment.controller; import java.util.List; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.fuyuanshen.equipment.domain.DeviceRepairRecords; +import com.fuyuanshen.equipment.domain.DeviceType; +import com.fuyuanshen.equipment.domain.query.DeviceRepairRecordsQueryCriteria; +import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.constraints.*; @@ -41,8 +46,10 @@ public class DeviceRepairRecordsController extends BaseController { */ @SaCheckPermission("equipment:repairRecords:list") @GetMapping("/list") - public TableDataInfo list(DeviceRepairRecordsBo bo, PageQuery pageQuery) { - return deviceRepairRecordsService.queryPageList(bo, pageQuery); + @Operation(summary = "分页查询维修记录列表") + public TableDataInfo list(DeviceRepairRecordsQueryCriteria criteria) { + Page page = new Page<>(criteria.getPageNum(), criteria.getPageSize()); + return deviceRepairRecordsService.queryPageList(criteria, page); } /** @@ -51,7 +58,7 @@ public class DeviceRepairRecordsController extends BaseController { @SaCheckPermission("equipment:repairRecords:export") @Log(title = "设备维修记录", businessType = BusinessType.EXPORT) @PostMapping("/export") - public void export(DeviceRepairRecordsBo bo, HttpServletResponse response) { + public void export(DeviceRepairRecordsQueryCriteria bo, HttpServletResponse response) { List list = deviceRepairRecordsService.queryList(bo); ExcelUtil.exportExcel(list, "设备维修记录", DeviceRepairRecordsVo.class, response); } @@ -74,8 +81,8 @@ public class DeviceRepairRecordsController extends BaseController { @SaCheckPermission("equipment:repairRecords:add") @Log(title = "设备维修记录", businessType = BusinessType.INSERT) @RepeatSubmit() - @PostMapping() - public R add(@Validated(AddGroup.class) @RequestBody DeviceRepairRecordsBo bo) { + @PostMapping(consumes = "multipart/form-data") + public R add(@Validated(AddGroup.class) DeviceRepairRecordsBo bo) { return toAjax(deviceRepairRecordsService.insertByBo(bo)); } @@ -85,8 +92,8 @@ public class DeviceRepairRecordsController extends BaseController { @SaCheckPermission("equipment:repairRecords:edit") @Log(title = "设备维修记录", businessType = BusinessType.UPDATE) @RepeatSubmit() - @PutMapping() - public R edit(@Validated(EditGroup.class) @RequestBody DeviceRepairRecordsBo bo) { + @PutMapping(consumes = "multipart/form-data") + public R edit(@Validated(EditGroup.class) DeviceRepairRecordsBo bo) { return toAjax(deviceRepairRecordsService.updateByBo(bo)); } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceRepairImages.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceRepairImages.java new file mode 100644 index 00000000..d26082de --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceRepairImages.java @@ -0,0 +1,41 @@ +package com.fuyuanshen.equipment.domain; + +import com.baomidou.mybatisplus.annotation.*; +import com.fuyuanshen.common.tenant.core.TenantEntity; +import com.fuyuanshen.equipment.enums.RepairImageType; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 设备维修图片对象 device_repair_images + * + * + * @date 2025-09-02 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("device_repair_images") +public class DeviceRepairImages extends TenantEntity { + /** + * 维修图片ID + */ + @TableId(value = "image_id", type = IdType.AUTO) + @TableField(insertStrategy = FieldStrategy.NEVER) + private Long imageId; + /** + * 维修记录ID + */ + @Schema(title = "维修记录ID") + private Long recordId; + /** + * 图片类型(维修前/维修后) + */ + @Schema(title = "图片类型(维修前/维修后)") + private RepairImageType imageType; + /** + * 图片存储路径 + */ + @Schema(title = "图片存储路径") + private String imageUrl; +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceRepairRecords.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceRepairRecords.java index 3fca3c4a..c962a3b5 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceRepairRecords.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceRepairRecords.java @@ -26,7 +26,8 @@ public class DeviceRepairRecords extends TenantEntity { /** * 维修记录ID */ - @TableId(value = "record_id") + @TableId(value = "record_id", type = IdType.AUTO) + @TableField(insertStrategy = FieldStrategy.NEVER) private Long recordId; /** 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 b40b9750..e134543a 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 @@ -1,15 +1,19 @@ package com.fuyuanshen.equipment.domain.bo; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fuyuanshen.common.core.validate.AddGroup; import com.fuyuanshen.common.core.validate.EditGroup; import com.fuyuanshen.equipment.domain.DeviceRepairRecords; import com.fuyuanshen.common.mybatis.core.domain.BaseEntity; import io.github.linpeilie.annotations.AutoMapper; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import jakarta.validation.constraints.*; import java.util.Date; import com.fasterxml.jackson.annotation.JsonFormat; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.web.multipart.MultipartFile; /** * 设备维修记录业务对象 device_repair_records @@ -37,6 +41,7 @@ public class DeviceRepairRecordsBo extends BaseEntity { /** * 维修时间 */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @NotNull(message = "维修时间不能为空", groups = { AddGroup.class, EditGroup.class }) private Date repairTime; @@ -58,5 +63,11 @@ public class DeviceRepairRecordsBo extends BaseEntity { @NotBlank(message = "维修人员不能为空", groups = { AddGroup.class, EditGroup.class }) private String repairPerson; + @Schema(title = "维修前图片") + @JsonIgnore + private MultipartFile beforeFile; + @Schema(title = "维修后图片") + @JsonIgnore + private MultipartFile afterFile; } 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 new file mode 100644 index 00000000..4df5ec6d --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/DeviceRepairRecordsQueryCriteria.java @@ -0,0 +1,72 @@ +package com.fuyuanshen.equipment.domain.query; + +import com.fuyuanshen.common.core.validate.AddGroup; +import com.fuyuanshen.common.core.validate.EditGroup; +import com.fuyuanshen.common.mybatis.core.domain.BaseEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.springframework.web.multipart.MultipartFile; + +import java.util.Date; + +/** + * 设备维修记录查询 + * + * @Description: + * @Author: WY + * @Date: 2025/5/16 + **/ +@Data +public class DeviceRepairRecordsQueryCriteria extends BaseEntity { + /** + * 维修记录ID + */ + private Long recordId; + + /** + * 设备ID + */ + private String deviceId; + + /** + * 维修时间 + */ + private Date repairTime; + + /** + * 维修部位 + */ + private String repairPart; + + /** + * 维修原因 + */ + private String repairReason; + + /** + * 维修人员 + */ + private String repairPerson; + + @Schema(title = "维修开始时间") + private Date repairBeginTime; + @Schema(title = "维修结束时间") + private Date repairEndTime; + + @Schema(title = "所属客户") + private Long customerId; + + @Schema(title = "com.fuyuanshen") + private Long tenantId; + /** + * 页码 + */ + private Integer pageNum = 1; + + /** + * 每页数据量 + */ + private Integer pageSize = 10; +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceRepairImagesVo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceRepairImagesVo.java new file mode 100644 index 00000000..7bc1a641 --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceRepairImagesVo.java @@ -0,0 +1,48 @@ +package com.fuyuanshen.equipment.domain.vo; + +import cn.idev.excel.annotation.ExcelIgnoreUnannotated; +import com.fuyuanshen.equipment.domain.DeviceRepairImages; +import com.fuyuanshen.equipment.domain.DeviceRepairRecords; +import com.fuyuanshen.equipment.enums.RepairImageType; +import io.github.linpeilie.annotations.AutoMapper; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 设备维修图片视图对象 device_repair_records + * + * @author Lion Li + * @date 2025-09-01 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = DeviceRepairImages.class) +public class DeviceRepairImagesVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 维修图片ID + */ + @Schema(title = "维修图片ID") + private Long imageId; + /** + * 维修记录ID + */ + @Schema(title = "维修记录ID") + private Long recordId; + /** + * 图片类型(维修前/维修后) + */ + @Schema(title = "图片类型(维修前/维修后)") + private RepairImageType imageType; + /** + * 图片存储路径 + */ + @Schema(title = "图片存储路径") + private String imageUrl; +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceRepairRecordsVo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceRepairRecordsVo.java index f66bb67f..0e970313 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceRepairRecordsVo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceRepairRecordsVo.java @@ -2,6 +2,7 @@ package com.fuyuanshen.equipment.domain.vo; import java.util.Date; import com.fasterxml.jackson.annotation.JsonFormat; +import com.fuyuanshen.common.tenant.core.TenantEntity; import com.fuyuanshen.equipment.domain.DeviceRepairRecords; import cn.idev.excel.annotation.ExcelIgnoreUnannotated; import cn.idev.excel.annotation.ExcelProperty; @@ -13,7 +14,7 @@ import lombok.Data; import java.io.Serial; import java.io.Serializable; import java.util.Date; - +import java.util.List; /** @@ -25,7 +26,7 @@ import java.util.Date; @Data @ExcelIgnoreUnannotated @AutoMapper(target = DeviceRepairRecords.class) -public class DeviceRepairRecordsVo implements Serializable { +public class DeviceRepairRecordsVo extends TenantEntity implements Serializable { @Serial private static final long serialVersionUID = 1L; @@ -65,6 +66,12 @@ public class DeviceRepairRecordsVo implements Serializable { */ @ExcelProperty(value = "维修人员") private String repairPerson; + /** + * 维修人员 + */ + @ExcelProperty(value = "设备名称") + private String deviceName; + private List images; } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/enums/RepairImageType.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/enums/RepairImageType.java new file mode 100644 index 00000000..c226d9bb --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/enums/RepairImageType.java @@ -0,0 +1,23 @@ +package com.fuyuanshen.equipment.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +@Getter +@AllArgsConstructor +public enum RepairImageType { + BEFORE(1, "维修前"), + AFTER(2, "维修后"); + + private final int code; + private final String desc; + + public static RepairImageType of(Integer code) { + return Arrays.stream(values()) + .filter(e -> e.getCode() == code) + .findFirst() + .orElse(null); + } +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceRepairImagesMapper.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceRepairImagesMapper.java new file mode 100644 index 00000000..02390e6a --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceRepairImagesMapper.java @@ -0,0 +1,8 @@ +package com.fuyuanshen.equipment.mapper; + +import com.fuyuanshen.common.mybatis.core.mapper.BaseMapperPlus; +import com.fuyuanshen.equipment.domain.DeviceRepairImages; +import com.fuyuanshen.equipment.domain.vo.DeviceRepairImagesVo; + +public interface DeviceRepairImagesMapper extends BaseMapperPlus { +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceRepairRecordsMapper.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceRepairRecordsMapper.java index eb3cec2d..80233244 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceRepairRecordsMapper.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceRepairRecordsMapper.java @@ -1,8 +1,16 @@ package com.fuyuanshen.equipment.mapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.fuyuanshen.equipment.domain.DeviceRepairRecords; +import com.fuyuanshen.equipment.domain.DeviceType; +import com.fuyuanshen.equipment.domain.query.DeviceRepairRecordsQueryCriteria; +import com.fuyuanshen.equipment.domain.query.DeviceTypeQueryCriteria; import com.fuyuanshen.equipment.domain.vo.DeviceRepairRecordsVo; import com.fuyuanshen.common.mybatis.core.mapper.BaseMapperPlus; +import org.apache.ibatis.annotations.Param; + +import java.util.List; /** * 设备维修记录Mapper接口 @@ -11,5 +19,19 @@ import com.fuyuanshen.common.mybatis.core.mapper.BaseMapperPlus; * @date 2025-08-08 */ public interface DeviceRepairRecordsMapper extends BaseMapperPlus { - + /** + * 分页查询维修记录 + * + * @param criteria + * @param page + * @return + */ + IPage findAll(@Param("criteria") DeviceRepairRecordsQueryCriteria criteria, Page page); + /** + * 查询所有维修记录 + * + * @param criteria + * @return + */ + List findAll(@Param("criteria") DeviceRepairRecordsQueryCriteria criteria); } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/IDeviceRepairRecordsService.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/IDeviceRepairRecordsService.java index 8fb838c2..cd8b88fc 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/IDeviceRepairRecordsService.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/IDeviceRepairRecordsService.java @@ -1,5 +1,10 @@ package com.fuyuanshen.equipment.service; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.fuyuanshen.equipment.domain.Device; +import com.fuyuanshen.equipment.domain.DeviceRepairRecords; +import com.fuyuanshen.equipment.domain.query.DeviceRepairRecordsQueryCriteria; import com.fuyuanshen.equipment.domain.vo.DeviceRepairRecordsVo; import com.fuyuanshen.equipment.domain.bo.DeviceRepairRecordsBo; import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; @@ -14,7 +19,7 @@ import java.util.List; * @author Lion Li * @date 2025-08-08 */ -public interface IDeviceRepairRecordsService { +public interface IDeviceRepairRecordsService extends IService { /** * 查询设备维修记录 @@ -31,7 +36,7 @@ public interface IDeviceRepairRecordsService { * @param pageQuery 分页参数 * @return 设备维修记录分页列表 */ - TableDataInfo queryPageList(DeviceRepairRecordsBo bo, PageQuery pageQuery); + TableDataInfo queryPageList(DeviceRepairRecordsQueryCriteria criteria, Page page); /** * 查询符合条件的设备维修记录列表 @@ -39,7 +44,7 @@ public interface IDeviceRepairRecordsService { * @param bo 查询条件 * @return 设备维修记录列表 */ - List queryList(DeviceRepairRecordsBo bo); + List queryList(DeviceRepairRecordsQueryCriteria criteria); /** * 新增设备维修记录 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 5eb4f89f..3b838d88 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 @@ -1,11 +1,25 @@ package com.fuyuanshen.equipment.service.impl; +import cn.hutool.core.date.DateUtil; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.fuyuanshen.common.core.utils.MapstructUtils; import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; import com.fuyuanshen.common.mybatis.core.page.PageQuery; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.fuyuanshen.common.satoken.utils.LoginHelper; +import com.fuyuanshen.equipment.domain.Device; +import com.fuyuanshen.equipment.domain.DeviceRepairImages; +import com.fuyuanshen.equipment.domain.DeviceType; +import com.fuyuanshen.equipment.domain.query.DeviceRepairRecordsQueryCriteria; +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.system.domain.vo.SysOssVo; +import com.fuyuanshen.system.service.ISysOssService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -15,10 +29,10 @@ import com.fuyuanshen.equipment.domain.vo.DeviceRepairRecordsVo; import com.fuyuanshen.equipment.domain.DeviceRepairRecords; import com.fuyuanshen.equipment.mapper.DeviceRepairRecordsMapper; import com.fuyuanshen.equipment.service.IDeviceRepairRecordsService; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; -import java.util.List; -import java.util.Map; -import java.util.Collection; +import java.util.*; /** * 设备维修记录Service业务层处理 @@ -29,9 +43,11 @@ import java.util.Collection; @Slf4j @RequiredArgsConstructor @Service -public class DeviceRepairRecordsServiceImpl implements IDeviceRepairRecordsService { +public class DeviceRepairRecordsServiceImpl extends ServiceImpl implements IDeviceRepairRecordsService { private final DeviceRepairRecordsMapper baseMapper; + private final DeviceRepairImagesMapper imagesMapper; + private final ISysOssService ossService; /** * 查询设备维修记录 @@ -41,33 +57,53 @@ public class DeviceRepairRecordsServiceImpl implements IDeviceRepairRecordsServi */ @Override public DeviceRepairRecordsVo queryById(Long recordId){ - return baseMapper.selectVoById(recordId); + // 1. 主表 + DeviceRepairRecordsVo vo = baseMapper.selectVoById(recordId); + if (vo == null) { + throw new RuntimeException("维修记录不存在"); + } + + // 2. 子表图片 + List images = imagesMapper.selectVoList( + new LambdaQueryWrapper() + .eq(DeviceRepairImages::getRecordId, recordId) + .orderByAsc(DeviceRepairImages::getImageType)); // 如有排序需求 + // 3. 组装到 VO + vo.setImages(images); // 需要在 DeviceRepairRecordsVo 里加该字段 + return vo; } /** * 分页查询设备维修记录列表 * - * @param bo 查询条件 - * @param pageQuery 分页参数 + * @param criteria 查询条件 + * @param page 分页参数 * @return 设备维修记录分页列表 */ @Override - public TableDataInfo queryPageList(DeviceRepairRecordsBo bo, PageQuery pageQuery) { - LambdaQueryWrapper lqw = buildQueryWrapper(bo); - Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); - return TableDataInfo.build(result); + public TableDataInfo queryPageList(DeviceRepairRecordsQueryCriteria criteria, Page page) { + // 管理员 + String username = LoginHelper.getUsername(); + if (!username.equals("admin")) { + criteria.setCustomerId(LoginHelper.getUserId()); + } + + if(criteria.getRepairEndTime() !=null ){ + criteria.setRepairEndTime(DateUtil.endOfDay(criteria.getRepairEndTime())); + } + IPage deviceRepairRecordsIPage = baseMapper.findAll(criteria, page); + return new TableDataInfo(deviceRepairRecordsIPage.getRecords(), deviceRepairRecordsIPage.getTotal()); } /** * 查询符合条件的设备维修记录列表 * - * @param bo 查询条件 + * @param criteria 查询条件 * @return 设备维修记录列表 */ @Override - public List queryList(DeviceRepairRecordsBo bo) { - LambdaQueryWrapper lqw = buildQueryWrapper(bo); - return baseMapper.selectVoList(lqw); + public List queryList(DeviceRepairRecordsQueryCriteria criteria) { + return baseMapper.findAll(criteria); } private LambdaQueryWrapper buildQueryWrapper(DeviceRepairRecordsBo bo) { @@ -89,14 +125,30 @@ public class DeviceRepairRecordsServiceImpl implements IDeviceRepairRecordsServi * @return 是否新增成功 */ @Override + @Transactional(rollbackFor = Exception.class) public Boolean insertByBo(DeviceRepairRecordsBo bo) { - DeviceRepairRecords add = MapstructUtils.convert(bo, DeviceRepairRecords.class); - validEntityBeforeSave(add); - boolean flag = baseMapper.insert(add) > 0; - if (flag) { - bo.setRecordId(add.getRecordId()); + // 1. 保存主表 + DeviceRepairRecords entity = MapstructUtils.convert(bo, DeviceRepairRecords.class); + validEntityBeforeSave(entity); + boolean ok = baseMapper.insert(entity) > 0; + if (!ok) { + return false; } - return flag; + + // 2. 回填主键 + Long recordId = entity.getRecordId(); + bo.setRecordId(recordId); + + // 3. 组装图片实体 + List images = new ArrayList<>(2); + addImageIfPresent(images, recordId, bo.getBeforeFile(), RepairImageType.BEFORE); + addImageIfPresent(images, recordId, bo.getAfterFile(), RepairImageType.AFTER); + + // 4. 批量插入 + if (!images.isEmpty()) { + imagesMapper.insertBatch(images); + } + return true; } /** @@ -106,10 +158,56 @@ public class DeviceRepairRecordsServiceImpl implements IDeviceRepairRecordsServi * @return 是否修改成功 */ @Override + @Transactional(rollbackFor = Exception.class) public Boolean updateByBo(DeviceRepairRecordsBo bo) { - DeviceRepairRecords update = MapstructUtils.convert(bo, DeviceRepairRecords.class); - validEntityBeforeSave(update); - return baseMapper.updateById(update) > 0; + Long recordId = bo.getRecordId(); + + // 1. 校验记录是否存在 + DeviceRepairRecords dbRecord = baseMapper.selectById(recordId); + if (dbRecord == null) { + throw new RuntimeException("维修记录不存在"); + } + + // 2. 更新主表 + DeviceRepairRecords entity = MapstructUtils.convert(bo, DeviceRepairRecords.class); + validEntityBeforeSave(entity); + if (!StringUtils.isNotEmpty(entity.getDeviceId())){ + throw new RuntimeException("维修记录不能为空!!!"); + } + boolean updated = baseMapper.updateById(entity) > 0; + if (!updated) { + return false; + } + + // 3. 收集需要保存的图片 + List images = new ArrayList<>(2); + addImageIfPresent(images, recordId, bo.getBeforeFile(), RepairImageType.BEFORE); + addImageIfPresent(images, recordId, bo.getAfterFile(), RepairImageType.AFTER); + + // 4. 批量插入 + if (!images.isEmpty()) { + imagesMapper.insertBatch(images); + } + return true; + } + + /* ---------- 工具方法 ---------- */ + + private void addImageIfPresent(List list, + Long recordId, + MultipartFile file, + RepairImageType imageType) { + if (file == null || file.isEmpty()) { + return; + } + SysOssVo ossVo = ossService.upload(file); + + DeviceRepairImages image = new DeviceRepairImages(); + image.setRecordId(recordId); + image.setImageType(imageType); + image.setImageUrl(ossVo.getUrl()); + + list.add(image); } /** @@ -117,6 +215,9 @@ public class DeviceRepairRecordsServiceImpl implements IDeviceRepairRecordsServi */ private void validEntityBeforeSave(DeviceRepairRecords entity){ //TODO 做一些数据校验,如唯一约束 + if (!StringUtils.isNotEmpty(entity.getDeviceId())){ + throw new RuntimeException("维修设备不能为空!!!"); + } } /** diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceRepairImagesMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceRepairImagesMapper.xml new file mode 100644 index 00000000..f933f7e5 --- /dev/null +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceRepairImagesMapper.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceRepairRecordsMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceRepairRecordsMapper.xml index 46af59f4..7e83e5c7 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceRepairRecordsMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceRepairRecordsMapper.xml @@ -3,5 +3,41 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> + + + + + + + + + + + + + + +