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 001/160] =?UTF-8?q?feat(device):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E5=8F=91=E9=80=81=E7=B4=A7=E6=80=A5=E9=80=9A=E7=9F=A5=E5=8A=9F?= =?UTF-8?q?=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 e4df695f5eca64d40bdbfcd0003e86a980390e80 Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Tue, 26 Aug 2025 09:26:33 +0800 Subject: [PATCH 002/160] =?UTF-8?q?=E7=BB=91=E5=AE=9A=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E5=88=86=E7=BB=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../device/DeviceGroupController.java | 17 +++++++++++++++++ .../equipment/service/IDeviceGroupService.java | 11 +++++++++++ .../service/impl/DeviceGroupServiceImpl.java | 18 +++++++++++++++++- 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceGroupController.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceGroupController.java index 28601a1a..3e1018e9 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceGroupController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceGroupController.java @@ -114,4 +114,21 @@ public class DeviceGroupController extends BaseController { return toAjax(deviceGroupService.deleteWithValidByIds(List.of(ids), true)); } + + /** + * 绑定设备分组 + * + * @param groupId 分组id + * @param deviceId 设备id + */ + @Operation(summary = "绑定设备分组") + // @SaCheckPermission("fys-equipment:group:remove") + @Log(title = "绑定设备分组", businessType = BusinessType.DELETE) + @GetMapping("/groupId/{deviceId}") + public R bindingDevice(@NotEmpty(message = "分组id 不能为空") @PathVariable Long groupId, + @NotEmpty(message = "设备id 不能为空") @PathVariable Long[] deviceId) { + return toAjax(deviceGroupService.bindingDevice(groupId, deviceId)); + } + + } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/IDeviceGroupService.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/IDeviceGroupService.java index 9662fef4..3be6a880 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/IDeviceGroupService.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/IDeviceGroupService.java @@ -3,6 +3,7 @@ package com.fuyuanshen.equipment.service; import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; import com.fuyuanshen.equipment.domain.vo.DeviceGroupVo; import com.fuyuanshen.equipment.domain.bo.DeviceGroupBo; +import jakarta.validation.constraints.NotEmpty; import java.util.Collection; import java.util.List; @@ -56,4 +57,14 @@ public interface IDeviceGroupService { * @return 是否删除成功 */ Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + + /** + * 绑定设备分组 + * + * @param groupId 分组id + * @param deviceId 设备id + * @return 是否绑定成功 + */ + Boolean bindingDevice(@NotEmpty(message = "分组id 不能为空") Long groupId, @NotEmpty(message = "设备id 不能为空") Long[] deviceId); } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceGroupServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceGroupServiceImpl.java index 51cf369b..2981a65f 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceGroupServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceGroupServiceImpl.java @@ -11,6 +11,7 @@ import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; import com.fuyuanshen.common.satoken.utils.LoginHelper; import com.fuyuanshen.equipment.domain.Device; import com.fuyuanshen.equipment.domain.DeviceTypeGrants; +import jakarta.validation.constraints.NotEmpty; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -63,7 +64,7 @@ public class DeviceGroupServiceImpl implements IDeviceGroupService { public List queryList(DeviceGroupBo bo) { Page page = new Page<>(bo.getPageNum(), bo.getPageSize()); // 1. 查询顶级分组(parent_id为null) - IPage rootGroups = baseMapper.selectRootGroups(bo, page); + IPage rootGroups = baseMapper.selectRootGroups(bo, page); List records = rootGroups.getRecords(); // 2. 递归构建树形结构 @@ -169,4 +170,19 @@ public class DeviceGroupServiceImpl implements IDeviceGroupService { } return baseMapper.deleteByIds(ids) > 0; } + + /** + * 绑定设备分组 + * + * @param groupId 分组id + * @param deviceId 设备id + * @return 是否绑定成功 + */ + @Override + public Boolean bindingDevice(Long groupId, Long[] deviceId) { + + + + return true; + } } From f9d9dadf08755500e29685b6de1e5d69bf85dadb Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Tue, 26 Aug 2025 10:00:35 +0800 Subject: [PATCH 003/160] =?UTF-8?q?=E7=BB=91=E5=AE=9A=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E5=88=86=E7=BB=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/DeviceGroupServiceImpl.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceGroupServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceGroupServiceImpl.java index 2981a65f..2d79529b 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceGroupServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceGroupServiceImpl.java @@ -1,6 +1,7 @@ package com.fuyuanshen.equipment.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.fuyuanshen.common.core.domain.R; @@ -11,6 +12,7 @@ import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; import com.fuyuanshen.common.satoken.utils.LoginHelper; import com.fuyuanshen.equipment.domain.Device; import com.fuyuanshen.equipment.domain.DeviceTypeGrants; +import com.fuyuanshen.equipment.mapper.DeviceMapper; import jakarta.validation.constraints.NotEmpty; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -23,6 +25,7 @@ import com.fuyuanshen.equipment.mapper.DeviceGroupMapper; import com.fuyuanshen.equipment.service.IDeviceGroupService; import java.text.SimpleDateFormat; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Collection; @@ -40,6 +43,7 @@ import java.util.stream.Collectors; public class DeviceGroupServiceImpl implements IDeviceGroupService { private final DeviceGroupMapper baseMapper; + private final DeviceMapper deviceMapper; /** @@ -181,7 +185,15 @@ public class DeviceGroupServiceImpl implements IDeviceGroupService { @Override public Boolean bindingDevice(Long groupId, Long[] deviceId) { + if (deviceId != null && deviceId.length > 0) { + // 创建更新条件 + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.in("id", Arrays.asList(deviceId)); + updateWrapper.set("group_id", groupId); + // 执行批量更新 + deviceMapper.update(updateWrapper); + } return true; } 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 004/160] =?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 364574eeae92ff4861972b4492cc89f5bebb62ca Mon Sep 17 00:00:00 2001 From: chenyouting <514333061@qq.com> Date: Wed, 27 Aug 2025 08:59:29 +0800 Subject: [PATCH 005/160] =?UTF-8?q?web=E7=AB=AF=E6=8E=A7=E5=88=B6=E4=B8=AD?= =?UTF-8?q?=E5=BF=833?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rule/bjq/BjqLaserModeSettingsRule.java | 15 ++--- .../device/DeviceControlCenterController.java | 16 ++++-- .../service/device/DeviceBJQBizService.java | 16 ++++++ .../web/service/device/DeviceBizService.java | 10 +++- .../mapper/app/AppDeviceShareMapper.xml | 4 +- .../fuyuanshen/equipment/domain/Device.java | 5 +- .../domain/dto/InstructionRecordDto.java | 32 +++++++++++ .../domain/query/DeviceQueryCriteria.java | 55 +++++++++++++------ .../domain/vo/InstructionRecordVo.java | 32 +++++++++++ .../equipment/mapper/DeviceLogMapper.java | 5 ++ .../equipment/mapper/DeviceMapper.java | 2 +- .../mapper/equipment/DeviceLogMapper.xml | 33 +++++++++++ .../mapper/equipment/DeviceMapper.xml | 15 +++-- 13 files changed, 196 insertions(+), 44 deletions(-) create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/InstructionRecordDto.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/InstructionRecordVo.java diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqLaserModeSettingsRule.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqLaserModeSettingsRule.java index b2bb3391..35b1ac3e 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqLaserModeSettingsRule.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqLaserModeSettingsRule.java @@ -15,8 +15,7 @@ import org.springframework.stereotype.Component; import java.time.Duration; import static com.fuyuanshen.common.core.constant.GlobalConstants.FUNCTION_ACCESS_KEY; -import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX; -import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_LIGHT_MODE_KEY_PREFIX; +import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.*; /** * 灯光模式订阅设备回传消息 @@ -37,14 +36,10 @@ public class BjqLaserModeSettingsRule implements MqttMessageRule { try { Object[] convertArr = context.getConvertArr(); - String mainLightMode = convertArr[1].toString(); - if(StringUtils.isNotBlank(mainLightMode)){ - if("0".equals(mainLightMode)){ - String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ context.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ; - RedisUtils.setCacheObject(deviceOnlineStatusRedisKey, "0", Duration.ofSeconds(60*15)); - } + String mode = convertArr[1].toString(); + if(StringUtils.isNotBlank(mode)){ // 发送设备状态和位置信息到Redis - syncSendDeviceDataToRedisWithFuture(context.getDeviceImei(),mainLightMode); + syncSendDeviceDataToRedisWithFuture(context.getDeviceImei(),mode); } RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.OK.getCode(), Duration.ofSeconds(30)); @@ -65,7 +60,7 @@ public class BjqLaserModeSettingsRule implements MqttMessageRule { // }); try { // 将设备状态信息存储到Redis中 - String deviceRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+DeviceRedisKeyConstants.DEVICE_KEY_PREFIX + deviceImei + DEVICE_LIGHT_MODE_KEY_PREFIX; + String deviceRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+DeviceRedisKeyConstants.DEVICE_KEY_PREFIX + deviceImei + DEVICE_LASER_MODE_KEY_PREFIX; // 存储到Redis RedisUtils.setCacheObject(deviceRedisKey, convertValue.toString()); diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceControlCenterController.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceControlCenterController.java index 76e39357..789ca852 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceControlCenterController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceControlCenterController.java @@ -8,19 +8,17 @@ import com.fuyuanshen.common.mybatis.core.page.PageQuery; import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; import com.fuyuanshen.common.web.core.BaseController; import com.fuyuanshen.equipment.domain.dto.AppDeviceBo; +import com.fuyuanshen.equipment.domain.dto.InstructionRecordDto; import com.fuyuanshen.equipment.domain.query.DeviceQueryCriteria; import com.fuyuanshen.equipment.domain.vo.AppDeviceVo; +import com.fuyuanshen.equipment.domain.vo.InstructionRecordVo; import com.fuyuanshen.equipment.domain.vo.WebDeviceVo; import com.fuyuanshen.web.service.device.DeviceBizService; -import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.util.Date; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -30,7 +28,7 @@ import java.util.Map; @Slf4j @RestController @RequiredArgsConstructor -@RequestMapping("/api/device/controlCenter") +@RequestMapping("/api/device") public class DeviceControlCenterController extends BaseController { private final DeviceBizService appDeviceService; @@ -95,4 +93,12 @@ public class DeviceControlCenterController extends BaseController { return R.ok(appDeviceService.getDeviceInfo(deviceMac)); } + /** + * 指令下发记录 + */ + @GetMapping("/instructionRecord") + public TableDataInfo getInstructionRecord(InstructionRecordDto dto, PageQuery pageQuery) { + return appDeviceService.getInstructionRecord(dto,pageQuery); + } + } diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBJQBizService.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBJQBizService.java index 9ee8236e..17c8d8ae 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBJQBizService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBJQBizService.java @@ -190,6 +190,22 @@ public class DeviceBJQBizService { vo.setBatteryPercentage("0"); } + String lightModeStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + device.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_LIGHT_MODE_KEY_PREFIX); + // 获取电量 + if(StringUtils.isNotBlank(deviceStatus)){ + vo.setMainLightMode(lightModeStatus); + } + + String lightBrightnessStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + device.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_LIGHT_BRIGHTNESS_KEY_PREFIX); + if(StringUtils.isNotBlank(lightBrightnessStatus)){ + vo.setLightBrightness(lightBrightnessStatus); + } + + String laserLightMode = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + device.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_LASER_MODE_KEY_PREFIX); + if(StringUtils.isNotBlank(laserLightMode)){ + vo.setLaserLightMode(laserLightMode); + } + // 获取经度纬度 String locationKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + device.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_LOCATION_KEY_PREFIX; String locationInfo = RedisUtils.getCacheObject(locationKey); diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBizService.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBizService.java index 90114467..4e468f30 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBizService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBizService.java @@ -14,7 +14,6 @@ import com.fuyuanshen.app.domain.vo.APPDeviceTypeVo; import com.fuyuanshen.app.domain.vo.AppUserVo; import com.fuyuanshen.app.mapper.AppDeviceBindRecordMapper; import com.fuyuanshen.app.mapper.AppDeviceShareMapper; -import com.fuyuanshen.app.mapper.AppPersonnelInfoMapper; import com.fuyuanshen.app.mapper.AppUserMapper; import com.fuyuanshen.app.mapper.equipment.APPDeviceMapper; import com.fuyuanshen.common.core.exception.ServiceException; @@ -26,15 +25,15 @@ import com.fuyuanshen.common.redis.utils.RedisUtils; import com.fuyuanshen.common.satoken.utils.AppLoginHelper; import com.fuyuanshen.equipment.domain.Device; import com.fuyuanshen.equipment.domain.dto.AppDeviceBo; +import com.fuyuanshen.equipment.domain.dto.InstructionRecordDto; import com.fuyuanshen.equipment.domain.query.DeviceQueryCriteria; import com.fuyuanshen.equipment.domain.vo.AppDeviceVo; +import com.fuyuanshen.equipment.domain.vo.InstructionRecordVo; import com.fuyuanshen.equipment.domain.vo.WebDeviceVo; import com.fuyuanshen.equipment.enums.BindingStatusEnum; import com.fuyuanshen.equipment.enums.CommunicationModeEnum; import com.fuyuanshen.equipment.mapper.DeviceLogMapper; import com.fuyuanshen.equipment.mapper.DeviceMapper; -import com.fuyuanshen.equipment.mapper.DeviceTypeMapper; -import com.fuyuanshen.global.mqtt.config.MqttGateway; import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants; import com.fuyuanshen.web.service.device.status.base.DeviceStatusRule; import com.fuyuanshen.web.service.device.status.base.RealTimeStatusEngine; @@ -332,4 +331,9 @@ public class DeviceBizService { // List devices = deviceMapper.selectList(queryWrapper); return deviceMapper.getDeviceInfo(deviceMac); } + + public TableDataInfo getInstructionRecord(InstructionRecordDto bo, PageQuery pageQuery) { + Page result = deviceLogMapper.getInstructionRecord(pageQuery.build(), bo); + return TableDataInfo.build(result); + } } diff --git a/fys-modules/fys-app/src/main/resources/mapper/app/AppDeviceShareMapper.xml b/fys-modules/fys-app/src/main/resources/mapper/app/AppDeviceShareMapper.xml index 39033bc1..137f67ff 100644 --- a/fys-modules/fys-app/src/main/resources/mapper/app/AppDeviceShareMapper.xml +++ b/fys-modules/fys-app/src/main/resources/mapper/app/AppDeviceShareMapper.xml @@ -12,7 +12,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" d.device_pic, dt.type_name, dt.communication_mode, - dt.model_dictionary detailPageUrl, + dt.app_model_dictionary detailPageUrl, d.bluetooth_name, c.binding_time, ad.*,u.user_name otherPhonenumber @@ -33,7 +33,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" dt.type_name, dt.communication_mode, d.bluetooth_name, - dt.model_dictionary detailPageUrl, + dt.app_model_dictionary detailPageUrl, c.binding_time, ad.*,u.user_name otherPhonenumber from diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/Device.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/Device.java index d166aacb..5c90dc82 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/Device.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/Device.java @@ -153,5 +153,8 @@ public class Device extends TenantEntity { */ @Schema(title = "出厂日期") private Date productionDate; - + /** + * 在线状态(0离线,1在线) + */ + private Integer onlineStatus; } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/InstructionRecordDto.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/InstructionRecordDto.java new file mode 100644 index 00000000..240fc482 --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/InstructionRecordDto.java @@ -0,0 +1,32 @@ +package com.fuyuanshen.equipment.domain.dto; + +import lombok.Data; + +@Data +public class InstructionRecordDto { + /** + * 设备类型 + */ + private String deviceType; + /** + * 设备名称 + */ + private String deviceName; + /** + * 设备MAC + */ + private String deviceMac; + /** + * 设备IMEI + */ + private String deviceImei; + /** + * 操作时间-开始时间 + */ + private String startTime; + + /** + * 操作时间-结束时间 + */ + private String endTime; +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/DeviceQueryCriteria.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/DeviceQueryCriteria.java index c9dcca99..b9643d6c 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/DeviceQueryCriteria.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/DeviceQueryCriteria.java @@ -18,22 +18,34 @@ import java.util.Set; @Data public class DeviceQueryCriteria extends BaseEntity { - @Schema(name = "设备id") + /** + * 设备id + */ private Long deviceId; - @Schema(name = "设备名称") + /** + * 设备名称 + */ private String deviceName; - @Schema(name = "设备类型") + /** + * 设备类型 + */ private Long deviceType; - @Schema(name = "设备MAC") + /** + * 设备MAC + */ private String deviceMac; - @Schema(name = "设备IMEI") + /** + * 设备IMEI + */ private String deviceImei; - @Schema(name = "设备SN") + /** + * 设备SN + */ private String deviceSn; /** @@ -41,26 +53,37 @@ public class DeviceQueryCriteria extends BaseEntity { * 0 失效 * 1 正常 */ - @Schema(name = "设备状态 0 失效 1 正常 ") private Integer deviceStatus; - @Schema(name = "页码", example = "1") + /** + * 页码 + */ private Integer pageNum = 1; - @Schema(name = "每页数据量", example = "10") + /** + * 每页数据量 + */ private Integer pageSize = 10; - @Schema(name = "客户id") + /** + * 客户id + */ private Long customerId; private Set customerIds; - @Schema(name = "当前所有者") + /** + * 当前所有者 + */ private Long currentOwnerId; - @Schema(name = "租户ID") + /** + * 租户ID + */ private String tenantId; - @Schema(name = "通讯方式", example = "0:4G;1:蓝牙") + /** + * 通讯方式 0:4G;1:蓝牙 + */ private Integer communicationMode; /* app绑定用户id */ @@ -72,22 +95,22 @@ public class DeviceQueryCriteria extends BaseEntity { */ private String personnelBy; + /** * 是否为管理员 */ - @Schema(name = "是否为管理员") private Boolean isAdmin = false; + /** * 设备所属分组 */ - @Schema(name = "设备所属分组") private Long groupId; + /** * 设备地区 */ - @Schema(name = "设备地区") private String area; } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/InstructionRecordVo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/InstructionRecordVo.java new file mode 100644 index 00000000..97964ec4 --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/InstructionRecordVo.java @@ -0,0 +1,32 @@ +package com.fuyuanshen.equipment.domain.vo; + +import lombok.Data; + +@Data +public class InstructionRecordVo { + private Long id; + /** + * 设备名称 + */ + private String deviceName; + + /** + * 设备类型 + */ + private String deviceType; + + /** + * 操作模块 + */ + private String deviceAction; + + /** + * 操作内容 + */ + private String content; + + /** + * 操作时间 + */ + private String createTime; +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceLogMapper.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceLogMapper.java index adff5de4..30b9fb82 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceLogMapper.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceLogMapper.java @@ -1,8 +1,12 @@ package com.fuyuanshen.equipment.mapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.fuyuanshen.equipment.domain.DeviceLog; +import com.fuyuanshen.equipment.domain.dto.InstructionRecordDto; import com.fuyuanshen.equipment.domain.vo.DeviceLogVo; import com.fuyuanshen.common.mybatis.core.mapper.BaseMapperPlus; +import com.fuyuanshen.equipment.domain.vo.InstructionRecordVo; +import org.apache.ibatis.annotations.Param; /** * 设备日志Mapper接口 @@ -12,4 +16,5 @@ import com.fuyuanshen.common.mybatis.core.mapper.BaseMapperPlus; */ public interface DeviceLogMapper extends BaseMapperPlus { + Page getInstructionRecord(Page page,@Param("bo") InstructionRecordDto bo); } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceMapper.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceMapper.java index 39896531..e75e1c26 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceMapper.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceMapper.java @@ -70,5 +70,5 @@ public interface DeviceMapper extends BaseMapper { AppDeviceVo getDeviceInfo(@Param("deviceMac") String deviceMac); - Page queryWebDeviceList(Page build, DeviceQueryCriteria bo); + Page queryWebDeviceList(Page build,@Param("criteria") DeviceQueryCriteria criteria); } diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceLogMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceLogMapper.xml index d4c99d6a..d15eeb8b 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceLogMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceLogMapper.xml @@ -4,4 +4,37 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> + diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml index 51f0a638..7e4423cb 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml @@ -151,7 +151,7 @@ dt.type_name, dt.communication_mode, d.bluetooth_name, - dt.model_dictionary detailPageUrl, + dt.app_model_dictionary detailPageUrl, c.binding_time from device d inner join device_type dt on d.device_type = dt.id @@ -181,7 +181,7 @@ d.device_pic, dt.type_name, dt.communication_mode, - dt.model_dictionary detailPageUrl, + dt.app_model_dictionary detailPageUrl, d.bluetooth_name from device d inner join device_type dt on d.device_type = dt.id @@ -227,7 +227,7 @@ dt.type_name, dt.communication_mode, d.bluetooth_name, - dt.model_dictionary detailPageUrl + dt.app_model_dictionary detailPageUrl from device d inner join device_type dt on d.device_type = dt.id where d.device_mac = #{deviceMac} @@ -242,7 +242,7 @@ dt.type_name, dt.communication_mode, d.bluetooth_name, - dt.model_dictionary detailPageUrl, + dt.pc_model_dictionary detailPageUrl, ap.name personnelBy, d.device_status, c.binding_time @@ -254,7 +254,7 @@ and d.device_type = #{criteria.deviceType} - + and d.device_name like concat('%', #{criteria.deviceName}, '%') @@ -270,7 +270,10 @@ and ap.name like concat('%', #{criteria.personnelBy}, '%') - and dt.communication_mode, = #{criteria.communicationMode} + and dt.communication_mode = #{criteria.communicationMode} + + + and d.group_id = #{criteria.groupId} From 0bbac2b4979515e31fcef83d51360aa2cc18fbfb Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Wed, 27 Aug 2025 09:08:27 +0800 Subject: [PATCH 006/160] =?UTF-8?q?=E8=AE=BE=E5=A4=87=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E8=AF=A6=E6=83=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../device/WEBDeviceController.java | 27 ++++++++++--------- .../web/service/WEBDeviceService.java | 9 +++++++ .../service/impl/WEBDeviceServiceImpl.java | 20 ++++++++++++++ .../service/impl/DeviceGroupServiceImpl.java | 2 ++ 4 files changed, 46 insertions(+), 12 deletions(-) diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/WEBDeviceController.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/WEBDeviceController.java index 86ec7ed6..9f7ddadb 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/WEBDeviceController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/WEBDeviceController.java @@ -1,29 +1,18 @@ package com.fuyuanshen.web.controller.device; -import com.fuyuanshen.app.domain.dto.APPReNameDTO; -import com.fuyuanshen.app.domain.dto.AppRealTimeStatusDto; -import com.fuyuanshen.app.domain.vo.APPDeviceTypeVo; +import com.fuyuanshen.app.domain.AppPersonnelInfoRecords; import com.fuyuanshen.common.core.domain.R; -import com.fuyuanshen.common.mybatis.core.page.PageQuery; -import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; import com.fuyuanshen.common.web.core.BaseController; -import com.fuyuanshen.equipment.domain.Device; -import com.fuyuanshen.equipment.domain.dto.AppDeviceBo; -import com.fuyuanshen.equipment.domain.query.DeviceQueryCriteria; -import com.fuyuanshen.equipment.domain.vo.AppDeviceVo; import com.fuyuanshen.equipment.domain.vo.WebDeviceVo; import com.fuyuanshen.web.service.WEBDeviceService; -import com.fuyuanshen.web.service.device.DeviceBizService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.util.List; -import java.util.Map; /** * @Description: @@ -68,6 +57,20 @@ public class WEBDeviceController extends BaseController { } + /** + * 设备用户详情 + * + * @param id + * @return + */ + @Operation(summary = "设备详情") + @GetMapping(value = "/getDeviceUser/{id}") + public R> getDeviceUser(@PathVariable Long id) { + List device = deviceService.getDeviceUser(id); + return R.ok(device); + } + + } diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/WEBDeviceService.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/WEBDeviceService.java index fe84f3b9..48edf4eb 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/service/WEBDeviceService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/WEBDeviceService.java @@ -2,6 +2,7 @@ package com.fuyuanshen.web.service; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.IService; +import com.fuyuanshen.app.domain.AppPersonnelInfoRecords; import com.fuyuanshen.common.mybatis.core.page.PageQuery; import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; import com.fuyuanshen.equipment.domain.Device; @@ -38,4 +39,12 @@ public interface WEBDeviceService extends IService { */ WebDeviceVo getDevice(Long id); + /** + * 设备用户详情 + * + * @param id + * @return + */ + List getDeviceUser(Long id); + } diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/WEBDeviceServiceImpl.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/WEBDeviceServiceImpl.java index 0d5097c1..d6a062f3 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/WEBDeviceServiceImpl.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/WEBDeviceServiceImpl.java @@ -6,9 +6,11 @@ import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.fuyuanshen.app.domain.AppDeviceBindRecord; import com.fuyuanshen.app.domain.AppDeviceShare; +import com.fuyuanshen.app.domain.AppPersonnelInfoRecords; import com.fuyuanshen.app.domain.vo.AppDeviceShareVo; import com.fuyuanshen.app.mapper.AppDeviceBindRecordMapper; import com.fuyuanshen.app.mapper.AppDeviceShareMapper; +import com.fuyuanshen.app.mapper.AppPersonnelInfoRecordsMapper; import com.fuyuanshen.equipment.domain.Device; import com.fuyuanshen.equipment.domain.DeviceAssignments; import com.fuyuanshen.equipment.domain.vo.WebDeviceVo; @@ -22,6 +24,8 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; + /** * @Description: * @Author: WY @@ -35,6 +39,7 @@ public class WEBDeviceServiceImpl extends ServiceImpl impl private final DeviceAssignmentsMapper deviceAssignmentsMapper; private final AppDeviceBindRecordMapper appDeviceBindRecordMapper; + private final AppPersonnelInfoRecordsMapper infoRecordsMapper; private final DeviceMapper deviceMapper; private final AppDeviceShareMapper appDeviceShareMapper; @@ -95,4 +100,19 @@ public class WEBDeviceServiceImpl extends ServiceImpl impl } + /** + * 设备用户详情 + * + * @param id + * @return + */ + @Override + public List getDeviceUser(Long id) { + List appPersonnelInfoRecords = infoRecordsMapper.selectList( + new QueryWrapper().eq("device_id", id) + .orderByDesc("create_time")); + return appPersonnelInfoRecords; + } + + } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceGroupServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceGroupServiceImpl.java index 2d79529b..ecf9a82a 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceGroupServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceGroupServiceImpl.java @@ -175,6 +175,7 @@ public class DeviceGroupServiceImpl implements IDeviceGroupService { return baseMapper.deleteByIds(ids) > 0; } + /** * 绑定设备分组 * @@ -197,4 +198,5 @@ public class DeviceGroupServiceImpl implements IDeviceGroupService { return true; } + } From 8b25fd9ba4323f1733f4f3a375a89732bd38151d Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Wed, 27 Aug 2025 09:25:08 +0800 Subject: [PATCH 007/160] =?UTF-8?q?=E8=AE=BE=E5=A4=87=E6=93=8D=E4=BD=9C?= =?UTF-8?q?=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../device/WEBDeviceController.java | 23 +++++++++++++++---- .../web/service/WEBDeviceService.java | 10 +++++++- .../service/impl/WEBDeviceServiceImpl.java | 18 +++++++++++++++ 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/WEBDeviceController.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/WEBDeviceController.java index 9f7ddadb..5908c27f 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/WEBDeviceController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/WEBDeviceController.java @@ -4,6 +4,7 @@ package com.fuyuanshen.web.controller.device; import com.fuyuanshen.app.domain.AppPersonnelInfoRecords; import com.fuyuanshen.common.core.domain.R; import com.fuyuanshen.common.web.core.BaseController; +import com.fuyuanshen.equipment.domain.DeviceLog; import com.fuyuanshen.equipment.domain.vo.WebDeviceVo; import com.fuyuanshen.web.service.WEBDeviceService; import io.swagger.v3.oas.annotations.Operation; @@ -60,13 +61,27 @@ public class WEBDeviceController extends BaseController { /** * 设备用户详情 * - * @param id + * @param deviceId * @return */ @Operation(summary = "设备详情") - @GetMapping(value = "/getDeviceUser/{id}") - public R> getDeviceUser(@PathVariable Long id) { - List device = deviceService.getDeviceUser(id); + @GetMapping(value = "/getDeviceUser/{deviceId}") + public R> getDeviceUser(@PathVariable Long deviceId) { + List device = deviceService.getDeviceUser(deviceId); + return R.ok(device); + } + + + /** + * 设备操作记录 + * + * @param deviceId + * @return + */ + @Operation(summary = "设备操作记录") + @GetMapping(value = "/getOperationRecord/{deviceId}") + public R> getOperationRecord(@PathVariable Long deviceId) { + List device = deviceService.getOperationRecord(deviceId); return R.ok(device); } diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/WEBDeviceService.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/WEBDeviceService.java index 48edf4eb..5c19beab 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/service/WEBDeviceService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/WEBDeviceService.java @@ -6,6 +6,7 @@ import com.fuyuanshen.app.domain.AppPersonnelInfoRecords; import com.fuyuanshen.common.mybatis.core.page.PageQuery; import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; import com.fuyuanshen.equipment.domain.Device; +import com.fuyuanshen.equipment.domain.DeviceLog; import com.fuyuanshen.equipment.domain.dto.AppDeviceBo; import com.fuyuanshen.equipment.domain.form.DeviceForm; import com.fuyuanshen.equipment.domain.query.DeviceQueryCriteria; @@ -45,6 +46,13 @@ public interface WEBDeviceService extends IService { * @param id * @return */ - List getDeviceUser(Long id); + List getDeviceUser(Long id); + /** + * 设备操作记录 + * + * @param deviceId + * @return + */ + List getOperationRecord(Long deviceId); } diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/WEBDeviceServiceImpl.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/WEBDeviceServiceImpl.java index d6a062f3..d4882c3d 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/WEBDeviceServiceImpl.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/WEBDeviceServiceImpl.java @@ -13,9 +13,11 @@ import com.fuyuanshen.app.mapper.AppDeviceShareMapper; import com.fuyuanshen.app.mapper.AppPersonnelInfoRecordsMapper; import com.fuyuanshen.equipment.domain.Device; import com.fuyuanshen.equipment.domain.DeviceAssignments; +import com.fuyuanshen.equipment.domain.DeviceLog; import com.fuyuanshen.equipment.domain.vo.WebDeviceVo; import com.fuyuanshen.equipment.enums.BindingStatusEnum; import com.fuyuanshen.equipment.mapper.DeviceAssignmentsMapper; +import com.fuyuanshen.equipment.mapper.DeviceLogMapper; import com.fuyuanshen.equipment.mapper.DeviceMapper; import com.fuyuanshen.web.service.WEBDeviceService; import com.fuyuanshen.web.service.device.DeviceBizService; @@ -40,6 +42,7 @@ public class WEBDeviceServiceImpl extends ServiceImpl impl private final AppDeviceBindRecordMapper appDeviceBindRecordMapper; private final AppPersonnelInfoRecordsMapper infoRecordsMapper; + private final DeviceLogMapper deviceLogMapper; private final DeviceMapper deviceMapper; private final AppDeviceShareMapper appDeviceShareMapper; @@ -115,4 +118,19 @@ public class WEBDeviceServiceImpl extends ServiceImpl impl } + /** + * 设备操作记录 + * + * @param deviceId + * @return + */ + @Override + public List getOperationRecord(Long deviceId) { + List logList = deviceLogMapper.selectList( + new QueryWrapper().eq("device_id", deviceId) + .orderByDesc("create_time")); + return logList; + } + + } From 837953bf3d8c871896abc3e613217888833c5971 Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Wed, 27 Aug 2025 10:20:37 +0800 Subject: [PATCH 008/160] =?UTF-8?q?=E8=AE=BE=E5=A4=87=E7=B1=BB=E5=9E=8BID?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/fuyuanshen/equipment/domain/DeviceType.java | 6 ++++++ .../main/resources/mapper/equipment/DeviceTypeMapper.xml | 5 +++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceType.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceType.java index d0ab5b0f..a8e46186 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceType.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceType.java @@ -21,6 +21,12 @@ public class DeviceType extends TenantEntity { @Schema(title = "ID", hidden = true) private Long id; + /** + * 设备类型ID + */ + @TableField(exist = false) + private Long deviceTypeId; + @Schema(title = "客户号") private Long customerId; diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceTypeMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceTypeMapper.xml index 624c395a..cdfbbca3 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceTypeMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceTypeMapper.xml @@ -4,6 +4,7 @@ + @@ -21,7 +22,7 @@ - select d.device_name, - d.device_mac, - d.device_sn, - d.device_imei, - d.device_pic, - dt.type_name, - dt.communication_mode, - dt.app_model_dictionary detailPageUrl, - d.bluetooth_name, - c.binding_time, - ad.*,u.user_name otherPhonenumber - from - app_device_share ad - left join device d on ad.device_id = d.id - left join app_user u on ad.create_by = u.user_id - inner join device_type dt on d.device_type = dt.id - inner join app_device_bind_record c on d.id = c.device_id + d.device_mac, + d.device_sn, + d.device_imei, + d.device_pic, + dt.type_name, + dt.communication_mode, + dt.app_model_dictionary detailPageUrl, + d.bluetooth_name, + c.binding_time, + ad.*, + u.user_name otherPhonenumber + from app_device_share ad + left join device d on ad.device_id = d.id + left join app_user u on ad.create_by = u.user_id + inner join device_type dt on d.device_type = dt.id + inner join app_device_bind_record c on d.id = c.device_id where ad.phonenumber = #{bo.phonenumber} + + + From e17a64ad570fb7c0f2ce0e8d6d13ec554e00f65b Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Fri, 29 Aug 2025 13:59:23 +0800 Subject: [PATCH 015/160] =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E5=91=8A=E8=AD=A6=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../equipment/domain/bo/DeviceAlarmBo.java | 29 ++++++++++++++++--- .../equipment/domain/vo/DeviceAlarmVo.java | 12 +++++++- .../equipment/mapper/DeviceAlarmMapper.java | 14 +++++++++ .../service/impl/DeviceAlarmServiceImpl.java | 18 +++++++----- .../mapper/equipment/DeviceAlarmMapper.xml | 29 +++++++++++++++++-- 5 files changed, 88 insertions(+), 14 deletions(-) diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceAlarmBo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceAlarmBo.java index e0df7d66..01697f5f 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceAlarmBo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceAlarmBo.java @@ -24,7 +24,7 @@ public class DeviceAlarmBo extends BaseEntity { /** * ID */ - @NotNull(message = "ID不能为空", groups = { EditGroup.class }) + @NotNull(message = "ID不能为空", groups = {EditGroup.class}) private Long id; /** @@ -34,14 +34,27 @@ public class DeviceAlarmBo extends BaseEntity { /** * 报警事项 + * device_action */ - private String deviceAction; + private Integer deviceAction; /** * 设备名称 */ private String deviceName; + /** + * 设备MAC + * device_mac + */ + private String deviceMac; + + /** + * 设备IMEI + * device_imei + */ + private String deviceImei; + /** * 数据来源 */ @@ -54,12 +67,12 @@ public class DeviceAlarmBo extends BaseEntity { /** * 设备类型 + * device_type */ private Long deviceType; /** * 经度 - */ private Long longitude; @@ -88,10 +101,18 @@ public class DeviceAlarmBo extends BaseEntity { */ private Date durationTime; + /** + * 报警查询时间 + */ + private Date queryTime1; + private Date queryTime2; + + + /** * 0已处理,1未处理 */ - private Long treatmentState; + private Integer treatmentState; } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceAlarmVo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceAlarmVo.java index ecd2d8e4..e893a6a2 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceAlarmVo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceAlarmVo.java @@ -44,15 +44,24 @@ public class DeviceAlarmVo implements Serializable { /** * 报警事项 + * 0-强制报警,1-撞击闯入,2-手动报警,3-电子围栏告警,4-强制告警 */ @ExcelProperty(value = "报警事项") - private String deviceAction; + private Integer deviceAction; /** * 设备名称 */ @ExcelProperty(value = "设备名称") private String deviceName; + /** + * 设备MAC + */ + private String deviceMac; + /** + * 设备IMEI + */ + private String deviceImei; /** * 数据来源 @@ -71,6 +80,7 @@ public class DeviceAlarmVo implements Serializable { */ @ExcelProperty(value = "设备类型") private Long deviceType; + private String deviceTypeName; /** * 经度 diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceAlarmMapper.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceAlarmMapper.java index 950e105e..c361cf2a 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceAlarmMapper.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceAlarmMapper.java @@ -1,8 +1,12 @@ package com.fuyuanshen.equipment.mapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.fuyuanshen.common.mybatis.core.page.PageQuery; import com.fuyuanshen.equipment.domain.DeviceAlarm; +import com.fuyuanshen.equipment.domain.bo.DeviceAlarmBo; import com.fuyuanshen.equipment.domain.vo.DeviceAlarmVo; import com.fuyuanshen.common.mybatis.core.mapper.BaseMapperPlus; +import org.apache.ibatis.annotations.Param; /** * 设备告警Mapper接口 @@ -12,4 +16,14 @@ import com.fuyuanshen.common.mybatis.core.mapper.BaseMapperPlus; */ public interface DeviceAlarmMapper extends BaseMapperPlus { + + /** + * 查询设备告警列表 + * + * @param bo 设备告警 + * @return 设备告警 + */ + Page selectVoPage(@Param("bo") DeviceAlarmBo bo, PageQuery pageQuery); + + } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceAlarmServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceAlarmServiceImpl.java index e1095548..22f65ace 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceAlarmServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceAlarmServiceImpl.java @@ -33,6 +33,7 @@ public class DeviceAlarmServiceImpl implements IDeviceAlarmService { private final DeviceAlarmMapper baseMapper; + /** * 查询设备告警 * @@ -40,10 +41,11 @@ public class DeviceAlarmServiceImpl implements IDeviceAlarmService { * @return 设备告警 */ @Override - public DeviceAlarmVo queryById(Long id){ + public DeviceAlarmVo queryById(Long id) { return baseMapper.selectVoById(id); } + /** * 分页查询设备告警列表 * @@ -54,10 +56,12 @@ public class DeviceAlarmServiceImpl implements IDeviceAlarmService { @Override public TableDataInfo queryPageList(DeviceAlarmBo bo, PageQuery pageQuery) { LambdaQueryWrapper lqw = buildQueryWrapper(bo); - Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + // Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + Page result = baseMapper.selectVoPage(bo, pageQuery); return TableDataInfo.build(result); } + /** * 查询符合条件的设备告警列表 * @@ -75,7 +79,7 @@ public class DeviceAlarmServiceImpl implements IDeviceAlarmService { LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); lqw.orderByAsc(DeviceAlarm::getId); lqw.eq(bo.getDeviceId() != null, DeviceAlarm::getDeviceId, bo.getDeviceId()); - lqw.eq(StringUtils.isNotBlank(bo.getDeviceAction()), DeviceAlarm::getDeviceAction, bo.getDeviceAction()); + // lqw.eq(StringUtils.isNotBlank(bo.getDeviceAction()), DeviceAlarm::getDeviceAction, bo.getDeviceAction()); lqw.like(StringUtils.isNotBlank(bo.getDeviceName()), DeviceAlarm::getDeviceName, bo.getDeviceName()); lqw.eq(StringUtils.isNotBlank(bo.getDataSource()), DeviceAlarm::getDataSource, bo.getDataSource()); lqw.eq(StringUtils.isNotBlank(bo.getContent()), DeviceAlarm::getContent, bo.getContent()); @@ -123,8 +127,8 @@ public class DeviceAlarmServiceImpl implements IDeviceAlarmService { /** * 保存前的数据校验 */ - private void validEntityBeforeSave(DeviceAlarm entity){ - //TODO 做一些数据校验,如唯一约束 + private void validEntityBeforeSave(DeviceAlarm entity) { + // TODO 做一些数据校验,如唯一约束 } /** @@ -136,8 +140,8 @@ public class DeviceAlarmServiceImpl implements IDeviceAlarmService { */ @Override public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { - if(isValid){ - //TODO 做一些业务上的校验,判断是否需要校验 + if (isValid) { + // TODO 做一些业务上的校验,判断是否需要校验 } return baseMapper.deleteByIds(ids) > 0; } diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceAlarmMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceAlarmMapper.xml index 4a8dc016..0d543b29 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceAlarmMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceAlarmMapper.xml @@ -1,7 +1,32 @@ + PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> + + From b5565da752cf346605a6c6ffc2e29fb46442409e Mon Sep 17 00:00:00 2001 From: chenyouting <514333061@qq.com> Date: Fri, 29 Aug 2025 16:49:16 +0800 Subject: [PATCH 016/160] =?UTF-8?q?web=E7=AB=AF=E6=8E=A7=E5=88=B6=E4=B8=AD?= =?UTF-8?q?=E5=BF=834?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mqtt/receiver/ReceiverMessageHandler.java | 4 + .../global/queue/MqttMessageConsumer.java | 110 ++++++++++++++++++ .../queue/MqttMessageQueueConstants.java | 6 + .../device/DeviceControlCenterController.java | 51 +++++++- .../web/service/device/DeviceBizService.java | 65 ++++++++++- .../common/redis/utils/RedisUtils.java | 92 +++++++++++++++ .../domain/dto/InstructionRecordDto.java | 5 + .../domain/vo/InstructionRecordVo.java | 7 ++ .../domain/vo/LocationHistoryDetailVo.java | 41 +++++++ .../domain/vo/LocationHistoryVo.java | 39 +++++++ .../equipment/domain/vo/WebDeviceVo.java | 1 + .../equipment/mapper/DeviceMapper.java | 4 + .../mapper/equipment/DeviceLogMapper.xml | 7 +- .../mapper/equipment/DeviceMapper.xml | 33 +++++- 14 files changed, 450 insertions(+), 15 deletions(-) create mode 100644 fys-admin/src/main/java/com/fuyuanshen/global/queue/MqttMessageConsumer.java create mode 100644 fys-admin/src/main/java/com/fuyuanshen/global/queue/MqttMessageQueueConstants.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/LocationHistoryDetailVo.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/LocationHistoryVo.java diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java index 4259cd18..2581db7c 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java @@ -10,6 +10,7 @@ import com.fuyuanshen.global.mqtt.base.MqttRuleContext; import com.fuyuanshen.global.mqtt.base.MqttRuleEngine; import com.fuyuanshen.global.mqtt.base.MqttXinghanCommandType; import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants; +import com.fuyuanshen.global.queue.MqttMessageQueueConstants; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.Message; @@ -51,6 +52,9 @@ public class ReceiverMessageHandler implements MessageHandler { //在线状态 String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ deviceImei + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ; RedisUtils.setCacheObject(deviceOnlineStatusRedisKey, "1", Duration.ofSeconds(62)); +// String queueKey = MqttMessageQueueConstants.MQTT_MESSAGE_QUEUE_KEY; +// String dedupKey = MqttMessageQueueConstants.MQTT_MESSAGE_DEDUP_KEY; +// RedisUtils.offerDeduplicated(queueKey,dedupKey,deviceImei, Duration.ofHours(24)); } String state = payloadDict.getStr("state"); diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/queue/MqttMessageConsumer.java b/fys-admin/src/main/java/com/fuyuanshen/global/queue/MqttMessageConsumer.java new file mode 100644 index 00000000..9172fa8e --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/queue/MqttMessageConsumer.java @@ -0,0 +1,110 @@ +package com.fuyuanshen.global.queue; + +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.fuyuanshen.common.redis.utils.RedisUtils; +import com.fuyuanshen.equipment.domain.Device; +import com.fuyuanshen.equipment.mapper.DeviceMapper; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +@Service +@Slf4j +public class MqttMessageConsumer { + + @Autowired + private DeviceMapper deviceMapper; + + // 创建两个线程池:一个用于消息获取,一个用于业务处理 + private ExecutorService messageConsumerPool = Executors.newFixedThreadPool(3); + private ExecutorService messageProcessorPool = Executors.newFixedThreadPool(10); + + // 初始化方法,启动消息监听 +// @PostConstruct + public void start() { + log.info("启动MQTT消息消费者..."); + // 启动消息获取线程 + for (int i = 0; i < 3; i++) { + messageConsumerPool.submit(this::consumeMessages); + } + } + + // 销毁方法,关闭线程池 + @PreDestroy + public void stop() { + log.info("关闭MQTT消息消费者..."); + shutdownExecutorService(messageConsumerPool); + shutdownExecutorService(messageProcessorPool); + } + + private void shutdownExecutorService(ExecutorService executorService) { + if (executorService != null && !executorService.isShutdown()) { + executorService.shutdown(); + try { + if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) { + executorService.shutdownNow(); + } + } catch (InterruptedException e) { + executorService.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + } + + // 消费者方法 - 专门负责从队列获取消息 + public void consumeMessages() { + String queueKey = MqttMessageQueueConstants.MQTT_MESSAGE_QUEUE_KEY; + String threadName = Thread.currentThread().getName(); + log.info("消息消费者线程 {} 开始监听队列: {}", threadName, queueKey); + + try { + while (!Thread.currentThread().isInterrupted() && !messageConsumerPool.isShutdown()) { + // 阻塞式获取队列中的消息 + String message = RedisUtils.pollDeduplicated( + queueKey, + MqttMessageQueueConstants.MQTT_MESSAGE_DEDUP_KEY, + 1, + TimeUnit.SECONDS + ); + + if (message != null) { + log.info("线程 {} 从队列中获取到消息,提交到处理线程池: {}", threadName, message); + // 将消息处理任务提交到处理线程池 + messageProcessorPool.submit(() -> processMessage(message)); + } + } + } catch (Exception e) { + log.error("线程 {} 消费消息时发生错误", threadName, e); + } + + log.info("消息消费者线程 {} 停止监听队列", threadName); + } + + // 处理具体业务逻辑的方法 + private void processMessage(String message) { + String threadName = Thread.currentThread().getName(); + try { + log.info("业务处理线程 {} 开始处理消息: {}", threadName, message); + + // 实现具体的业务逻辑 + // 例如更新数据库、发送通知等 + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("device_imei", message) + .set("online_status", 1); + deviceMapper.update(updateWrapper); + // 模拟业务处理耗时 + Thread.sleep(200); + + log.info("业务处理线程 {} 完成消息处理: {}", threadName, message); + } catch (Exception e) { + log.error("业务处理线程 {} 处理消息时发生错误: {}", threadName, message, e); + } + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/queue/MqttMessageQueueConstants.java b/fys-admin/src/main/java/com/fuyuanshen/global/queue/MqttMessageQueueConstants.java new file mode 100644 index 00000000..b4eb34d2 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/queue/MqttMessageQueueConstants.java @@ -0,0 +1,6 @@ +package com.fuyuanshen.global.queue; + +public class MqttMessageQueueConstants { + public static final String MQTT_MESSAGE_QUEUE_KEY = "mqtt:message:queue"; + public static final String MQTT_MESSAGE_DEDUP_KEY = "mqtt:message:dedup"; +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceControlCenterController.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceControlCenterController.java index 789ca852..ed124fef 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceControlCenterController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceControlCenterController.java @@ -1,19 +1,20 @@ package com.fuyuanshen.web.controller.device; +import com.alibaba.fastjson2.JSONObject; import com.fuyuanshen.app.domain.dto.APPReNameDTO; import com.fuyuanshen.app.domain.dto.AppRealTimeStatusDto; import com.fuyuanshen.app.domain.vo.APPDeviceTypeVo; import com.fuyuanshen.common.core.domain.R; +import com.fuyuanshen.common.excel.utils.ExcelUtil; import com.fuyuanshen.common.mybatis.core.page.PageQuery; import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; import com.fuyuanshen.common.web.core.BaseController; import com.fuyuanshen.equipment.domain.dto.AppDeviceBo; import com.fuyuanshen.equipment.domain.dto.InstructionRecordDto; import com.fuyuanshen.equipment.domain.query.DeviceQueryCriteria; -import com.fuyuanshen.equipment.domain.vo.AppDeviceVo; -import com.fuyuanshen.equipment.domain.vo.InstructionRecordVo; -import com.fuyuanshen.equipment.domain.vo.WebDeviceVo; +import com.fuyuanshen.equipment.domain.vo.*; import com.fuyuanshen.web.service.device.DeviceBizService; +import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; @@ -101,4 +102,48 @@ public class DeviceControlCenterController extends BaseController { return appDeviceService.getInstructionRecord(dto,pageQuery); } + /** + * 导出 + */ + @GetMapping("/export") + public void export(InstructionRecordDto dto, PageQuery pageQuery, HttpServletResponse response) { + pageQuery.setPageNum(1); + pageQuery.setPageSize(2000); + TableDataInfo instructionRecord = appDeviceService.getInstructionRecord(dto, pageQuery); + if(instructionRecord.getRows() == null){ + return; + } + ExcelUtil.exportExcel(instructionRecord.getRows(), "设备操作日志", InstructionRecordVo.class, response); + } + + + /** + * 历史轨迹查询 + */ + @GetMapping("/locationHistory") + public TableDataInfo getLocationHistory(InstructionRecordDto dto, PageQuery pageQuery) { + return appDeviceService.getLocationHistory(dto,pageQuery); + } + + /** + * 历史轨迹导出 + */ + @GetMapping("/locationHistoryExport") + public void locationHistoryExport(InstructionRecordDto dto, PageQuery pageQuery, HttpServletResponse response) { + pageQuery.setPageNum(1); + pageQuery.setPageSize(2000); + TableDataInfo result = appDeviceService.getLocationHistory(dto, pageQuery); + if(result.getRows() == null){ + return; + } + ExcelUtil.exportExcel(result.getRows(), "历史轨迹记录", LocationHistoryVo.class, response); + } + + /** + * 历史轨迹导出 + */ + @GetMapping("/getLocationHistoryDetail") + public R> getLocationHistoryDetail(Long id) { + return R.ok(appDeviceService.getLocationHistoryDetail(id)); + } } diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBizService.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBizService.java index 4e468f30..0b02d7c7 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBizService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBizService.java @@ -27,9 +27,7 @@ import com.fuyuanshen.equipment.domain.Device; import com.fuyuanshen.equipment.domain.dto.AppDeviceBo; import com.fuyuanshen.equipment.domain.dto.InstructionRecordDto; import com.fuyuanshen.equipment.domain.query.DeviceQueryCriteria; -import com.fuyuanshen.equipment.domain.vo.AppDeviceVo; -import com.fuyuanshen.equipment.domain.vo.InstructionRecordVo; -import com.fuyuanshen.equipment.domain.vo.WebDeviceVo; +import com.fuyuanshen.equipment.domain.vo.*; import com.fuyuanshen.equipment.enums.BindingStatusEnum; import com.fuyuanshen.equipment.enums.CommunicationModeEnum; import com.fuyuanshen.equipment.mapper.DeviceLogMapper; @@ -41,9 +39,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import java.util.Date; -import java.util.List; -import java.util.Map; +import java.time.*; +import java.util.*; import static com.fuyuanshen.common.core.constant.GlobalConstants.GLOBAL_REDIS_KEY; import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.*; @@ -336,4 +333,60 @@ public class DeviceBizService { Page result = deviceLogMapper.getInstructionRecord(pageQuery.build(), bo); return TableDataInfo.build(result); } + + public TableDataInfo getLocationHistory(InstructionRecordDto bo, PageQuery pageQuery) { + Page result = deviceMapper.getLocationHistory(pageQuery.build(), bo); + return TableDataInfo.build(result); + } + + public List getLocationHistoryDetail(Long id) { + Device device = deviceMapper.selectById(id); + if (device == null) { + throw new ServiceException("设备不存在"); + } + + // 计算七天前的凌晨时间戳 + LocalDateTime sevenDaysAgo = LocalDateTime.of(LocalDate.now().minusDays(7), LocalTime.MIDNIGHT); + long startTime = sevenDaysAgo.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); + + // 计算今天的凌晨时间戳 + LocalDateTime today = LocalDateTime.of(LocalDate.now(), LocalTime.MAX); + long endTime = today.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); + + String deviceImei = device.getDeviceImei(); + String a = GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ deviceImei + DEVICE_LOCATION_KEY_PREFIX + ":history"; + Collection list = RedisUtils.zRangeByScore(a, startTime, endTime); + if (CollectionUtil.isEmpty(list)) { + return null; + } + + + Map> map = new LinkedHashMap<>(); + for (String obj : list){ + JSONObject jsonObject = JSONObject.parseObject(obj); + Long timestamp = jsonObject.getLong("timestamp"); + LocalDate date = LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.systemDefault()).toLocalDate(); + if (map.containsKey(date.toString())) { + map.get(date.toString()).add(jsonObject); + } else { + ArrayList jsonList = new ArrayList<>(); + jsonList.add(jsonObject); + map.put(date.toString(), jsonList); + } + } + + List result = new ArrayList<>(); + for (Map.Entry> entry : map.entrySet()) { + LocationHistoryDetailVo detailVo = new LocationHistoryDetailVo(); + detailVo.setDate(entry.getKey()); + detailVo.setDeviceName(device.getDeviceName()); + detailVo.setStartLocation(entry.getValue().get(0).getString("address")); + detailVo.setEndLocation(entry.getValue().get(entry.getValue().size()-1).getString("address")); + detailVo.setDetailList(entry.getValue()); + result.add(detailVo); + } + + + return result; + } } diff --git a/fys-common/fys-common-redis/src/main/java/com/fuyuanshen/common/redis/utils/RedisUtils.java b/fys-common/fys-common-redis/src/main/java/com/fuyuanshen/common/redis/utils/RedisUtils.java index 17292c0b..6357d167 100644 --- a/fys-common/fys-common-redis/src/main/java/com/fuyuanshen/common/redis/utils/RedisUtils.java +++ b/fys-common/fys-common-redis/src/main/java/com/fuyuanshen/common/redis/utils/RedisUtils.java @@ -11,6 +11,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -373,6 +374,28 @@ public class RedisUtils { return 0; } } + + /** + * 根据时间范围查询Sorted Set中的元素(重载方法,适用于时间戳查询) + * + * @param key 键 + * @param startTime 开始时间戳 + * @param endTime 结束时间戳 + * @return 指定时间范围内的元素集合 + */ + public static Collection zRangeByScore(String key, Long startTime, Long endTime) { + try { + RScoredSortedSet sortedSet = CLIENT.getScoredSortedSet(key); + return sortedSet.valueRange(startTime, true, endTime, true); + } catch (Exception e) { + // 记录错误日志(如果项目中有日志工具的话) + // log.error("根据时间范围查询Sorted Set中的元素失败: key={}, startTime={}, endTime={}, error={}", + // key, startTime, endTime, e.getMessage(), e); + return null; + } + } + + /** * 追加缓存Set数据 * @@ -614,4 +637,73 @@ public class RedisUtils { RKeys rKeys = CLIENT.getKeys(); return rKeys.countExists(key) > 0; } + + + /** + * 向去重阻塞队列中添加元素 + * + * @param queueKey 队列键 + * @param dedupKey 去重集合键 + * @param value 消息值 + * @param timeout 过期时间 + * @return 是否添加成功 + */ + public static boolean offerDeduplicated(String queueKey, String dedupKey, String value, Duration timeout) { +// String jsonValue = value instanceof String ? (String) value : JsonUtils.toJsonString(value); + + RLock lock = CLIENT.getLock("lock:" + queueKey); + try { + lock.lock(); + + RSet dedupSet = CLIENT.getSet(dedupKey); + if (dedupSet.contains(value)) { + return false; // 元素已存在,不重复添加 + } + + // 添加到去重集合 + dedupSet.add(value); + + // 添加到阻塞队列 + RBlockingQueue queue = CLIENT.getBlockingQueue(queueKey); + boolean offered = queue.offer(value); + + // 设置过期时间 + if (timeout != null) { + queue.expire(timeout); + dedupSet.expire(timeout); + } + + return offered; + } finally { + lock.unlock(); + } + } + + /** + * 从去重阻塞队列中消费元素 + * + * @param queueKey 队列键 + * @param dedupKey 去重集合键 + * @param timeout 超时时间 + * @param timeUnit 时间单位 + * @return 消息值 + */ + public static String pollDeduplicated(String queueKey, String dedupKey, long timeout, TimeUnit timeUnit) { + try { + RBlockingQueue queue = CLIENT.getBlockingQueue(queueKey); + String value = queue.poll(timeout, timeUnit); + + // 从去重集合中移除 + if (value != null) { + RSet dedupSet = CLIENT.getSet(dedupKey); + dedupSet.remove(value); + } + + return value; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return null; + } + } + } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/InstructionRecordDto.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/InstructionRecordDto.java index 240fc482..ad711656 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/InstructionRecordDto.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/InstructionRecordDto.java @@ -29,4 +29,9 @@ public class InstructionRecordDto { * 操作时间-结束时间 */ private String endTime; + + /** + * 分组id + */ + private Long groupId; } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/InstructionRecordVo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/InstructionRecordVo.java index 97964ec4..9d37e0b2 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/InstructionRecordVo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/InstructionRecordVo.java @@ -1,5 +1,6 @@ package com.fuyuanshen.equipment.domain.vo; +import cn.idev.excel.annotation.ExcelProperty; import lombok.Data; @Data @@ -8,25 +9,31 @@ public class InstructionRecordVo { /** * 设备名称 */ + + @ExcelProperty(value = "设备名称") private String deviceName; /** * 设备类型 */ + @ExcelProperty(value = "设备型号") private String deviceType; /** * 操作模块 */ + @ExcelProperty(value = "操作模块") private String deviceAction; /** * 操作内容 */ + @ExcelProperty(value = "操作内容") private String content; /** * 操作时间 */ + @ExcelProperty(value = "操作时间") private String createTime; } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/LocationHistoryDetailVo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/LocationHistoryDetailVo.java new file mode 100644 index 00000000..cf499741 --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/LocationHistoryDetailVo.java @@ -0,0 +1,41 @@ +package com.fuyuanshen.equipment.domain.vo; + +import cn.idev.excel.annotation.ExcelProperty; +import com.alibaba.fastjson2.JSONObject; +import lombok.Data; + +import java.util.List; + +@Data +public class LocationHistoryDetailVo { + + /** + * 日期 + */ + @ExcelProperty(value = "日期") + private String date; + /** + * 设备名称 + */ + @ExcelProperty(value = "设备名称") + private String deviceName; + + /** + * 初始地点 + */ + @ExcelProperty(value = "初始地点") + private String startLocation; + + /** + * 结束地点 + */ + @ExcelProperty(value = "结束地点") + private String endLocation; + + /** + * 轨迹详情 + */ + @ExcelProperty(value = "轨迹详情") + private List detailList; + +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/LocationHistoryVo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/LocationHistoryVo.java new file mode 100644 index 00000000..702ea3f5 --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/LocationHistoryVo.java @@ -0,0 +1,39 @@ +package com.fuyuanshen.equipment.domain.vo; + +import cn.idev.excel.annotation.ExcelProperty; +import lombok.Data; + +@Data +public class LocationHistoryVo { + private Long id; + /** + * 设备名称 + */ + + @ExcelProperty(value = "设备名称") + private String deviceName; + + /** + * 设备类型 + */ + private String deviceType; + + /** + * 设备类型 + */ + @ExcelProperty(value = "设备型号") + private String deviceTypeName; + + /** + * 设备IMEI + */ + @ExcelProperty(value = "设备IMEI") + private String deviceImei; + + /** + * 设备MAC + */ + @ExcelProperty(value = "设备MAC") + private String deviceMac; + +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/WebDeviceVo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/WebDeviceVo.java index 94b20e80..82c3eb10 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/WebDeviceVo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/WebDeviceVo.java @@ -1,5 +1,6 @@ package com.fuyuanshen.equipment.domain.vo; +import cn.idev.excel.annotation.ExcelProperty; import lombok.Data; import java.io.Serializable; diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceMapper.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceMapper.java index e75e1c26..1a38350a 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceMapper.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceMapper.java @@ -4,8 +4,10 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.fuyuanshen.equipment.domain.Device; +import com.fuyuanshen.equipment.domain.dto.InstructionRecordDto; import com.fuyuanshen.equipment.domain.query.DeviceQueryCriteria; import com.fuyuanshen.equipment.domain.vo.AppDeviceVo; +import com.fuyuanshen.equipment.domain.vo.LocationHistoryVo; import com.fuyuanshen.equipment.domain.vo.WebDeviceVo; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; @@ -71,4 +73,6 @@ public interface DeviceMapper extends BaseMapper { AppDeviceVo getDeviceInfo(@Param("deviceMac") String deviceMac); Page queryWebDeviceList(Page build,@Param("criteria") DeviceQueryCriteria criteria); + + Page getLocationHistory(Page build, @Param("bo") InstructionRecordDto criteria); } diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceLogMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceLogMapper.xml index d15eeb8b..e825f13f 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceLogMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceLogMapper.xml @@ -29,10 +29,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" AND b.device_imei = #{bo.deviceImei} - AND create_time =]]> #{bo.startTime} + AND a.create_time =]]> #{bo.startTime} - AND create_time #{bo.startTime} + AND a.create_time #{bo.endTime} + + + and b.group_id = #{bo.groupId} ORDER BY a.create_time DESC diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml index 7e4423cb..d12a65c4 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml @@ -257,11 +257,8 @@ and d.device_name like concat('%', #{criteria.deviceName}, '%') - - and d.device_mac = #{criteria.deviceMac} - - and d.device_imei = #{criteria.deviceImei} + and (d.device_imei = #{criteria.deviceImei} or d.device_mac = #{criteria.deviceImei}) and d.device_status = #{criteria.deviceStatus} @@ -276,5 +273,33 @@ and d.group_id = #{criteria.groupId} + \ No newline at end of file From d66fb7d2c23668ccd9080d2b5ce69525ba6a2432 Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Fri, 29 Aug 2025 18:58:16 +0800 Subject: [PATCH 017/160] =?UTF-8?q?=E8=AE=BE=E5=A4=87=E5=91=8A=E8=AD=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../device/DeviceAlarmController.java | 32 +++++++++---------- .../fuyuanshen/equipment/domain/Device.java | 1 + .../equipment/domain/DeviceAlarm.java | 2 +- .../equipment/domain/bo/DeviceAlarmBo.java | 8 ++--- .../equipment/domain/vo/DeviceAlarmVo.java | 6 ++-- .../equipment/mapper/DeviceAlarmMapper.java | 3 +- .../service/impl/DeviceAlarmServiceImpl.java | 4 +-- .../mapper/equipment/DeviceAlarmMapper.xml | 13 ++++---- 8 files changed, 35 insertions(+), 34 deletions(-) diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceAlarmController.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceAlarmController.java index a7b16c01..494557a9 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceAlarmController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceAlarmController.java @@ -1,26 +1,26 @@ package com.fuyuanshen.web.controller.device; -import java.util.List; - -import lombok.RequiredArgsConstructor; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.constraints.*; -import cn.dev33.satoken.annotation.SaCheckPermission; -import org.springframework.web.bind.annotation.*; -import org.springframework.validation.annotation.Validated; -import com.fuyuanshen.common.idempotent.annotation.RepeatSubmit; -import com.fuyuanshen.common.log.annotation.Log; -import com.fuyuanshen.common.web.core.BaseController; -import com.fuyuanshen.common.mybatis.core.page.PageQuery; import com.fuyuanshen.common.core.domain.R; import com.fuyuanshen.common.core.validate.AddGroup; import com.fuyuanshen.common.core.validate.EditGroup; -import com.fuyuanshen.common.log.enums.BusinessType; import com.fuyuanshen.common.excel.utils.ExcelUtil; -import com.fuyuanshen.equipment.domain.vo.DeviceAlarmVo; -import com.fuyuanshen.equipment.domain.bo.DeviceAlarmBo; -import com.fuyuanshen.equipment.service.IDeviceAlarmService; +import com.fuyuanshen.common.idempotent.annotation.RepeatSubmit; +import com.fuyuanshen.common.log.annotation.Log; +import com.fuyuanshen.common.log.enums.BusinessType; +import com.fuyuanshen.common.mybatis.core.page.PageQuery; import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; +import com.fuyuanshen.common.web.core.BaseController; +import com.fuyuanshen.equipment.domain.bo.DeviceAlarmBo; +import com.fuyuanshen.equipment.domain.vo.DeviceAlarmVo; +import com.fuyuanshen.equipment.service.IDeviceAlarmService; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; /** * 设备告警 diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/Device.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/Device.java index 5c90dc82..7ee0d622 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/Device.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/Device.java @@ -153,6 +153,7 @@ public class Device extends TenantEntity { */ @Schema(title = "出厂日期") private Date productionDate; + /** * 在线状态(0离线,1在线) */ diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceAlarm.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceAlarm.java index 203655f3..f83e1611 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceAlarm.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceAlarm.java @@ -88,7 +88,7 @@ public class DeviceAlarm extends TenantEntity { /** * 报警持续时间 */ - private Date durationTime; + private Long durationTime; /** * 0已处理,1未处理 diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceAlarmBo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceAlarmBo.java index 01697f5f..0918f185 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceAlarmBo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceAlarmBo.java @@ -99,15 +99,13 @@ public class DeviceAlarmBo extends BaseEntity { /** * 报警持续时间 */ - private Date durationTime; + private Long durationTime; /** * 报警查询时间 */ - private Date queryTime1; - private Date queryTime2; - - + private String queryTime1; + private String queryTime2; /** * 0已处理,1未处理 diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceAlarmVo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceAlarmVo.java index e893a6a2..6800f809 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceAlarmVo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceAlarmVo.java @@ -54,10 +54,12 @@ public class DeviceAlarmVo implements Serializable { */ @ExcelProperty(value = "设备名称") private String deviceName; + /** * 设备MAC */ private String deviceMac; + /** * 设备IMEI */ @@ -116,13 +118,13 @@ public class DeviceAlarmVo implements Serializable { * 报警持续时间 */ @ExcelProperty(value = "报警持续时间") - private Date durationTime; + private Long durationTime; /** * 0已处理,1未处理 */ @ExcelProperty(value = "0已处理,1未处理") - private Long treatmentState; + private Integer treatmentState; } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceAlarmMapper.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceAlarmMapper.java index c361cf2a..39fbe991 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceAlarmMapper.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceAlarmMapper.java @@ -23,7 +23,6 @@ public interface DeviceAlarmMapper extends BaseMapperPlus selectVoPage(@Param("bo") DeviceAlarmBo bo, PageQuery pageQuery); - + Page selectVoPage( Page pageQuery,@Param("bo") DeviceAlarmBo bo); } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceAlarmServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceAlarmServiceImpl.java index 22f65ace..1bf06710 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceAlarmServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceAlarmServiceImpl.java @@ -55,9 +55,9 @@ public class DeviceAlarmServiceImpl implements IDeviceAlarmService { */ @Override public TableDataInfo queryPageList(DeviceAlarmBo bo, PageQuery pageQuery) { - LambdaQueryWrapper lqw = buildQueryWrapper(bo); + // LambdaQueryWrapper lqw = buildQueryWrapper(bo); // Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); - Page result = baseMapper.selectVoPage(bo, pageQuery); + Page result = baseMapper.selectVoPage(pageQuery.build(), bo); return TableDataInfo.build(result); } diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceAlarmMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceAlarmMapper.xml index 0d543b29..5752328d 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceAlarmMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceAlarmMapper.xml @@ -6,27 +6,28 @@ + From c2ce9679c4dbc7124bc11097a46bb5b66a3531d4 Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Sat, 30 Aug 2025 09:39:41 +0800 Subject: [PATCH 018/160] =?UTF-8?q?=E8=AE=BE=E5=A4=87=E5=85=85=E6=94=BE?= =?UTF-8?q?=E7=94=B5=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../device/DeviceAlarmController.java | 1 + .../DeviceChargeDischargeController.java | 105 +++++++++++++ .../domain/DeviceChargeDischarge.java | 108 +++++++++++++ .../domain/bo/DeviceChargeDischargeBo.java | 110 +++++++++++++ .../domain/vo/DeviceChargeDischargeVo.java | 130 ++++++++++++++++ .../mapper/DeviceChargeDischargeMapper.java | 15 ++ .../IDeviceChargeDischargeService.java | 68 ++++++++ .../DeviceChargeDischargeServiceImpl.java | 146 ++++++++++++++++++ .../equipment/DeviceChargeDischargeMapper.xml | 7 + 9 files changed, 690 insertions(+) create mode 100644 fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceChargeDischargeController.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceChargeDischarge.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceChargeDischargeBo.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceChargeDischargeVo.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceChargeDischargeMapper.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/IDeviceChargeDischargeService.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceChargeDischargeServiceImpl.java create mode 100644 fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceChargeDischargeMapper.xml diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceAlarmController.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceAlarmController.java index 494557a9..309bc14a 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceAlarmController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceAlarmController.java @@ -36,6 +36,7 @@ public class DeviceAlarmController extends BaseController { private final IDeviceAlarmService deviceAlarmService; + /** * 查询设备告警列表 */ diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceChargeDischargeController.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceChargeDischargeController.java new file mode 100644 index 00000000..c8cc77c4 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceChargeDischargeController.java @@ -0,0 +1,105 @@ +package com.fuyuanshen.web.controller.device; + +import java.util.List; + +import lombok.RequiredArgsConstructor; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.*; +import cn.dev33.satoken.annotation.SaCheckPermission; +import org.springframework.web.bind.annotation.*; +import org.springframework.validation.annotation.Validated; +import com.fuyuanshen.common.idempotent.annotation.RepeatSubmit; +import com.fuyuanshen.common.log.annotation.Log; +import com.fuyuanshen.common.web.core.BaseController; +import com.fuyuanshen.common.mybatis.core.page.PageQuery; +import com.fuyuanshen.common.core.domain.R; +import com.fuyuanshen.common.core.validate.AddGroup; +import com.fuyuanshen.common.core.validate.EditGroup; +import com.fuyuanshen.common.log.enums.BusinessType; +import com.fuyuanshen.common.excel.utils.ExcelUtil; +import com.fuyuanshen.equipment.domain.vo.DeviceChargeDischargeVo; +import com.fuyuanshen.equipment.domain.bo.DeviceChargeDischargeBo; +import com.fuyuanshen.equipment.service.IDeviceChargeDischargeService; +import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; + +/** + * 设备充放电记录 + * + * @author Lion Li + * @date 2025-08-30 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/equipment/chargeDischarge") +public class DeviceChargeDischargeController extends BaseController { + + private final IDeviceChargeDischargeService deviceChargeDischargeService; + + /** + * 查询设备充放电记录列表 + */ + @SaCheckPermission("equipment:chargeDischarge:list") + @GetMapping("/list") + public TableDataInfo list(DeviceChargeDischargeBo bo, PageQuery pageQuery) { + return deviceChargeDischargeService.queryPageList(bo, pageQuery); + } + + /** + * 设备充放电记录列表 + */ + @SaCheckPermission("equipment:chargeDischarge:export") + @Log(title = "设备充放电记录", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(DeviceChargeDischargeBo bo, HttpServletResponse response) { + List list = deviceChargeDischargeService.queryList(bo); + ExcelUtil.exportExcel(list, "设备充放电记录", DeviceChargeDischargeVo.class, response); + } + + /** + * 获取设备充放电记录详细信息 + * + * @param id 主键 + */ + @SaCheckPermission("equipment:chargeDischarge:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(deviceChargeDischargeService.queryById(id)); + } + + /** + * 新增设备充放电记录 + */ + @SaCheckPermission("equipment:chargeDischarge:add") + @Log(title = "设备充放电记录", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody DeviceChargeDischargeBo bo) { + return toAjax(deviceChargeDischargeService.insertByBo(bo)); + } + + /** + * 修改设备充放电记录 + */ + @SaCheckPermission("equipment:chargeDischarge:edit") + @Log(title = "设备充放电记录", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody DeviceChargeDischargeBo bo) { + return toAjax(deviceChargeDischargeService.updateByBo(bo)); + } + + /** + * 删除设备充放电记录 + * + * @param ids 主键串 + */ + @SaCheckPermission("equipment:chargeDischarge:remove") + @Log(title = "设备充放电记录", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(deviceChargeDischargeService.deleteWithValidByIds(List.of(ids), true)); + } +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceChargeDischarge.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceChargeDischarge.java new file mode 100644 index 00000000..67b3d48a --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceChargeDischarge.java @@ -0,0 +1,108 @@ +package com.fuyuanshen.equipment.domain; + +import com.fuyuanshen.common.tenant.core.TenantEntity; +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; + +import java.io.Serial; + +/** + * 设备充放电记录对象 device_charge_discharge + * + * @author Lion Li + * @date 2025-08-30 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("device_charge_discharge") +public class DeviceChargeDischarge extends TenantEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 记录唯一标识 + */ + @TableId(value = "id") + private Long id; + + /** + * 设备唯一标识 + */ + private String deviceId; + + /** + * 操作类型: 0 charge-充电, 1 discharge-放电 + */ + private Long operationType; + + /** + * 开始时间 + */ + private Date startTime; + + /** + * 结束时间 + */ + private Date endTime; + + /** + * 起始电量百分比(0-100) + */ + private Long initialSoc; + + /** + * 结束电量百分比(0-100) + */ + private Long finalSoc; + + /** + * 充放电量(kWh) + */ + private Long energyKwh; + + /** + * 设备额定功率(kW) + */ + private Long powerRating; + + /** + * 电压(V) + */ + private Long voltage; + + /** + * 电流(A) + */ + private Long current; + + /** + * 温度(℃) + */ + private Long temperature; + + /** + * 当前状态 + */ + private Long status; + + /** + * 错误代码 + */ + private String errorCode; + + /** + * 记录创建时间 + */ + private Date createdAt; + + /** + * 记录更新时间 + */ + private Date updatedAt; + + +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceChargeDischargeBo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceChargeDischargeBo.java new file mode 100644 index 00000000..e953b0e8 --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceChargeDischargeBo.java @@ -0,0 +1,110 @@ +package com.fuyuanshen.equipment.domain.bo; + +import com.fuyuanshen.common.core.validate.AddGroup; +import com.fuyuanshen.common.core.validate.EditGroup; +import com.fuyuanshen.equipment.domain.DeviceChargeDischarge; +import com.fuyuanshen.common.mybatis.core.domain.BaseEntity; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import lombok.EqualsAndHashCode; +import jakarta.validation.constraints.*; +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 设备充放电记录业务对象 device_charge_discharge + * + * @author Lion Li + * @date 2025-08-30 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = DeviceChargeDischarge.class, reverseConvertGenerate = false) +public class DeviceChargeDischargeBo extends BaseEntity { + + /** + * 记录唯一标识 + */ + @NotNull(message = "记录唯一标识不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 设备唯一标识 + */ + @NotBlank(message = "设备唯一标识不能为空", groups = { AddGroup.class, EditGroup.class }) + private String deviceId; + + /** + * 操作类型: 0 charge-充电, 1 discharge-放电 + */ + @NotNull(message = "操作类型: 0 charge-充电, 1 discharge-放电不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long operationType; + + /** + * 开始时间 + */ + @NotNull(message = "开始时间不能为空", groups = { AddGroup.class, EditGroup.class }) + private Date startTime; + + /** + * 结束时间 + */ + private Date endTime; + + /** + * 起始电量百分比(0-100) + */ + private Long initialSoc; + + /** + * 结束电量百分比(0-100) + */ + private Long finalSoc; + + /** + * 充放电量(kWh) + */ + private Long energyKwh; + + /** + * 设备额定功率(kW) + */ + private Long powerRating; + + /** + * 电压(V) + */ + private Long voltage; + + /** + * 电流(A) + */ + private Long current; + + /** + * 温度(℃) + */ + private Long temperature; + + /** + * 当前状态 + */ + private Long status; + + /** + * 错误代码 + */ + private String errorCode; + + /** + * 记录创建时间 + */ + private Date createdAt; + + /** + * 记录更新时间 + */ + private Date updatedAt; + + +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceChargeDischargeVo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceChargeDischargeVo.java new file mode 100644 index 00000000..35e04131 --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceChargeDischargeVo.java @@ -0,0 +1,130 @@ +package com.fuyuanshen.equipment.domain.vo; + +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fuyuanshen.equipment.domain.DeviceChargeDischarge; +import cn.idev.excel.annotation.ExcelIgnoreUnannotated; +import cn.idev.excel.annotation.ExcelProperty; +import com.fuyuanshen.common.excel.annotation.ExcelDictFormat; +import com.fuyuanshen.common.excel.convert.ExcelDictConvert; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + + + +/** + * 设备充放电记录视图对象 device_charge_discharge + * + * @author Lion Li + * @date 2025-08-30 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = DeviceChargeDischarge.class) +public class DeviceChargeDischargeVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 记录唯一标识 + */ + @ExcelProperty(value = "记录唯一标识") + private Long id; + + /** + * 设备唯一标识 + */ + @ExcelProperty(value = "设备唯一标识") + private String deviceId; + + /** + * 操作类型: 0 charge-充电, 1 discharge-放电 + */ + @ExcelProperty(value = "操作类型: 0 charge-充电, 1 discharge-放电") + private Long operationType; + + /** + * 开始时间 + */ + @ExcelProperty(value = "开始时间") + private Date startTime; + + /** + * 结束时间 + */ + @ExcelProperty(value = "结束时间") + private Date endTime; + + /** + * 起始电量百分比(0-100) + */ + @ExcelProperty(value = "起始电量百分比(0-100)") + private Long initialSoc; + + /** + * 结束电量百分比(0-100) + */ + @ExcelProperty(value = "结束电量百分比(0-100)") + private Long finalSoc; + + /** + * 充放电量(kWh) + */ + @ExcelProperty(value = "充放电量(kWh)") + private Long energyKwh; + + /** + * 设备额定功率(kW) + */ + @ExcelProperty(value = "设备额定功率(kW)") + private Long powerRating; + + /** + * 电压(V) + */ + @ExcelProperty(value = "电压(V)") + private Long voltage; + + /** + * 电流(A) + */ + @ExcelProperty(value = "电流(A)") + private Long current; + + /** + * 温度(℃) + */ + @ExcelProperty(value = "温度(℃)") + private Long temperature; + + /** + * 当前状态 + */ + @ExcelProperty(value = "当前状态") + private Long status; + + /** + * 错误代码 + */ + @ExcelProperty(value = "错误代码") + private String errorCode; + + /** + * 记录创建时间 + */ + @ExcelProperty(value = "记录创建时间") + private Date createdAt; + + /** + * 记录更新时间 + */ + @ExcelProperty(value = "记录更新时间") + private Date updatedAt; + + +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceChargeDischargeMapper.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceChargeDischargeMapper.java new file mode 100644 index 00000000..1c731966 --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceChargeDischargeMapper.java @@ -0,0 +1,15 @@ +package com.fuyuanshen.equipment.mapper; + +import com.fuyuanshen.equipment.domain.DeviceChargeDischarge; +import com.fuyuanshen.equipment.domain.vo.DeviceChargeDischargeVo; +import com.fuyuanshen.common.mybatis.core.mapper.BaseMapperPlus; + +/** + * 设备充放电记录Mapper接口 + * + * @author Lion Li + * @date 2025-08-30 + */ +public interface DeviceChargeDischargeMapper extends BaseMapperPlus { + +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/IDeviceChargeDischargeService.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/IDeviceChargeDischargeService.java new file mode 100644 index 00000000..079a659e --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/IDeviceChargeDischargeService.java @@ -0,0 +1,68 @@ +package com.fuyuanshen.equipment.service; + +import com.fuyuanshen.equipment.domain.vo.DeviceChargeDischargeVo; +import com.fuyuanshen.equipment.domain.bo.DeviceChargeDischargeBo; +import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; +import com.fuyuanshen.common.mybatis.core.page.PageQuery; + +import java.util.Collection; +import java.util.List; + +/** + * 设备充放电记录Service接口 + * + * @author Lion Li + * @date 2025-08-30 + */ +public interface IDeviceChargeDischargeService { + + /** + * 查询设备充放电记录 + * + * @param id 主键 + * @return 设备充放电记录 + */ + DeviceChargeDischargeVo queryById(Long id); + + /** + * 分页查询设备充放电记录列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 设备充放电记录分页列表 + */ + TableDataInfo queryPageList(DeviceChargeDischargeBo bo, PageQuery pageQuery); + + /** + * 查询符合条件的设备充放电记录列表 + * + * @param bo 查询条件 + * @return 设备充放电记录列表 + */ + List queryList(DeviceChargeDischargeBo bo); + + /** + * 新增设备充放电记录 + * + * @param bo 设备充放电记录 + * @return 是否新增成功 + */ + Boolean insertByBo(DeviceChargeDischargeBo bo); + + /** + * 修改设备充放电记录 + * + * @param bo 设备充放电记录 + * @return 是否修改成功 + */ + Boolean updateByBo(DeviceChargeDischargeBo bo); + + /** + * 校验并批量删除设备充放电记录信息 + * + * @param ids 待删除的主键集合 + * @param isValid 是否进行有效性校验 + * @return 是否删除成功 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceChargeDischargeServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceChargeDischargeServiceImpl.java new file mode 100644 index 00000000..bcfb9cb6 --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceChargeDischargeServiceImpl.java @@ -0,0 +1,146 @@ +package com.fuyuanshen.equipment.service.impl; + +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 lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import com.fuyuanshen.equipment.domain.bo.DeviceChargeDischargeBo; +import com.fuyuanshen.equipment.domain.vo.DeviceChargeDischargeVo; +import com.fuyuanshen.equipment.domain.DeviceChargeDischarge; +import com.fuyuanshen.equipment.mapper.DeviceChargeDischargeMapper; +import com.fuyuanshen.equipment.service.IDeviceChargeDischargeService; + +import java.util.List; +import java.util.Map; +import java.util.Collection; + +/** + * 设备充放电记录Service业务层处理 + * + * @author Lion Li + * @date 2025-08-30 + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class DeviceChargeDischargeServiceImpl implements IDeviceChargeDischargeService { + + private final DeviceChargeDischargeMapper baseMapper; + + /** + * 查询设备充放电记录 + * + * @param id 主键 + * @return 设备充放电记录 + */ + @Override + public DeviceChargeDischargeVo queryById(Long id){ + return baseMapper.selectVoById(id); + } + + /** + * 分页查询设备充放电记录列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 设备充放电记录分页列表 + */ + @Override + public TableDataInfo queryPageList(DeviceChargeDischargeBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询符合条件的设备充放电记录列表 + * + * @param bo 查询条件 + * @return 设备充放电记录列表 + */ + @Override + public List queryList(DeviceChargeDischargeBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(DeviceChargeDischargeBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.orderByAsc(DeviceChargeDischarge::getId); + lqw.eq(StringUtils.isNotBlank(bo.getDeviceId()), DeviceChargeDischarge::getDeviceId, bo.getDeviceId()); + lqw.eq(bo.getOperationType() != null, DeviceChargeDischarge::getOperationType, bo.getOperationType()); + lqw.eq(bo.getStartTime() != null, DeviceChargeDischarge::getStartTime, bo.getStartTime()); + lqw.eq(bo.getEndTime() != null, DeviceChargeDischarge::getEndTime, bo.getEndTime()); + lqw.eq(bo.getInitialSoc() != null, DeviceChargeDischarge::getInitialSoc, bo.getInitialSoc()); + lqw.eq(bo.getFinalSoc() != null, DeviceChargeDischarge::getFinalSoc, bo.getFinalSoc()); + lqw.eq(bo.getEnergyKwh() != null, DeviceChargeDischarge::getEnergyKwh, bo.getEnergyKwh()); + lqw.eq(bo.getPowerRating() != null, DeviceChargeDischarge::getPowerRating, bo.getPowerRating()); + lqw.eq(bo.getVoltage() != null, DeviceChargeDischarge::getVoltage, bo.getVoltage()); + lqw.eq(bo.getCurrent() != null, DeviceChargeDischarge::getCurrent, bo.getCurrent()); + lqw.eq(bo.getTemperature() != null, DeviceChargeDischarge::getTemperature, bo.getTemperature()); + lqw.eq(bo.getStatus() != null, DeviceChargeDischarge::getStatus, bo.getStatus()); + lqw.eq(StringUtils.isNotBlank(bo.getErrorCode()), DeviceChargeDischarge::getErrorCode, bo.getErrorCode()); + lqw.eq(bo.getCreatedAt() != null, DeviceChargeDischarge::getCreatedAt, bo.getCreatedAt()); + lqw.eq(bo.getUpdatedAt() != null, DeviceChargeDischarge::getUpdatedAt, bo.getUpdatedAt()); + return lqw; + } + + /** + * 新增设备充放电记录 + * + * @param bo 设备充放电记录 + * @return 是否新增成功 + */ + @Override + public Boolean insertByBo(DeviceChargeDischargeBo bo) { + DeviceChargeDischarge add = MapstructUtils.convert(bo, DeviceChargeDischarge.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改设备充放电记录 + * + * @param bo 设备充放电记录 + * @return 是否修改成功 + */ + @Override + public Boolean updateByBo(DeviceChargeDischargeBo bo) { + DeviceChargeDischarge update = MapstructUtils.convert(bo, DeviceChargeDischarge.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(DeviceChargeDischarge entity){ + //TODO 做一些数据校验,如唯一约束 + } + + /** + * 校验并批量删除设备充放电记录信息 + * + * @param ids 待删除的主键集合 + * @param isValid 是否进行有效性校验 + * @return 是否删除成功 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if(isValid){ + //TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteByIds(ids) > 0; + } +} diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceChargeDischargeMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceChargeDischargeMapper.xml new file mode 100644 index 00000000..5d60dc4d --- /dev/null +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceChargeDischargeMapper.xml @@ -0,0 +1,7 @@ + + + + + From 896c501cd689faad4054a0156c22ed2f613f0165 Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Sat, 30 Aug 2025 11:07:04 +0800 Subject: [PATCH 019/160] =?UTF-8?q?=E6=8A=A5=E8=AD=A6=E6=8C=81=E7=BB=AD?= =?UTF-8?q?=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fuyuanshen/equipment/domain/vo/DeviceAlarmVo.java | 9 ++++++++- .../resources/mapper/equipment/DeviceAlarmMapper.xml | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceAlarmVo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceAlarmVo.java index 6800f809..4e9935d3 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceAlarmVo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceAlarmVo.java @@ -9,6 +9,7 @@ import cn.idev.excel.annotation.ExcelProperty; import com.fuyuanshen.common.excel.annotation.ExcelDictFormat; import com.fuyuanshen.common.excel.convert.ExcelDictConvert; import io.github.linpeilie.annotations.AutoMapper; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.io.Serial; @@ -118,7 +119,7 @@ public class DeviceAlarmVo implements Serializable { * 报警持续时间 */ @ExcelProperty(value = "报警持续时间") - private Long durationTime; + private String durationTime; /** * 0已处理,1未处理 @@ -126,5 +127,11 @@ public class DeviceAlarmVo implements Serializable { @ExcelProperty(value = "0已处理,1未处理") private Integer treatmentState; + /** + * 设备图片 + * device_pic + */ + @Schema(name = "设备图片") + private String devicePic; } diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceAlarmMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceAlarmMapper.xml index 5752328d..cdb76f8a 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceAlarmMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceAlarmMapper.xml @@ -7,7 +7,7 @@ + + + + + + + + + + + + + + \ No newline at end of file From d97928b38abdbf837a0ecbf2663e82217af6240d Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Wed, 3 Sep 2025 09:45:17 +0800 Subject: [PATCH 025/160] =?UTF-8?q?=E5=88=86=E9=85=8D=E5=AE=A2=E6=88=B7=20?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/DeviceController.java | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceController.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceController.java index 229c2da3..030452a1 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceController.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceController.java @@ -126,26 +126,26 @@ public class DeviceController extends BaseController { // @Log("分配客户") - @Operation(summary = "分配客户") - @PutMapping(value = "/assignCustomer") - public R assignCustomer(@Validated @RequestBody CustomerVo customerVo) { - deviceService.assignCustomer(customerVo); - return R.ok(); - } + // @Operation(summary = "分配客户") + // @PutMapping(value = "/assignCustomer") + // public R assignCustomer(@Validated @RequestBody CustomerVo customerVo) { + // deviceService.assignCustomer(customerVo); + // return R.ok(); + // } // @Log("撤回设备") - @Operation(summary = "撤回分配设备") - @PostMapping(value = "/withdraw") - public R withdrawDevice(@RequestBody List ids) { - try { - deviceService.withdrawDevice(ids); - } catch (Exception e) { - log.error("updateDevice error: " + e.getMessage()); - return R.fail(e.getMessage()); - } - return R.ok(); - } + // @Operation(summary = "撤回分配设备") + // @PostMapping(value = "/withdraw") + // public R withdrawDevice(@RequestBody List ids) { + // try { + // deviceService.withdrawDevice(ids); + // } catch (Exception e) { + // log.error("updateDevice error: " + e.getMessage()); + // return R.fail(e.getMessage()); + // } + // return R.ok(); + // } // // /** From 98cb67b1365a0538c18609fec92ea89606211888 Mon Sep 17 00:00:00 2001 From: chenyouting <514333061@qq.com> Date: Wed, 3 Sep 2025 15:11:24 +0800 Subject: [PATCH 026/160] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E7=BB=8F=E7=BA=AC=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mqtt/rule/bjq/BjqLocationDataRule.java | 42 +++++++++---------- .../device/DeviceShareController.java | 16 +++++-- .../service/impl/DeviceLogServiceImpl.java | 1 + 3 files changed, 34 insertions(+), 25 deletions(-) diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqLocationDataRule.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqLocationDataRule.java index 0c3bda1f..69637bfd 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqLocationDataRule.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqLocationDataRule.java @@ -117,28 +117,28 @@ public class BjqLocationDataRule implements MqttMessageRule { if(StringUtils.isBlank(latitude) || StringUtils.isBlank(longitude)){ return; } - String[] latArr = latitude.split("\\."); - String[] lonArr = longitude.split("\\."); - // 将位置信息存储到Redis中 +// String[] latArr = latitude.split("\\."); +// String[] lonArr = longitude.split("\\."); +// // 将位置信息存储到Redis中 String redisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ deviceImei + DEVICE_LOCATION_KEY_PREFIX; - String redisObj = RedisUtils.getCacheObject(redisKey); - JSONObject jsonOBj = JSONObject.parseObject(redisObj); - if(jsonOBj != null){ - String str1 = latArr[0] +"."+ latArr[1].substring(0,4); - String str2 = lonArr[0] +"."+ lonArr[1].substring(0,4); - - String cacheLatitude = jsonOBj.getString("wgs84_latitude"); - String cacheLongitude = jsonOBj.getString("wgs84_longitude"); - String[] latArr1 = cacheLatitude.split("\\."); - String[] lonArr1 = cacheLongitude.split("\\."); - - String cacheStr1 = latArr1[0] +"."+ latArr1[1].substring(0,4); - String cacheStr2 = lonArr1[0] +"."+ lonArr1[1].substring(0,4); - if(str1.equals(cacheStr1) && str2.equals(cacheStr2)){ - log.info("位置信息未发生变化: device={}, lat={}, lon={}", deviceImei, latitude, longitude); - return; - } - } +// String redisObj = RedisUtils.getCacheObject(redisKey); +// JSONObject jsonOBj = JSONObject.parseObject(redisObj); +// if(jsonOBj != null){ +// String str1 = latArr[0] +"."+ latArr[1].substring(0,4); +// String str2 = lonArr[0] +"."+ lonArr[1].substring(0,4); +// +// String cacheLatitude = jsonOBj.getString("wgs84_latitude"); +// String cacheLongitude = jsonOBj.getString("wgs84_longitude"); +// String[] latArr1 = cacheLatitude.split("\\."); +// String[] lonArr1 = cacheLongitude.split("\\."); +// +// String cacheStr1 = latArr1[0] +"."+ latArr1[1].substring(0,4); +// String cacheStr2 = lonArr1[0] +"."+ lonArr1[1].substring(0,4); +// if(str1.equals(cacheStr1) && str2.equals(cacheStr2)){ +// log.info("位置信息未发生变化: device={}, lat={}, lon={}", deviceImei, latitude, longitude); +// return; +// } +// } // 构造位置信息对象 Map locationInfo = new LinkedHashMap<>(); diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceShareController.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceShareController.java index e623e9d6..e271fc64 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceShareController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceShareController.java @@ -2,6 +2,9 @@ package com.fuyuanshen.web.controller.device; import com.fuyuanshen.app.domain.bo.AppDeviceShareBo; import com.fuyuanshen.app.domain.vo.AppDeviceShareVo; +import com.fuyuanshen.common.core.domain.R; +import com.fuyuanshen.common.core.validate.AddGroup; +import com.fuyuanshen.common.idempotent.annotation.RepeatSubmit; import com.fuyuanshen.common.mybatis.core.page.PageQuery; import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; import com.fuyuanshen.common.web.core.BaseController; @@ -10,9 +13,7 @@ import com.fuyuanshen.equipment.domain.vo.DeviceAlarmVo; import com.fuyuanshen.web.service.DeviceShareService; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; /** * 设备分享管理 @@ -39,5 +40,12 @@ public class DeviceShareController extends BaseController { } - + /** + * 新增设备分享 + */ + @RepeatSubmit() + @PostMapping("/deviceShare") + public R deviceShare(@Validated(AddGroup.class) @RequestBody AppDeviceShareBo bo) { + return toAjax(appDeviceShareService.deviceShare(bo)); + } } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceLogServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceLogServiceImpl.java index 16ca6d3d..9ded85ab 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceLogServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceLogServiceImpl.java @@ -99,6 +99,7 @@ public class DeviceLogServiceImpl implements IDeviceLogService { lqw.eq(StringUtils.isNotBlank(bo.getDataSource()), DeviceLog::getDataSource, bo.getDataSource()); lqw.like(StringUtils.isNotBlank(bo.getContent()), DeviceLog::getContent, bo.getContent()); lqw.in(CollectionUtil.isNotEmpty(bo.getDeviceIds()), DeviceLog::getDeviceId, bo.getDeviceIds()); + lqw.orderByDesc(DeviceLog::getCreateTime); return lqw; } 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 027/160] =?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"> + + + + + + + + + + + + + + + From ff4db34e2adfec2af15f67487fc7447604f6cf06 Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Thu, 4 Sep 2025 11:29:07 +0800 Subject: [PATCH 028/160] =?UTF-8?q?=E5=9E=8B=E5=8F=B7=E5=AD=97=E5=85=B8?= =?UTF-8?q?=E7=94=A8=E4=BA=8EAPP=E9=A1=B5=E9=9D=A2=E8=B7=B3=E8=BD=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../equipment/domain/form/DeviceTypeForm.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/form/DeviceTypeForm.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/form/DeviceTypeForm.java index d5e1df70..93197f47 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/form/DeviceTypeForm.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/form/DeviceTypeForm.java @@ -35,4 +35,18 @@ public class DeviceTypeForm { @Schema(title = "型号字典用于APP页面跳转") private String modelDictionary; + /** + * 型号字典用于APP页面跳转 + * app_model_dictionary + */ + @Schema(title = "型号字典用于APP页面跳转") + private String appModelDictionary; + + /** + * 型号字典用于PC页面跳转 + * pc_model_dictionary + */ + @Schema(title = "型号字典用于PC页面跳转") + private String pcModelDictionary; + } From d5a9a92f9f8c02e1adf4d7d4afcee5d7aa2b3326 Mon Sep 17 00:00:00 2001 From: DragonWenLong <552045633@qq.com> Date: Thu, 4 Sep 2025 11:50:18 +0800 Subject: [PATCH 029/160] =?UTF-8?q?feat(device):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E6=8E=A7=E5=88=B6=E7=9B=B8=E5=85=B3=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 人员信息登记- 发送紧急通知 -上传设备 logo 图片 -静电预警档位设置- 照明档位设置- SOS 档位设置- 静止报警状态设置 --- .../device/DeviceXinghanController.java | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceXinghanController.java diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceXinghanController.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceXinghanController.java new file mode 100644 index 00000000..8600c788 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceXinghanController.java @@ -0,0 +1,106 @@ +package com.fuyuanshen.web.controller.device; + +import com.fuyuanshen.app.domain.bo.AppPersonnelInfoBo; +import com.fuyuanshen.app.domain.dto.AppDeviceLogoUploadDto; +import com.fuyuanshen.app.domain.dto.DeviceInstructDto; +import com.fuyuanshen.common.core.domain.R; +import com.fuyuanshen.common.core.validate.AddGroup; +import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessAnnotation; +import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessBatcAnnotation; +import com.fuyuanshen.common.web.core.BaseController; +import com.fuyuanshen.equipment.domain.dto.AppDeviceSendMsgBo; +import com.fuyuanshen.web.service.device.DeviceXinghanBizService; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +/** + * 设备控制类 HBY670 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/xinghan/device") +public class DeviceXinghanController extends BaseController { + + private final DeviceXinghanBizService deviceXinghanBizService; + + /** + * 人员信息登记 + */ + @PostMapping(value = "/registerPersonInfo") +// @FunctionAccessAnnotation("registerPersonInfo") + public R registerPersonInfo(@Validated(AddGroup.class) @RequestBody AppPersonnelInfoBo bo) { + return toAjax(deviceXinghanBizService.registerPersonInfo(bo)); + } + + /** + * 发送紧急通知 + */ + @PostMapping(value = "/sendAlarmMessage") + @FunctionAccessBatcAnnotation(value = "sendAlarmMessage", timeOut = 5, batchMaxTimeOut = 10) + public R sendAlarmMessage(@RequestBody AppDeviceSendMsgBo bo) { + return toAjax(deviceXinghanBizService.sendAlarmMessage(bo)); + } + + /** + * 上传设备logo图片 + */ + @PostMapping("/uploadLogo") + @FunctionAccessAnnotation("uploadLogo") + public R upload(@Validated @ModelAttribute AppDeviceLogoUploadDto bo) { + + MultipartFile file = bo.getFile(); + if(file.getSize()>1024*1024*2){ + return R.warn("图片不能大于2M"); + } + deviceXinghanBizService.uploadDeviceLogo(bo); + + return R.ok(); + } + + /** + * 静电预警档位 + * 3,2,1,0,分别表示高档/中档/低挡/关闭 + */ + @PostMapping("/DetectGradeSettings") + public R DetectGradeSettings(@RequestBody DeviceInstructDto params) { + // params 转 JSONObject + deviceXinghanBizService.upDetectGradeSettings(params); + return R.ok(); + } + + /** + * 照明档位 + * 照明档位,2,1,0,分别表示弱光/强光/关闭 + */ + @PostMapping("/LightGradeSettings") + public R LightGradeSettings(@RequestBody DeviceInstructDto params) { + // params 转 JSONObject + deviceXinghanBizService.upLightGradeSettings(params); + return R.ok(); + } + + /** + * SOS档位 + * SOS档位,2,1,0, 分别表示红蓝模式/爆闪模式/关闭 + */ + @PostMapping("/SOSGradeSettings") + public R SOSGradeSettings(@RequestBody DeviceInstructDto params) { + // params 转 JSONObject + deviceXinghanBizService.upSOSGradeSettings(params); + return R.ok(); + } + + /** + * 静止报警状态 + * 静止报警状态,0-未静止报警,1-正在静止报警。 + */ + @PostMapping("/ShakeBitSettings") + public R ShakeBitSettings(@RequestBody DeviceInstructDto params) { + // params 转 JSONObject + deviceXinghanBizService.upShakeBitSettings(params); + return R.ok(); + } +} From 452c37c4caf2da52151d93607aee6fc460a12531 Mon Sep 17 00:00:00 2001 From: chenyouting <514333061@qq.com> Date: Thu, 4 Sep 2025 18:40:08 +0800 Subject: [PATCH 030/160] =?UTF-8?q?=E7=A6=BB=E7=BA=BF=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E5=8F=98=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../listener/RedisKeyExpirationListener.java | 29 +++++++++++++++++-- .../service/device/DeviceBJQBizService.java | 1 - .../domain/query/DeviceQueryCriteria.java | 6 ++++ .../mapper/equipment/DeviceMapper.xml | 4 +++ 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/listener/RedisKeyExpirationListener.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/listener/RedisKeyExpirationListener.java index 86e669a0..11305d19 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/listener/RedisKeyExpirationListener.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/listener/RedisKeyExpirationListener.java @@ -1,23 +1,37 @@ package com.fuyuanshen.global.mqtt.listener; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.fuyuanshen.common.core.constant.GlobalConstants; import com.fuyuanshen.common.redis.utils.RedisUtils; +import com.fuyuanshen.equipment.domain.Device; +import com.fuyuanshen.equipment.mapper.DeviceMapper; import com.fuyuanshen.global.mqtt.listener.domain.FunctionAccessStatus; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.data.redis.connection.Message; import org.springframework.data.redis.connection.MessageListener; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Component; import java.time.Duration; -import java.util.HashMap; -import java.util.Map; import static com.fuyuanshen.common.core.constant.GlobalConstants.FUNCTION_ACCESS_KEY; import static com.fuyuanshen.common.core.constant.GlobalConstants.FUNCTION_ACCESS_TIMEOUT_KEY; +import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX; +import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX; @Component @Slf4j public class RedisKeyExpirationListener implements MessageListener { + @Autowired + @Qualifier("threadPoolTaskExecutor") + private ThreadPoolTaskExecutor threadPoolTaskExecutor; + + @Autowired + private DeviceMapper deviceMapper; + @Override public void onMessage(Message message, byte[] pattern) { String expiredKey = new String(message.getBody()); @@ -26,8 +40,17 @@ public class RedisKeyExpirationListener implements MessageListener { String element = expiredKey.substring(FUNCTION_ACCESS_KEY.length()); handleFunctionAccessExpired(element); } + if(expiredKey.endsWith(DEVICE_ONLINE_STATUS_KEY_PREFIX)){ + threadPoolTaskExecutor.execute(() -> { + log.info("设备离线:{}", expiredKey); + String element = expiredKey.substring(GlobalConstants.GLOBAL_REDIS_KEY.length() + DEVICE_KEY_PREFIX.length(), expiredKey.length() - DEVICE_ONLINE_STATUS_KEY_PREFIX.length()); + UpdateWrapper deviceUpdateWrapper = new UpdateWrapper<>(); + deviceUpdateWrapper.eq("device_imei", element); + deviceUpdateWrapper.set("online_status", 0); + deviceMapper.update(deviceUpdateWrapper); + }); + } } - /** * 访问key过期事件 * @param element 批次ID diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBJQBizService.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBJQBizService.java index 17c8d8ae..591ed6ef 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBJQBizService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBJQBizService.java @@ -100,7 +100,6 @@ public class DeviceBJQBizService { UpdateWrapper updateWrapper = new UpdateWrapper<>(); updateWrapper.eq("id", deviceId) - .eq("binding_user_id", AppLoginHelper.getUserId()) .set("send_msg", bo.getSendMsg()); deviceMapper.update(updateWrapper); diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/DeviceQueryCriteria.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/DeviceQueryCriteria.java index d4cb7766..1ec9137f 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/DeviceQueryCriteria.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/DeviceQueryCriteria.java @@ -114,4 +114,10 @@ public class DeviceQueryCriteria extends BaseEntity { private String content; + /** + * 设备在线状态 + * 0:离线;1:在线 + */ + private Integer onlineStatus; + } diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml index 88b17759..81c66744 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml @@ -281,6 +281,10 @@ and d.group_id = #{criteria.groupId} + + and d.online_status = #{criteria.onlineStatus} + + ORDER BY d.create_time DESC - select * + select d.device_name, + d.device_mac, + d.device_sn, + d.device_imei, + d.device_pic, + dt.type_name, + dt.communication_mode, + d.bluetooth_name, + dt.app_model_dictionary detailPageUrl, + c.binding_time, + ad.*,u.user_name otherPhonenumber from app_device_share ad + left join device d on ad.device_id = d.id + left join app_user u on ad.create_by = u.user_id + inner join device_type dt on d.device_type = dt.id + inner join app_device_bind_record c on d.id = c.device_id and ad.device_id = #{bo.deviceId} - and ad.share_user = #{bo.shareUser} + and u.user_name = #{bo.shareUser} - and d.create_time between #{bo.shareStartTime} and #{bo.shareEndTime} + and ad.create_time between #{bo.shareStartTime} and #{bo.shareEndTime} diff --git a/fys-modules/fys-generator/src/main/resources/vm/java/controller.java.vm b/fys-modules/fys-generator/src/main/resources/vm/java/controller.java.vm index 9f9c8185..6b836491 100644 --- a/fys-modules/fys-generator/src/main/resources/vm/java/controller.java.vm +++ b/fys-modules/fys-generator/src/main/resources/vm/java/controller.java.vm @@ -56,7 +56,7 @@ public class ${ClassName}Controller extends BaseController { #end /** - * 导出${functionName}列表 + * ${functionName}列表 */ @SaCheckPermission("${permissionPrefix}:export") @Log(title = "${functionName}", businessType = BusinessType.EXPORT) From 870f94b2d4cb8dc4f7ad7a5f3dcfde70511602d3 Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Mon, 8 Sep 2025 16:12:02 +0800 Subject: [PATCH 035/160] =?UTF-8?q?=E5=BC=80=E5=A7=8B=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../device/DeviceShareController.java | 1 + .../device/WEBDeviceController.java | 19 +++++++++--- .../web/service/WEBDeviceService.java | 9 ++++-- .../service/impl/WEBDeviceServiceImpl.java | 31 +++++++++++++++---- 4 files changed, 47 insertions(+), 13 deletions(-) diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceShareController.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceShareController.java index 9b448047..26c0fc02 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceShareController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceShareController.java @@ -46,4 +46,5 @@ public class DeviceShareController extends BaseController { public R deviceShare(@Validated(AddGroup.class) @RequestBody AppDeviceShareBo bo) { return toAjax(appDeviceShareService.deviceShare(bo)); } + } diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/WEBDeviceController.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/WEBDeviceController.java index c9580591..7f61a218 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/WEBDeviceController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/WEBDeviceController.java @@ -65,7 +65,7 @@ public class WEBDeviceController extends BaseController { * @param deviceId * @return */ - @Operation(summary = "设备详情") + @Operation(summary = "设备用户详情") @GetMapping(value = "/getDeviceUser/{deviceId}") public R> getDeviceUser(@PathVariable Long deviceId) { List device = deviceService.getDeviceUser(deviceId); @@ -77,12 +77,16 @@ public class WEBDeviceController extends BaseController { * 设备操作记录 * * @param deviceId + * @param startTime 开始时间 + * @param endTime 结束时间 * @return */ @Operation(summary = "设备操作记录") @GetMapping(value = "/getOperationRecord/{deviceId}") - public R> getOperationRecord(@PathVariable Long deviceId) { - List device = deviceService.getOperationRecord(deviceId); + public R> getOperationRecord(@PathVariable Long deviceId, + @RequestParam(required = false) String startTime, + @RequestParam(required = false) String endTime) { + List device = deviceService.getOperationRecord(deviceId, startTime, endTime); return R.ok(device); } @@ -91,16 +95,21 @@ public class WEBDeviceController extends BaseController { * 设备告警记录 * * @param deviceId + * @param startTime 开始时间 + * @param endTime 结束时间 * @return */ @Operation(summary = "设备告警记录") @GetMapping(value = "/getAlarmRecord/{deviceId}") - public R> getAlarmRecord(@PathVariable Long deviceId) { - List device = deviceService.getAlarmRecord(deviceId); + public R> getAlarmRecord(@PathVariable Long deviceId, + @RequestParam(required = false) String startTime, + @RequestParam(required = false) String endTime) { + List device = deviceService.getAlarmRecord(deviceId, startTime, endTime); return R.ok(device); } + } diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/WEBDeviceService.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/WEBDeviceService.java index 7066a519..f83658c6 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/service/WEBDeviceService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/WEBDeviceService.java @@ -53,16 +53,21 @@ public interface WEBDeviceService extends IService { * 设备操作记录 * * @param deviceId + * @param startTime 开始时间 + * @param endTime 结束时间 * @return */ - List getOperationRecord(Long deviceId); + List getOperationRecord(Long deviceId, String startTime, String endTime); /** * 设备告警记录 * * @param deviceId + * @param startTime 开始时间 + * @param endTime 结束时间 * @return */ - List getAlarmRecord(Long deviceId); + List getAlarmRecord(Long deviceId, String startTime, String endTime); + } diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/WEBDeviceServiceImpl.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/WEBDeviceServiceImpl.java index cc927804..b6ec1f15 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/WEBDeviceServiceImpl.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/WEBDeviceServiceImpl.java @@ -1,6 +1,7 @@ package com.fuyuanshen.web.service.impl; import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; @@ -129,10 +130,19 @@ public class WEBDeviceServiceImpl extends ServiceImpl impl * @return */ @Override - public List getOperationRecord(Long deviceId) { + public List getOperationRecord(Long deviceId, String startTime, String endTime) { + QueryWrapper queryWrapper = new QueryWrapper().eq("device_id", deviceId); + + if (StrUtil.isNotEmpty(startTime)) { + queryWrapper.ge("create_time", startTime); + } + + if (StrUtil.isNotEmpty(endTime)) { + queryWrapper.le("create_time", endTime); + } + List logList = deviceLogMapper.selectList( - new QueryWrapper().eq("device_id", deviceId) - .orderByDesc("create_time")); + queryWrapper.orderByDesc("create_time")); return logList; } @@ -144,10 +154,19 @@ public class WEBDeviceServiceImpl extends ServiceImpl impl * @return */ @Override - public List getAlarmRecord(Long deviceId) { + public List getAlarmRecord(Long deviceId, String startTime, String endTime) { + QueryWrapper queryWrapper = new QueryWrapper().eq("device_id", deviceId); + + if (StrUtil.isNotEmpty(startTime)) { + queryWrapper.ge("create_time", startTime); + } + + if (StrUtil.isNotEmpty(endTime)) { + queryWrapper.le("create_time", endTime); + } + List alarmList = deviceAlarmMapper.selectList( - new QueryWrapper().eq("device_id", deviceId) - .orderByDesc("create_time")); + queryWrapper.orderByDesc("create_time")); List deviceAlarmVoList = BeanUtil.copyToList(alarmList, DeviceAlarmVo.class); return deviceAlarmVoList; } From 91f787eec7493ff8ade1df888f13b87518ef54dd Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Mon, 8 Sep 2025 17:37:01 +0800 Subject: [PATCH 036/160] pageQuery --- .../device/WEBDeviceController.java | 13 ++++++---- .../web/service/WEBDeviceService.java | 2 +- .../service/impl/WEBDeviceServiceImpl.java | 25 +++++++++++-------- .../equipment/domain/DeviceAlarm.java | 5 ++-- 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/WEBDeviceController.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/WEBDeviceController.java index 7f61a218..e05e6b74 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/WEBDeviceController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/WEBDeviceController.java @@ -3,6 +3,8 @@ package com.fuyuanshen.web.controller.device; import com.fuyuanshen.app.domain.AppPersonnelInfoRecords; import com.fuyuanshen.common.core.domain.R; +import com.fuyuanshen.common.mybatis.core.page.PageQuery; +import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; import com.fuyuanshen.common.web.core.BaseController; import com.fuyuanshen.equipment.domain.DeviceLog; import com.fuyuanshen.equipment.domain.vo.DeviceAlarmVo; @@ -101,15 +103,16 @@ public class WEBDeviceController extends BaseController { */ @Operation(summary = "设备告警记录") @GetMapping(value = "/getAlarmRecord/{deviceId}") - public R> getAlarmRecord(@PathVariable Long deviceId, - @RequestParam(required = false) String startTime, - @RequestParam(required = false) String endTime) { - List device = deviceService.getAlarmRecord(deviceId, startTime, endTime); - return R.ok(device); + public TableDataInfo getAlarmRecord(@PathVariable Long deviceId, + @RequestParam(required = false) String startTime, + @RequestParam(required = false) String endTime, + PageQuery pageQuery) { + return deviceService.getAlarmRecord(deviceId, startTime, endTime, pageQuery); } + } diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/WEBDeviceService.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/WEBDeviceService.java index f83658c6..e891de0f 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/service/WEBDeviceService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/WEBDeviceService.java @@ -68,6 +68,6 @@ public interface WEBDeviceService extends IService { * @param endTime 结束时间 * @return */ - List getAlarmRecord(Long deviceId, String startTime, String endTime); + TableDataInfo getAlarmRecord(Long deviceId, String startTime, String endTime, PageQuery pageQuery); } diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/WEBDeviceServiceImpl.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/WEBDeviceServiceImpl.java index b6ec1f15..ea7199a1 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/WEBDeviceServiceImpl.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/WEBDeviceServiceImpl.java @@ -4,14 +4,17 @@ import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.fuyuanshen.app.domain.AppDeviceBindRecord; import com.fuyuanshen.app.domain.AppDeviceShare; import com.fuyuanshen.app.domain.AppPersonnelInfoRecords; -import com.fuyuanshen.app.domain.vo.AppDeviceShareVo; import com.fuyuanshen.app.mapper.AppDeviceBindRecordMapper; import com.fuyuanshen.app.mapper.AppDeviceShareMapper; import com.fuyuanshen.app.mapper.AppPersonnelInfoRecordsMapper; +import com.fuyuanshen.common.mybatis.core.page.PageQuery; +import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; import com.fuyuanshen.equipment.domain.Device; import com.fuyuanshen.equipment.domain.DeviceAlarm; import com.fuyuanshen.equipment.domain.DeviceAssignments; @@ -24,7 +27,6 @@ import com.fuyuanshen.equipment.mapper.DeviceAssignmentsMapper; import com.fuyuanshen.equipment.mapper.DeviceLogMapper; import com.fuyuanshen.equipment.mapper.DeviceMapper; import com.fuyuanshen.web.service.WEBDeviceService; -import com.fuyuanshen.web.service.device.DeviceBizService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -154,22 +156,23 @@ public class WEBDeviceServiceImpl extends ServiceImpl impl * @return */ @Override - public List getAlarmRecord(Long deviceId, String startTime, String endTime) { + public TableDataInfo getAlarmRecord(Long deviceId, String startTime, String endTime, PageQuery pageQuery) { + Page page = pageQuery.build(); QueryWrapper queryWrapper = new QueryWrapper().eq("device_id", deviceId); if (StrUtil.isNotEmpty(startTime)) { - queryWrapper.ge("create_time", startTime); + queryWrapper.ge("start_time", startTime); } - if (StrUtil.isNotEmpty(endTime)) { - queryWrapper.le("create_time", endTime); + queryWrapper.le("start_time", endTime); } + queryWrapper.orderByDesc("start_time"); + IPage alarmPage = deviceAlarmMapper.selectPage(page, queryWrapper); - List alarmList = deviceAlarmMapper.selectList( - queryWrapper.orderByDesc("create_time")); - List deviceAlarmVoList = BeanUtil.copyToList(alarmList, DeviceAlarmVo.class); - return deviceAlarmVoList; + // List deviceAlarmVoList = BeanUtil.copyToList(alarmPage.getRecords(), DeviceAlarmVo.class); + return TableDataInfo.build(alarmPage); } - } + + diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceAlarm.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceAlarm.java index 65b4e88c..720ee9bc 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceAlarm.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceAlarm.java @@ -4,7 +4,9 @@ import com.fuyuanshen.common.tenant.core.TenantEntity; import com.baomidou.mybatisplus.annotation.*; import lombok.Data; import lombok.EqualsAndHashCode; + import java.util.Date; + import com.fasterxml.jackson.annotation.JsonFormat; import java.io.Serial; @@ -63,7 +65,6 @@ public class DeviceAlarm extends TenantEntity { /** * 经度 - */ private Long longitude; @@ -90,7 +91,7 @@ public class DeviceAlarm extends TenantEntity { /** * 报警持续时间 */ - private Long durationTime; + private String durationTime; /** * 0已处理,1未处理 From f7a82ef13800c3d9ca6a511fdf78a88dc2907d45 Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Tue, 9 Sep 2025 09:55:58 +0800 Subject: [PATCH 037/160] =?UTF-8?q?=E8=AE=BE=E5=A4=87=E6=93=8D=E4=BD=9C?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=20=E5=88=86=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../device/DeviceChargeDischargeController.java | 14 ++++++++------ .../web/controller/device/WEBDeviceController.java | 11 +++++------ .../fuyuanshen/web/service/WEBDeviceService.java | 2 +- .../web/service/impl/WEBDeviceServiceImpl.java | 11 +++++------ 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceChargeDischargeController.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceChargeDischargeController.java index c8cc77c4..c43890fd 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceChargeDischargeController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceChargeDischargeController.java @@ -39,7 +39,7 @@ public class DeviceChargeDischargeController extends BaseController { /** * 查询设备充放电记录列表 */ - @SaCheckPermission("equipment:chargeDischarge:list") + // @SaCheckPermission("equipment:chargeDischarge:list") @GetMapping("/list") public TableDataInfo list(DeviceChargeDischargeBo bo, PageQuery pageQuery) { return deviceChargeDischargeService.queryPageList(bo, pageQuery); @@ -48,7 +48,7 @@ public class DeviceChargeDischargeController extends BaseController { /** * 设备充放电记录列表 */ - @SaCheckPermission("equipment:chargeDischarge:export") + // @SaCheckPermission("equipment:chargeDischarge:export") @Log(title = "设备充放电记录", businessType = BusinessType.EXPORT) @PostMapping("/export") public void export(DeviceChargeDischargeBo bo, HttpServletResponse response) { @@ -61,7 +61,7 @@ public class DeviceChargeDischargeController extends BaseController { * * @param id 主键 */ - @SaCheckPermission("equipment:chargeDischarge:query") + // @SaCheckPermission("equipment:chargeDischarge:query") @GetMapping("/{id}") public R getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) { @@ -71,7 +71,7 @@ public class DeviceChargeDischargeController extends BaseController { /** * 新增设备充放电记录 */ - @SaCheckPermission("equipment:chargeDischarge:add") + // @SaCheckPermission("equipment:chargeDischarge:add") @Log(title = "设备充放电记录", businessType = BusinessType.INSERT) @RepeatSubmit() @PostMapping() @@ -82,7 +82,7 @@ public class DeviceChargeDischargeController extends BaseController { /** * 修改设备充放电记录 */ - @SaCheckPermission("equipment:chargeDischarge:edit") + // @SaCheckPermission("equipment:chargeDischarge:edit") @Log(title = "设备充放电记录", businessType = BusinessType.UPDATE) @RepeatSubmit() @PutMapping() @@ -95,11 +95,13 @@ public class DeviceChargeDischargeController extends BaseController { * * @param ids 主键串 */ - @SaCheckPermission("equipment:chargeDischarge:remove") + // @SaCheckPermission("equipment:chargeDischarge:remove") @Log(title = "设备充放电记录", businessType = BusinessType.DELETE) @DeleteMapping("/{ids}") public R remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) { return toAjax(deviceChargeDischargeService.deleteWithValidByIds(List.of(ids), true)); } + + } diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/WEBDeviceController.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/WEBDeviceController.java index e05e6b74..aa1fc9f4 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/WEBDeviceController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/WEBDeviceController.java @@ -85,11 +85,12 @@ public class WEBDeviceController extends BaseController { */ @Operation(summary = "设备操作记录") @GetMapping(value = "/getOperationRecord/{deviceId}") - public R> getOperationRecord(@PathVariable Long deviceId, + public TableDataInfo getOperationRecord(@PathVariable Long deviceId, @RequestParam(required = false) String startTime, - @RequestParam(required = false) String endTime) { - List device = deviceService.getOperationRecord(deviceId, startTime, endTime); - return R.ok(device); + @RequestParam(required = false) String endTime, + PageQuery pageQuery) { + TableDataInfo device = deviceService.getOperationRecord(deviceId, startTime, endTime,pageQuery); + return device; } @@ -111,8 +112,6 @@ public class WEBDeviceController extends BaseController { } - - } diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/WEBDeviceService.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/WEBDeviceService.java index e891de0f..121982ff 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/service/WEBDeviceService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/WEBDeviceService.java @@ -57,7 +57,7 @@ public interface WEBDeviceService extends IService { * @param endTime 结束时间 * @return */ - List getOperationRecord(Long deviceId, String startTime, String endTime); + TableDataInfo getOperationRecord(Long deviceId, String startTime, String endTime, PageQuery pageQuery); /** diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/WEBDeviceServiceImpl.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/WEBDeviceServiceImpl.java index ea7199a1..6e7d5a16 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/WEBDeviceServiceImpl.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/WEBDeviceServiceImpl.java @@ -132,20 +132,19 @@ public class WEBDeviceServiceImpl extends ServiceImpl impl * @return */ @Override - public List getOperationRecord(Long deviceId, String startTime, String endTime) { + public TableDataInfo getOperationRecord(Long deviceId, String startTime, String endTime, PageQuery pageQuery) { + Page page = pageQuery.build(); QueryWrapper queryWrapper = new QueryWrapper().eq("device_id", deviceId); if (StrUtil.isNotEmpty(startTime)) { queryWrapper.ge("create_time", startTime); } - if (StrUtil.isNotEmpty(endTime)) { queryWrapper.le("create_time", endTime); } - - List logList = deviceLogMapper.selectList( - queryWrapper.orderByDesc("create_time")); - return logList; + queryWrapper.orderByDesc("create_time"); + IPage logList = deviceLogMapper.selectPage(page, queryWrapper); + return TableDataInfo.build(logList); } From 5e3307d2b04f0a5ee9ff7cec9678a49e6fd0589e Mon Sep 17 00:00:00 2001 From: chenyouting <514333061@qq.com> Date: Tue, 9 Sep 2025 10:29:51 +0800 Subject: [PATCH 038/160] =?UTF-8?q?=E8=AE=BE=E5=A4=87=E5=88=86=E4=BA=AB2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AppDeviceShareController.java | 3 +- .../device/AppDeviceHBYController.java | 119 ++++++++ .../app/service/AppDeviceShareService.java | 262 ++++++++++++++++++ .../queue/MqttMessageQueueConstants.java | 6 +- .../device/DeviceShareController.java | 17 +- .../web/service/DeviceShareService.java | 24 +- .../mapper/app/AppDeviceShareMapper.xml | 2 +- 7 files changed, 418 insertions(+), 15 deletions(-) create mode 100644 fys-admin/src/main/java/com/fuyuanshen/app/controller/device/AppDeviceHBYController.java create mode 100644 fys-admin/src/main/java/com/fuyuanshen/app/service/AppDeviceShareService.java diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppDeviceShareController.java b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppDeviceShareController.java index 613bb7ea..388c84f7 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppDeviceShareController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppDeviceShareController.java @@ -5,6 +5,7 @@ import cn.hutool.core.util.RandomUtil; import com.fuyuanshen.app.domain.bo.AppDeviceShareBo; import com.fuyuanshen.app.domain.vo.AppDeviceShareDetailVo; import com.fuyuanshen.app.domain.vo.AppDeviceShareVo; +import com.fuyuanshen.app.service.AppDeviceShareService; import com.fuyuanshen.app.service.IAppDeviceShareService; import com.fuyuanshen.common.core.constant.Constants; import com.fuyuanshen.common.core.domain.R; @@ -45,7 +46,7 @@ public class AppDeviceShareController extends BaseController { private final IAppDeviceShareService deviceShareService; - private final DeviceShareService appDeviceShareService; + private final AppDeviceShareService appDeviceShareService; /** * 分享管理列表 diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/controller/device/AppDeviceHBYController.java b/fys-admin/src/main/java/com/fuyuanshen/app/controller/device/AppDeviceHBYController.java new file mode 100644 index 00000000..01883072 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/app/controller/device/AppDeviceHBYController.java @@ -0,0 +1,119 @@ +package com.fuyuanshen.app.controller.device; + +import com.fuyuanshen.app.domain.bo.AppPersonnelInfoBo; +import com.fuyuanshen.app.domain.dto.AppDeviceLogoUploadDto; +import com.fuyuanshen.app.domain.dto.DeviceInstructDto; +import com.fuyuanshen.app.domain.vo.AppDeviceDetailVo; +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 jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +/** + * HBY210设备控制类 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/app/hby/device") +public class AppDeviceHBYController extends BaseController { + + private final DeviceBJQBizService appDeviceService; + + /** + * 获取设备详细信息 + * + * @param id 主键 + */ + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(appDeviceService.getInfo(id)); + } + + /** + * 人员信息登记 + */ + @PostMapping(value = "/registerPersonInfo") +// @FunctionAccessAnnotation("registerPersonInfo") + public R registerPersonInfo(@Validated(AddGroup.class) @RequestBody AppPersonnelInfoBo bo) { + return toAjax(appDeviceService.registerPersonInfo(bo)); + } + + /** + * 发送信息 + */ + @PostMapping(value = "/sendMessage") + @FunctionAccessBatcAnnotation(value = "sendMessage", timeOut = 30, batchMaxTimeOut = 40) + public R sendMessage(@RequestBody AppDeviceSendMsgBo bo) { + return toAjax(appDeviceService.sendMessage(bo)); + } + + /** + * 发送报警信息 + */ + @PostMapping(value = "/sendAlarmMessage") + @FunctionAccessBatcAnnotation(value = "sendAlarmMessage", timeOut = 5, batchMaxTimeOut = 10) + public R sendAlarmMessage(@RequestBody AppDeviceSendMsgBo bo) { + return toAjax(appDeviceService.sendAlarmMessage(bo)); + } + + /** + * 上传设备logo图片 + */ + @PostMapping("/uploadLogo") + @FunctionAccessAnnotation("uploadLogo") + public R upload(@Validated @ModelAttribute AppDeviceLogoUploadDto bo) { + + MultipartFile file = bo.getFile(); + if(file.getSize()>1024*1024*2){ + return R.warn("图片不能大于2M"); + } + appDeviceService.uploadDeviceLogo(bo); + + return R.ok(); + } + + /** + * 灯光模式 + * 0(关灯),1(强光模式),2(弱光模式), 3(爆闪模式), 4(泛光模式) + */ +// @FunctionAccessAnnotation("lightModeSettings") + @PostMapping("/lightModeSettings") + public R lightModeSettings(@RequestBody DeviceInstructDto params) { + // params 转 JSONObject + appDeviceService.lightModeSettings(params); + return R.ok(); + } + + /** + * 灯光亮度设置 + * + */ +// @FunctionAccessAnnotation("lightBrightnessSettings") + @PostMapping("/lightBrightnessSettings") + public R lightBrightnessSettings(@RequestBody DeviceInstructDto params) { + appDeviceService.lightBrightnessSettings(params); + return R.ok(); + } + + /** + * 激光模式设置 + * + */ + @PostMapping("/laserModeSettings") +// @FunctionAccessAnnotation("laserModeSettings") + public R laserModeSettings(@RequestBody DeviceInstructDto params) { + appDeviceService.laserModeSettings(params); + return R.ok(); + } + +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/service/AppDeviceShareService.java b/fys-admin/src/main/java/com/fuyuanshen/app/service/AppDeviceShareService.java new file mode 100644 index 00000000..44d1ba42 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/app/service/AppDeviceShareService.java @@ -0,0 +1,262 @@ +package com.fuyuanshen.app.service; + +import com.alibaba.fastjson2.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.fuyuanshen.app.domain.AppDeviceShare; +import com.fuyuanshen.app.domain.AppPersonnelInfo; +import com.fuyuanshen.app.domain.bo.AppDeviceShareBo; +import com.fuyuanshen.app.domain.vo.AppDeviceShareDetailVo; +import com.fuyuanshen.app.domain.vo.AppDeviceShareVo; +import com.fuyuanshen.app.domain.vo.AppPersonnelInfoVo; +import com.fuyuanshen.app.mapper.AppDeviceShareMapper; +import com.fuyuanshen.app.mapper.AppPersonnelInfoMapper; +import com.fuyuanshen.common.core.constant.GlobalConstants; +import com.fuyuanshen.common.core.exception.ServiceException; +import com.fuyuanshen.common.core.utils.StringUtils; +import com.fuyuanshen.common.mybatis.core.page.PageQuery; +import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; +import com.fuyuanshen.common.redis.utils.RedisUtils; +import com.fuyuanshen.common.satoken.utils.AppLoginHelper; +import com.fuyuanshen.equipment.domain.Device; +import com.fuyuanshen.equipment.domain.DeviceType; +import com.fuyuanshen.equipment.mapper.DeviceMapper; +import com.fuyuanshen.equipment.mapper.DeviceTypeMapper; +import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +import static com.fuyuanshen.common.core.constant.GlobalConstants.GLOBAL_REDIS_KEY; +import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.*; + + +@RequiredArgsConstructor +@Slf4j +@Service +public class AppDeviceShareService { + + private final AppDeviceShareMapper appDeviceShareMapper; + + private final DeviceMapper deviceMapper; + + private final DeviceTypeMapper deviceTypeMapper; + + private final AppPersonnelInfoMapper appPersonnelInfoMapper; + + public TableDataInfo queryPageList(AppDeviceShareBo bo, PageQuery pageQuery) { + Long userId = AppLoginHelper.getUserId(); + bo.setCreateBy(userId); + Page page = new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize()); + Page result = appDeviceShareMapper.selectAppDeviceShareList(bo, page); + List records = result.getRecords(); + records.forEach(AppDeviceShareService::buildDeviceStatus); + return TableDataInfo.build(result); + } + + public TableDataInfo queryWebPageList(AppDeviceShareBo bo, PageQuery pageQuery) { +// Long userId = AppLoginHelper.getUserId(); +// bo.setCreateBy(userId); + Page page = new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize()); + Page result = appDeviceShareMapper.selectWebDeviceShareList(bo, page); + List records = result.getRecords(); + records.forEach(AppDeviceShareService::buildDeviceStatus); + return TableDataInfo.build(result); + } + + private static void buildDeviceStatus(AppDeviceShareVo item) { + // 设备在线状态 + String onlineStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + item.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX); + if (StringUtils.isNotBlank(onlineStatus)) { + + item.setOnlineStatus(1); + } else { + item.setOnlineStatus(0); + } + String deviceStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + item.getDeviceImei() + DEVICE_STATUS_KEY_PREFIX); + // 获取电量 + if (StringUtils.isNotBlank(deviceStatus)) { + JSONObject jsonObject = JSONObject.parseObject(deviceStatus); + item.setBattery(jsonObject.getString("batteryPercentage")); + } else { + item.setBattery("0"); + } + + String location = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + item.getDeviceImei() + DEVICE_LOCATION_KEY_PREFIX); + if (StringUtils.isNotBlank(location)) { + JSONObject jsonObject = JSONObject.parseObject(location); + item.setLatitude(jsonObject.getString("latitude")); + item.setLongitude(jsonObject.getString("longitude")); + } + + String alarmStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + item.getDeviceImei() + DEVICE_ALARM_KEY_PREFIX); + if (StringUtils.isNotBlank(alarmStatus)) { + item.setAlarmStatus(alarmStatus); + } + } + + public AppDeviceShareDetailVo getInfo(Long id) { + + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(AppDeviceShare::getId, id); + List appDeviceShareVos = appDeviceShareMapper.selectVoList(queryWrapper); + if (appDeviceShareVos == null || appDeviceShareVos.isEmpty()) { + return null; + } + + AppDeviceShareVo shareVo = appDeviceShareVos.get(0); + AppDeviceShareDetailVo shareDetailVo = new AppDeviceShareDetailVo(); + shareDetailVo.setId(shareVo.getId()); + shareDetailVo.setDeviceId(shareVo.getDeviceId()); + shareDetailVo.setPhonenumber(shareVo.getPhonenumber()); + shareDetailVo.setPermission(shareVo.getPermission()); + + Device device = deviceMapper.selectById(shareVo.getDeviceId()); + shareDetailVo.setDeviceName(device.getDeviceName()); + shareDetailVo.setDeviceImei(device.getDeviceImei()); + shareDetailVo.setDeviceMac(device.getDeviceMac()); + + DeviceType deviceType = deviceTypeMapper.selectById(device.getDeviceType()); + if (deviceType != null) { + shareDetailVo.setCommunicationMode(Integer.valueOf(deviceType.getCommunicationMode())); + } + shareDetailVo.setDevicePic(device.getDevicePic()); + shareDetailVo.setTypeName(deviceType.getTypeName()); + shareDetailVo.setBluetoothName(device.getBluetoothName()); + shareDetailVo.setDeviceStatus(device.getDeviceStatus()); + shareDetailVo.setSendMsg(device.getSendMsg()); + + LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); + qw.eq(AppPersonnelInfo::getDeviceId, device.getId()); + List appPersonnelInfoVos = appPersonnelInfoMapper.selectVoList(qw); + if (appPersonnelInfoVos != null && !appPersonnelInfoVos.isEmpty()) { + shareDetailVo.setPersonnelInfo(appPersonnelInfoVos.get(0)); + } + // 设备在线状态 + String onlineStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + device.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX); + if (StringUtils.isNotBlank(onlineStatus)) { + shareDetailVo.setOnlineStatus(1); + } else { + shareDetailVo.setOnlineStatus(0); + } + String deviceStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + device.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_STATUS_KEY_PREFIX); + // 获取电量 + if (StringUtils.isNotBlank(deviceStatus)) { + JSONObject jsonObject = JSONObject.parseObject(deviceStatus); + shareDetailVo.setMainLightMode(jsonObject.getString("mainLightMode")); + shareDetailVo.setLaserLightMode(jsonObject.getString("laserLightMode")); + shareDetailVo.setBatteryPercentage(jsonObject.getString("batteryPercentage")); + shareDetailVo.setChargeState(jsonObject.getString("chargeState")); + shareDetailVo.setBatteryRemainingTime(jsonObject.getString("batteryRemainingTime")); + } else { + shareDetailVo.setBatteryPercentage("0"); + } + + // 获取经度纬度 + String locationKey = GlobalConstants.GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + device.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_LOCATION_KEY_PREFIX; + String locationInfo = RedisUtils.getCacheObject(locationKey); + if (StringUtils.isNotBlank(locationInfo)) { + JSONObject jsonObject = JSONObject.parseObject(locationInfo); + shareDetailVo.setLongitude(jsonObject.get("longitude").toString()); + shareDetailVo.setLatitude(jsonObject.get("latitude").toString()); + shareDetailVo.setAddress((String) jsonObject.get("address")); + } + + + String alarmStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + device.getDeviceImei() + DEVICE_ALARM_KEY_PREFIX); + if (StringUtils.isNotBlank(alarmStatus)) { + shareDetailVo.setAlarmStatus(alarmStatus); + } + + String lightBrightness = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + device.getDeviceImei() + DEVICE_LIGHT_BRIGHTNESS_KEY_PREFIX); + if (StringUtils.isNotBlank(lightBrightness)) { + shareDetailVo.setLightBrightness(lightBrightness); + } + + return shareDetailVo; + } + + /** + * 校验短信验证码 + */ + private boolean validateSmsCode(String tenantId, String phonenumber, String smsCode) { + String code = RedisUtils.getCacheObject(GlobalConstants.DEVICE_SHARE_CODES_KEY + phonenumber); + if (StringUtils.isBlank(code)) { + throw new ServiceException("验证码失效"); + } + return code.equals(smsCode); + } + + public int deviceShare(AppDeviceShareBo bo) { + boolean flag = validateSmsCode(AppLoginHelper.getTenantId(), bo.getPhonenumber(), bo.getSmsCode()); + if (!flag) { + throw new ServiceException("验证码错误"); + } + + Device device = deviceMapper.selectById(bo.getDeviceId()); + if (device == null) { + throw new ServiceException("设备不存在"); + } + Long userId = AppLoginHelper.getUserId(); + LambdaQueryWrapper lqw = new LambdaQueryWrapper<>(); + lqw.eq(AppDeviceShare::getDeviceId, bo.getDeviceId()); + lqw.eq(AppDeviceShare::getPhonenumber, bo.getPhonenumber()); + Long count = appDeviceShareMapper.selectCount(lqw); + if (count > 0) { + + UpdateWrapper uw = new UpdateWrapper<>(); + uw.eq("device_id", bo.getDeviceId()); + uw.eq("phonenumber", bo.getPhonenumber()); + uw.set("permission", bo.getPermission()); + uw.set("update_by", userId); + uw.set("update_time", new Date()); + + return appDeviceShareMapper.update(uw); + } else { + AppDeviceShare appDeviceShare = new AppDeviceShare(); + appDeviceShare.setDeviceId(bo.getDeviceId()); + appDeviceShare.setPhonenumber(bo.getPhonenumber()); + appDeviceShare.setPermission(bo.getPermission()); + appDeviceShare.setCreateBy(userId); + return appDeviceShareMapper.insert(appDeviceShare); + } + } + + public int remove(Long[] ids) { + return appDeviceShareMapper.deleteByIds(Arrays.asList(ids)); + } + + public TableDataInfo otherDeviceShareList(AppDeviceShareBo bo, PageQuery pageQuery) { + String username = AppLoginHelper.getUsername(); + bo.setPhonenumber(username); + Page page = new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize()); + IPage result = appDeviceShareMapper.otherDeviceShareList(bo, page); + List records = result.getRecords(); + + records.forEach(AppDeviceShareService::buildDeviceStatus); + return TableDataInfo.build(result); + } + + + /** + * 查询设备分享列表(web) + * + * @param bo + * @param pageQuery + * @return + */ + public TableDataInfo queryWebList(AppDeviceShareBo bo, PageQuery pageQuery) { + Page page = new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize()); + Page result = appDeviceShareMapper.selectWebDeviceShareList(bo, page); + List records = result.getRecords(); + records.forEach(AppDeviceShareService::buildDeviceStatus); + return TableDataInfo.build(result); + } + +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/queue/MqttMessageQueueConstants.java b/fys-admin/src/main/java/com/fuyuanshen/global/queue/MqttMessageQueueConstants.java index b4eb34d2..392e06bc 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/queue/MqttMessageQueueConstants.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/queue/MqttMessageQueueConstants.java @@ -1,6 +1,8 @@ package com.fuyuanshen.global.queue; +import com.fuyuanshen.common.core.constant.GlobalConstants; + public class MqttMessageQueueConstants { - public static final String MQTT_MESSAGE_QUEUE_KEY = "mqtt:message:queue"; - public static final String MQTT_MESSAGE_DEDUP_KEY = "mqtt:message:dedup"; + public static final String MQTT_MESSAGE_QUEUE_KEY = GlobalConstants.GLOBAL_REDIS_KEY + "mqtt:message:queue"; + public static final String MQTT_MESSAGE_DEDUP_KEY = GlobalConstants.GLOBAL_REDIS_KEY + "mqtt:message:dedup"; } diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceShareController.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceShareController.java index 5f214e60..ce2cc2be 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceShareController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceShareController.java @@ -40,7 +40,7 @@ import static com.fuyuanshen.common.core.constant.GlobalConstants.DEVICE_SHARE_C @RequestMapping("api/equipment/share") public class DeviceShareController extends BaseController { - private final DeviceShareService appDeviceShareService; + private final DeviceShareService deviceShareService; /** @@ -48,7 +48,7 @@ public class DeviceShareController extends BaseController { */ @GetMapping("/deviceShareList") public TableDataInfo list(AppDeviceShareBo bo, PageQuery pageQuery) { - return appDeviceShareService.queryWebPageList(bo, pageQuery); + return deviceShareService.queryWebPageList(bo, pageQuery); } @@ -58,7 +58,16 @@ public class DeviceShareController extends BaseController { @RepeatSubmit() @PostMapping("/deviceShare") public R deviceShare(@Validated(AddGroup.class) @RequestBody AppDeviceShareBo bo) { - return toAjax(appDeviceShareService.deviceShare(bo)); + return toAjax(deviceShareService.deviceShare(bo)); + } + + /** + * 权限管理 + */ + @RepeatSubmit() + @PostMapping("/permission") + public R permission(@Validated(AddGroup.class) @RequestBody AppDeviceShareBo bo) { + return toAjax(deviceShareService.deviceShare(bo)); } /** @@ -69,7 +78,7 @@ public class DeviceShareController extends BaseController { @DeleteMapping("/{ids}") public R remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) { - return toAjax(appDeviceShareService.remove(ids)); + return toAjax(deviceShareService.remove(ids)); } /** diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/DeviceShareService.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/DeviceShareService.java index 9983fc8f..42204bb7 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/service/DeviceShareService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/DeviceShareService.java @@ -2,15 +2,18 @@ package com.fuyuanshen.web.service; import com.alibaba.fastjson2.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.fuyuanshen.app.domain.AppDeviceBindRecord; import com.fuyuanshen.app.domain.AppDeviceShare; import com.fuyuanshen.app.domain.AppPersonnelInfo; import com.fuyuanshen.app.domain.bo.AppDeviceShareBo; import com.fuyuanshen.app.domain.vo.AppDeviceShareDetailVo; import com.fuyuanshen.app.domain.vo.AppDeviceShareVo; import com.fuyuanshen.app.domain.vo.AppPersonnelInfoVo; +import com.fuyuanshen.app.mapper.AppDeviceBindRecordMapper; import com.fuyuanshen.app.mapper.AppDeviceShareMapper; import com.fuyuanshen.app.mapper.AppPersonnelInfoMapper; import com.fuyuanshen.common.core.constant.GlobalConstants; @@ -50,6 +53,8 @@ public class DeviceShareService { private final AppPersonnelInfoMapper appPersonnelInfoMapper; + private final AppDeviceBindRecordMapper appDeviceBindRecordMapper; + public TableDataInfo queryPageList(AppDeviceShareBo bo, PageQuery pageQuery) { Long userId = AppLoginHelper.getUserId(); bo.setCreateBy(userId); @@ -185,7 +190,7 @@ public class DeviceShareService { /** * 校验短信验证码 */ - private boolean validateSmsCode(String tenantId, String phonenumber, String smsCode) { + private boolean validateSmsCode(String phonenumber, String smsCode) { String code = RedisUtils.getCacheObject(GlobalConstants.DEVICE_SHARE_CODES_KEY + phonenumber); if (StringUtils.isBlank(code)) { throw new ServiceException("验证码失效"); @@ -194,16 +199,21 @@ public class DeviceShareService { } public int deviceShare(AppDeviceShareBo bo) { - boolean flag = validateSmsCode(AppLoginHelper.getTenantId(), bo.getPhonenumber(), bo.getSmsCode()); - if (!flag) { - throw new ServiceException("验证码错误"); - } - Device device = deviceMapper.selectById(bo.getDeviceId()); if (device == null) { throw new ServiceException("设备不存在"); } - Long userId = AppLoginHelper.getUserId(); + + boolean flag = validateSmsCode( bo.getPhonenumber(), bo.getSmsCode()); + if (!flag) { + throw new ServiceException("验证码错误"); + } + + LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); + qw.eq(AppDeviceBindRecord::getDeviceId, bo.getDeviceId()); + AppDeviceBindRecord bindRecord = appDeviceBindRecordMapper.selectOne(qw); + Long userId = bindRecord.getBindingUserId(); + LambdaQueryWrapper lqw = new LambdaQueryWrapper<>(); lqw.eq(AppDeviceShare::getDeviceId, bo.getDeviceId()); lqw.eq(AppDeviceShare::getPhonenumber, bo.getPhonenumber()); diff --git a/fys-modules/fys-app/src/main/resources/mapper/app/AppDeviceShareMapper.xml b/fys-modules/fys-app/src/main/resources/mapper/app/AppDeviceShareMapper.xml index 9208f90a..8b77aecb 100644 --- a/fys-modules/fys-app/src/main/resources/mapper/app/AppDeviceShareMapper.xml +++ b/fys-modules/fys-app/src/main/resources/mapper/app/AppDeviceShareMapper.xml @@ -74,7 +74,7 @@ and u.user_name = #{bo.shareUser} - + and ad.create_time between #{bo.shareStartTime} and #{bo.shareEndTime} From ebd86681781345b83d3c74f3daebc0c47a83080f Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Tue, 9 Sep 2025 11:03:38 +0800 Subject: [PATCH 039/160] =?UTF-8?q?=E5=9C=A8=E7=BA=BF=E8=AE=BE=E5=A4=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/vo/EquipmentClassificationVo.java | 20 +++++++++++++++++++ .../service/impl/DeviceServiceImpl.java | 1 + .../mapper/equipment/DeviceMapper.xml | 4 ++-- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/EquipmentClassificationVo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/EquipmentClassificationVo.java index d84f59ad..2ebecdc1 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/EquipmentClassificationVo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/EquipmentClassificationVo.java @@ -26,4 +26,24 @@ public class EquipmentClassificationVo { */ private Integer devices4GAndBluetooth = 0; + /** + * 设备总数 + */ + private Integer total; + + + /** + * 计算设备总数 + * + * @return 设备总数 + */ + public Integer getTotal() { + if (total == null) { + total = (equipment4G == null ? 0 : equipment4G) + + (deviceBluetooth == null ? 0 : deviceBluetooth) + + (devices4GAndBluetooth == null ? 0 : devices4GAndBluetooth); + } + return total; + } + } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java index 967bc391..f356e414 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java @@ -630,6 +630,7 @@ public class DeviceServiceImpl extends ServiceImpl impleme @Override public EquipmentClassificationVo getEquipmentClassification() { EquipmentClassificationVo equipmentClassification = deviceMapper.getEquipmentClassification(); + equipmentClassification.getTotal(); return equipmentClassification; } diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml index 81c66744..0ce9fc67 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml @@ -321,11 +321,11 @@ From 7e87971c0b5ed51a76ce47a4df06b4945e7f3702 Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Tue, 9 Sep 2025 15:02:46 +0800 Subject: [PATCH 040/160] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=BB=91=E5=AE=9A?= =?UTF-8?q?=E8=AE=BE=E5=A4=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/mapper/equipment/DeviceMapper.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml index 0ce9fc67..d1ebc0ea 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml @@ -322,7 +322,7 @@ - SELECT COUNT(CASE WHEN MONTH (dl.create_time) = 1 THEN 1 END) AS m1, - COUNT(CASE WHEN MONTH (dl.create_time) = 2 THEN 1 END) AS m2, - COUNT(CASE WHEN MONTH (dl.create_time) = 3 THEN 1 END) AS m3, - COUNT(CASE WHEN MONTH (dl.create_time) = 4 THEN 1 END) AS m4, - COUNT(CASE WHEN MONTH (dl.create_time) = 5 THEN 1 END) AS m5, - COUNT(CASE WHEN MONTH (dl.create_time) = 6 THEN 1 END) AS m6, - COUNT(CASE WHEN MONTH (dl.create_time) = 7 THEN 1 END) AS m7, - COUNT(CASE WHEN MONTH (dl.create_time) = 8 THEN 1 END) AS m8, - COUNT(CASE WHEN MONTH (dl.create_time) = 9 THEN 1 END) AS m9, - COUNT(CASE WHEN MONTH (dl.create_time) = 10 THEN 1 END) AS m10, - COUNT(CASE WHEN MONTH (dl.create_time) = 11 THEN 1 END) AS m11, - COUNT(CASE WHEN MONTH (dl.create_time) = 12 THEN 1 END) AS m12 + SELECT COUNT(CASE WHEN MONTH (dl.create_time) = 1 THEN 1 END) AS m1, + COUNT(CASE WHEN MONTH (dl.create_time) = 2 THEN 1 END) AS m2, + COUNT(CASE WHEN MONTH (dl.create_time) = 3 THEN 1 END) AS m3, + COUNT(CASE WHEN MONTH (dl.create_time) = 4 THEN 1 END) AS m4, + COUNT(CASE WHEN MONTH (dl.create_time) = 5 THEN 1 END) AS m5, + COUNT(CASE WHEN MONTH (dl.create_time) = 6 THEN 1 END) AS m6, + COUNT(CASE WHEN MONTH (dl.create_time) = 7 THEN 1 END) AS m7, + COUNT(CASE WHEN MONTH (dl.create_time) = 8 THEN 1 END) AS m8, + COUNT(CASE WHEN MONTH (dl.create_time) = 9 THEN 1 END) AS m9, + COUNT(CASE WHEN MONTH (dl.create_time) = 10 THEN 1 END) AS m10, + COUNT(CASE WHEN MONTH (dl.create_time) = 11 THEN 1 END) AS m11, + COUNT(CASE WHEN MONTH (dl.create_time) = 12 THEN 1 END) AS m12 FROM device_log dl - LEFT JOIN device d ON dl.device_id = d.id - LEFT JOIN device_type dt ON d.device_type = dt.id - WHERE dt.id = #{deviceId} - AND ( + LEFT JOIN device d ON dl.device_id = d.id + LEFT JOIN device_type dt ON d.device_type = dt.id + + + dt.id = #{deviceTypeId} + + AND ( (#{range} = 1 AND dl.create_time >= DATE_SUB(NOW(), INTERVAL 6 MONTH)) OR (#{range} = 2 AND dl.create_time >= DATE_SUB(NOW(), INTERVAL 12 MONTH)) ) + - SELECT (SELECT COUNT(1) FROM device_alarm WHERE treatment_state = 0 AND DATE (create_time) = CURDATE()) AS alarmsTotal, ( + SELECT (SELECT COUNT(1) FROM device_alarm WHERE treatment_state = 0) AS alarmsTotal + , (SELECT COUNT(1) + FROM device_alarm + WHERE treatment_state = 0) AS processingAlarm + , (SELECT COUNT(1) + FROM device_alarm + WHERE treatment_state = 0 AND + DATE (create_time) = CURDATE()) AS alarmsTotalToday + , ( SELECT COUNT (1) FROM device_alarm WHERE treatment_state = 0 - AND DATE (create_time) = CURDATE()) AS processingAlarm + AND DATE (create_time) = CURDATE()) AS processingAlarmToday , ( SELECT COUNT (1) FROM device_alarm WHERE device_action = 0 - AND DATE (create_time) = CURDATE()) AS alarmForced + ) AS alarmForced , ( SELECT COUNT (1) FROM device_alarm WHERE device_action = 1 - AND DATE (create_time) = CURDATE()) AS intrusionImpact + ) AS intrusionImpact , ( SELECT COUNT (1) FROM device_alarm WHERE device_action = 2 - AND DATE (create_time) = CURDATE()) AS alarmManual + ) AS alarmManual , ( SELECT COUNT (1) FROM device_alarm - WHERE device_action = 3 AND DATE (create_time) = CURDATE()) AS fenceElectronic + WHERE device_action = 3) AS fenceElectronic From 801b0267e6910a6bb3b76b5cd5d6a4babbac182d Mon Sep 17 00:00:00 2001 From: chenyouting <514333061@qq.com> Date: Tue, 9 Sep 2025 17:01:51 +0800 Subject: [PATCH 043/160] =?UTF-8?q?=E8=AE=BE=E5=A4=87=E5=88=86=E4=BA=AB3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../device/DeviceShareController.java | 2 +- .../web/service/DeviceShareService.java | 40 +++++++++++++++++++ .../app/domain/bo/AppDeviceShareBo.java | 4 +- .../app/domain/vo/AppDeviceShareVo.java | 5 +++ .../mapper/app/AppDeviceShareMapper.xml | 7 +++- 5 files changed, 53 insertions(+), 5 deletions(-) diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceShareController.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceShareController.java index ce2cc2be..83eb7509 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceShareController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceShareController.java @@ -67,7 +67,7 @@ public class DeviceShareController extends BaseController { @RepeatSubmit() @PostMapping("/permission") public R permission(@Validated(AddGroup.class) @RequestBody AppDeviceShareBo bo) { - return toAjax(deviceShareService.deviceShare(bo)); + return toAjax(deviceShareService.permission(bo)); } /** diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/DeviceShareService.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/DeviceShareService.java index 42204bb7..e295d796 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/service/DeviceShareService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/DeviceShareService.java @@ -212,6 +212,46 @@ public class DeviceShareService { LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); qw.eq(AppDeviceBindRecord::getDeviceId, bo.getDeviceId()); AppDeviceBindRecord bindRecord = appDeviceBindRecordMapper.selectOne(qw); + if (bindRecord == null) { + throw new ServiceException("设备未绑定"); + } + Long userId = bindRecord.getBindingUserId(); + + LambdaQueryWrapper lqw = new LambdaQueryWrapper<>(); + lqw.eq(AppDeviceShare::getDeviceId, bo.getDeviceId()); + lqw.eq(AppDeviceShare::getPhonenumber, bo.getPhonenumber()); + Long count = appDeviceShareMapper.selectCount(lqw); + if (count > 0) { + + UpdateWrapper uw = new UpdateWrapper<>(); + uw.eq("device_id", bo.getDeviceId()); + uw.eq("phonenumber", bo.getPhonenumber()); + uw.set("permission", bo.getPermission()); + uw.set("update_by", userId); + uw.set("update_time", new Date()); + + return appDeviceShareMapper.update(uw); + } else { + AppDeviceShare appDeviceShare = new AppDeviceShare(); + appDeviceShare.setDeviceId(bo.getDeviceId()); + appDeviceShare.setPhonenumber(bo.getPhonenumber()); + appDeviceShare.setPermission(bo.getPermission()); + appDeviceShare.setCreateBy(userId); + return appDeviceShareMapper.insert(appDeviceShare); + } + } + + public int permission(AppDeviceShareBo bo) { + Device device = deviceMapper.selectById(bo.getDeviceId()); + if (device == null) { + throw new ServiceException("设备不存在"); + } + LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); + qw.eq(AppDeviceBindRecord::getDeviceId, bo.getDeviceId()); + AppDeviceBindRecord bindRecord = appDeviceBindRecordMapper.selectOne(qw); + if (bindRecord == null) { + throw new ServiceException("设备未绑定"); + } Long userId = bindRecord.getBindingUserId(); LambdaQueryWrapper lqw = new LambdaQueryWrapper<>(); diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/bo/AppDeviceShareBo.java b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/bo/AppDeviceShareBo.java index 0262567c..38530d48 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/bo/AppDeviceShareBo.java +++ b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/bo/AppDeviceShareBo.java @@ -60,7 +60,7 @@ public class AppDeviceShareBo extends BaseEntity { /** * 分享时间 */ - private Date shareStartTime; - private Date shareEndTime; + private String shareStartTime; + private String shareEndTime; } diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/vo/AppDeviceShareVo.java b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/vo/AppDeviceShareVo.java index 224ca8ce..3e1d6179 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/vo/AppDeviceShareVo.java +++ b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/vo/AppDeviceShareVo.java @@ -109,4 +109,9 @@ public class AppDeviceShareVo implements Serializable { * 告警状态(0解除告警,1告警) */ private String alarmStatus; + + /** + * 创建时间 + */ + private String createTime; } diff --git a/fys-modules/fys-app/src/main/resources/mapper/app/AppDeviceShareMapper.xml b/fys-modules/fys-app/src/main/resources/mapper/app/AppDeviceShareMapper.xml index 8b77aecb..ce0ffbcf 100644 --- a/fys-modules/fys-app/src/main/resources/mapper/app/AppDeviceShareMapper.xml +++ b/fys-modules/fys-app/src/main/resources/mapper/app/AppDeviceShareMapper.xml @@ -72,9 +72,12 @@ and ad.device_id = #{bo.deviceId} - and u.user_name = #{bo.shareUser} + and ad.phonenumber like concat('%',#{bo.shareUser},'%') - + + and ad.phonenumber like concat('%',#{bo.phonenumber},'%') + + and ad.create_time between #{bo.shareStartTime} and #{bo.shareEndTime} From 6bc1d5b20bbecd5917289d6f0389483e7c22dfd3 Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Wed, 10 Sep 2025 09:56:39 +0800 Subject: [PATCH 044/160] =?UTF-8?q?=E7=BB=91=E5=AE=9A=E8=AE=BE=E5=A4=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/fuyuanshen/equipment/domain/vo/DataOverviewVo.java | 7 +++++++ .../src/main/resources/mapper/equipment/DeviceMapper.xml | 3 +++ 2 files changed, 10 insertions(+) diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DataOverviewVo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DataOverviewVo.java index c8c016d7..10fbe2e7 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DataOverviewVo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DataOverviewVo.java @@ -26,6 +26,13 @@ public class DataOverviewVo { */ private Integer bindingNew = 0; + + /** + * 绑定设备 + */ + private Integer binding = 0; + + /** * 异常设备 */ diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml index f70aeca7..4d1a80fd 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml @@ -325,6 +325,9 @@ (SELECT COUNT(1) FROM device WHERE DATE (create_time) = CURDATE() AND binding_status = 1 ) AS bindingNew, ( SELECT COUNT (1) FROM device + WHERE binding_status = 1 ) AS binding, ( + SELECT COUNT (1) + FROM device WHERE online_status = 2) AS equipmentAbnormal From b8af6b511cbddb30bb894a937a78430d99f484e1 Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Thu, 11 Sep 2025 10:54:56 +0800 Subject: [PATCH 045/160] =?UTF-8?q?=E7=94=B5=E5=AD=90=E5=9B=B4=E6=A0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DeviceFenceAccessRecordController.java | 108 ++++++++++ .../fence/DeviceGeoFenceController.java | 124 +++++++++++ .../domain/DeviceFenceAccessRecord.java | 78 +++++++ .../equipment/domain/DeviceGeoFence.java | 78 +++++++ .../domain/bo/DeviceChargeDischargeBo.java | 13 ++ .../domain/bo/DeviceFenceAccessRecordBo.java | 83 ++++++++ .../equipment/domain/bo/DeviceGeoFenceBo.java | 80 +++++++ .../domain/dto/FenceCheckResponse.java | 63 ++++++ .../domain/query/FenceCheckRequest.java | 39 ++++ .../domain/vo/AlarmInformationVo.java | 2 +- .../equipment/domain/vo/DataOverviewVo.java | 4 +- .../domain/vo/DeviceFenceAccessRecordVo.java | 94 +++++++++ .../equipment/domain/vo/DeviceGeoFenceVo.java | 94 +++++++++ .../mapper/DeviceFenceAccessRecordMapper.java | 15 ++ .../mapper/DeviceGeoFenceMapper.java | 15 ++ .../IDeviceFenceAccessRecordService.java | 68 ++++++ .../service/IDeviceGeoFenceService.java | 78 +++++++ .../DeviceFenceAccessRecordServiceImpl.java | 140 ++++++++++++ .../impl/DeviceGeoFenceServiceImpl.java | 199 ++++++++++++++++++ .../equipment/utils/map/Coordinate.java | 24 +++ .../equipment/utils/map/GeoFenceChecker.java | 135 ++++++++++++ .../DeviceFenceAccessRecordMapper.xml | 7 + .../mapper/equipment/DeviceGeoFenceMapper.xml | 7 + 23 files changed, 1544 insertions(+), 4 deletions(-) create mode 100644 fys-admin/src/main/java/com/fuyuanshen/web/controller/device/fence/DeviceFenceAccessRecordController.java create mode 100644 fys-admin/src/main/java/com/fuyuanshen/web/controller/device/fence/DeviceGeoFenceController.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceFenceAccessRecord.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceGeoFence.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceFenceAccessRecordBo.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceGeoFenceBo.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/FenceCheckResponse.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/FenceCheckRequest.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceFenceAccessRecordVo.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceGeoFenceVo.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceFenceAccessRecordMapper.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceGeoFenceMapper.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/IDeviceFenceAccessRecordService.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/IDeviceGeoFenceService.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceFenceAccessRecordServiceImpl.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceGeoFenceServiceImpl.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/utils/map/Coordinate.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/utils/map/GeoFenceChecker.java create mode 100644 fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceFenceAccessRecordMapper.xml create mode 100644 fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceGeoFenceMapper.xml diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/fence/DeviceFenceAccessRecordController.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/fence/DeviceFenceAccessRecordController.java new file mode 100644 index 00000000..97e7aa97 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/fence/DeviceFenceAccessRecordController.java @@ -0,0 +1,108 @@ +package com.fuyuanshen.web.controller.device.fence; + +import java.util.List; + +import com.fuyuanshen.equipment.domain.bo.DeviceFenceAccessRecordBo; +import com.fuyuanshen.equipment.domain.vo.DeviceFenceAccessRecordVo; +import com.fuyuanshen.equipment.service.IDeviceFenceAccessRecordService; +import lombok.RequiredArgsConstructor; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.*; +import cn.dev33.satoken.annotation.SaCheckPermission; +import org.springframework.web.bind.annotation.*; +import org.springframework.validation.annotation.Validated; +import com.fuyuanshen.common.idempotent.annotation.RepeatSubmit; +import com.fuyuanshen.common.log.annotation.Log; +import com.fuyuanshen.common.web.core.BaseController; +import com.fuyuanshen.common.mybatis.core.page.PageQuery; +import com.fuyuanshen.common.core.domain.R; +import com.fuyuanshen.common.core.validate.AddGroup; +import com.fuyuanshen.common.core.validate.EditGroup; +import com.fuyuanshen.common.log.enums.BusinessType; +import com.fuyuanshen.common.excel.utils.ExcelUtil; + +import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; + +/** + * 围栏进出记录 + * + * @author Lion Li + * @date 2025-09-11 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/fys-equipment/fenceAccessRecord") +public class DeviceFenceAccessRecordController extends BaseController { + + private final IDeviceFenceAccessRecordService deviceFenceAccessRecordService; + + /** + * 查询围栏进出记录列表 + */ + @SaCheckPermission("fys-equipment:fenceAccessRecord:list") + @GetMapping("/list") + public TableDataInfo list(DeviceFenceAccessRecordBo bo, PageQuery pageQuery) { + return deviceFenceAccessRecordService.queryPageList(bo, pageQuery); + } + + /** + * 围栏进出记录列表 + */ + @SaCheckPermission("fys-equipment:fenceAccessRecord:export") + @Log(title = "围栏进出记录", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(DeviceFenceAccessRecordBo bo, HttpServletResponse response) { + List list = deviceFenceAccessRecordService.queryList(bo); + ExcelUtil.exportExcel(list, "围栏进出记录", DeviceFenceAccessRecordVo.class, response); + } + + /** + * 获取围栏进出记录详细信息 + * + * @param id 主键 + */ + @SaCheckPermission("fys-equipment:fenceAccessRecord:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(deviceFenceAccessRecordService.queryById(id)); + } + + /** + * 新增围栏进出记录 + */ + @SaCheckPermission("fys-equipment:fenceAccessRecord:add") + @Log(title = "围栏进出记录", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody DeviceFenceAccessRecordBo bo) { + return toAjax(deviceFenceAccessRecordService.insertByBo(bo)); + } + + /** + * 修改围栏进出记录 + */ + @SaCheckPermission("fys-equipment:fenceAccessRecord:edit") + @Log(title = "围栏进出记录", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody DeviceFenceAccessRecordBo bo) { + return toAjax(deviceFenceAccessRecordService.updateByBo(bo)); + } + + /** + * 删除围栏进出记录 + * + * @param ids 主键串 + */ + @SaCheckPermission("fys-equipment:fenceAccessRecord:remove") + @Log(title = "围栏进出记录", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(deviceFenceAccessRecordService.deleteWithValidByIds(List.of(ids), true)); + } + + +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/fence/DeviceGeoFenceController.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/fence/DeviceGeoFenceController.java new file mode 100644 index 00000000..cae25e3f --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/fence/DeviceGeoFenceController.java @@ -0,0 +1,124 @@ +package com.fuyuanshen.web.controller.device.fence; + +import java.util.List; + +import com.fuyuanshen.equipment.domain.bo.DeviceGeoFenceBo; +import com.fuyuanshen.equipment.domain.dto.FenceCheckResponse; +import com.fuyuanshen.equipment.domain.query.FenceCheckRequest; +import com.fuyuanshen.equipment.domain.vo.DeviceGeoFenceVo; +import com.fuyuanshen.equipment.service.IDeviceGeoFenceService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.*; +import cn.dev33.satoken.annotation.SaCheckPermission; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.validation.annotation.Validated; +import com.fuyuanshen.common.idempotent.annotation.RepeatSubmit; +import com.fuyuanshen.common.log.annotation.Log; +import com.fuyuanshen.common.web.core.BaseController; +import com.fuyuanshen.common.mybatis.core.page.PageQuery; +import com.fuyuanshen.common.core.domain.R; +import com.fuyuanshen.common.core.validate.AddGroup; +import com.fuyuanshen.common.core.validate.EditGroup; +import com.fuyuanshen.common.log.enums.BusinessType; +import com.fuyuanshen.common.excel.utils.ExcelUtil; +import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; + +/** + * 电子围栏 + * + * @author Lion Li + * @date 2025-09-11 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/fys-equipment/geoFence") +public class DeviceGeoFenceController extends BaseController { + + private final IDeviceGeoFenceService deviceGeoFenceService; + + /** + * 查询电子围栏列表 + */ + @SaCheckPermission("fys-equipment:geoFence:list") + @GetMapping("/list") + public TableDataInfo list(DeviceGeoFenceBo bo, PageQuery pageQuery) { + return deviceGeoFenceService.queryPageList(bo, pageQuery); + } + + /** + * 电子围栏列表 + */ + @SaCheckPermission("fys-equipment:geoFence:export") + @Log(title = "电子围栏", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(DeviceGeoFenceBo bo, HttpServletResponse response) { + List list = deviceGeoFenceService.queryList(bo); + ExcelUtil.exportExcel(list, "电子围栏", DeviceGeoFenceVo.class, response); + } + + /** + * 获取电子围栏详细信息 + * + * @param id 主键 + */ + @SaCheckPermission("fys-equipment:geoFence:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(deviceGeoFenceService.queryById(id)); + } + + /** + * 新增电子围栏 + */ + @SaCheckPermission("fys-equipment:geoFence:add") + @Log(title = "电子围栏", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody DeviceGeoFenceBo bo) { + return toAjax(deviceGeoFenceService.insertByBo(bo)); + } + + /** + * 修改电子围栏 + */ + @SaCheckPermission("fys-equipment:geoFence:edit") + @Log(title = "电子围栏", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody DeviceGeoFenceBo bo) { + return toAjax(deviceGeoFenceService.updateByBo(bo)); + } + + /** + * 删除电子围栏 + * + * @param ids 主键串 + */ + @SaCheckPermission("fys-equipment:geoFence:remove") + @Log(title = "电子围栏", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(deviceGeoFenceService.deleteWithValidByIds(List.of(ids), true)); + } + + + /** + * 位置检查 + * + * @param request + * @return + */ + @PostMapping("/check") + public ResponseEntity checkPosition( + @Valid @RequestBody FenceCheckRequest request) { + FenceCheckResponse response = deviceGeoFenceService.checkPosition(request); + return ResponseEntity.ok(response); + } + +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceFenceAccessRecord.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceFenceAccessRecord.java new file mode 100644 index 00000000..8b50674c --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceFenceAccessRecord.java @@ -0,0 +1,78 @@ +package com.fuyuanshen.equipment.domain; + +import com.baomidou.mybatisplus.annotation.*; +import com.fuyuanshen.common.mybatis.core.domain.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; + +import java.io.Serial; + +/** + * 围栏进出记录对象 device_fence_access_record + * + * @author Lion Li + * @date 2025-09-11 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("device_fence_access_record") +public class DeviceFenceAccessRecord extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 记录ID + */ + @TableId(value = "id") + private Long id; + + /** + * 围栏ID + */ + private Long fenceId; + + /** + * 设备标识 + */ + private String deviceId; + + /** + * 用户ID + */ + private Long userId; + + /** + * 事件类型 + */ + private Long eventType; + + /** + * 纬度 + */ + private Long latitude; + + /** + * 经度 + */ + private Long longitude; + + /** + * 定位精度 + */ + private Long accuracy; + + /** + * 事件时间 + */ + private Date eventTime; + + /** + * 记录创建时间 + */ + private Date createdTime; + + +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceGeoFence.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceGeoFence.java new file mode 100644 index 00000000..37285038 --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceGeoFence.java @@ -0,0 +1,78 @@ +package com.fuyuanshen.equipment.domain; + +import com.baomidou.mybatisplus.annotation.*; +import com.fuyuanshen.common.mybatis.core.domain.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; + +import java.io.Serial; + +/** + * 电子围栏对象 device_geo_fence + * + * @author Lion Li + * @date 2025-09-11 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("device_geo_fence") +public class DeviceGeoFence extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 围栏唯一标识 + */ + @TableId(value = "id") + private Long id; + + /** + * 围栏名称 + */ + private String name; + + /** + * 围栏描述 + */ + private String description; + + /** + * 围栏区域类型, 0 POLYGON, 1 CIRCLE + */ + private Long areaType; + + /** + * 围栏坐标数据 + */ + private String coordinates; + + /** + * 圆形围栏半径(米) + */ + private Long radius; + + /** + * 是否激活 + */ + private Long isActive; + + /** + * 创建人 + */ + private Long createdBy; + + /** + * 创建时间 + */ + private Date createdTime; + + /** + * 更新时间 + */ + private Date updatedTime; + + +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceChargeDischargeBo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceChargeDischargeBo.java index e953b0e8..cda7d679 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceChargeDischargeBo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceChargeDischargeBo.java @@ -6,6 +6,7 @@ import com.fuyuanshen.equipment.domain.DeviceChargeDischarge; import com.fuyuanshen.common.mybatis.core.domain.BaseEntity; import io.github.linpeilie.annotations.AutoMapper; import lombok.Data; +import cn.hutool.core.date.DateUtil; import lombok.EqualsAndHashCode; import jakarta.validation.constraints.*; import java.util.Date; @@ -44,13 +45,25 @@ public class DeviceChargeDischargeBo extends BaseEntity { * 开始时间 */ @NotNull(message = "开始时间不能为空", groups = { AddGroup.class, EditGroup.class }) + @JsonFormat(pattern = "yyyy-MM-dd") private Date startTime; + // 添加字符串类型的setter方法来处理前端传递的字符串日期 + public void setStartTime(String startTime) { + this.startTime = DateUtil.parseDate(startTime); + } + /** * 结束时间 */ + @JsonFormat(pattern = "yyyy-MM-dd") private Date endTime; + // 添加字符串类型的setter方法来处理前端传递的字符串日期 + public void setEndTime(String endTime) { + this.endTime = DateUtil.parseDate(endTime); + } + /** * 起始电量百分比(0-100) */ diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceFenceAccessRecordBo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceFenceAccessRecordBo.java new file mode 100644 index 00000000..60d00f4e --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceFenceAccessRecordBo.java @@ -0,0 +1,83 @@ +package com.fuyuanshen.equipment.domain.bo; + +import com.fuyuanshen.common.core.validate.AddGroup; +import com.fuyuanshen.common.core.validate.EditGroup; +import com.fuyuanshen.common.mybatis.core.domain.BaseEntity; +import com.fuyuanshen.equipment.domain.DeviceFenceAccessRecord; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import lombok.EqualsAndHashCode; +import jakarta.validation.constraints.*; +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 围栏进出记录业务对象 device_fence_access_record + * + * @author Lion Li + * @date 2025-09-11 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = DeviceFenceAccessRecord.class, reverseConvertGenerate = false) +public class DeviceFenceAccessRecordBo extends BaseEntity { + + /** + * 记录ID + */ + @NotNull(message = "记录ID不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 围栏ID + */ + @NotNull(message = "围栏ID不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long fenceId; + + /** + * 设备标识 + */ + @NotBlank(message = "设备标识不能为空", groups = { AddGroup.class, EditGroup.class }) + private String deviceId; + + /** + * 用户ID + */ + private Long userId; + + /** + * 事件类型 + */ + @NotNull(message = "事件类型不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long eventType; + + /** + * 纬度 + */ + @NotNull(message = "纬度不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long latitude; + + /** + * 经度 + */ + @NotNull(message = "经度不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long longitude; + + /** + * 定位精度 + */ + private Long accuracy; + + /** + * 事件时间 + */ + @NotNull(message = "事件时间不能为空", groups = { AddGroup.class, EditGroup.class }) + private Date eventTime; + + /** + * 记录创建时间 + */ + private Date createdTime; + + +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceGeoFenceBo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceGeoFenceBo.java new file mode 100644 index 00000000..d13217d9 --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceGeoFenceBo.java @@ -0,0 +1,80 @@ +package com.fuyuanshen.equipment.domain.bo; + +import com.fuyuanshen.common.core.validate.AddGroup; +import com.fuyuanshen.common.core.validate.EditGroup; +import com.fuyuanshen.common.mybatis.core.domain.BaseEntity; +import com.fuyuanshen.equipment.domain.DeviceGeoFence; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import lombok.EqualsAndHashCode; +import jakarta.validation.constraints.*; +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 电子围栏业务对象 device_geo_fence + * + * @author Lion Li + * @date 2025-09-11 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = DeviceGeoFence.class, reverseConvertGenerate = false) +public class DeviceGeoFenceBo extends BaseEntity { + + /** + * 围栏唯一标识 + */ + @NotNull(message = "围栏唯一标识不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 围栏名称 + */ + @NotBlank(message = "围栏名称不能为空", groups = { AddGroup.class, EditGroup.class }) + private String name; + + /** + * 围栏描述 + */ + private String description; + + /** + * 围栏区域类型, 0 POLYGON, 1 CIRCLE + */ + @NotNull(message = "围栏区域类型, 0 POLYGON, 1 CIRCLE不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long areaType; + + /** + * 围栏坐标数据 + */ + @NotBlank(message = "围栏坐标数据不能为空", groups = { AddGroup.class, EditGroup.class }) + private String coordinates; + + /** + * 圆形围栏半径(米) + */ + private Long radius; + + /** + * 是否激活 + */ + private Long isActive; + + /** + * 创建人 + */ + private Long createdBy; + + /** + * 创建时间 + */ + private Date createdTime; + + /** + * 更新时间 + */ + private Date updatedTime; + + +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/FenceCheckResponse.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/FenceCheckResponse.java new file mode 100644 index 00000000..5d47f15d --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/FenceCheckResponse.java @@ -0,0 +1,63 @@ +package com.fuyuanshen.equipment.domain.dto; + +import lombok.Data; + +import java.util.List; + + +/** + * 围栏位置检查响应结果 + * + * @author: 默苍璃 + * @date: 2025-09-1110:11 + */ + +@Data +public class FenceCheckResponse { + + /** + * 设备ID + */ + private String deviceId; + + /** + * 检查时间 + */ + private Long checkTime; + + /** + * 进入围栏列表 + */ + private List enteredFences; + + /** + * 离开围栏列表 + */ + private List exitedFences; + + /** + * 当前所在围栏列表 + */ + private List currentFences; + + /** + * 围栏信息 + */ + @Data + public static class FenceInfo { + /** + * 围栏ID + */ + private Long fenceId; + + /** + * 围栏名称 + */ + private String fenceName; + + /** + * 围栏类型 + */ + private Integer fenceType; + } +} \ No newline at end of file diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/FenceCheckRequest.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/FenceCheckRequest.java new file mode 100644 index 00000000..de556516 --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/FenceCheckRequest.java @@ -0,0 +1,39 @@ +package com.fuyuanshen.equipment.domain.query; + +/** + * @author: 默苍璃 + * @date: 2025-09-1110:10 + */ + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 围栏位置检查请求参数 + */ +@Data +public class FenceCheckRequest { + + /** + * 设备ID + */ + @NotNull(message = "设备ID不能为空") + private String deviceId; + + /** + * 纬度 + */ + @NotNull(message = "纬度不能为空") + private Double latitude; + + /** + * 经度 + */ + @NotNull(message = "经度不能为空") + private Double longitude; + + /** + * 定位精度(米) + */ + private Double accuracy; +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/AlarmInformationVo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/AlarmInformationVo.java index 58216893..1d2e2484 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/AlarmInformationVo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/AlarmInformationVo.java @@ -49,7 +49,7 @@ public class AlarmInformationVo { private Integer alarmManual = 0; /** - * 电子围栏 + * */ private Integer fenceElectronic = 0; diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DataOverviewVo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DataOverviewVo.java index 10fbe2e7..c4b6d39e 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DataOverviewVo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DataOverviewVo.java @@ -26,13 +26,11 @@ public class DataOverviewVo { */ private Integer bindingNew = 0; - /** - * 绑定设备 + * 已绑定设备 */ private Integer binding = 0; - /** * 异常设备 */ diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceFenceAccessRecordVo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceFenceAccessRecordVo.java new file mode 100644 index 00000000..a91b3950 --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceFenceAccessRecordVo.java @@ -0,0 +1,94 @@ +package com.fuyuanshen.equipment.domain.vo; + +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; +import cn.idev.excel.annotation.ExcelIgnoreUnannotated; +import cn.idev.excel.annotation.ExcelProperty; +import com.fuyuanshen.common.excel.annotation.ExcelDictFormat; +import com.fuyuanshen.common.excel.convert.ExcelDictConvert; +import com.fuyuanshen.equipment.domain.DeviceFenceAccessRecord; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + + + +/** + * 围栏进出记录视图对象 device_fence_access_record + * + * @author Lion Li + * @date 2025-09-11 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = DeviceFenceAccessRecord.class) +public class DeviceFenceAccessRecordVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 记录ID + */ + @ExcelProperty(value = "记录ID") + private Long id; + + /** + * 围栏ID + */ + @ExcelProperty(value = "围栏ID") + private Long fenceId; + + /** + * 设备标识 + */ + @ExcelProperty(value = "设备标识") + private String deviceId; + + /** + * 用户ID + */ + @ExcelProperty(value = "用户ID") + private Long userId; + + /** + * 事件类型 + */ + @ExcelProperty(value = "事件类型") + private Long eventType; + + /** + * 纬度 + */ + @ExcelProperty(value = "纬度") + private Long latitude; + + /** + * 经度 + */ + @ExcelProperty(value = "经度") + private Long longitude; + + /** + * 定位精度 + */ + @ExcelProperty(value = "定位精度") + private Long accuracy; + + /** + * 事件时间 + */ + @ExcelProperty(value = "事件时间") + private Date eventTime; + + /** + * 记录创建时间 + */ + @ExcelProperty(value = "记录创建时间") + private Date createdTime; + + +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceGeoFenceVo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceGeoFenceVo.java new file mode 100644 index 00000000..50fbc372 --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceGeoFenceVo.java @@ -0,0 +1,94 @@ +package com.fuyuanshen.equipment.domain.vo; + +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; +import cn.idev.excel.annotation.ExcelIgnoreUnannotated; +import cn.idev.excel.annotation.ExcelProperty; +import com.fuyuanshen.common.excel.annotation.ExcelDictFormat; +import com.fuyuanshen.common.excel.convert.ExcelDictConvert; +import com.fuyuanshen.equipment.domain.DeviceGeoFence; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + + + +/** + * 电子围栏视图对象 device_geo_fence + * + * @author Lion Li + * @date 2025-09-11 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = DeviceGeoFence.class) +public class DeviceGeoFenceVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 围栏唯一标识 + */ + @ExcelProperty(value = "围栏唯一标识") + private Long id; + + /** + * 围栏名称 + */ + @ExcelProperty(value = "围栏名称") + private String name; + + /** + * 围栏描述 + */ + @ExcelProperty(value = "围栏描述") + private String description; + + /** + * 围栏区域类型, 0 POLYGON, 1 CIRCLE + */ + @ExcelProperty(value = "围栏区域类型, 0 POLYGON, 1 CIRCLE") + private Integer areaType; + + /** + * 围栏坐标数据 + */ + @ExcelProperty(value = "围栏坐标数据") + private String coordinates; + + /** + * 圆形围栏半径(米) + */ + @ExcelProperty(value = "圆形围栏半径(米)") + private Long radius; + + /** + * 是否激活 + */ + @ExcelProperty(value = "是否激活") + private Long isActive; + + /** + * 创建人 + */ + @ExcelProperty(value = "创建人") + private Long createdBy; + + /** + * 创建时间 + */ + @ExcelProperty(value = "创建时间") + private Date createdTime; + + /** + * 更新时间 + */ + @ExcelProperty(value = "更新时间") + private Date updatedTime; + + +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceFenceAccessRecordMapper.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceFenceAccessRecordMapper.java new file mode 100644 index 00000000..f6f03f6d --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceFenceAccessRecordMapper.java @@ -0,0 +1,15 @@ +package com.fuyuanshen.equipment.mapper; + +import com.fuyuanshen.common.mybatis.core.mapper.BaseMapperPlus; +import com.fuyuanshen.equipment.domain.DeviceFenceAccessRecord; +import com.fuyuanshen.equipment.domain.vo.DeviceFenceAccessRecordVo; + +/** + * 围栏进出记录Mapper接口 + * + * @author Lion Li + * @date 2025-09-11 + */ +public interface DeviceFenceAccessRecordMapper extends BaseMapperPlus { + +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceGeoFenceMapper.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceGeoFenceMapper.java new file mode 100644 index 00000000..f6352b93 --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceGeoFenceMapper.java @@ -0,0 +1,15 @@ +package com.fuyuanshen.equipment.mapper; + +import com.fuyuanshen.common.mybatis.core.mapper.BaseMapperPlus; +import com.fuyuanshen.equipment.domain.DeviceGeoFence; +import com.fuyuanshen.equipment.domain.vo.DeviceGeoFenceVo; + +/** + * 电子围栏Mapper接口 + * + * @author Lion Li + * @date 2025-09-11 + */ +public interface DeviceGeoFenceMapper extends BaseMapperPlus { + +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/IDeviceFenceAccessRecordService.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/IDeviceFenceAccessRecordService.java new file mode 100644 index 00000000..0d596719 --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/IDeviceFenceAccessRecordService.java @@ -0,0 +1,68 @@ +package com.fuyuanshen.equipment.service; + +import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; +import com.fuyuanshen.common.mybatis.core.page.PageQuery; +import com.fuyuanshen.equipment.domain.bo.DeviceFenceAccessRecordBo; +import com.fuyuanshen.equipment.domain.vo.DeviceFenceAccessRecordVo; + +import java.util.Collection; +import java.util.List; + +/** + * 围栏进出记录Service接口 + * + * @author Lion Li + * @date 2025-09-11 + */ +public interface IDeviceFenceAccessRecordService { + + /** + * 查询围栏进出记录 + * + * @param id 主键 + * @return 围栏进出记录 + */ + DeviceFenceAccessRecordVo queryById(Long id); + + /** + * 分页查询围栏进出记录列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 围栏进出记录分页列表 + */ + TableDataInfo queryPageList(DeviceFenceAccessRecordBo bo, PageQuery pageQuery); + + /** + * 查询符合条件的围栏进出记录列表 + * + * @param bo 查询条件 + * @return 围栏进出记录列表 + */ + List queryList(DeviceFenceAccessRecordBo bo); + + /** + * 新增围栏进出记录 + * + * @param bo 围栏进出记录 + * @return 是否新增成功 + */ + Boolean insertByBo(DeviceFenceAccessRecordBo bo); + + /** + * 修改围栏进出记录 + * + * @param bo 围栏进出记录 + * @return 是否修改成功 + */ + Boolean updateByBo(DeviceFenceAccessRecordBo bo); + + /** + * 校验并批量删除围栏进出记录信息 + * + * @param ids 待删除的主键集合 + * @param isValid 是否进行有效性校验 + * @return 是否删除成功 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/IDeviceGeoFenceService.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/IDeviceGeoFenceService.java new file mode 100644 index 00000000..69a7d8f3 --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/IDeviceGeoFenceService.java @@ -0,0 +1,78 @@ +package com.fuyuanshen.equipment.service; + +import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; +import com.fuyuanshen.common.mybatis.core.page.PageQuery; +import com.fuyuanshen.equipment.domain.bo.DeviceGeoFenceBo; +import com.fuyuanshen.equipment.domain.dto.FenceCheckResponse; +import com.fuyuanshen.equipment.domain.query.FenceCheckRequest; +import com.fuyuanshen.equipment.domain.vo.DeviceGeoFenceVo; + +import java.util.Collection; +import java.util.List; + +/** + * 电子围栏Service接口 + * + * @author Lion Li + * @date 2025-09-11 + */ +public interface IDeviceGeoFenceService { + + /** + * 查询电子围栏 + * + * @param id 主键 + * @return 电子围栏 + */ + DeviceGeoFenceVo queryById(Long id); + + /** + * 分页查询电子围栏列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 电子围栏分页列表 + */ + TableDataInfo queryPageList(DeviceGeoFenceBo bo, PageQuery pageQuery); + + /** + * 查询符合条件的电子围栏列表 + * + * @param bo 查询条件 + * @return 电子围栏列表 + */ + List queryList(DeviceGeoFenceBo bo); + + /** + * 新增电子围栏 + * + * @param bo 电子围栏 + * @return 是否新增成功 + */ + Boolean insertByBo(DeviceGeoFenceBo bo); + + /** + * 修改电子围栏 + * + * @param bo 电子围栏 + * @return 是否修改成功 + */ + Boolean updateByBo(DeviceGeoFenceBo bo); + + /** + * 校验并批量删除电子围栏信息 + * + * @param ids 待删除的主键集合 + * @param isValid 是否进行有效性校验 + * @return 是否删除成功 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + /** + * 检查设备位置与围栏的关系 + * + * @param request 位置检查请求 + * @return 位置检查结果 + */ + FenceCheckResponse checkPosition(FenceCheckRequest request); +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceFenceAccessRecordServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceFenceAccessRecordServiceImpl.java new file mode 100644 index 00000000..a09f8386 --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceFenceAccessRecordServiceImpl.java @@ -0,0 +1,140 @@ +package com.fuyuanshen.equipment.service.impl; + +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.equipment.domain.DeviceFenceAccessRecord; +import com.fuyuanshen.equipment.domain.bo.DeviceFenceAccessRecordBo; +import com.fuyuanshen.equipment.domain.vo.DeviceFenceAccessRecordVo; +import com.fuyuanshen.equipment.mapper.DeviceFenceAccessRecordMapper; +import com.fuyuanshen.equipment.service.IDeviceFenceAccessRecordService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; +import java.util.Collection; + +/** + * 围栏进出记录Service业务层处理 + * + * @author Lion Li + * @date 2025-09-11 + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class DeviceFenceAccessRecordServiceImpl implements IDeviceFenceAccessRecordService { + + private final DeviceFenceAccessRecordMapper baseMapper; + + /** + * 查询围栏进出记录 + * + * @param id 主键 + * @return 围栏进出记录 + */ + @Override + public DeviceFenceAccessRecordVo queryById(Long id){ + return baseMapper.selectVoById(id); + } + + /** + * 分页查询围栏进出记录列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 围栏进出记录分页列表 + */ + @Override + public TableDataInfo queryPageList(DeviceFenceAccessRecordBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询符合条件的围栏进出记录列表 + * + * @param bo 查询条件 + * @return 围栏进出记录列表 + */ + @Override + public List queryList(DeviceFenceAccessRecordBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(DeviceFenceAccessRecordBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.orderByAsc(DeviceFenceAccessRecord::getId); + lqw.eq(bo.getFenceId() != null, DeviceFenceAccessRecord::getFenceId, bo.getFenceId()); + lqw.eq(StringUtils.isNotBlank(bo.getDeviceId()), DeviceFenceAccessRecord::getDeviceId, bo.getDeviceId()); + lqw.eq(bo.getUserId() != null, DeviceFenceAccessRecord::getUserId, bo.getUserId()); + lqw.eq(bo.getEventType() != null, DeviceFenceAccessRecord::getEventType, bo.getEventType()); + lqw.eq(bo.getLatitude() != null, DeviceFenceAccessRecord::getLatitude, bo.getLatitude()); + lqw.eq(bo.getLongitude() != null, DeviceFenceAccessRecord::getLongitude, bo.getLongitude()); + lqw.eq(bo.getAccuracy() != null, DeviceFenceAccessRecord::getAccuracy, bo.getAccuracy()); + lqw.eq(bo.getEventTime() != null, DeviceFenceAccessRecord::getEventTime, bo.getEventTime()); + lqw.eq(bo.getCreatedTime() != null, DeviceFenceAccessRecord::getCreatedTime, bo.getCreatedTime()); + return lqw; + } + + /** + * 新增围栏进出记录 + * + * @param bo 围栏进出记录 + * @return 是否新增成功 + */ + @Override + public Boolean insertByBo(DeviceFenceAccessRecordBo bo) { + DeviceFenceAccessRecord add = MapstructUtils.convert(bo, DeviceFenceAccessRecord.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改围栏进出记录 + * + * @param bo 围栏进出记录 + * @return 是否修改成功 + */ + @Override + public Boolean updateByBo(DeviceFenceAccessRecordBo bo) { + DeviceFenceAccessRecord update = MapstructUtils.convert(bo, DeviceFenceAccessRecord.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(DeviceFenceAccessRecord entity){ + //TODO 做一些数据校验,如唯一约束 + } + + /** + * 校验并批量删除围栏进出记录信息 + * + * @param ids 待删除的主键集合 + * @param isValid 是否进行有效性校验 + * @return 是否删除成功 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if(isValid){ + //TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteByIds(ids) > 0; + } +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceGeoFenceServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceGeoFenceServiceImpl.java new file mode 100644 index 00000000..c5fbb2df --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceGeoFenceServiceImpl.java @@ -0,0 +1,199 @@ +package com.fuyuanshen.equipment.service.impl; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +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.equipment.domain.DeviceGeoFence; +import com.fuyuanshen.equipment.domain.bo.DeviceGeoFenceBo; +import com.fuyuanshen.equipment.domain.dto.FenceCheckResponse; +import com.fuyuanshen.equipment.domain.query.FenceCheckRequest; +import com.fuyuanshen.equipment.domain.vo.DeviceGeoFenceVo; +import com.fuyuanshen.equipment.mapper.DeviceGeoFenceMapper; +import com.fuyuanshen.equipment.service.IDeviceGeoFenceService; +import com.fuyuanshen.equipment.utils.map.GeoFenceChecker; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; +import java.util.Collection; + +/** + * 电子围栏Service业务层处理 + * + * @author Lion Li + * @date 2025-09-11 + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class DeviceGeoFenceServiceImpl implements IDeviceGeoFenceService { + + private final DeviceGeoFenceMapper baseMapper; + + /** + * 查询电子围栏 + * + * @param id 主键 + * @return 电子围栏 + */ + @Override + public DeviceGeoFenceVo queryById(Long id) { + return baseMapper.selectVoById(id); + } + + /** + * 分页查询电子围栏列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 电子围栏分页列表 + */ + @Override + public TableDataInfo queryPageList(DeviceGeoFenceBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询符合条件的电子围栏列表 + * + * @param bo 查询条件 + * @return 电子围栏列表 + */ + @Override + public List queryList(DeviceGeoFenceBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(DeviceGeoFenceBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.orderByAsc(DeviceGeoFence::getId); + lqw.like(StringUtils.isNotBlank(bo.getName()), DeviceGeoFence::getName, bo.getName()); + lqw.eq(StringUtils.isNotBlank(bo.getDescription()), DeviceGeoFence::getDescription, bo.getDescription()); + lqw.eq(bo.getAreaType() != null, DeviceGeoFence::getAreaType, bo.getAreaType()); + lqw.eq(StringUtils.isNotBlank(bo.getCoordinates()), DeviceGeoFence::getCoordinates, bo.getCoordinates()); + lqw.eq(bo.getRadius() != null, DeviceGeoFence::getRadius, bo.getRadius()); + lqw.eq(bo.getIsActive() != null, DeviceGeoFence::getIsActive, bo.getIsActive()); + lqw.eq(bo.getCreatedBy() != null, DeviceGeoFence::getCreatedBy, bo.getCreatedBy()); + lqw.eq(bo.getCreatedTime() != null, DeviceGeoFence::getCreatedTime, bo.getCreatedTime()); + lqw.eq(bo.getUpdatedTime() != null, DeviceGeoFence::getUpdatedTime, bo.getUpdatedTime()); + return lqw; + } + + /** + * 新增电子围栏 + * + * @param bo 电子围栏 + * @return 是否新增成功 + */ + @Override + public Boolean insertByBo(DeviceGeoFenceBo bo) { + DeviceGeoFence add = MapstructUtils.convert(bo, DeviceGeoFence.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改电子围栏 + * + * @param bo 电子围栏 + * @return 是否修改成功 + */ + @Override + public Boolean updateByBo(DeviceGeoFenceBo bo) { + DeviceGeoFence update = MapstructUtils.convert(bo, DeviceGeoFence.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(DeviceGeoFence entity) { + // TODO 做一些数据校验,如唯一约束 + } + + /** + * 校验并批量删除电子围栏信息 + * + * @param ids 待删除的主键集合 + * @param isValid 是否进行有效性校验 + * @return 是否删除成功 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if (isValid) { + // TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteByIds(ids) > 0; + } + + + /** + * 检查设备位置与围栏的关系 + * + * @param request 位置检查请求 + * @return 位置检查结果 + */ + @Override + public FenceCheckResponse checkPosition(FenceCheckRequest request) { + // 1. 获取所有激活的围栏 + DeviceGeoFenceBo bo = new DeviceGeoFenceBo(); + bo.setIsActive(1L); // 假设1表示激活状态 + List activeFences = queryList(bo); + + // 2. 判断设备位置与各围栏的关系 + FenceCheckResponse response = new FenceCheckResponse(); + response.setDeviceId(request.getDeviceId()); + response.setCheckTime(System.currentTimeMillis()); + + // 这里需要实现具体的围栏判断逻辑 + // 根据您之前提供的算法实现点在围栏内的判断 + + for (DeviceGeoFenceVo fence : activeFences) { + String coordinates = fence.getCoordinates(); + + // 在需要转换的地方 + ObjectMapper objectMapper = new ObjectMapper(); + List coordinateList = List.of(); + try { + coordinateList = objectMapper.readValue(coordinates, + new TypeReference>() { + }); + } catch (Exception e) { + // 处理解析异常 + log.error("坐标数据解析失败: {}", e.getMessage()); + } + + FenceCheckResponse.FenceInfo fenceInfo = new FenceCheckResponse.FenceInfo(); + boolean pointInFence = GeoFenceChecker.isPointInFence(request.getLatitude(), request.getLongitude(), fence.getAreaType(), coordinateList, fence.getRadius()); + if (pointInFence) { + fenceInfo.setFenceId(fence.getId()); + fenceInfo.setFenceName(fence.getName()); + fenceInfo.setFenceType(fence.getAreaType()); + response.getEnteredFences().add(fenceInfo); + } else { + response.getExitedFences().add(fenceInfo); + } + } + + return response; + } + + +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/utils/map/Coordinate.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/utils/map/Coordinate.java new file mode 100644 index 00000000..781c9a23 --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/utils/map/Coordinate.java @@ -0,0 +1,24 @@ +package com.fuyuanshen.equipment.utils.map; + +/** + * @author: 默苍璃 + * @date: 2025-09-1110:27 + */ +public class Coordinate { + private double lat; + private double lng; + + // 构造函数 + public Coordinate() {} + + public Coordinate(double lat, double lng) { + this.lat = lat; + this.lng = lng; + } + + // getter和setter方法 + public double getLat() { return lat; } + public void setLat(double lat) { this.lat = lat; } + public double getLng() { return lng; } + public void setLng(double lng) { this.lng = lng; } +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/utils/map/GeoFenceChecker.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/utils/map/GeoFenceChecker.java new file mode 100644 index 00000000..5f38139c --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/utils/map/GeoFenceChecker.java @@ -0,0 +1,135 @@ +package com.fuyuanshen.equipment.utils.map; + +import java.util.List; + +/** + * @author: 默苍璃 + * @date: 2025-09-1110:13 + */ +public class GeoFenceChecker { + + /** + * 点是否在围栏内 + * + * @param pointLat 点的纬度 + * @param pointLng 点的经度 + * @param fenceType 围栏类型:0 POLYGON 或 1 CIRCLE + * @param coordinates 围栏坐标点列表 + * @param radius 圆形围栏半径(米),仅对CIRCLE类型有效 + * @return true表示在围栏内,false表示在围栏外 + */ + public static boolean isPointInFence(double pointLat, double pointLng, + Integer fenceType, + List coordinates, + Long radius) { + if (fenceType == 0) { + return isPointInPolygon(pointLat, pointLng, coordinates); + } else if (fenceType == 1) { + if (coordinates == null || coordinates.isEmpty() || radius == null) { + return false; + } + // 圆形围栏只有一个坐标点(圆心) + Coordinate center = coordinates.get(0); + return isPointInCircle(pointLat, pointLng, center.lat, center.lng, radius); + } + return false; + } + + /** + * 使用射线投射算法判断点是否在多边形内 + * + * @param pointLat 点的纬度 + * @param pointLng 点的经度 + * @param polygon 多边形顶点坐标列表 + * @return true表示在多边形内,false表示在多边形外 + */ + public static boolean isPointInPolygon(double pointLat, double pointLng, + List polygon) { + if (polygon == null || polygon.size() < 3) { + return false; + } + + int intersectCount = 0; + int vertexCount = polygon.size(); + + // 遍历多边形的每条边 + for (int i = 0; i < vertexCount; i++) { + Coordinate p1 = polygon.get(i); + Coordinate p2 = polygon.get((i + 1) % vertexCount); + + // 检查点是否在边的y范围内 + if (p1.lat != p2.lat && + (pointLat >= Math.min(p1.lat, p2.lat)) && + (pointLat < Math.max(p1.lat, p2.lat))) { + + // 计算边与从点发出的水平射线的交点经度 + double intersectLng = (pointLat - p1.lat) * (p2.lng - p1.lng) / (p2.lat - p1.lat) + p1.lng; + + // 如果交点在点的右侧,则计数加1 + if (pointLng < intersectLng) { + intersectCount++; + } + } + } + + // 奇数个交点表示点在多边形内 + return intersectCount % 2 == 1; + } + + /** + * 判断点是否在圆形内 + * + * @param pointLat 点的纬度 + * @param pointLng 点的经度 + * @param centerLat 圆心纬度 + * @param centerLng 圆心经度 + * @param radius 半径(米) + * @return true表示在圆内,false表示在圆外 + */ + public static boolean isPointInCircle(double pointLat, double pointLng, + double centerLat, double centerLng, + Long radius) { + double distance = calculateDistance(pointLat, pointLng, centerLat, centerLng); + return distance <= radius; + } + + /** + * 计算两点间距离(使用Haversine公式) + * + * @param lat1 点1纬度 + * @param lng1 点1经度 + * @param lat2 点2纬度 + * @param lng2 点2经度 + * @return 距离(米) + */ + public static double calculateDistance(double lat1, double lng1, double lat2, double lng2) { + final double EARTH_RADIUS = 6371000; // 地球半径(米) + + double dLat = Math.toRadians(lat2 - lat1); + double dLng = Math.toRadians(lng2 - lng1); + + double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * + Math.sin(dLng / 2) * Math.sin(dLng / 2); + + double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + + return EARTH_RADIUS * c; + } + + /** + * 坐标点类 + */ + public static class Coordinate { + public double lat; + public double lng; + + public Coordinate() { + } + + public Coordinate(double lat, double lng) { + this.lat = lat; + this.lng = lng; + } + } +} \ No newline at end of file diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceFenceAccessRecordMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceFenceAccessRecordMapper.xml new file mode 100644 index 00000000..2667e1b3 --- /dev/null +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceFenceAccessRecordMapper.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceGeoFenceMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceGeoFenceMapper.xml new file mode 100644 index 00000000..1590ebe0 --- /dev/null +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceGeoFenceMapper.xml @@ -0,0 +1,7 @@ + + + + + From e2274bdf09e08b0da4923013e0166ae99526a6b6 Mon Sep 17 00:00:00 2001 From: DragonWenLong <552045633@qq.com> Date: Thu, 11 Sep 2025 11:07:58 +0800 Subject: [PATCH 046/160] =?UTF-8?q?feat(web):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E8=81=94=E8=B0=83=E4=B8=AD=E5=BF=83=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增设备联调中心相关控制器、服务、DTO和VO - 实现设备列表查询、文件上传、操作视频添加、设备详情等功能 - 优化设备 logo 上传逻辑,支持批量上传 - 重构部分代码结构,提高可维护性 --- .../device/AppDeviceXinghanController.java | 8 + .../fuyuanshen/app/domain/dto/AppFileDto.java | 5 + .../rule/xinghan/XinghanBootLogoRule.java | 2 +- .../rule/xinghan/XinghanDeviceDataRule.java | 2 +- .../xinghan/XinghanSendAlarmMessageRule.java | 2 +- .../mqtt/rule/xinghan/XinghanSendMsgRule.java | 2 +- .../device/DeviceDebugController.java | 160 ++++++++++++++++++ .../web/domain/Dto/DeviceDebugEditDto.java | 34 ++++ .../domain/Dto/DeviceDebugLogoUploadDto.java | 18 ++ .../web/domain/vo/DeviceInfoVo.java | 27 +++ .../service/device/DeviceDebugService.java | 150 ++++++++++++++++ .../device/DeviceXinghanBizService.java | 66 ++++++++ .../com/fuyuanshen/web/util/FileHashUtil.java | 28 +++ .../app/domain/bo/AppOperationVideoBo.java | 5 + .../fuyuanshen/app/domain/vo/AppFileVo.java | 4 + .../app/service/IAppBusinessFileService.java | 9 + .../service/IAppOperationVideoService.java | 10 ++ .../impl/AppBusinessFileServiceImpl.java | 15 ++ .../impl/AppOperationVideoServiceImpl.java | 15 ++ .../mapper/app/AppBusinessFileMapper.xml | 2 +- .../equipment/DeviceRepairRecordsMapper.xml | 2 +- .../com/fuyuanshen/system/domain/SysOss.java | 4 + .../fuyuanshen/system/domain/bo/SysOssBo.java | 4 + .../fuyuanshen/system/domain/vo/SysOssVo.java | 4 + .../system/mapper/SysOssMapper.java | 23 +++ .../system/service/ISysOssService.java | 16 ++ .../service/impl/SysOssServiceImpl.java | 10 ++ .../resources/mapper/system/SysOssMapper.xml | 7 + 28 files changed, 628 insertions(+), 6 deletions(-) create mode 100644 fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceDebugController.java create mode 100644 fys-admin/src/main/java/com/fuyuanshen/web/domain/Dto/DeviceDebugEditDto.java create mode 100644 fys-admin/src/main/java/com/fuyuanshen/web/domain/Dto/DeviceDebugLogoUploadDto.java create mode 100644 fys-admin/src/main/java/com/fuyuanshen/web/domain/vo/DeviceInfoVo.java create mode 100644 fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceDebugService.java create mode 100644 fys-admin/src/main/java/com/fuyuanshen/web/util/FileHashUtil.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 d60d321e..15d9df5d 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 @@ -5,6 +5,7 @@ import com.fuyuanshen.app.domain.dto.AppDeviceLogoUploadDto; import com.fuyuanshen.app.domain.dto.DeviceInstructDto; import com.fuyuanshen.common.core.domain.R; import com.fuyuanshen.common.core.validate.AddGroup; +import com.fuyuanshen.common.log.annotation.Log; import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessAnnotation; import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessBatcAnnotation; import com.fuyuanshen.common.web.core.BaseController; @@ -29,6 +30,7 @@ public class AppDeviceXinghanController extends BaseController { /** * 人员信息登记 */ + @Log(title = "xinghan指令-人员信息登记") @PostMapping(value = "/registerPersonInfo") // @FunctionAccessAnnotation("registerPersonInfo") public R registerPersonInfo(@Validated(AddGroup.class) @RequestBody AppPersonnelInfoBo bo) { @@ -38,6 +40,7 @@ public class AppDeviceXinghanController extends BaseController { /** * 发送紧急通知 */ + @Log(title = "xinghan指令-发送紧急通知") @PostMapping(value = "/sendAlarmMessage") @FunctionAccessBatcAnnotation(value = "sendAlarmMessage", timeOut = 5, batchMaxTimeOut = 10) public R sendAlarmMessage(@RequestBody AppDeviceSendMsgBo bo) { @@ -47,6 +50,7 @@ public class AppDeviceXinghanController extends BaseController { /** * 上传设备logo图片 */ + @Log(title = "xinghan指令-上传设备logo图片") @PostMapping("/uploadLogo") @FunctionAccessAnnotation("uploadLogo") public R upload(@Validated @ModelAttribute AppDeviceLogoUploadDto bo) { @@ -64,6 +68,7 @@ public class AppDeviceXinghanController extends BaseController { * 静电预警档位 * 3,2,1,0,分别表示高档/中档/低挡/关闭 */ + @Log(title = "xinghan指令-静电预警档位") @PostMapping("/DetectGradeSettings") public R DetectGradeSettings(@RequestBody DeviceInstructDto params) { // params 转 JSONObject @@ -75,6 +80,7 @@ public class AppDeviceXinghanController extends BaseController { * 照明档位 * 照明档位,2,1,0,分别表示弱光/强光/关闭 */ + @Log(title = "xinghan指令-照明档位") @PostMapping("/LightGradeSettings") public R LightGradeSettings(@RequestBody DeviceInstructDto params) { // params 转 JSONObject @@ -86,6 +92,7 @@ public class AppDeviceXinghanController extends BaseController { * SOS档位s * SOS档位,2,1,0, 分别表示红蓝模式/爆闪模式/关闭 */ + @Log(title = "xinghan指令-SOS档位s") @PostMapping("/SOSGradeSettings") public R SOSGradeSettings(@RequestBody DeviceInstructDto params) { // params 转 JSONObject @@ -97,6 +104,7 @@ public class AppDeviceXinghanController extends BaseController { * 静止报警状态 * 静止报警状态,0-未静止报警,1-正在静止报警。 */ + @Log(title = "xinghan指令-静止报警状态") @PostMapping("/ShakeBitSettings") public R ShakeBitSettings(@RequestBody DeviceInstructDto params) { // params 转 JSONObject diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/domain/dto/AppFileDto.java b/fys-admin/src/main/java/com/fuyuanshen/app/domain/dto/AppFileDto.java index 1a5361ce..a9dc8e5c 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/domain/dto/AppFileDto.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/domain/dto/AppFileDto.java @@ -18,4 +18,9 @@ public class AppFileDto { */ private MultipartFile[] files; + /** + * 多设备id + */ + private Long[] deviceIds; + } 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 9109b942..2b92071f 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 @@ -54,7 +54,7 @@ public class XinghanBootLogoRule implements MqttMessageRule { @Override public void execute(MqttRuleContext ctx) { - final String functionAccessKey = FUNCTION_ACCESS_KEY + ctx.getDeviceImei(); + final String functionAccessKey = FUNCTION_ACCESS_KEY + "LOGO:" + ctx.getDeviceImei(); try { MqttXinghanLogoJson payload = objectMapper.convertValue( ctx.getPayloadDict(), MqttXinghanLogoJson.class); 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 23f92be0..cf4fbd03 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 @@ -60,7 +60,7 @@ public class XinghanDeviceDataRule implements MqttMessageRule { @Override public void execute(MqttRuleContext context) { - String functionAccess = FUNCTION_ACCESS_KEY + context.getDeviceImei(); + String functionAccess = FUNCTION_ACCESS_KEY + "DATA:" + context.getDeviceImei(); try { // Latitude, longitude //主灯档位,激光灯档位,电量百分比,充电状态,电池剩余续航时间 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 49f82c97..3a4b368e 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 @@ -48,7 +48,7 @@ public class XinghanSendAlarmMessageRule implements MqttMessageRule { @Override public void execute(MqttRuleContext ctx) { - String functionAccess = FUNCTION_ACCESS_KEY + ctx.getDeviceImei(); + String functionAccess = FUNCTION_ACCESS_KEY + "ALARM:" + ctx.getDeviceImei(); try { XinghanSendAlarmMessageRule.MqttXinghanAlarmMsgJson payload = objectMapper.convertValue( ctx.getPayloadDict(), XinghanSendAlarmMessageRule.MqttXinghanAlarmMsgJson.class); 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 6568b57c..d68d18e3 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 @@ -47,7 +47,7 @@ public class XinghanSendMsgRule implements MqttMessageRule { @Override public void execute(MqttRuleContext ctx) { - String functionAccess = FUNCTION_ACCESS_KEY + ctx.getDeviceImei(); + String functionAccess = FUNCTION_ACCESS_KEY + "MSG:" + ctx.getDeviceImei(); try { XinghanSendMsgRule.MqttXinghanMsgJson payload = objectMapper.convertValue( ctx.getPayloadDict(), XinghanSendMsgRule.MqttXinghanMsgJson.class); diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceDebugController.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceDebugController.java new file mode 100644 index 00000000..d098e8c6 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceDebugController.java @@ -0,0 +1,160 @@ +package com.fuyuanshen.web.controller.device; + +import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; +import com.fuyuanshen.app.domain.bo.AppOperationVideoBo; +import com.fuyuanshen.app.domain.dto.AppDeviceLogoUploadDto; +import com.fuyuanshen.app.domain.dto.AppFileDto; +import com.fuyuanshen.common.core.domain.R; +import com.fuyuanshen.common.core.exception.ServiceException; +import com.fuyuanshen.common.log.annotation.Log; +import com.fuyuanshen.common.log.enums.BusinessType; +import com.fuyuanshen.common.mybatis.core.page.PageQuery; +import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; +import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessAnnotation; +import com.fuyuanshen.common.web.core.BaseController; +import com.fuyuanshen.equipment.domain.query.DeviceQueryCriteria; +import com.fuyuanshen.equipment.domain.vo.AppDeviceVo; +import com.fuyuanshen.equipment.domain.vo.WebDeviceVo; +import com.fuyuanshen.web.domain.Dto.DeviceDebugEditDto; +import com.fuyuanshen.web.domain.Dto.DeviceDebugLogoUploadDto; +import com.fuyuanshen.web.domain.vo.DeviceInfoVo; +import com.fuyuanshen.web.service.device.DeviceBizService; +import com.fuyuanshen.web.service.device.DeviceDebugService; +import com.fuyuanshen.web.service.device.DeviceXinghanBizService; +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * 联调中心 + * + * @author Lion Li + * @date 2025-08-28 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("api/device/debug") +public class DeviceDebugController extends BaseController { + + private final DeviceBizService appDeviceService; + private final DeviceXinghanBizService deviceXinghanBizService; + private final DeviceDebugService deviceDebugService; + + /** + * 查询设备列表 + */ + @GetMapping("/list") + public TableDataInfo list(DeviceQueryCriteria bo, PageQuery pageQuery) { + return appDeviceService.queryWebDeviceList(bo, pageQuery); + } + + /** + * 上传文件 + */ + @Log(title = "批量上传文件") + @PostMapping("/addFile") + public R uploadFile(@Validated @ModelAttribute AppFileDto bo) throws IOException { + return toAjax(deviceDebugService.addFileHash(bo)); + } + + /** + * 操作视频添加 + */ + @Log(title = "批量添加操作视频") + @PostMapping("/addVideo") + public R addOperationVideo(@RequestBody AppOperationVideoBo bo) { + return toAjax(deviceDebugService.addVideoList(bo)); + } + + /** + * 上传设备logo图片 + */ + @Log(title = "批量上传设备logo图片") + @PostMapping("/addLogo") + @FunctionAccessAnnotation("uploadLogo") + public R uploadLogo670(@Validated @ModelAttribute DeviceDebugLogoUploadDto bo) { + + MultipartFile file = bo.getFile(); + if(file.getSize()>1024*1024*2){ + return R.warn("图片不能大于2M"); + } + deviceXinghanBizService.uploadDeviceLogoBatch(bo); + return R.ok(); + } + + /** + * 设备详情 + */ + @Operation(summary = "设备详情") + @GetMapping(value = "/detail/{id}") + public R getDeviceInfo(@PathVariable Long id) { + return R.ok(deviceDebugService.getDeviceInfo(id)); + } + + /** + * 修改设备联调信息 + */ + @Log(title = "修改设备联调信息") + @PostMapping("/editDebug") + public R editDeviceDebug(@Validated @ModelAttribute DeviceDebugEditDto bo) throws Exception { + // 1. 基础参数必填校验 + validateDeviceDebugEdit(bo); + + // 修改上传设备说明 + if (bo.getExplanationFiles() != null) { + AppFileDto appFileDto = new AppFileDto(); + appFileDto.setDeviceIds(new Long[]{ bo.getDeviceId() }); + appFileDto.setFileType(1L); + appFileDto.setFiles(bo.getExplanationFiles()); + deviceDebugService.addFileHash(appFileDto); + } + // 修改上传设备参数 + if (bo.getParameterFiles() != null) { + AppFileDto appFileDto = new AppFileDto(); + appFileDto.setDeviceIds(new Long[]{ bo.getDeviceId() }); + appFileDto.setFileType(2L); + appFileDto.setFiles(bo.getParameterFiles()); + deviceDebugService.addFileHash(appFileDto); + } + // 修改操作视频 + if (bo.getVideoUrl().isEmpty()) { + AppOperationVideoBo appOperationVideoBo = new AppOperationVideoBo(); + appOperationVideoBo.setDeviceIds(new Long[]{ bo.getDeviceId() }); + appOperationVideoBo.setVideoUrl(bo.getVideoUrl()); + deviceDebugService.addVideoList(appOperationVideoBo); + } + // 修改设备logo 每个型号设备走不同协议无法共用同一个上传 +// if(bo.getLogoFile() != null){ +// MultipartFile file = bo.getLogoFile(); +// if(file.getSize()>1024*1024*2){ +// return R.warn("图片不能大于2M"); +// } +// AppDeviceLogoUploadDto bo1 = new AppDeviceLogoUploadDto(); +// bo1.setDeviceId(bo.getDeviceId()); +// bo1.setDeviceImei(bo.getDeviceImei()); +// bo1.setFile(file); +// deviceXinghanBizService.uploadDeviceLogo(bo1); +// } + + return R.ok(); + } + + /* ------------------ 私有复用 ------------------ */ + + private void validateDeviceDebugEdit(DeviceDebugEditDto bo) { + if (bo.getDeviceId() == null || bo.getDeviceId() == 0L) { + throw new ServiceException("请选择设备"); + } +// if (bo.getDeviceImei().isEmpty()) { +// throw new ServiceException("设备 IMEI 不能为空"); +// } + } + +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/domain/Dto/DeviceDebugEditDto.java b/fys-admin/src/main/java/com/fuyuanshen/web/domain/Dto/DeviceDebugEditDto.java new file mode 100644 index 00000000..9eb34174 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/web/domain/Dto/DeviceDebugEditDto.java @@ -0,0 +1,34 @@ +package com.fuyuanshen.web.domain.Dto; + +import lombok.Data; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +@Data +public class DeviceDebugEditDto { + /** + * 设备主键列表 + */ + private Long deviceId; + /** + * 设备 IMEI + */ + //private String deviceImei; + /** + * 上传 logo 图片 + */ + //private MultipartFile LogoFile; // 同一张图 + /** + * 参数文件 + */ + private MultipartFile[] parameterFiles; + /** + * 说明文件 + */ + private MultipartFile[] explanationFiles; + /** + * 视频链接 + */ + private String videoUrl; +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/domain/Dto/DeviceDebugLogoUploadDto.java b/fys-admin/src/main/java/com/fuyuanshen/web/domain/Dto/DeviceDebugLogoUploadDto.java new file mode 100644 index 00000000..5f4ebecf --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/web/domain/Dto/DeviceDebugLogoUploadDto.java @@ -0,0 +1,18 @@ +package com.fuyuanshen.web.domain.Dto; + +import lombok.Data; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +@Data +public class DeviceDebugLogoUploadDto { + /** + * 设备主键列表 + */ + private List deviceIds; // 设备主键列表 + /** + * 上传 图片 + */ + private MultipartFile file; // 同一张图 +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/domain/vo/DeviceInfoVo.java b/fys-admin/src/main/java/com/fuyuanshen/web/domain/vo/DeviceInfoVo.java new file mode 100644 index 00000000..e0c637b5 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/web/domain/vo/DeviceInfoVo.java @@ -0,0 +1,27 @@ +package com.fuyuanshen.web.domain.vo; + +import com.fuyuanshen.app.domain.vo.AppBusinessFileVo; +import com.fuyuanshen.app.domain.vo.AppFileVo; +import com.fuyuanshen.app.domain.vo.AppOperationVideoVo; +import com.fuyuanshen.equipment.domain.Device; +import com.fuyuanshen.equipment.domain.vo.AppDeviceVo; +import lombok.Data; + +import java.util.List; + +/** + * 设备信息 视图对象 + * + * @author Michelle.Chung + */ +@Data +public class DeviceInfoVo { + /** + * 设备业务文件 + */ + private List appBusinessFileVoList; + /** + * 设备操作视频 + */ + private List appOperationVideoVoList; +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceDebugService.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceDebugService.java new file mode 100644 index 00000000..2f2e6b85 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceDebugService.java @@ -0,0 +1,150 @@ +package com.fuyuanshen.web.service.device; + +import cn.hutool.core.collection.CollUtil; +import com.fuyuanshen.app.domain.AppBusinessFile; +import com.fuyuanshen.app.domain.AppOperationVideo; +import com.fuyuanshen.app.domain.bo.AppBusinessFileBo; +import com.fuyuanshen.app.domain.bo.AppOperationVideoBo; +import com.fuyuanshen.app.domain.dto.AppFileDto; +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 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; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * 设备调试服务 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class DeviceDebugService { + private final ISysOssService sysOssService; + + private final IAppBusinessFileService appBusinessFileService; + private final IAppOperationVideoService appOperationVideoService; + private final DeviceService deviceService; + + /** + * 文件上传并添加文件信息哈希去重 + * @param bo + * @return + * @throws IOException + */ + @Transactional(rollbackFor = Exception.class) + public Boolean addFileHash(AppFileDto bo) throws IOException { + MultipartFile[] files = bo.getFiles(); + if (files == null || files.length == 0) { + throw new ServiceException("请选择要上传的文件"); + } + if (files.length > 5) { + throw new ServiceException("最多只能上传5个文件"); + } + if (bo.getDeviceIds() == null || bo.getDeviceIds().length == 0) { + throw new ServiceException("请选择你要上传的设备"); + } + + Map hash2OssId = new LinkedHashMap<>(files.length); + for (MultipartFile file : files) { + // 1. 计算文件哈希 + String hash = FileHashUtil.hash(file); + + // 2. 先根据 hash 查库(秒传) + SysOssVo exist = sysOssService.selectByHash(hash); + Long ossId; + if (exist != null) { + // 2.1 已存在,直接复用 + ossId = exist.getOssId(); + hash2OssId.putIfAbsent(hash, ossId); + } else { + // 2.2 不存在,真正上传 + SysOssVo upload = sysOssService.upload(file); + if (upload == null) { + return false; + } + ossId = upload.getOssId(); + hash2OssId.putIfAbsent(hash, ossId); + // 2.3 把 hash 写回记录(供下次去重) + sysOssService.updateHashById(ossId, hash); + } + } + // 4. 组装业务中间表 + List bizList = new ArrayList<>(bo.getDeviceIds().length * hash2OssId.size()); + Long userId = AppLoginHelper.getUserId(); + for (Long deviceId : bo.getDeviceIds()) { + for (Long ossId : hash2OssId.values()) { + // 3. 关联业务表 + AppBusinessFile appFile = new AppBusinessFile(); + appFile.setFileId(ossId); + appFile.setBusinessId(deviceId); + appFile.setFileType(bo.getFileType()); + appFile.setCreateBy(userId); + bizList.add(appFile); + } + } + if (CollUtil.isEmpty(bizList)) { // 空集合直接返回 + throw new ServiceException("请选择要上传的文件"); + } + return appBusinessFileService.insertBatch(bizList); + } + + public Boolean addVideoList(AppOperationVideoBo bo){ + if (bo.getVideoUrl().isEmpty()) { + throw new ServiceException("请输入视频地址"); + } + if (bo.getDeviceIds() == null || bo.getDeviceIds().length == 0) { + throw new ServiceException("请选择你要上传的设备"); + } + List bizList = new ArrayList<>(bo.getDeviceIds().length); + for (Long deviceId : bo.getDeviceIds()) { + + AppOperationVideo appVideo = new AppOperationVideo(); + appVideo.setVideoName(bo.getVideoName()); + appVideo.setDeviceId(deviceId); + appVideo.setVideoUrl(bo.getVideoUrl()); + bizList.add(appVideo); + } + if (CollUtil.isEmpty(bizList)) { // 空集合直接返回 + throw new ServiceException("请选择要上传的视频"); + } + return appOperationVideoService.insertBatch(bizList); + } + + /** + * 设备详情 + * @param deviceId + * @return + */ + public DeviceInfoVo getDeviceInfo(Long deviceId) { + if(deviceId == null || deviceId <= 0L) { + throw new ServiceException("请选择设备"); + } + DeviceInfoVo vo = new DeviceInfoVo(); + var device = deviceService.getById(deviceId); + AppBusinessFileBo fileBo = new AppBusinessFileBo(); + fileBo.setBusinessId(deviceId); + AppOperationVideoBo videoBo = new AppOperationVideoBo(); + videoBo.setDeviceId(deviceId); + vo.setAppBusinessFileVoList(appBusinessFileService.queryAppFileList(fileBo)); + vo.setAppOperationVideoVoList(appOperationVideoService.queryList(videoBo)); + return vo; + } +} 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 52a6b41b..8370ed9d 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 @@ -3,6 +3,7 @@ package com.fuyuanshen.web.service.device; import com.alibaba.fastjson2.JSON; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; import com.fuyuanshen.app.domain.AppPersonnelInfo; import com.fuyuanshen.app.domain.bo.AppPersonnelInfoBo; import com.fuyuanshen.app.domain.dto.AppDeviceLogoUploadDto; @@ -27,13 +28,17 @@ import com.fuyuanshen.equipment.mapper.DeviceTypeMapper; import com.fuyuanshen.global.mqtt.config.MqttGateway; import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants; import com.fuyuanshen.global.mqtt.constants.MqttConstants; +import com.fuyuanshen.web.domain.Dto.DeviceDebugLogoUploadDto; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; import java.time.Duration; import java.util.*; +import java.util.stream.Collectors; import static com.fuyuanshen.common.core.constant.GlobalConstants.GLOBAL_REDIS_KEY; import static com.fuyuanshen.common.core.utils.Bitmap80x12Generator.buildArr; @@ -142,6 +147,67 @@ public class DeviceXinghanBizService { } } + /** + * 批量上传设备logo + */ + @Transactional(rollbackFor = Exception.class) + public void uploadDeviceLogoBatch(DeviceDebugLogoUploadDto batchDto) { + if (CollectionUtils.isEmpty(batchDto.getDeviceIds())) { + throw new ServiceException("设备列表为空"); + } + + // 1. 一次性把设备查出来(N -> 1) + QueryWrapper query = new QueryWrapper<>(); + query.in("id", batchDto.getDeviceIds()); + List devices = deviceMapper.selectList(query); + if (devices.size() != batchDto.getDeviceIds().size()) { + throw new ServiceException("部分设备不存在"); + } + + // 2. 图片只转换一次(160*80 固定尺寸) + byte[] largeData; + try { + largeData = ImageToCArrayConverter.convertImageToCArray( + batchDto.getFile().getInputStream(), 160, 80, 25600); + } catch (IOException e) { + throw new ServiceException("图片解析失败"); + } + int[] picArray = convertHexToDecimal(largeData); + + // 3. 过滤离线设备 & 组装指令 + List onlineDevices = devices.stream() + .filter(d -> !isDeviceOffline(d.getDeviceImei())) + .toList(); + onlineDevices.forEach(d -> { + String redisKey = GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + d.getDeviceImei() + DEVICE_BOOT_LOGO_KEY_PREFIX; + + // 如果 Redis 里已存在,直接跳过 + if (RedisUtils.getCacheObject(redisKey) != null) { + return; // 跳过本次循环 + } + + RedisUtils.setCacheObject( + redisKey, + Arrays.toString(picArray), + Duration.ofSeconds(5 * 60L)); + // 3.2 MQTT 下发 + Map payload = + Collections.singletonMap("ins_PicTrans", Collections.singletonList(0)); + String topic = MqttConstants.GLOBAL_PUB_KEY + d.getDeviceImei(); + String json = JsonUtils.toJsonString(payload); + + try { + mqttGateway.sendMsgToMqtt(topic, 1, json); + } catch (Exception e) { + log.error("上传开机画面失败, topic={}, payload={}", topic, json, e); + throw new ServiceException("上传LOGO失败:" + e.getMessage()); + } + + recordDeviceLog(d.getId(), d.getDeviceName(), "上传开机画面", "上传开机画面", AppLoginHelper.getUserId()); + + }); + } + /** * 人员登记 * @param bo diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/util/FileHashUtil.java b/fys-admin/src/main/java/com/fuyuanshen/web/util/FileHashUtil.java new file mode 100644 index 00000000..f81c9169 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/web/util/FileHashUtil.java @@ -0,0 +1,28 @@ +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()); + } +} diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/bo/AppOperationVideoBo.java b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/bo/AppOperationVideoBo.java index 7abd0f68..d15765fe 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/bo/AppOperationVideoBo.java +++ b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/bo/AppOperationVideoBo.java @@ -45,5 +45,10 @@ public class AppOperationVideoBo extends BaseEntity { */ private String remark; + /** + * 多设备id + */ + private Long[] deviceIds; + } diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/vo/AppFileVo.java b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/vo/AppFileVo.java index ab8839f0..fdad1f1a 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/vo/AppFileVo.java +++ b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/vo/AppFileVo.java @@ -19,6 +19,10 @@ public class AppFileVo { * 文件名称 */ private String fileName; + /** + * 文件类型(1:操作说明,2:产品参数) + */ + private Long fileType; /** * 文件url */ diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/IAppBusinessFileService.java b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/IAppBusinessFileService.java index c99eb10f..a3676723 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/IAppBusinessFileService.java +++ b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/IAppBusinessFileService.java @@ -1,5 +1,6 @@ package com.fuyuanshen.app.service; +import com.fuyuanshen.app.domain.AppBusinessFile; import com.fuyuanshen.app.domain.vo.AppBusinessFileVo; import com.fuyuanshen.app.domain.bo.AppBusinessFileBo; import com.fuyuanshen.app.domain.vo.AppFileVo; @@ -50,6 +51,14 @@ public interface IAppBusinessFileService { */ Boolean insertByBo(AppBusinessFileBo bo); + /** + * 批量新增app业务文件 + * + * @param bo 批量新增app业务文件 + * @return 是否新增成功 + */ + Boolean insertBatch(Collection bo); + /** * 修改app业务文件 * diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/IAppOperationVideoService.java b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/IAppOperationVideoService.java index 54fcf444..ed38f869 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/IAppOperationVideoService.java +++ b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/IAppOperationVideoService.java @@ -1,5 +1,7 @@ package com.fuyuanshen.app.service; +import com.fuyuanshen.app.domain.AppBusinessFile; +import com.fuyuanshen.app.domain.AppOperationVideo; import com.fuyuanshen.app.domain.vo.AppOperationVideoVo; import com.fuyuanshen.app.domain.bo.AppOperationVideoBo; import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; @@ -49,6 +51,14 @@ public interface IAppOperationVideoService { */ Boolean insertByBo(AppOperationVideoBo bo); + /** + * 批量新增操作视频 + * + * @param bo 批量新增操作视频 + * @return 是否新增成功 + */ + Boolean insertBatch(Collection bo); + /** * 修改操作视频 * diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/impl/AppBusinessFileServiceImpl.java b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/impl/AppBusinessFileServiceImpl.java index 4b643b4c..73d96a11 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/impl/AppBusinessFileServiceImpl.java +++ b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/impl/AppBusinessFileServiceImpl.java @@ -1,5 +1,6 @@ package com.fuyuanshen.app.service.impl; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.fuyuanshen.app.domain.vo.AppFileVo; import com.fuyuanshen.common.core.utils.MapstructUtils; import com.fuyuanshen.common.core.utils.StringUtils; @@ -100,6 +101,20 @@ public class AppBusinessFileServiceImpl implements IAppBusinessFileService { return flag; } + @Override + public Boolean insertBatch(Collection bo) { + // 1. 去重后的 businessId 集合 + List businessIds = bo.stream() + .map(AppBusinessFile::getBusinessId) + .distinct() + .toList(); + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.in("business_id", businessIds); + queryWrapper.eq("file_type", bo.stream().findFirst().orElseThrow().getFileType()); + baseMapper.delete(queryWrapper); + return baseMapper.insertBatch(bo); + } + /** * 修改app业务文件 * diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/impl/AppOperationVideoServiceImpl.java b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/impl/AppOperationVideoServiceImpl.java index 8141548a..32f8781e 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/impl/AppOperationVideoServiceImpl.java +++ b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/impl/AppOperationVideoServiceImpl.java @@ -1,5 +1,7 @@ package com.fuyuanshen.app.service.impl; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.fuyuanshen.app.domain.AppBusinessFile; import com.fuyuanshen.common.core.utils.MapstructUtils; import com.fuyuanshen.common.core.utils.StringUtils; import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; @@ -97,6 +99,19 @@ public class AppOperationVideoServiceImpl implements IAppOperationVideoService { return flag; } + @Override + public Boolean insertBatch(Collection bo) { + // 1. 去重后的 businessId 集合 + List deviceIds = bo.stream() + .map(AppOperationVideo::getDeviceId) + .distinct() + .toList(); + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.in("device_id", deviceIds); + baseMapper.delete(queryWrapper); + return baseMapper.insertBatch(bo); + } + /** * 修改操作视频 * diff --git a/fys-modules/fys-app/src/main/resources/mapper/app/AppBusinessFileMapper.xml b/fys-modules/fys-app/src/main/resources/mapper/app/AppBusinessFileMapper.xml index 71cb53d4..2b0a9a93 100644 --- a/fys-modules/fys-app/src/main/resources/mapper/app/AppBusinessFileMapper.xml +++ b/fys-modules/fys-app/src/main/resources/mapper/app/AppBusinessFileMapper.xml @@ -5,7 +5,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + select oss_id,file_name,original_name,file_suffix,url,service,file_hash from sys_oss WHERE file_hash = #{fileHash} LIMIT 1 + + + + UPDATE sys_oss SET file_hash = #{fileHash} WHERE oss_id = #{ossId} + From f1aad91421319f6421bd00ddd3bffd498c85cdda Mon Sep 17 00:00:00 2001 From: chenyouting <514333061@qq.com> Date: Thu, 11 Sep 2025 14:37:40 +0800 Subject: [PATCH 047/160] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=BB=91=E5=AE=9A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/fuyuanshen/web/service/device/DeviceBizService.java | 5 +++++ .../java/com/fuyuanshen/app/domain/AppDeviceBindRecord.java | 1 + .../src/main/resources/mapper/equipment/DeviceMapper.xml | 4 ++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBizService.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBizService.java index 21cf14eb..fc355b59 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBizService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBizService.java @@ -181,12 +181,14 @@ public class DeviceBizService { QueryWrapper bindRecordQueryWrapper = new QueryWrapper<>(); bindRecordQueryWrapper.eq("device_id", device.getId()); + bindRecordQueryWrapper.eq("communication_mode", 0); AppDeviceBindRecord appDeviceBindRecord = appDeviceBindRecordMapper.selectOne(bindRecordQueryWrapper); if (appDeviceBindRecord != null) { UpdateWrapper deviceUpdateWrapper = new UpdateWrapper<>(); deviceUpdateWrapper.eq("device_id", device.getId()) .set("binding_status", BindingStatusEnum.BOUND.getCode()) .set("binding_user_id", userId) + .set("communication_mode", 0) .set("update_time", new Date()) .set("binding_time", new Date()); return appDeviceBindRecordMapper.update(null, deviceUpdateWrapper); @@ -195,6 +197,7 @@ public class DeviceBizService { bindRecord.setDeviceId(device.getId()); bindRecord.setBindingUserId(userId); bindRecord.setBindingTime(new Date()); + bindRecord.setCommunicationMode(0); bindRecord.setCreateBy(userId); appDeviceBindRecordMapper.insert(bindRecord); } @@ -224,6 +227,7 @@ public class DeviceBizService { deviceUpdateWrapper.eq("device_id", device.getId()) .eq("binding_user_id", userId) .set("binding_user_id", userId) + .set("communication_mode", 1) .set("binding_time", new Date()); return appDeviceBindRecordMapper.update(null, deviceUpdateWrapper); } else { @@ -231,6 +235,7 @@ public class DeviceBizService { bindRecord.setDeviceId(device.getId()); bindRecord.setBindingUserId(userId); bindRecord.setBindingTime(new Date()); + bindRecord.setCommunicationMode(1); bindRecord.setCreateBy(userId); appDeviceBindRecordMapper.insert(bindRecord); } diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/AppDeviceBindRecord.java b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/AppDeviceBindRecord.java index d6019213..0bad2abf 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/AppDeviceBindRecord.java +++ b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/AppDeviceBindRecord.java @@ -50,5 +50,6 @@ public class AppDeviceBindRecord extends TenantEntity { */ private Date bindingTime; + private Integer communicationMode; } diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml index 81c66744..ef6b6d19 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml @@ -254,9 +254,9 @@ c.binding_time from device d inner join device_type dt on d.device_type = dt.id - inner join app_device_bind_record c on d.id = c.device_id + inner join app_device_bind_record c on d.id = c.device_id and c.communication_mode = 0 left join app_personnel_info ap on ap.device_id = d.id - where dt.communication_mode = 0 + where dt.communication_mode in (0, 2) and d.device_type = #{criteria.deviceType} From 1722f92328d24995b33b31748917d33244ff7554 Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Fri, 12 Sep 2025 09:04:47 +0800 Subject: [PATCH 048/160] =?UTF-8?q?=E7=94=B5=E5=AD=90=E5=9B=B4=E6=A0=8F-?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fence/DeviceFenceAccessRecordController.java | 7 ++++--- .../device/fence/DeviceGeoFenceController.java | 9 +++++---- .../equipment/domain/DeviceFenceAccessRecord.java | 6 ------ .../equipment/domain/DeviceGeoFence.java | 15 --------------- .../domain/bo/DeviceFenceAccessRecordBo.java | 2 +- .../equipment/domain/bo/DeviceGeoFenceBo.java | 9 ++------- .../equipment/domain/query/FenceCheckRequest.java | 3 ++- .../domain/vo/DeviceFenceAccessRecordVo.java | 2 +- .../equipment/domain/vo/DeviceGeoFenceVo.java | 6 +++--- .../impl/DeviceFenceAccessRecordServiceImpl.java | 2 +- .../service/impl/DeviceGeoFenceServiceImpl.java | 10 +++++++--- .../equipment/service/impl/DeviceServiceImpl.java | 3 ++- 12 files changed, 28 insertions(+), 46 deletions(-) diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/fence/DeviceFenceAccessRecordController.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/fence/DeviceFenceAccessRecordController.java index 97e7aa97..fc2e2c66 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/fence/DeviceFenceAccessRecordController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/fence/DeviceFenceAccessRecordController.java @@ -32,11 +32,12 @@ import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; @Validated @RequiredArgsConstructor @RestController -@RequestMapping("/fys-equipment/fenceAccessRecord") +@RequestMapping("/api/equipment/fenceAccessRecord") public class DeviceFenceAccessRecordController extends BaseController { private final IDeviceFenceAccessRecordService deviceFenceAccessRecordService; + /** * 查询围栏进出记录列表 */ @@ -47,10 +48,10 @@ public class DeviceFenceAccessRecordController extends BaseController { } /** - * 围栏进出记录列表 + * 导出围栏进出记录列表 */ @SaCheckPermission("fys-equipment:fenceAccessRecord:export") - @Log(title = "围栏进出记录", businessType = BusinessType.EXPORT) + @Log(title = "导出围栏进出记录列表", businessType = BusinessType.EXPORT) @PostMapping("/export") public void export(DeviceFenceAccessRecordBo bo, HttpServletResponse response) { List list = deviceFenceAccessRecordService.queryList(bo); diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/fence/DeviceGeoFenceController.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/fence/DeviceGeoFenceController.java index cae25e3f..1e70c64c 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/fence/DeviceGeoFenceController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/fence/DeviceGeoFenceController.java @@ -35,11 +35,12 @@ import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; @Validated @RequiredArgsConstructor @RestController -@RequestMapping("/fys-equipment/geoFence") +@RequestMapping("/api/equipment/geoFence") public class DeviceGeoFenceController extends BaseController { private final IDeviceGeoFenceService deviceGeoFenceService; + /** * 查询电子围栏列表 */ @@ -50,10 +51,10 @@ public class DeviceGeoFenceController extends BaseController { } /** - * 电子围栏列表 + * 导出电子围栏列表 */ @SaCheckPermission("fys-equipment:geoFence:export") - @Log(title = "电子围栏", businessType = BusinessType.EXPORT) + @Log(title = "导出电子围栏列表", businessType = BusinessType.EXPORT) @PostMapping("/export") public void export(DeviceGeoFenceBo bo, HttpServletResponse response) { List list = deviceGeoFenceService.queryList(bo); @@ -109,7 +110,7 @@ public class DeviceGeoFenceController extends BaseController { /** - * 位置检查 + * 电子围栏-位置检查 * * @param request * @return diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceFenceAccessRecord.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceFenceAccessRecord.java index 8b50674c..9652c302 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceFenceAccessRecord.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceFenceAccessRecord.java @@ -69,10 +69,4 @@ public class DeviceFenceAccessRecord extends BaseEntity { */ private Date eventTime; - /** - * 记录创建时间 - */ - private Date createdTime; - - } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceGeoFence.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceGeoFence.java index 37285038..7cade915 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceGeoFence.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceGeoFence.java @@ -59,20 +59,5 @@ public class DeviceGeoFence extends BaseEntity { */ private Long isActive; - /** - * 创建人 - */ - private Long createdBy; - - /** - * 创建时间 - */ - private Date createdTime; - - /** - * 更新时间 - */ - private Date updatedTime; - } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceFenceAccessRecordBo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceFenceAccessRecordBo.java index 60d00f4e..84b70f40 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceFenceAccessRecordBo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceFenceAccessRecordBo.java @@ -77,7 +77,7 @@ public class DeviceFenceAccessRecordBo extends BaseEntity { /** * 记录创建时间 */ - private Date createdTime; + private Date createTime; } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceGeoFenceBo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceGeoFenceBo.java index d13217d9..a2506385 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceGeoFenceBo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceGeoFenceBo.java @@ -61,20 +61,15 @@ public class DeviceGeoFenceBo extends BaseEntity { */ private Long isActive; - /** - * 创建人 - */ - private Long createdBy; - /** * 创建时间 */ - private Date createdTime; + private Date createTime; /** * 更新时间 */ - private Date updatedTime; + private Date updateTime; } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/FenceCheckRequest.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/FenceCheckRequest.java index de556516..00704953 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/FenceCheckRequest.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/FenceCheckRequest.java @@ -35,5 +35,6 @@ public class FenceCheckRequest { /** * 定位精度(米) */ - private Double accuracy; + private Long accuracy; + } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceFenceAccessRecordVo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceFenceAccessRecordVo.java index a91b3950..8e4ab2b3 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceFenceAccessRecordVo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceFenceAccessRecordVo.java @@ -88,7 +88,7 @@ public class DeviceFenceAccessRecordVo implements Serializable { * 记录创建时间 */ @ExcelProperty(value = "记录创建时间") - private Date createdTime; + private Date createTime; } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceGeoFenceVo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceGeoFenceVo.java index 50fbc372..119dd8d7 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceGeoFenceVo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceGeoFenceVo.java @@ -76,19 +76,19 @@ public class DeviceGeoFenceVo implements Serializable { * 创建人 */ @ExcelProperty(value = "创建人") - private Long createdBy; + private Long createBy; /** * 创建时间 */ @ExcelProperty(value = "创建时间") - private Date createdTime; + private Date createTime; /** * 更新时间 */ @ExcelProperty(value = "更新时间") - private Date updatedTime; + private Date updateTime; } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceFenceAccessRecordServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceFenceAccessRecordServiceImpl.java index a09f8386..51df807a 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceFenceAccessRecordServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceFenceAccessRecordServiceImpl.java @@ -82,7 +82,7 @@ public class DeviceFenceAccessRecordServiceImpl implements IDeviceFenceAccessRec lqw.eq(bo.getLongitude() != null, DeviceFenceAccessRecord::getLongitude, bo.getLongitude()); lqw.eq(bo.getAccuracy() != null, DeviceFenceAccessRecord::getAccuracy, bo.getAccuracy()); lqw.eq(bo.getEventTime() != null, DeviceFenceAccessRecord::getEventTime, bo.getEventTime()); - lqw.eq(bo.getCreatedTime() != null, DeviceFenceAccessRecord::getCreatedTime, bo.getCreatedTime()); + lqw.eq(bo.getCreateTime() != null, DeviceFenceAccessRecord::getCreateTime, bo.getCreateTime()); return lqw; } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceGeoFenceServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceGeoFenceServiceImpl.java index c5fbb2df..242cf77b 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceGeoFenceServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceGeoFenceServiceImpl.java @@ -21,6 +21,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Collection; @@ -85,9 +86,9 @@ public class DeviceGeoFenceServiceImpl implements IDeviceGeoFenceService { lqw.eq(StringUtils.isNotBlank(bo.getCoordinates()), DeviceGeoFence::getCoordinates, bo.getCoordinates()); lqw.eq(bo.getRadius() != null, DeviceGeoFence::getRadius, bo.getRadius()); lqw.eq(bo.getIsActive() != null, DeviceGeoFence::getIsActive, bo.getIsActive()); - lqw.eq(bo.getCreatedBy() != null, DeviceGeoFence::getCreatedBy, bo.getCreatedBy()); - lqw.eq(bo.getCreatedTime() != null, DeviceGeoFence::getCreatedTime, bo.getCreatedTime()); - lqw.eq(bo.getUpdatedTime() != null, DeviceGeoFence::getUpdatedTime, bo.getUpdatedTime()); + lqw.eq(bo.getCreateBy() != null, DeviceGeoFence::getCreateBy, bo.getCreateBy()); + lqw.eq(bo.getCreateTime() != null, DeviceGeoFence::getCreateTime, bo.getCreateTime()); + lqw.eq(bo.getUpdateTime() != null, DeviceGeoFence::getUpdateTime, bo.getUpdateTime()); return lqw; } @@ -181,13 +182,16 @@ public class DeviceGeoFenceServiceImpl implements IDeviceGeoFenceService { } FenceCheckResponse.FenceInfo fenceInfo = new FenceCheckResponse.FenceInfo(); + List list = new ArrayList<>(); boolean pointInFence = GeoFenceChecker.isPointInFence(request.getLatitude(), request.getLongitude(), fence.getAreaType(), coordinateList, fence.getRadius()); if (pointInFence) { fenceInfo.setFenceId(fence.getId()); fenceInfo.setFenceName(fence.getName()); fenceInfo.setFenceType(fence.getAreaType()); + response.setEnteredFences(list); response.getEnteredFences().add(fenceInfo); } else { + response.setExitedFences(list); response.getExitedFences().add(fenceInfo); } } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java index 5646e991..48f09d86 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java @@ -238,7 +238,8 @@ public class DeviceServiceImpl extends ServiceImpl impleme DeviceTypeQueryCriteria deviceTypeQueryCriteria = new DeviceTypeQueryCriteria(); deviceTypeQueryCriteria.setDeviceTypeId(deviceAssignments.getDeviceId()); - deviceTypeQueryCriteria.setCustomerId(LoginHelper.getUserId()); + // 被授权的客户 + // deviceTypeQueryCriteria.setCustomerId(LoginHelper.getUserId()); List deviceTypes = deviceTypeMapper.findAll(deviceTypeQueryCriteria); if (deviceTypes.isEmpty()) { throw new Exception("设备类型不存在!!!"); From 01a1a6e25bf3d61451cca36ef7526bffd057d7db Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Fri, 12 Sep 2025 11:48:39 +0800 Subject: [PATCH 049/160] =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E5=9B=B4=E6=A0=8F?= =?UTF-8?q?=E8=BF=9B=E5=87=BA=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/vo/DeviceFenceAccessRecordVo.java | 16 ++++++++-- .../mapper/DeviceFenceAccessRecordMapper.java | 12 ++++++++ .../DeviceFenceAccessRecordServiceImpl.java | 4 +-- .../DeviceFenceAccessRecordMapper.xml | 29 +++++++++++++++++-- 4 files changed, 55 insertions(+), 6 deletions(-) diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceFenceAccessRecordVo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceFenceAccessRecordVo.java index 8e4ab2b3..b0f69026 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceFenceAccessRecordVo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceFenceAccessRecordVo.java @@ -1,6 +1,7 @@ package com.fuyuanshen.equipment.domain.vo; import java.util.Date; + import com.fasterxml.jackson.annotation.JsonFormat; import cn.idev.excel.annotation.ExcelIgnoreUnannotated; import cn.idev.excel.annotation.ExcelProperty; @@ -15,7 +16,6 @@ import java.io.Serializable; import java.util.Date; - /** * 围栏进出记录视图对象 device_fence_access_record * @@ -42,11 +42,23 @@ public class DeviceFenceAccessRecordVo implements Serializable { @ExcelProperty(value = "围栏ID") private Long fenceId; + /** + * 围栏名称 + */ + @ExcelProperty(value = "围栏名称") + private String fenceName; + /** * 设备标识 */ @ExcelProperty(value = "设备标识") - private String deviceId; + private Long deviceId; + + /** + * 设备名称 + */ + @ExcelProperty(value = "设备名称") + private String deviceName; /** * 用户ID diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceFenceAccessRecordMapper.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceFenceAccessRecordMapper.java index f6f03f6d..8bdb2d57 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceFenceAccessRecordMapper.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceFenceAccessRecordMapper.java @@ -1,8 +1,12 @@ package com.fuyuanshen.equipment.mapper; +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.fuyuanshen.common.mybatis.core.mapper.BaseMapperPlus; import com.fuyuanshen.equipment.domain.DeviceFenceAccessRecord; import com.fuyuanshen.equipment.domain.vo.DeviceFenceAccessRecordVo; +import org.apache.ibatis.annotations.Param; /** * 围栏进出记录Mapper接口 @@ -12,4 +16,12 @@ import com.fuyuanshen.equipment.domain.vo.DeviceFenceAccessRecordVo; */ public interface DeviceFenceAccessRecordMapper extends BaseMapperPlus { + /** + * 分页查询围栏进出记录列表(包含围栏名称和设备名称) + * + * @param page 分页参数 + * @param wrapper 查询条件 + * @return 围栏进出记录分页列表 + */ + Page selectVoPageWithFenceAndDeviceName(Page page, @Param(Constants.WRAPPER) Wrapper wrapper); } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceFenceAccessRecordServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceFenceAccessRecordServiceImpl.java index 51df807a..4acc8ce2 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceFenceAccessRecordServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceFenceAccessRecordServiceImpl.java @@ -54,7 +54,7 @@ public class DeviceFenceAccessRecordServiceImpl implements IDeviceFenceAccessRec @Override public TableDataInfo queryPageList(DeviceFenceAccessRecordBo bo, PageQuery pageQuery) { LambdaQueryWrapper lqw = buildQueryWrapper(bo); - Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + Page result = baseMapper.selectVoPageWithFenceAndDeviceName(pageQuery.build(), lqw); return TableDataInfo.build(result); } @@ -73,7 +73,7 @@ public class DeviceFenceAccessRecordServiceImpl implements IDeviceFenceAccessRec private LambdaQueryWrapper buildQueryWrapper(DeviceFenceAccessRecordBo bo) { Map params = bo.getParams(); LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); - lqw.orderByAsc(DeviceFenceAccessRecord::getId); + // lqw.orderByAsc(DeviceFenceAccessRecord::getId); lqw.eq(bo.getFenceId() != null, DeviceFenceAccessRecord::getFenceId, bo.getFenceId()); lqw.eq(StringUtils.isNotBlank(bo.getDeviceId()), DeviceFenceAccessRecord::getDeviceId, bo.getDeviceId()); lqw.eq(bo.getUserId() != null, DeviceFenceAccessRecord::getUserId, bo.getUserId()); diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceFenceAccessRecordMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceFenceAccessRecordMapper.xml index 2667e1b3..819ba779 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceFenceAccessRecordMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceFenceAccessRecordMapper.xml @@ -1,7 +1,32 @@ + PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> + + + From 8597dc5a9fd85a3a8979dce24ea8c0fc3c466410 Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Fri, 12 Sep 2025 14:50:42 +0800 Subject: [PATCH 050/160] =?UTF-8?q?=E5=AF=BC=E5=87=BA=E5=9B=B4=E6=A0=8F?= =?UTF-8?q?=E8=BF=9B=E5=87=BA=E8=AE=B0=E5=BD=95=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/bo/DeviceFenceAccessRecordBo.java | 4 ++-- .../domain/vo/DeviceFenceAccessRecordVo.java | 11 ++++++----- .../mapper/DeviceFenceAccessRecordMapper.java | 6 ++++++ .../impl/DeviceFenceAccessRecordServiceImpl.java | 12 ++++++------ .../equipment/DeviceFenceAccessRecordMapper.xml | 2 +- 5 files changed, 21 insertions(+), 14 deletions(-) diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceFenceAccessRecordBo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceFenceAccessRecordBo.java index 84b70f40..295eac01 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceFenceAccessRecordBo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceFenceAccessRecordBo.java @@ -55,13 +55,13 @@ public class DeviceFenceAccessRecordBo extends BaseEntity { * 纬度 */ @NotNull(message = "纬度不能为空", groups = { AddGroup.class, EditGroup.class }) - private Long latitude; + private Double latitude; /** * 经度 */ @NotNull(message = "经度不能为空", groups = { AddGroup.class, EditGroup.class }) - private Long longitude; + private Double longitude; /** * 定位精度 diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceFenceAccessRecordVo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceFenceAccessRecordVo.java index b0f69026..309158d0 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceFenceAccessRecordVo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceFenceAccessRecordVo.java @@ -33,13 +33,13 @@ public class DeviceFenceAccessRecordVo implements Serializable { /** * 记录ID */ - @ExcelProperty(value = "记录ID") + // @ExcelProperty(value = "记录ID") private Long id; /** * 围栏ID */ - @ExcelProperty(value = "围栏ID") + // @ExcelProperty(value = "围栏ID") private Long fenceId; /** @@ -51,7 +51,7 @@ public class DeviceFenceAccessRecordVo implements Serializable { /** * 设备标识 */ - @ExcelProperty(value = "设备标识") + // @ExcelProperty(value = "设备标识") private Long deviceId; /** @@ -63,13 +63,14 @@ public class DeviceFenceAccessRecordVo implements Serializable { /** * 用户ID */ - @ExcelProperty(value = "用户ID") + // @ExcelProperty(value = "用户ID") private Long userId; /** * 事件类型 */ - @ExcelProperty(value = "事件类型") + @ExcelProperty(value = "事件类型", converter = ExcelDictConvert.class) + @ExcelDictFormat(readConverterExp = "1=进入围栏,2=离开围栏") private Long eventType; /** diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceFenceAccessRecordMapper.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceFenceAccessRecordMapper.java index 8bdb2d57..5fc944ee 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceFenceAccessRecordMapper.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceFenceAccessRecordMapper.java @@ -8,6 +8,8 @@ import com.fuyuanshen.equipment.domain.DeviceFenceAccessRecord; import com.fuyuanshen.equipment.domain.vo.DeviceFenceAccessRecordVo; import org.apache.ibatis.annotations.Param; +import java.util.List; + /** * 围栏进出记录Mapper接口 * @@ -24,4 +26,8 @@ public interface DeviceFenceAccessRecordMapper extends BaseMapperPlus selectVoPageWithFenceAndDeviceName(Page page, @Param(Constants.WRAPPER) Wrapper wrapper); + + List selectVoPageWithFenceAndDeviceName(@Param(Constants.WRAPPER) Wrapper wrapper); + + } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceFenceAccessRecordServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceFenceAccessRecordServiceImpl.java index 4acc8ce2..2c273fa9 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceFenceAccessRecordServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceFenceAccessRecordServiceImpl.java @@ -40,7 +40,7 @@ public class DeviceFenceAccessRecordServiceImpl implements IDeviceFenceAccessRec * @return 围栏进出记录 */ @Override - public DeviceFenceAccessRecordVo queryById(Long id){ + public DeviceFenceAccessRecordVo queryById(Long id) { return baseMapper.selectVoById(id); } @@ -67,7 +67,7 @@ public class DeviceFenceAccessRecordServiceImpl implements IDeviceFenceAccessRec @Override public List queryList(DeviceFenceAccessRecordBo bo) { LambdaQueryWrapper lqw = buildQueryWrapper(bo); - return baseMapper.selectVoList(lqw); + return baseMapper.selectVoPageWithFenceAndDeviceName(lqw); } private LambdaQueryWrapper buildQueryWrapper(DeviceFenceAccessRecordBo bo) { @@ -119,8 +119,8 @@ public class DeviceFenceAccessRecordServiceImpl implements IDeviceFenceAccessRec /** * 保存前的数据校验 */ - private void validEntityBeforeSave(DeviceFenceAccessRecord entity){ - //TODO 做一些数据校验,如唯一约束 + private void validEntityBeforeSave(DeviceFenceAccessRecord entity) { + // TODO 做一些数据校验,如唯一约束 } /** @@ -132,8 +132,8 @@ public class DeviceFenceAccessRecordServiceImpl implements IDeviceFenceAccessRec */ @Override public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { - if(isValid){ - //TODO 做一些业务上的校验,判断是否需要校验 + if (isValid) { + // TODO 做一些业务上的校验,判断是否需要校验 } return baseMapper.deleteByIds(ids) > 0; } diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceFenceAccessRecordMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceFenceAccessRecordMapper.xml index 819ba779..065b2e41 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceFenceAccessRecordMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceFenceAccessRecordMapper.xml @@ -22,7 +22,7 @@ r.create_time FROM device_fence_access_record r LEFT JOIN device_geo_fence f ON r.fence_id = f.id - LEFT JOIN device d ON r.device_id = d.device_id + LEFT JOIN device d ON r.device_id = d.id ${ew.customSqlSegment} From 41eb3d9a339f8cba935dc8a458180fafddd80f7d Mon Sep 17 00:00:00 2001 From: chenyouting <514333061@qq.com> Date: Sat, 13 Sep 2025 15:37:48 +0800 Subject: [PATCH 051/160] =?UTF-8?q?=E5=9C=A8=E7=BA=BF=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/mqtt/receiver/ReceiverMessageHandler.java | 6 +++--- .../com/fuyuanshen/global/queue/MqttMessageConsumer.java | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java index 2581db7c..979cd352 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java @@ -52,9 +52,9 @@ public class ReceiverMessageHandler implements MessageHandler { //在线状态 String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ deviceImei + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ; RedisUtils.setCacheObject(deviceOnlineStatusRedisKey, "1", Duration.ofSeconds(62)); -// String queueKey = MqttMessageQueueConstants.MQTT_MESSAGE_QUEUE_KEY; -// String dedupKey = MqttMessageQueueConstants.MQTT_MESSAGE_DEDUP_KEY; -// RedisUtils.offerDeduplicated(queueKey,dedupKey,deviceImei, Duration.ofHours(24)); + String queueKey = MqttMessageQueueConstants.MQTT_MESSAGE_QUEUE_KEY; + String dedupKey = MqttMessageQueueConstants.MQTT_MESSAGE_DEDUP_KEY; + RedisUtils.offerDeduplicated(queueKey,dedupKey,deviceImei, Duration.ofHours(24)); } String state = payloadDict.getStr("state"); diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/queue/MqttMessageConsumer.java b/fys-admin/src/main/java/com/fuyuanshen/global/queue/MqttMessageConsumer.java index 9172fa8e..2c364fe6 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/queue/MqttMessageConsumer.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/queue/MqttMessageConsumer.java @@ -27,7 +27,7 @@ public class MqttMessageConsumer { private ExecutorService messageProcessorPool = Executors.newFixedThreadPool(10); // 初始化方法,启动消息监听 -// @PostConstruct + @PostConstruct public void start() { log.info("启动MQTT消息消费者..."); // 启动消息获取线程 @@ -100,7 +100,7 @@ public class MqttMessageConsumer { .set("online_status", 1); deviceMapper.update(updateWrapper); // 模拟业务处理耗时 - Thread.sleep(200); +// Thread.sleep(200); log.info("业务处理线程 {} 完成消息处理: {}", threadName, message); } catch (Exception e) { From 64c81ac44eb8eea0732600fcb48ecf0d2cfe1c0a Mon Sep 17 00:00:00 2001 From: chenyouting <514333061@qq.com> Date: Sat, 13 Sep 2025 15:51:34 +0800 Subject: [PATCH 052/160] =?UTF-8?q?=E5=9C=A8=E7=BA=BF=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E4=BF=AE=E6=94=B92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../listener/RedisKeyExpirationListener.java | 15 +++++++++++---- .../global/queue/MqttMessageConsumer.java | 19 ++++++++++++------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/listener/RedisKeyExpirationListener.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/listener/RedisKeyExpirationListener.java index b3f64ecd..dbfcfe85 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/listener/RedisKeyExpirationListener.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/listener/RedisKeyExpirationListener.java @@ -4,6 +4,7 @@ import cn.hutool.core.thread.ThreadUtil; import com.baomidou.lock.LockInfo; import com.baomidou.lock.LockTemplate; import com.baomidou.lock.executor.RedissonLockExecutor; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.fuyuanshen.common.core.constant.GlobalConstants; import com.fuyuanshen.common.redis.utils.RedisUtils; @@ -69,10 +70,16 @@ public class RedisKeyExpirationListener implements MessageListener { if (lockInfo != null) { try { - UpdateWrapper deviceUpdateWrapper = new UpdateWrapper<>(); - deviceUpdateWrapper.eq("device_imei", element); - deviceUpdateWrapper.set("online_status", 0); - deviceMapper.update(deviceUpdateWrapper); + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("device_imei", element); + queryWrapper.eq("online_status", 0); + Long count = deviceMapper.selectCount(queryWrapper); + if(count == 0){ + UpdateWrapper deviceUpdateWrapper = new UpdateWrapper<>(); + deviceUpdateWrapper.eq("device_imei", element); + deviceUpdateWrapper.set("online_status", 0); + deviceMapper.update(deviceUpdateWrapper); + } } finally { //释放锁 lockTemplate.releaseLock(lockInfo); diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/queue/MqttMessageConsumer.java b/fys-admin/src/main/java/com/fuyuanshen/global/queue/MqttMessageConsumer.java index 2c364fe6..56084f83 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/queue/MqttMessageConsumer.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/queue/MqttMessageConsumer.java @@ -1,5 +1,6 @@ package com.fuyuanshen.global.queue; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.fuyuanshen.common.redis.utils.RedisUtils; import com.fuyuanshen.equipment.domain.Device; @@ -92,13 +93,17 @@ public class MqttMessageConsumer { String threadName = Thread.currentThread().getName(); try { log.info("业务处理线程 {} 开始处理消息: {}", threadName, message); - - // 实现具体的业务逻辑 - // 例如更新数据库、发送通知等 - UpdateWrapper updateWrapper = new UpdateWrapper<>(); - updateWrapper.eq("device_imei", message) - .set("online_status", 1); - deviceMapper.update(updateWrapper); + + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("device_imei", message); + queryWrapper.eq("online_status", 1); + Long count = deviceMapper.selectCount(queryWrapper); + if(count == 0){ + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("device_imei", message) + .set("online_status", 1); + deviceMapper.update(updateWrapper); + } // 模拟业务处理耗时 // Thread.sleep(200); From 39c8375f08c3b32b64d72b123af14211c5469c44 Mon Sep 17 00:00:00 2001 From: chenyouting <514333061@qq.com> Date: Sat, 13 Sep 2025 16:46:05 +0800 Subject: [PATCH 053/160] =?UTF-8?q?=E5=9C=A8=E7=BA=BF=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../listener/RedisKeyExpirationListener.java | 12 ++--------- .../mqtt/receiver/ReceiverMessageHandler.java | 6 +++--- .../global/queue/MqttMessageConsumer.java | 21 +++++++++++++------ 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/listener/RedisKeyExpirationListener.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/listener/RedisKeyExpirationListener.java index dbfcfe85..55071cf5 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/listener/RedisKeyExpirationListener.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/listener/RedisKeyExpirationListener.java @@ -49,14 +49,6 @@ public class RedisKeyExpirationListener implements MessageListener { handleFunctionAccessExpired(element); } if(expiredKey.endsWith(DEVICE_ONLINE_STATUS_KEY_PREFIX)) { -// threadPoolTaskExecutor.execute(() -> { -// log.info("设备离线:{}", expiredKey); -// String element = expiredKey.substring(GlobalConstants.GLOBAL_REDIS_KEY.length() + DEVICE_KEY_PREFIX.length(), expiredKey.length() - DEVICE_ONLINE_STATUS_KEY_PREFIX.length()); -// UpdateWrapper deviceUpdateWrapper = new UpdateWrapper<>(); -// deviceUpdateWrapper.eq("device_imei", element); -// deviceUpdateWrapper.set("online_status", 0); -// deviceMapper.update(deviceUpdateWrapper); -// }); threadPoolTaskExecutor.execute(() -> { log.info("设备离线:{}", expiredKey); @@ -72,9 +64,9 @@ public class RedisKeyExpirationListener implements MessageListener { try { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("device_imei", element); - queryWrapper.eq("online_status", 0); + queryWrapper.eq("online_status", 1); Long count = deviceMapper.selectCount(queryWrapper); - if(count == 0){ + if(count > 0){ UpdateWrapper deviceUpdateWrapper = new UpdateWrapper<>(); deviceUpdateWrapper.eq("device_imei", element); deviceUpdateWrapper.set("online_status", 0); diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java index 979cd352..4e4b2fc2 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java @@ -49,12 +49,12 @@ public class ReceiverMessageHandler implements MessageHandler { String[] subStr = receivedTopic.split("/"); String deviceImei = subStr[1]; if(StringUtils.isNotBlank(deviceImei)){ - //在线状态 - String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ deviceImei + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ; - RedisUtils.setCacheObject(deviceOnlineStatusRedisKey, "1", Duration.ofSeconds(62)); String queueKey = MqttMessageQueueConstants.MQTT_MESSAGE_QUEUE_KEY; String dedupKey = MqttMessageQueueConstants.MQTT_MESSAGE_DEDUP_KEY; RedisUtils.offerDeduplicated(queueKey,dedupKey,deviceImei, Duration.ofHours(24)); + //在线状态 + String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ deviceImei + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ; + RedisUtils.setCacheObject(deviceOnlineStatusRedisKey, "1", Duration.ofSeconds(62)); } String state = payloadDict.getStr("state"); diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/queue/MqttMessageConsumer.java b/fys-admin/src/main/java/com/fuyuanshen/global/queue/MqttMessageConsumer.java index 56084f83..1d30483d 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/queue/MqttMessageConsumer.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/queue/MqttMessageConsumer.java @@ -2,9 +2,12 @@ package com.fuyuanshen.global.queue; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.fuyuanshen.common.core.constant.GlobalConstants; +import com.fuyuanshen.common.core.utils.StringUtils; import com.fuyuanshen.common.redis.utils.RedisUtils; import com.fuyuanshen.equipment.domain.Device; import com.fuyuanshen.equipment.mapper.DeviceMapper; +import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants; import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; import lombok.extern.slf4j.Slf4j; @@ -16,6 +19,8 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX; + @Service @Slf4j public class MqttMessageConsumer { @@ -93,17 +98,21 @@ public class MqttMessageConsumer { String threadName = Thread.currentThread().getName(); try { log.info("业务处理线程 {} 开始处理消息: {}", threadName, message); - - QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("device_imei", message); - queryWrapper.eq("online_status", 1); - Long count = deviceMapper.selectCount(queryWrapper); - if(count == 0){ + String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ message + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ; + String deviceOnlineStatusRedis = RedisUtils.getCacheObject(deviceOnlineStatusRedisKey); + if(StringUtils.isBlank(deviceOnlineStatusRedis)){ UpdateWrapper updateWrapper = new UpdateWrapper<>(); updateWrapper.eq("device_imei", message) .set("online_status", 1); deviceMapper.update(updateWrapper); } +// QueryWrapper queryWrapper = new QueryWrapper<>(); +// queryWrapper.eq("device_imei", message); +// queryWrapper.eq("online_status", 1); +// Long count = deviceMapper.selectCount(queryWrapper); +// if(count == 0){ +// +// } // 模拟业务处理耗时 // Thread.sleep(200); From 4004aa1090b56756f6910699f09780b04af1ad14 Mon Sep 17 00:00:00 2001 From: chenyouting <514333061@qq.com> Date: Sat, 13 Sep 2025 16:51:56 +0800 Subject: [PATCH 054/160] =?UTF-8?q?=E5=9C=A8=E7=BA=BF=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BC=98=E5=8C=962?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/fuyuanshen/global/queue/MqttMessageConsumer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/queue/MqttMessageConsumer.java b/fys-admin/src/main/java/com/fuyuanshen/global/queue/MqttMessageConsumer.java index 1d30483d..998695e2 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/queue/MqttMessageConsumer.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/queue/MqttMessageConsumer.java @@ -68,7 +68,7 @@ public class MqttMessageConsumer { public void consumeMessages() { String queueKey = MqttMessageQueueConstants.MQTT_MESSAGE_QUEUE_KEY; String threadName = Thread.currentThread().getName(); - log.info("消息消费者线程 {} 开始监听队列: {}", threadName, queueKey); +// log.info("消息消费者线程 {} 开始监听队列: {}", threadName, queueKey); try { while (!Thread.currentThread().isInterrupted() && !messageConsumerPool.isShutdown()) { @@ -81,7 +81,7 @@ public class MqttMessageConsumer { ); if (message != null) { - log.info("线程 {} 从队列中获取到消息,提交到处理线程池: {}", threadName, message); +// log.info("线程 {} 从队列中获取到消息,提交到处理线程池: {}", threadName, message); // 将消息处理任务提交到处理线程池 messageProcessorPool.submit(() -> processMessage(message)); } From 9fbb0aefcf4f5104f6b1994e87586f9fb464d49a Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Mon, 15 Sep 2025 09:41:10 +0800 Subject: [PATCH 055/160] =?UTF-8?q?=E6=A3=80=E6=9F=A5=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE=E4=B8=8E=E5=9B=B4=E6=A0=8F=E7=9A=84=E5=85=B3?= =?UTF-8?q?=E7=B3=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DeviceFenceStatusController.java | 105 ++++++++++++ .../equipment/domain/DeviceFenceStatus.java | 53 ++++++ .../domain/bo/DeviceFenceStatusBo.java | 56 +++++++ .../domain/vo/DeviceFenceStatusVo.java | 64 ++++++++ .../mapper/DeviceFenceStatusMapper.java | 15 ++ .../service/IDeviceFenceStatusService.java | 81 ++++++++++ .../impl/DeviceFenceStatusServiceImpl.java | 152 ++++++++++++++++++ .../impl/DeviceGeoFenceServiceImpl.java | 97 ++++++++--- .../equipment/DeviceFenceStatusMapper.xml | 7 + 9 files changed, 605 insertions(+), 25 deletions(-) create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceFenceStatusController.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceFenceStatus.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceFenceStatusBo.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceFenceStatusVo.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceFenceStatusMapper.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/IDeviceFenceStatusService.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceFenceStatusServiceImpl.java create mode 100644 fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceFenceStatusMapper.xml diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceFenceStatusController.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceFenceStatusController.java new file mode 100644 index 00000000..1deee76e --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceFenceStatusController.java @@ -0,0 +1,105 @@ +package com.fuyuanshen.equipment.controller; + +import java.util.List; + +import lombok.RequiredArgsConstructor; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.*; +import cn.dev33.satoken.annotation.SaCheckPermission; +import org.springframework.web.bind.annotation.*; +import org.springframework.validation.annotation.Validated; +import com.fuyuanshen.common.idempotent.annotation.RepeatSubmit; +import com.fuyuanshen.common.log.annotation.Log; +import com.fuyuanshen.common.web.core.BaseController; +import com.fuyuanshen.common.mybatis.core.page.PageQuery; +import com.fuyuanshen.common.core.domain.R; +import com.fuyuanshen.common.core.validate.AddGroup; +import com.fuyuanshen.common.core.validate.EditGroup; +import com.fuyuanshen.common.log.enums.BusinessType; +import com.fuyuanshen.common.excel.utils.ExcelUtil; +import com.fuyuanshen.equipment.domain.vo.DeviceFenceStatusVo; +import com.fuyuanshen.equipment.domain.bo.DeviceFenceStatusBo; +import com.fuyuanshen.equipment.service.IDeviceFenceStatusService; +import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; + +/** + * 设备进入围栏状态 + * + * @author Lion Li + * @date 2025-09-15 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/equipment/fenceStatus") +public class DeviceFenceStatusController extends BaseController { + + private final IDeviceFenceStatusService deviceFenceStatusService; + + /** + * 查询设备进入围栏状态列表 + */ + @SaCheckPermission("equipment:fenceStatus:list") + @GetMapping("/list") + public TableDataInfo list(DeviceFenceStatusBo bo, PageQuery pageQuery) { + return deviceFenceStatusService.queryPageList(bo, pageQuery); + } + + /** + * 导出设备进入围栏状态列表 + */ + @SaCheckPermission("equipment:fenceStatus:export") + @Log(title = "设备进入围栏状态", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(DeviceFenceStatusBo bo, HttpServletResponse response) { + List list = deviceFenceStatusService.queryList(bo); + ExcelUtil.exportExcel(list, "设备进入围栏状态", DeviceFenceStatusVo.class, response); + } + + /** + * 获取设备进入围栏状态详细信息 + * + * @param id 主键 + */ + @SaCheckPermission("equipment:fenceStatus:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(deviceFenceStatusService.queryById(id)); + } + + /** + * 新增设备进入围栏状态 + */ + @SaCheckPermission("equipment:fenceStatus:add") + @Log(title = "设备进入围栏状态", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody DeviceFenceStatusBo bo) { + return toAjax(deviceFenceStatusService.insertByBo(bo)); + } + + /** + * 修改设备进入围栏状态 + */ + @SaCheckPermission("equipment:fenceStatus:edit") + @Log(title = "设备进入围栏状态", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody DeviceFenceStatusBo bo) { + return toAjax(deviceFenceStatusService.updateByBo(bo)); + } + + /** + * 删除设备进入围栏状态 + * + * @param ids 主键串 + */ + @SaCheckPermission("equipment:fenceStatus:remove") + @Log(title = "设备进入围栏状态", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(deviceFenceStatusService.deleteWithValidByIds(List.of(ids), true)); + } +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceFenceStatus.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceFenceStatus.java new file mode 100644 index 00000000..8e9e480e --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceFenceStatus.java @@ -0,0 +1,53 @@ +package com.fuyuanshen.equipment.domain; + +import com.fuyuanshen.common.tenant.core.TenantEntity; +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; + +import java.io.Serial; + +/** + * 设备进入围栏状态对象 device_fence_status + * + * @author Lion Li + * @date 2025-09-15 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("device_fence_status") +public class DeviceFenceStatus extends TenantEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * + */ + @TableId(value = "id") + private Long id; + + /** + * 设备ID + */ + private String deviceId; + + /** + * 围栏ID + */ + private Long fenceId; + + /** + * 状态: 0-在围栏外, 1-在围栏内 + */ + private Long status; + + /** + * 上次检查时间 + */ + private Date lastCheckTime; + + +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceFenceStatusBo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceFenceStatusBo.java new file mode 100644 index 00000000..8efb75bb --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceFenceStatusBo.java @@ -0,0 +1,56 @@ +package com.fuyuanshen.equipment.domain.bo; + +import com.fuyuanshen.common.core.validate.AddGroup; +import com.fuyuanshen.common.core.validate.EditGroup; +import com.fuyuanshen.equipment.domain.DeviceFenceStatus; +import com.fuyuanshen.common.mybatis.core.domain.BaseEntity; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import lombok.EqualsAndHashCode; +import jakarta.validation.constraints.*; +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 设备进入围栏状态业务对象 device_fence_status + * + * @author Lion Li + * @date 2025-09-15 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = DeviceFenceStatus.class, reverseConvertGenerate = false) +public class DeviceFenceStatusBo extends BaseEntity { + + /** + * + */ + @NotNull(message = "不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 设备ID + */ + @NotBlank(message = "设备ID不能为空", groups = { AddGroup.class, EditGroup.class }) + private String deviceId; + + /** + * 围栏ID + */ + @NotNull(message = "围栏ID不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long fenceId; + + /** + * 状态: 0-在围栏外, 1-在围栏内 + */ + @NotNull(message = "状态: 0-在围栏外, 1-在围栏内不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long status; + + /** + * 上次检查时间 + */ + @NotNull(message = "上次检查时间不能为空", groups = { AddGroup.class, EditGroup.class }) + private Date lastCheckTime; + + +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceFenceStatusVo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceFenceStatusVo.java new file mode 100644 index 00000000..63c52e2e --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceFenceStatusVo.java @@ -0,0 +1,64 @@ +package com.fuyuanshen.equipment.domain.vo; + +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fuyuanshen.equipment.domain.DeviceFenceStatus; +import cn.idev.excel.annotation.ExcelIgnoreUnannotated; +import cn.idev.excel.annotation.ExcelProperty; +import com.fuyuanshen.common.excel.annotation.ExcelDictFormat; +import com.fuyuanshen.common.excel.convert.ExcelDictConvert; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + + + +/** + * 设备进入围栏状态视图对象 device_fence_status + * + * @author Lion Li + * @date 2025-09-15 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = DeviceFenceStatus.class) +public class DeviceFenceStatusVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * + */ + @ExcelProperty(value = "") + private Long id; + + /** + * 设备ID + */ + @ExcelProperty(value = "设备ID") + private String deviceId; + + /** + * 围栏ID + */ + @ExcelProperty(value = "围栏ID") + private Long fenceId; + + /** + * 状态: 0-在围栏外, 1-在围栏内 + */ + @ExcelProperty(value = "状态: 0-在围栏外, 1-在围栏内") + private Long status; + + /** + * 上次检查时间 + */ + @ExcelProperty(value = "上次检查时间") + private Date lastCheckTime; + + +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceFenceStatusMapper.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceFenceStatusMapper.java new file mode 100644 index 00000000..54139e3d --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceFenceStatusMapper.java @@ -0,0 +1,15 @@ +package com.fuyuanshen.equipment.mapper; + +import com.fuyuanshen.equipment.domain.DeviceFenceStatus; +import com.fuyuanshen.equipment.domain.vo.DeviceFenceStatusVo; +import com.fuyuanshen.common.mybatis.core.mapper.BaseMapperPlus; + +/** + * 设备进入围栏状态Mapper接口 + * + * @author Lion Li + * @date 2025-09-15 + */ +public interface DeviceFenceStatusMapper extends BaseMapperPlus { + +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/IDeviceFenceStatusService.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/IDeviceFenceStatusService.java new file mode 100644 index 00000000..1722fba4 --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/IDeviceFenceStatusService.java @@ -0,0 +1,81 @@ +package com.fuyuanshen.equipment.service; + +import com.fuyuanshen.equipment.domain.vo.DeviceFenceStatusVo; +import com.fuyuanshen.equipment.domain.bo.DeviceFenceStatusBo; +import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; +import com.fuyuanshen.common.mybatis.core.page.PageQuery; + +import java.util.Collection; +import java.util.List; + +/** + * 设备进入围栏状态Service接口 + * + * @author Lion Li + * @date 2025-09-15 + */ +public interface IDeviceFenceStatusService { + + /** + * 查询设备进入围栏状态 + * + * @param id 主键 + * @return 设备进入围栏状态 + */ + DeviceFenceStatusVo queryById(Long id); + + /** + * 分页查询设备进入围栏状态列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 设备进入围栏状态分页列表 + */ + TableDataInfo queryPageList(DeviceFenceStatusBo bo, PageQuery pageQuery); + + /** + * 查询符合条件的设备进入围栏状态列表 + * + * @param bo 查询条件 + * @return 设备进入围栏状态列表 + */ + List queryList(DeviceFenceStatusBo bo); + + /** + * 新增设备进入围栏状态 + * + * @param bo 设备进入围栏状态 + * @return 是否新增成功 + */ + Boolean insertByBo(DeviceFenceStatusBo bo); + + /** + * 修改设备进入围栏状态 + * + * @param bo 设备进入围栏状态 + * @return 是否修改成功 + */ + Boolean updateByBo(DeviceFenceStatusBo bo); + + /** + * 校验并批量删除设备进入围栏状态信息 + * + * @param ids 待删除的主键集合 + * @param isValid 是否进行有效性校验 + * @return 是否删除成功 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + + /** + * 查询设备在特定围栏的最新状态 + * + * @param deviceId 设备ID + * @param fenceId 围栏ID + * @return 最新状态记录,如果不存在则返回null + */ + DeviceFenceStatusVo getLatestStatusByDeviceAndFence(String deviceId, Long fenceId); + + + +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceFenceStatusServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceFenceStatusServiceImpl.java new file mode 100644 index 00000000..f00e715b --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceFenceStatusServiceImpl.java @@ -0,0 +1,152 @@ +package com.fuyuanshen.equipment.service.impl; + +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 lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import com.fuyuanshen.equipment.domain.bo.DeviceFenceStatusBo; +import com.fuyuanshen.equipment.domain.vo.DeviceFenceStatusVo; +import com.fuyuanshen.equipment.domain.DeviceFenceStatus; +import com.fuyuanshen.equipment.mapper.DeviceFenceStatusMapper; +import com.fuyuanshen.equipment.service.IDeviceFenceStatusService; + +import java.util.List; +import java.util.Map; +import java.util.Collection; + +/** + * 设备进入围栏状态Service业务层处理 + * + * @author Lion Li + * @date 2025-09-15 + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class DeviceFenceStatusServiceImpl implements IDeviceFenceStatusService { + + private final DeviceFenceStatusMapper baseMapper; + + /** + * 查询设备进入围栏状态 + * + * @param id 主键 + * @return 设备进入围栏状态 + */ + @Override + public DeviceFenceStatusVo queryById(Long id){ + return baseMapper.selectVoById(id); + } + + /** + * 分页查询设备进入围栏状态列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 设备进入围栏状态分页列表 + */ + @Override + public TableDataInfo queryPageList(DeviceFenceStatusBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询符合条件的设备进入围栏状态列表 + * + * @param bo 查询条件 + * @return 设备进入围栏状态列表 + */ + @Override + public List queryList(DeviceFenceStatusBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(DeviceFenceStatusBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.orderByAsc(DeviceFenceStatus::getId); + lqw.eq(StringUtils.isNotBlank(bo.getDeviceId()), DeviceFenceStatus::getDeviceId, bo.getDeviceId()); + lqw.eq(bo.getFenceId() != null, DeviceFenceStatus::getFenceId, bo.getFenceId()); + lqw.eq(bo.getStatus() != null, DeviceFenceStatus::getStatus, bo.getStatus()); + lqw.eq(bo.getLastCheckTime() != null, DeviceFenceStatus::getLastCheckTime, bo.getLastCheckTime()); + return lqw; + } + + /** + * 新增设备进入围栏状态 + * + * @param bo 设备进入围栏状态 + * @return 是否新增成功 + */ + @Override + public Boolean insertByBo(DeviceFenceStatusBo bo) { + DeviceFenceStatus add = MapstructUtils.convert(bo, DeviceFenceStatus.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改设备进入围栏状态 + * + * @param bo 设备进入围栏状态 + * @return 是否修改成功 + */ + @Override + public Boolean updateByBo(DeviceFenceStatusBo bo) { + DeviceFenceStatus update = MapstructUtils.convert(bo, DeviceFenceStatus.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(DeviceFenceStatus entity){ + //TODO 做一些数据校验,如唯一约束 + } + + /** + * 校验并批量删除设备进入围栏状态信息 + * + * @param ids 待删除的主键集合 + * @param isValid 是否进行有效性校验 + * @return 是否删除成功 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if(isValid){ + //TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteByIds(ids) > 0; + } + + + /** + * 查询设备在特定围栏的最新状态 + */ + @Override + public DeviceFenceStatusVo getLatestStatusByDeviceAndFence(String deviceId, Long fenceId) { + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(DeviceFenceStatus::getDeviceId, deviceId); + lqw.eq(DeviceFenceStatus::getFenceId, fenceId); + lqw.orderByDesc(DeviceFenceStatus::getLastCheckTime); + lqw.last("LIMIT 1"); + DeviceFenceStatus status = baseMapper.selectOne(lqw); + return MapstructUtils.convert(status, DeviceFenceStatusVo.class); + } + + +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceGeoFenceServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceGeoFenceServiceImpl.java index 242cf77b..3957a90d 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceGeoFenceServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceGeoFenceServiceImpl.java @@ -9,11 +9,14 @@ 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.equipment.domain.DeviceGeoFence; +import com.fuyuanshen.equipment.domain.bo.DeviceFenceStatusBo; import com.fuyuanshen.equipment.domain.bo.DeviceGeoFenceBo; import com.fuyuanshen.equipment.domain.dto.FenceCheckResponse; import com.fuyuanshen.equipment.domain.query.FenceCheckRequest; +import com.fuyuanshen.equipment.domain.vo.DeviceFenceStatusVo; import com.fuyuanshen.equipment.domain.vo.DeviceGeoFenceVo; import com.fuyuanshen.equipment.mapper.DeviceGeoFenceMapper; +import com.fuyuanshen.equipment.service.IDeviceFenceStatusService; import com.fuyuanshen.equipment.service.IDeviceGeoFenceService; import com.fuyuanshen.equipment.utils.map.GeoFenceChecker; import lombok.RequiredArgsConstructor; @@ -21,10 +24,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Collection; +import java.util.*; /** * 电子围栏Service业务层处理 @@ -39,6 +39,10 @@ public class DeviceGeoFenceServiceImpl implements IDeviceGeoFenceService { private final DeviceGeoFenceMapper baseMapper; + private final IDeviceFenceStatusService fenceStatusService; // 添加此行 + + + /** * 查询电子围栏 * @@ -158,41 +162,84 @@ public class DeviceGeoFenceServiceImpl implements IDeviceGeoFenceService { bo.setIsActive(1L); // 假设1表示激活状态 List activeFences = queryList(bo); - // 2. 判断设备位置与各围栏的关系 + // 2. 初始化响应对象 FenceCheckResponse response = new FenceCheckResponse(); response.setDeviceId(request.getDeviceId()); response.setCheckTime(System.currentTimeMillis()); + response.setEnteredFences(new ArrayList<>()); + response.setExitedFences(new ArrayList<>()); + response.setCurrentFences(new ArrayList<>()); - // 这里需要实现具体的围栏判断逻辑 - // 根据您之前提供的算法实现点在围栏内的判断 - + // 3. 遍历所有激活的围栏 for (DeviceGeoFenceVo fence : activeFences) { - String coordinates = fence.getCoordinates(); - - // 在需要转换的地方 + // 解析围栏坐标 ObjectMapper objectMapper = new ObjectMapper(); - List coordinateList = List.of(); + List coordinateList = new ArrayList<>(); try { - coordinateList = objectMapper.readValue(coordinates, + coordinateList = objectMapper.readValue(fence.getCoordinates(), new TypeReference>() { }); } catch (Exception e) { - // 处理解析异常 log.error("坐标数据解析失败: {}", e.getMessage()); + continue; // 解析失败则跳过该围栏 } + // 检查设备是否在围栏内 + boolean pointInFence = GeoFenceChecker.isPointInFence( + request.getLatitude(), + request.getLongitude(), + fence.getAreaType(), + coordinateList, + fence.getRadius()); + + // 创建围栏信息对象 FenceCheckResponse.FenceInfo fenceInfo = new FenceCheckResponse.FenceInfo(); - List list = new ArrayList<>(); - boolean pointInFence = GeoFenceChecker.isPointInFence(request.getLatitude(), request.getLongitude(), fence.getAreaType(), coordinateList, fence.getRadius()); - if (pointInFence) { - fenceInfo.setFenceId(fence.getId()); - fenceInfo.setFenceName(fence.getName()); - fenceInfo.setFenceType(fence.getAreaType()); - response.setEnteredFences(list); - response.getEnteredFences().add(fenceInfo); - } else { - response.setExitedFences(list); - response.getExitedFences().add(fenceInfo); + fenceInfo.setFenceId(fence.getId()); + fenceInfo.setFenceName(fence.getName()); + fenceInfo.setFenceType(fence.getAreaType()); + + // // 查询设备在该围栏的历史状态 + // DeviceFenceStatusBo statusQuery = new DeviceFenceStatusBo(); + // statusQuery.setDeviceId(request.getDeviceId()); + // statusQuery.setFenceId(fence.getId()); + // List statusHistory = fenceStatusService.queryList(statusQuery); + // + // // 获取最新的状态记录 + // DeviceFenceStatusVo latestStatus = statusHistory.stream() + // .max(Comparator.comparing(DeviceFenceStatusVo::getLastCheckTime)) + // .orElse(null); + + // 查询设备在该围栏的最新状态 + DeviceFenceStatusVo latestStatus = fenceStatusService.getLatestStatusByDeviceAndFence( + request.getDeviceId(), fence.getId()); + + // 判断设备与围栏的关系变化 + Long previousStatus = latestStatus != null ? latestStatus.getStatus() : 0L; // 默认在围栏外 + Long currentStatus = pointInFence ? 1L : 0L; // 当前状态:1-在围栏内,0-在围栏外 + + // 如果状态发生变化,则记录 + if (!previousStatus.equals(currentStatus)) { + // 创建新的状态记录 + DeviceFenceStatusBo newStatus = new DeviceFenceStatusBo(); + newStatus.setDeviceId(request.getDeviceId()); + newStatus.setFenceId(fence.getId()); + newStatus.setStatus(currentStatus); + newStatus.setLastCheckTime(new Date()); + + // 保存状态记录 + fenceStatusService.insertByBo(newStatus); + + // 根据状态变化更新响应 + if (currentStatus == 1L) { + // 设备进入围栏 + response.getEnteredFences().add(fenceInfo); + } else { + // 设备离开围栏 + response.getExitedFences().add(fenceInfo); + } + } else if (currentStatus == 1L) { + // 状态未变,但设备仍在围栏内 + response.getCurrentFences().add(fenceInfo); } } diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceFenceStatusMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceFenceStatusMapper.xml new file mode 100644 index 00000000..f0db21f7 --- /dev/null +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceFenceStatusMapper.xml @@ -0,0 +1,7 @@ + + + + + From dca5a6c48d5798498e8f778369c4feee072f27a5 Mon Sep 17 00:00:00 2001 From: chenyouting <514333061@qq.com> Date: Mon, 15 Sep 2025 09:43:15 +0800 Subject: [PATCH 056/160] =?UTF-8?q?=E5=9C=A8=E7=BA=BF=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BC=98=E5=8C=963?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mqtt/listener/RedisKeyExpirationListener.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/listener/RedisKeyExpirationListener.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/listener/RedisKeyExpirationListener.java index 55071cf5..2a53c856 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/listener/RedisKeyExpirationListener.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/listener/RedisKeyExpirationListener.java @@ -7,9 +7,11 @@ import com.baomidou.lock.executor.RedissonLockExecutor; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.fuyuanshen.common.core.constant.GlobalConstants; +import com.fuyuanshen.common.core.utils.StringUtils; import com.fuyuanshen.common.redis.utils.RedisUtils; import com.fuyuanshen.equipment.domain.Device; import com.fuyuanshen.equipment.mapper.DeviceMapper; +import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants; import com.fuyuanshen.global.mqtt.listener.domain.FunctionAccessStatus; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -62,11 +64,9 @@ public class RedisKeyExpirationListener implements MessageListener { if (lockInfo != null) { try { - QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("device_imei", element); - queryWrapper.eq("online_status", 1); - Long count = deviceMapper.selectCount(queryWrapper); - if(count > 0){ + String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ message + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ; + String deviceOnlineStatusRedis = RedisUtils.getCacheObject(deviceOnlineStatusRedisKey); + if(StringUtils.isBlank(deviceOnlineStatusRedis)){ UpdateWrapper deviceUpdateWrapper = new UpdateWrapper<>(); deviceUpdateWrapper.eq("device_imei", element); deviceUpdateWrapper.set("online_status", 0); From b278a3087ec2c2ab2e90398bd8c20c74de2b4b96 Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Mon, 15 Sep 2025 14:59:08 +0800 Subject: [PATCH 057/160] =?UTF-8?q?=E6=8A=A5=E8=AD=A6=E4=BA=8B=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/fuyuanshen/equipment/domain/vo/DeviceAlarmVo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceAlarmVo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceAlarmVo.java index 4e9935d3..431c0460 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceAlarmVo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceAlarmVo.java @@ -45,7 +45,7 @@ public class DeviceAlarmVo implements Serializable { /** * 报警事项 - * 0-强制报警,1-撞击闯入,2-手动报警,3-电子围栏告警,4-强制告警 + * 0-强制报警,1-撞击闯入,2-自动报警,3-电子围栏告警 */ @ExcelProperty(value = "报警事项") private Integer deviceAction; From 07449e4a1e187e3d8eaa92b535fb81b12116880c Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Mon, 15 Sep 2025 16:49:38 +0800 Subject: [PATCH 058/160] =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E5=9B=B4=E6=A0=8F?= =?UTF-8?q?=E8=BF=9B=E5=87=BA=E8=AE=B0=E5=BD=95=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DeviceFenceAccessRecordController.java | 1 + .../fence/DeviceGeoFenceController.java | 1 + .../src/main/resources/application-dev.yml | 2 +- .../domain/DeviceFenceAccessRecord.java | 4 +-- .../domain/bo/DeviceFenceAccessRecordBo.java | 10 ++++++ .../DeviceFenceAccessRecordServiceImpl.java | 3 ++ .../impl/DeviceGeoFenceServiceImpl.java | 24 ++++++++++--- .../DeviceFenceAccessRecordMapper.xml | 35 +++++++++---------- 8 files changed, 54 insertions(+), 26 deletions(-) diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/fence/DeviceFenceAccessRecordController.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/fence/DeviceFenceAccessRecordController.java index fc2e2c66..644d5585 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/fence/DeviceFenceAccessRecordController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/fence/DeviceFenceAccessRecordController.java @@ -47,6 +47,7 @@ public class DeviceFenceAccessRecordController extends BaseController { return deviceFenceAccessRecordService.queryPageList(bo, pageQuery); } + /** * 导出围栏进出记录列表 */ diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/fence/DeviceGeoFenceController.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/fence/DeviceGeoFenceController.java index 1e70c64c..1092b203 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/fence/DeviceGeoFenceController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/fence/DeviceGeoFenceController.java @@ -50,6 +50,7 @@ public class DeviceGeoFenceController extends BaseController { return deviceGeoFenceService.queryPageList(bo, pageQuery); } + /** * 导出电子围栏列表 */ diff --git a/fys-admin/src/main/resources/application-dev.yml b/fys-admin/src/main/resources/application-dev.yml index 64650383..680aa370 100644 --- a/fys-admin/src/main/resources/application-dev.yml +++ b/fys-admin/src/main/resources/application-dev.yml @@ -301,7 +301,7 @@ file: mqtt: username: admin password: #YtvpSfCNG - url: tcp://47.120.79.150:2883 + url: tcp://47.120.79.150:3883 subClientId: fys_subClient subTopic: worker/location/# pubTopic: B/# diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceFenceAccessRecord.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceFenceAccessRecord.java index 9652c302..58ab238f 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceFenceAccessRecord.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceFenceAccessRecord.java @@ -52,12 +52,12 @@ public class DeviceFenceAccessRecord extends BaseEntity { /** * 纬度 */ - private Long latitude; + private Double latitude; /** * 经度 */ - private Long longitude; + private Double longitude; /** * 定位精度 diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceFenceAccessRecordBo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceFenceAccessRecordBo.java index 295eac01..2300d839 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceFenceAccessRecordBo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceFenceAccessRecordBo.java @@ -74,6 +74,16 @@ public class DeviceFenceAccessRecordBo extends BaseEntity { @NotNull(message = "事件时间不能为空", groups = { AddGroup.class, EditGroup.class }) private Date eventTime; + /** + * 开始时间 + */ + private String beginTime; + + /** + * 结束时间 + */ + private String endTime; + /** * 记录创建时间 */ diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceFenceAccessRecordServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceFenceAccessRecordServiceImpl.java index 2c273fa9..88002377 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceFenceAccessRecordServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceFenceAccessRecordServiceImpl.java @@ -58,6 +58,7 @@ public class DeviceFenceAccessRecordServiceImpl implements IDeviceFenceAccessRec return TableDataInfo.build(result); } + /** * 查询符合条件的围栏进出记录列表 * @@ -70,6 +71,7 @@ public class DeviceFenceAccessRecordServiceImpl implements IDeviceFenceAccessRec return baseMapper.selectVoPageWithFenceAndDeviceName(lqw); } + private LambdaQueryWrapper buildQueryWrapper(DeviceFenceAccessRecordBo bo) { Map params = bo.getParams(); LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); @@ -86,6 +88,7 @@ public class DeviceFenceAccessRecordServiceImpl implements IDeviceFenceAccessRec return lqw; } + /** * 新增围栏进出记录 * diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceGeoFenceServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceGeoFenceServiceImpl.java index 3957a90d..2595b0ad 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceGeoFenceServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceGeoFenceServiceImpl.java @@ -9,6 +9,7 @@ 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.equipment.domain.DeviceGeoFence; +import com.fuyuanshen.equipment.domain.bo.DeviceFenceAccessRecordBo; import com.fuyuanshen.equipment.domain.bo.DeviceFenceStatusBo; import com.fuyuanshen.equipment.domain.bo.DeviceGeoFenceBo; import com.fuyuanshen.equipment.domain.dto.FenceCheckResponse; @@ -16,6 +17,7 @@ import com.fuyuanshen.equipment.domain.query.FenceCheckRequest; import com.fuyuanshen.equipment.domain.vo.DeviceFenceStatusVo; import com.fuyuanshen.equipment.domain.vo.DeviceGeoFenceVo; import com.fuyuanshen.equipment.mapper.DeviceGeoFenceMapper; +import com.fuyuanshen.equipment.service.IDeviceFenceAccessRecordService; import com.fuyuanshen.equipment.service.IDeviceFenceStatusService; import com.fuyuanshen.equipment.service.IDeviceGeoFenceService; import com.fuyuanshen.equipment.utils.map.GeoFenceChecker; @@ -40,6 +42,8 @@ public class DeviceGeoFenceServiceImpl implements IDeviceGeoFenceService { private final DeviceGeoFenceMapper baseMapper; private final IDeviceFenceStatusService fenceStatusService; // 添加此行 + + private final IDeviceFenceAccessRecordService fenceAccessRecordService; // 添加此行 @@ -96,6 +100,7 @@ public class DeviceGeoFenceServiceImpl implements IDeviceGeoFenceService { return lqw; } + /** * 新增电子围栏 * @@ -213,9 +218,9 @@ public class DeviceGeoFenceServiceImpl implements IDeviceGeoFenceService { DeviceFenceStatusVo latestStatus = fenceStatusService.getLatestStatusByDeviceAndFence( request.getDeviceId(), fence.getId()); - // 判断设备与围栏的关系变化 - Long previousStatus = latestStatus != null ? latestStatus.getStatus() : 0L; // 默认在围栏外 - Long currentStatus = pointInFence ? 1L : 0L; // 当前状态:1-在围栏内,0-在围栏外 + // 判断设备与围栏的关系变化 1=进入围栏,2=离开围栏 + Long previousStatus = latestStatus != null ? latestStatus.getStatus() : 2L; // 默认在围栏外 + Long currentStatus = pointInFence ? 1L : 2L; // 当前状态:1-在围栏内,2-在围栏外 // 如果状态发生变化,则记录 if (!previousStatus.equals(currentStatus)) { @@ -229,6 +234,17 @@ public class DeviceGeoFenceServiceImpl implements IDeviceGeoFenceService { // 保存状态记录 fenceStatusService.insertByBo(newStatus); + // 添加围栏进出记录 + DeviceFenceAccessRecordBo recordBo = new DeviceFenceAccessRecordBo(); + recordBo.setDeviceId(request.getDeviceId()); + recordBo.setFenceId(fence.getId()); + recordBo.setLatitude(request.getLatitude()); + recordBo.setLongitude(request.getLongitude()); + recordBo.setEventTime(new Date()); + // 1表示进入围栏,2表示离开围栏 + recordBo.setEventType(currentStatus); + fenceAccessRecordService.insertByBo(recordBo); + // 根据状态变化更新响应 if (currentStatus == 1L) { // 设备进入围栏 @@ -247,4 +263,4 @@ public class DeviceGeoFenceServiceImpl implements IDeviceGeoFenceService { } -} +} \ No newline at end of file diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceFenceAccessRecordMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceFenceAccessRecordMapper.xml index 065b2e41..b0541143 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceFenceAccessRecordMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceFenceAccessRecordMapper.xml @@ -4,29 +4,26 @@ "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> - + - + \ No newline at end of file From 4106260e5f1b9018fe1558538426277adf673054 Mon Sep 17 00:00:00 2001 From: chenyouting <514333061@qq.com> Date: Mon, 15 Sep 2025 17:49:28 +0800 Subject: [PATCH 059/160] =?UTF-8?q?=E5=9C=A8=E7=BA=BF=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BC=98=E5=8C=964?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/fuyuanshen/web/service/device/DeviceBizService.java | 2 +- .../src/main/resources/mapper/equipment/DeviceMapper.xml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBizService.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBizService.java index fc355b59..ec1c182b 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBizService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBizService.java @@ -126,7 +126,7 @@ public class DeviceBizService { List records = result.getRecords(); if(records != null && !records.isEmpty()){ records.forEach(item -> { - if(item.getCommunicationMode()!=null && item.getCommunicationMode() == 0){ + if(item.getCommunicationMode()!=null && (item.getCommunicationMode() == 0 || item.getCommunicationMode() == 2)){ //设备在线状态 String onlineStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ item.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX); diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml index 74c45573..5f6123e4 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml @@ -251,6 +251,7 @@ dt.pc_model_dictionary detailPageUrl, ap.name personnelBy, d.device_status, + d.online_status, c.binding_time from device d inner join device_type dt on d.device_type = dt.id From 23b48cfb069bc0e4b91da83f315e9fe1371aa4f3 Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Tue, 16 Sep 2025 17:04:45 +0800 Subject: [PATCH 060/160] =?UTF-8?q?=E8=AE=BE=E5=A4=87=E5=91=8A=E8=AD=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mqtt/config/MqttInboundConfiguration.java | 7 ++ .../global/mqtt/rule/bjq/BjqAlarmRule.java | 73 +++++++++++- .../mqtt/DeviceAlrmMessageHandler.java | 85 ++++++++++++++ .../src/main/resources/application-dev.yml | 2 +- .../common/core/utils/date/DurationUtils.java | 108 ++++++++++++++++++ .../equipment/domain/DeviceAlarm.java | 10 ++ .../equipment/domain/DeviceType.java | 4 +- .../equipment/domain/bo/DeviceAlarmBo.java | 6 +- .../equipment/domain/dto/AppDeviceBo.java | 2 +- .../equipment/domain/form/DeviceTypeForm.java | 8 +- .../domain/query/APPDeviceQueryCriteria1.java | 2 +- .../domain/query/DeviceQueryCriteria.java | 3 +- .../equipment/domain/vo/AppDeviceVo.java | 2 +- .../equipment/domain/vo/DeviceAlarmVo.java | 2 + .../equipment/domain/vo/WebDeviceVo.java | 2 +- .../equipment/mapper/DeviceAlarmMapper.java | 9 ++ .../equipment/mapper/DeviceMapper.java | 10 +- .../equipment/service/DeviceService.java | 10 +- .../service/IDeviceAlarmService.java | 10 ++ .../service/impl/DeviceAlarmServiceImpl.java | 24 +++- .../service/impl/DeviceServiceImpl.java | 5 + .../mapper/equipment/DeviceAlarmMapper.xml | 18 ++- .../mapper/equipment/DeviceMapper.xml | 5 + 23 files changed, 384 insertions(+), 23 deletions(-) create mode 100644 fys-admin/src/main/java/com/fuyuanshen/web/handler/mqtt/DeviceAlrmMessageHandler.java create mode 100644 fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/date/DurationUtils.java diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/config/MqttInboundConfiguration.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/config/MqttInboundConfiguration.java index 102fe71e..a9f9311c 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/config/MqttInboundConfiguration.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/config/MqttInboundConfiguration.java @@ -3,6 +3,7 @@ package com.fuyuanshen.global.mqtt.config; import cn.hutool.core.lang.UUID; import com.fuyuanshen.global.mqtt.receiver.ReceiverMessageHandler; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -55,4 +56,10 @@ public class MqttInboundConfiguration { public MessageHandler messageHandler(){ return receiverMessageHandler; } + + // @Bean + // @ServiceActivator(inputChannel = "messageInboundChannel") // 确保通道名称正确 + // public MessageHandler deviceAlarmMessageHandler() { + // return new DeviceAlrmMessageHandler(); + // } } \ No newline at end of file diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqAlarmRule.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqAlarmRule.java index 759f15b9..39fea9dd 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqAlarmRule.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqAlarmRule.java @@ -1,8 +1,18 @@ package com.fuyuanshen.global.mqtt.rule.bjq; +import cn.hutool.core.bean.BeanUtil; +import com.alibaba.fastjson2.JSONObject; import com.fuyuanshen.common.core.constant.GlobalConstants; +import com.fuyuanshen.common.core.domain.model.LoginUser; import com.fuyuanshen.common.core.utils.StringUtils; +import com.fuyuanshen.common.core.utils.date.DurationUtils; import com.fuyuanshen.common.redis.utils.RedisUtils; +import com.fuyuanshen.common.satoken.utils.LoginHelper; +import com.fuyuanshen.equipment.domain.Device; +import com.fuyuanshen.equipment.domain.bo.DeviceAlarmBo; +import com.fuyuanshen.equipment.domain.vo.DeviceAlarmVo; +import com.fuyuanshen.equipment.service.DeviceService; +import com.fuyuanshen.equipment.service.IDeviceAlarmService; import com.fuyuanshen.global.mqtt.base.MqttMessageRule; import com.fuyuanshen.global.mqtt.base.MqttRuleContext; import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants; @@ -13,9 +23,11 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.time.Duration; +import java.util.Date; 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.*; /** @@ -26,6 +38,11 @@ import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.*; @Slf4j public class BjqAlarmRule implements MqttMessageRule { + + private final IDeviceAlarmService deviceAlarmService; + private final DeviceService deviceService; + + @Override public String getCommandType() { return LightingCommandTypeConstants.ALARM_MESSAGE; @@ -38,14 +55,21 @@ public class BjqAlarmRule implements MqttMessageRule { Object[] convertArr = context.getConvertArr(); String convertValue = convertArr[1].toString(); - if(StringUtils.isNotBlank(convertValue)){ + if (StringUtils.isNotBlank(convertValue)) { // 将设备状态信息存储到Redis中 - String deviceRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+DeviceRedisKeyConstants.DEVICE_KEY_PREFIX + context.getDeviceImei() + DEVICE_ALARM_KEY_PREFIX; + String deviceRedisKey = GlobalConstants.GLOBAL_REDIS_KEY + DeviceRedisKeyConstants.DEVICE_KEY_PREFIX + context.getDeviceImei() + DEVICE_ALARM_KEY_PREFIX; // 存储到Redis RedisUtils.setCacheObject(deviceRedisKey, convertValue); } RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.OK.getCode(), Duration.ofSeconds(20)); + + // 保存告警信息 + String deviceImei = context.getDeviceImei(); + // 设备告警状态 0:解除告警 1:报警产生 + Byte state = (Byte) convertArr[1]; + savaAlarm(deviceImei, state); + } catch (Exception e) { log.error("处理告警命令时出错", e); RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.FAILED.getCode(), Duration.ofSeconds(20)); @@ -53,4 +77,49 @@ public class BjqAlarmRule implements MqttMessageRule { } + public void savaAlarm(String deviceImei, Byte state) { + DeviceAlarmVo deviceAlarmVo = deviceAlarmService.queryLatestByDeviceImei(deviceImei); + DeviceAlarmBo deviceAlarmBo = new DeviceAlarmBo(); + + // 解除告警 + if (state == 0) { + if (deviceAlarmVo != null) { + if (deviceAlarmVo.getFinishTime() == null) { + BeanUtil.copyProperties(deviceAlarmVo, deviceAlarmBo); + deviceAlarmBo.setFinishTime(new Date()); + String durationBetween = DurationUtils.getDurationBetween(deviceAlarmVo.getStartTime(), deviceAlarmBo.getFinishTime()); + deviceAlarmBo.setDurationTime(durationBetween); + deviceAlarmService.updateByBo(deviceAlarmBo); + } + } + } + + // 报警产生 + if (state == 1) { + if (deviceAlarmVo == null || deviceAlarmVo.getFinishTime() != null) { + + Device device = deviceService.selectDeviceByImei(deviceImei); + deviceAlarmBo.setDeviceId(device.getId()); + deviceAlarmBo.setDeviceImei(deviceImei); + // 0-强制报警,1-撞击闯入,2-自动报警,3-电子围栏告警 + deviceAlarmBo.setDeviceAction(0); + deviceAlarmBo.setStartTime(new Date()); + // 0已处理,1未处理 + deviceAlarmBo.setTreatmentState(1); + + // LoginUser loginUser = LoginHelper.getLoginUser(); + // deviceAlarmBo.setCreateBy(loginUser.getUserId()); + deviceAlarmBo.setTenantId(device.getTenantId()); + + String location = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + deviceImei + DEVICE_LOCATION_KEY_PREFIX); + if (StringUtils.isNotBlank(location)) { + JSONObject jsonObject = JSONObject.parseObject(location); + deviceAlarmBo.setLocation(jsonObject.getString("address")); + } + deviceAlarmService.insertByBo(deviceAlarmBo); + } + } + + } + } diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/handler/mqtt/DeviceAlrmMessageHandler.java b/fys-admin/src/main/java/com/fuyuanshen/web/handler/mqtt/DeviceAlrmMessageHandler.java new file mode 100644 index 00000000..6d7717cc --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/web/handler/mqtt/DeviceAlrmMessageHandler.java @@ -0,0 +1,85 @@ +// package com.fuyuanshen.web.handler.mqtt; +// +// import cn.hutool.core.lang.Dict; +// import com.fuyuanshen.common.core.constant.GlobalConstants; +// import com.fuyuanshen.common.json.utils.JsonUtils; +// import com.fuyuanshen.common.redis.utils.RedisUtils; +// import com.fuyuanshen.global.mqtt.base.MqttRuleContext; +// import com.fuyuanshen.global.mqtt.base.MqttRuleEngine; +// import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants; +// import lombok.extern.slf4j.Slf4j; +// import org.apache.commons.lang3.StringUtils; +// import org.springframework.beans.factory.annotation.Autowired; +// import org.springframework.messaging.Message; +// import org.springframework.messaging.MessageHandler; +// import org.springframework.messaging.MessageHeaders; +// import org.springframework.messaging.MessagingException; +// import org.springframework.stereotype.Service; +// +// import java.time.Duration; +// import java.util.Objects; +// +// import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX; +// +// /** +// * @author: 默苍璃 +// * @date: 2025-09-1609:28 +// */ +// @Service +// @Slf4j +// public class DeviceAlrmMessageHandler implements MessageHandler { +// +// @Autowired +// private MqttRuleEngine ruleEngine; +// +// @Override +// public void handleMessage(Message message) throws MessagingException { +// +// // 处理新类型的消息 +// Object payload = message.getPayload(); +// MessageHeaders headers = message.getHeaders(); +// String receivedTopic = Objects.requireNonNull(headers.get("mqtt_receivedTopic")).toString(); +// +// log.info("设备强制报警消息处理器 - MQTT payload= {} \n receivedTopic = {}", payload, receivedTopic); +// +// // 解析消息并执行相应逻辑 +// Dict payloadDict = JsonUtils.parseMap(payload.toString()); +// if (payloadDict != null) { +// // 根据主题或消息内容执行不同的处理逻辑 +// processMessage(receivedTopic, payloadDict); +// } +// } +// +// private void processMessage(String topic, Dict payloadDict) { +// // 实现具体的业务逻辑 +// // 可以根据不同的主题执行不同的操作 +// if (topic.contains("newTopic")) { +// // 处理特定主题的消息 +// handleNewTopicMessage(payloadDict); +// } +// } +// +// private void handleNewTopicMessage(Dict payloadDict) { +// // 实现新主题消息的具体处理逻辑 +// String deviceImei = payloadDict.getStr("imei"); +// if (StringUtils.isNotBlank(deviceImei)) { +// // 更新设备状态到Redis +// String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY + +// DEVICE_KEY_PREFIX + deviceImei + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX; +// RedisUtils.setCacheObject(deviceOnlineStatusRedisKey, "1", Duration.ofSeconds(62)); +// +// // 执行规则引擎 +// MqttRuleContext context = new MqttRuleContext(); +// context.setDeviceImei(deviceImei); +// context.setPayloadDict(payloadDict); +// // 设置命令类型 +// context.setCommandType((byte) 0x02); // 根据实际需要设置命令类型 +// +// boolean ruleExecuted = ruleEngine.executeRule(context); +// if (!ruleExecuted) { +// log.warn("未找到匹配的规则来处理新主题消息,设备IMEI: {}", deviceImei); +// } +// } +// } +// } +// diff --git a/fys-admin/src/main/resources/application-dev.yml b/fys-admin/src/main/resources/application-dev.yml index 680aa370..902d6f93 100644 --- a/fys-admin/src/main/resources/application-dev.yml +++ b/fys-admin/src/main/resources/application-dev.yml @@ -303,6 +303,6 @@ mqtt: password: #YtvpSfCNG url: tcp://47.120.79.150:3883 subClientId: fys_subClient - subTopic: worker/location/# + subTopic: A/# pubTopic: B/# pubClientId: fys_pubClient \ No newline at end of file diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/date/DurationUtils.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/date/DurationUtils.java new file mode 100644 index 00000000..8efc3d8f --- /dev/null +++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/date/DurationUtils.java @@ -0,0 +1,108 @@ +package com.fuyuanshen.common.core.utils.date; + +import java.util.Date; +import java.util.concurrent.TimeUnit; + +/** + * 持续时间工具类 + * 提供计算两个日期之间差值的方法,并以时分秒格式返回 + * + * @author fys + */ +public class DurationUtils { + + /** + * 计算两个日期之间的差值,返回时分秒格式的字符串 + * 格式为 "X小时 Y分钟 Z秒" + * + * @param startDate 开始日期 + * @param endDate 结束日期 + * @return 时分秒格式的时间差字符串 + */ + public static String getDurationBetween(Date startDate, Date endDate) { + if (startDate == null || endDate == null) { + return "0小时 0分钟 0秒"; + } + + long diffInMillis = Math.abs(endDate.getTime() - startDate.getTime()); + long hours = TimeUnit.MILLISECONDS.toHours(diffInMillis); + long minutes = TimeUnit.MILLISECONDS.toMinutes(diffInMillis) % 60; + long seconds = TimeUnit.MILLISECONDS.toSeconds(diffInMillis) % 60; + + return String.format("%d小时 %d分钟 %d秒", hours, minutes, seconds); + } + + /** + * 计算两个日期之间的差值,返回时分秒格式的字符串 + * 仅显示非零单位 + * + * @param startDate 开始日期 + * @param endDate 结束日期 + * @return 时分秒格式的时间差字符串,仅显示非零单位 + */ + public static String getDurationBetweenPretty(Date startDate, Date endDate) { + if (startDate == null || endDate == null) { + return "0秒"; + } + + long diffInMillis = Math.abs(endDate.getTime() - startDate.getTime()); + long hours = TimeUnit.MILLISECONDS.toHours(diffInMillis); + long minutes = TimeUnit.MILLISECONDS.toMinutes(diffInMillis) % 60; + long seconds = TimeUnit.MILLISECONDS.toSeconds(diffInMillis) % 60; + + StringBuilder result = new StringBuilder(); + if (hours > 0) { + result.append(hours).append("小时 "); + } + if (minutes > 0) { + result.append(minutes).append("分钟 "); + } + if (seconds > 0 || result.length() == 0) { + result.append(seconds).append("秒"); + } + + return result.toString().trim(); + } + + /** + * 计算指定毫秒数的持续时间,返回时分秒格式的字符串 + * + * @param millis 毫秒数 + * @return 时分秒格式的持续时间字符串 + */ + public static String getDurationFromMillis(long millis) { + long absMillis = Math.abs(millis); + long hours = TimeUnit.MILLISECONDS.toHours(absMillis); + long minutes = TimeUnit.MILLISECONDS.toMinutes(absMillis) % 60; + long seconds = TimeUnit.MILLISECONDS.toSeconds(absMillis) % 60; + + return String.format("%d小时 %d分钟 %d秒", hours, minutes, seconds); + } + + /** + * 计算指定毫秒数的持续时间,返回时分秒格式的字符串 + * 仅显示非零单位 + * + * @param millis 毫秒数 + * @return 时分秒格式的持续时间字符串,仅显示非零单位 + */ + public static String getDurationFromMillisPretty(long millis) { + long absMillis = Math.abs(millis); + long hours = TimeUnit.MILLISECONDS.toHours(absMillis); + long minutes = TimeUnit.MILLISECONDS.toMinutes(absMillis) % 60; + long seconds = TimeUnit.MILLISECONDS.toSeconds(absMillis) % 60; + + StringBuilder result = new StringBuilder(); + if (hours > 0) { + result.append(hours).append("小时 "); + } + if (minutes > 0) { + result.append(minutes).append("分钟 "); + } + if (seconds > 0 || result.length() == 0) { + result.append(seconds).append("秒"); + } + + return result.toString().trim(); + } +} \ No newline at end of file diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceAlarm.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceAlarm.java index 720ee9bc..10f19e6c 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceAlarm.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceAlarm.java @@ -2,6 +2,8 @@ package com.fuyuanshen.equipment.domain; import com.fuyuanshen.common.tenant.core.TenantEntity; import com.baomidou.mybatisplus.annotation.*; +import io.github.linpeilie.annotations.AutoMapping; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; @@ -100,4 +102,12 @@ public class DeviceAlarm extends TenantEntity { private Long treatmentState; + /** + * 设备IMEI + * device_imei + */ + @Schema(title = "设备IMEI") + @AutoMapping(target = "deviceImei") + private String deviceImei; + } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceType.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceType.java index ef3e3691..bfef80b5 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceType.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceType.java @@ -59,8 +59,8 @@ public class DeviceType extends TenantEntity { @Schema(title = "联网方式", example = "0:无;1:4G;2:WIFI") private String networkWay; - @Schema(title = "通讯方式", example = "0:4G;1:蓝牙,2 4G&蓝牙") - private String communicationMode; + @Schema(title = "通讯方式", example = "通讯方式 0:4G;1:蓝牙,2 4G&蓝牙") + private Integer communicationMode; /** * 创建人名称 diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceAlarmBo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceAlarmBo.java index 0918f185..ab50363b 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceAlarmBo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceAlarmBo.java @@ -2,6 +2,7 @@ package com.fuyuanshen.equipment.domain.bo; import com.fuyuanshen.common.core.validate.EditGroup; import com.fuyuanshen.common.mybatis.core.domain.BaseEntity; +import com.fuyuanshen.common.tenant.core.TenantEntity; import com.fuyuanshen.equipment.domain.DeviceAlarm; import io.github.linpeilie.annotations.AutoMapper; import jakarta.validation.constraints.NotNull; @@ -19,7 +20,7 @@ import java.util.Date; @Data @EqualsAndHashCode(callSuper = true) @AutoMapper(target = DeviceAlarm.class, reverseConvertGenerate = false) -public class DeviceAlarmBo extends BaseEntity { +public class DeviceAlarmBo extends TenantEntity { /** * ID @@ -35,6 +36,7 @@ public class DeviceAlarmBo extends BaseEntity { /** * 报警事项 * device_action + * 0-强制报警,1-撞击闯入,2-自动报警,3-电子围栏告警 */ private Integer deviceAction; @@ -99,7 +101,7 @@ public class DeviceAlarmBo extends BaseEntity { /** * 报警持续时间 */ - private Long durationTime; + private String durationTime; /** * 报警查询时间 diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/AppDeviceBo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/AppDeviceBo.java index 51156125..5f440df4 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/AppDeviceBo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/AppDeviceBo.java @@ -23,7 +23,7 @@ public class AppDeviceBo { private String deviceMac; /** - * 通讯方式 0:4G; 1:蓝牙 + * 通讯方式 0:4G;1:蓝牙,2 4G&蓝牙 */ @NotNull(message = "通讯方式不能为空", groups = { EditGroup.class }) private Integer communicationMode; diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/form/DeviceTypeForm.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/form/DeviceTypeForm.java index 93197f47..271611d9 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/form/DeviceTypeForm.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/form/DeviceTypeForm.java @@ -21,13 +21,13 @@ public class DeviceTypeForm { private Boolean isSupportBle; @Schema(title = "定位方式", example = "0:无;1:GPS;2:基站;3:wifi;4:北斗") - private String locateMode; + private Integer locateMode; @Schema(title = "联网方式", example = "0:无;1:4G;2:WIFI") - private String networkWay; + private Integer networkWay; - @Schema(title = "通讯方式", example = "0:4G;1:蓝牙") - private String communicationMode; + @Schema(title = "通讯方式", example = "通讯方式 0:4G;1:蓝牙,2 4G&蓝牙") + private Integer communicationMode; /** * 型号字典用于APP页面跳转 diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/APPDeviceQueryCriteria1.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/APPDeviceQueryCriteria1.java index a95085be..6e0b68c8 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/APPDeviceQueryCriteria1.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/APPDeviceQueryCriteria1.java @@ -59,7 +59,7 @@ public class APPDeviceQueryCriteria1 { @Schema(name = "租户ID") private Long tenantId; - @Schema(name = "通讯方式", example = "0:4G;1:蓝牙") + @Schema(name = "通讯方式", example = "通讯方式 0:4G;1:蓝牙,2 4G&蓝牙") private Integer communicationMode; } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/DeviceQueryCriteria.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/DeviceQueryCriteria.java index 1ec9137f..684bc86e 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/DeviceQueryCriteria.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/DeviceQueryCriteria.java @@ -82,9 +82,10 @@ public class DeviceQueryCriteria extends BaseEntity { private String tenantId; /** - * 通讯方式 0:4G;1:蓝牙 + * 通讯方式 0:4G;1:蓝牙,2 4G&蓝牙 * communication_mode */ + @Schema(title = "通讯方式", example = "0:4G;1:蓝牙,2 4G&蓝牙") private Integer communicationMode; /* app绑定用户id */ diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/AppDeviceVo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/AppDeviceVo.java index aafc6fc2..760fc3a4 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/AppDeviceVo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/AppDeviceVo.java @@ -26,7 +26,7 @@ public class AppDeviceVo implements Serializable { private String deviceMac; /** - * 通讯方式 0:4G;1:蓝牙 + * 通讯方式 0:4G;1:蓝牙,2 4G&蓝牙 */ private Integer communicationMode; diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceAlarmVo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceAlarmVo.java index 431c0460..167cf414 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceAlarmVo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceAlarmVo.java @@ -10,6 +10,7 @@ import com.fuyuanshen.common.excel.annotation.ExcelDictFormat; import com.fuyuanshen.common.excel.convert.ExcelDictConvert; import io.github.linpeilie.annotations.AutoMapper; import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; import lombok.Data; import java.io.Serial; @@ -134,4 +135,5 @@ public class DeviceAlarmVo implements Serializable { @Schema(name = "设备图片") private String devicePic; + } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/WebDeviceVo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/WebDeviceVo.java index 82c3eb10..f1d0879f 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/WebDeviceVo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/WebDeviceVo.java @@ -27,7 +27,7 @@ public class WebDeviceVo implements Serializable { private String deviceMac; /** - * 通讯方式 0:4G;1:蓝牙 + * 通讯方式 0:4G;1:蓝牙,2 4G&蓝牙 */ private Integer communicationMode; diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceAlarmMapper.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceAlarmMapper.java index 39fbe991..d6b5e455 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceAlarmMapper.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceAlarmMapper.java @@ -25,4 +25,13 @@ public interface DeviceAlarmMapper extends BaseMapperPlus selectVoPage( Page pageQuery,@Param("bo") DeviceAlarmBo bo); + /** + * 根据设备IMEI查询最新的一条告警数据 + * + * @param deviceImei 设备IMEI + * @return 设备告警 + */ + DeviceAlarmVo selectLatestByDeviceImei(@Param("deviceImei") String deviceImei); + + } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceMapper.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceMapper.java index 5d772caa..7b9a99fb 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceMapper.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceMapper.java @@ -109,4 +109,12 @@ public interface DeviceMapper extends BaseMapper { int getUsageDataForMonth(@Param("deviceId") Long deviceId, @Param("year") int year, @Param("month") int month); -} + + /** + * 根据设备IMEI查询设备 + * + * @param deviceImei 设备IMEI + * @return 设备信息 + */ + Device selectDeviceByImei(@Param("deviceImei") String deviceImei); +} \ No newline at end of file diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/DeviceService.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/DeviceService.java index bae56010..dd86af56 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/DeviceService.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/DeviceService.java @@ -144,4 +144,12 @@ public interface DeviceService extends IService { * @return */ List> getEquipmentUsageData(Long deviceTypeId, Integer range); -} + + /** + * 根据设备IMEI查询设备 + * + * @param deviceImei 设备IMEI + * @return 设备信息 + */ + Device selectDeviceByImei(String deviceImei); +} \ No newline at end of file diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/IDeviceAlarmService.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/IDeviceAlarmService.java index c3dc9ff2..377e55eb 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/IDeviceAlarmService.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/IDeviceAlarmService.java @@ -65,4 +65,14 @@ public interface IDeviceAlarmService { * @return 是否删除成功 */ Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + /** + * 根据设备IMEI查询最新的一条告警数据 + * + * @param deviceImei 设备IMEI + * @return 设备告警 + */ + DeviceAlarmVo queryLatestByDeviceImei(String deviceImei); + + } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceAlarmServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceAlarmServiceImpl.java index 1bf06710..b345b393 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceAlarmServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceAlarmServiceImpl.java @@ -1,5 +1,6 @@ package com.fuyuanshen.equipment.service.impl; +import cn.hutool.core.bean.BeanUtil; import com.fuyuanshen.common.core.utils.MapstructUtils; import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; import com.fuyuanshen.common.mybatis.core.page.PageQuery; @@ -102,11 +103,13 @@ public class DeviceAlarmServiceImpl implements IDeviceAlarmService { */ @Override public Boolean insertByBo(DeviceAlarmBo bo) { - DeviceAlarm add = MapstructUtils.convert(bo, DeviceAlarm.class); - validEntityBeforeSave(add); - boolean flag = baseMapper.insert(add) > 0; + DeviceAlarm deviceAlarm = new DeviceAlarm(); + // DeviceAlarm add = MapstructUtils.convert(bo, DeviceAlarm.class); + BeanUtil.copyProperties(bo, deviceAlarm); + validEntityBeforeSave(deviceAlarm); + boolean flag = baseMapper.insert(deviceAlarm) > 0; if (flag) { - bo.setId(add.getId()); + bo.setId(deviceAlarm.getId()); } return flag; } @@ -145,4 +148,17 @@ public class DeviceAlarmServiceImpl implements IDeviceAlarmService { } return baseMapper.deleteByIds(ids) > 0; } + + + /** + * 根据设备IMEI查询最新的一条告警数据 + * + * @param deviceImei 设备IMEI + * @return 设备告警 + */ + @Override + public DeviceAlarmVo queryLatestByDeviceImei(String deviceImei) { + return baseMapper.selectLatestByDeviceImei(deviceImei); + } + } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java index 48f09d86..4b77bda6 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java @@ -661,4 +661,9 @@ public class DeviceServiceImpl extends ServiceImpl impleme } + @Override + public Device selectDeviceByImei(String deviceImei) { + return baseMapper.selectDeviceByImei(deviceImei); + } + } diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceAlarmMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceAlarmMapper.xml index cdb76f8a..ad2823e1 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceAlarmMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceAlarmMapper.xml @@ -6,7 +6,7 @@ + + + + + diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml index 74c45573..92d6e5af 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml @@ -422,4 +422,9 @@ AND MONTH (dl.create_time) = #{month} + + + \ No newline at end of file From b463e97d28b941847e15c468170f07781f5426a9 Mon Sep 17 00:00:00 2001 From: chenyouting <514333061@qq.com> Date: Tue, 16 Sep 2025 17:35:29 +0800 Subject: [PATCH 061/160] =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6mqt?= =?UTF-8?q?t=E7=AB=AF=E5=8F=A3=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fys-admin/src/main/resources/application-dev.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fys-admin/src/main/resources/application-dev.yml b/fys-admin/src/main/resources/application-dev.yml index 902d6f93..9d12395f 100644 --- a/fys-admin/src/main/resources/application-dev.yml +++ b/fys-admin/src/main/resources/application-dev.yml @@ -301,7 +301,7 @@ file: mqtt: username: admin password: #YtvpSfCNG - url: tcp://47.120.79.150:3883 + url: tcp://47.120.79.150:2883 subClientId: fys_subClient subTopic: A/# pubTopic: B/# From aeea8f907205f89974a082ea24243eb04eba537c Mon Sep 17 00:00:00 2001 From: chenyouting <514333061@qq.com> Date: Wed, 17 Sep 2025 10:56:25 +0800 Subject: [PATCH 062/160] =?UTF-8?q?=E7=99=BB=E5=BD=95=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/controller/AppAuthController.java | 118 ++++++++++++-- .../fuyuanshen/app/model/AppRegisterBody.java | 30 ++++ .../fuyuanshen/app/model/AppSmsLoginBody.java | 11 +- .../app/service/AppRegisterService.java | 145 ++++++++++++++++++ .../service/impl/AppPasswordAuthStrategy.java | 144 +++++++++++++++++ .../common/core/constant/GlobalConstants.java | 10 ++ .../domain/model/AppPasswordLoginBody.java | 31 ++++ 7 files changed, 471 insertions(+), 18 deletions(-) create mode 100644 fys-admin/src/main/java/com/fuyuanshen/app/model/AppRegisterBody.java create mode 100644 fys-admin/src/main/java/com/fuyuanshen/app/service/AppRegisterService.java create mode 100644 fys-admin/src/main/java/com/fuyuanshen/web/service/impl/AppPasswordAuthStrategy.java create mode 100644 fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/AppPasswordLoginBody.java diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppAuthController.java b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppAuthController.java index d483e457..980fd55f 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppAuthController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppAuthController.java @@ -4,15 +4,24 @@ import cn.dev33.satoken.annotation.SaIgnore; import cn.dev33.satoken.exception.NotLoginException; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.RandomUtil; +import com.fuyuanshen.app.model.AppRegisterBody; import com.fuyuanshen.app.model.AppSmsLoginBody; import com.fuyuanshen.app.service.AppLoginService; +import com.fuyuanshen.app.service.AppRegisterService; +import com.fuyuanshen.common.core.constant.Constants; +import com.fuyuanshen.common.core.constant.GlobalConstants; import com.fuyuanshen.common.core.constant.SystemConstants; import com.fuyuanshen.common.core.domain.R; +import com.fuyuanshen.common.core.domain.model.AppPasswordLoginBody; import com.fuyuanshen.common.core.domain.model.RegisterBody; import com.fuyuanshen.common.core.domain.model.SmsLoginBody; +import com.fuyuanshen.common.core.enums.LoginType; import com.fuyuanshen.common.core.utils.*; import com.fuyuanshen.common.encrypt.annotation.ApiEncrypt; import com.fuyuanshen.common.json.utils.JsonUtils; +import com.fuyuanshen.common.ratelimiter.annotation.RateLimiter; +import com.fuyuanshen.common.redis.utils.RedisUtils; import com.fuyuanshen.common.satoken.utils.LoginHelper; import com.fuyuanshen.common.tenant.helper.TenantHelper; import com.fuyuanshen.system.domain.bo.SysTenantBo; @@ -27,6 +36,7 @@ import com.fuyuanshen.web.domain.vo.TenantListVo; import com.fuyuanshen.web.service.IAuthStrategy; import com.fuyuanshen.web.service.SysRegisterService; import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.constraints.NotBlank; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.dromara.sms4j.api.SmsBlend; @@ -36,8 +46,12 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.net.URL; +import java.time.Duration; +import java.util.LinkedHashMap; import java.util.List; +import static com.fuyuanshen.common.core.constant.GlobalConstants.DEVICE_SHARE_CODES_KEY; + /** * APP认证 * @@ -51,7 +65,7 @@ import java.util.List; public class AppAuthController { private final AppLoginService loginService; - private final SysRegisterService registerService; + private final AppRegisterService registerService; private final ISysConfigService configService; private final ISysTenantService tenantService; private final ISysClientService clientService; @@ -66,16 +80,34 @@ public class AppAuthController { @PostMapping("/login") public R login(@RequestBody AppSmsLoginBody appSmsLoginBody) { // SmsLoginBody loginBody = JsonUtils.parseObject(body, SmsLoginBody.class); - ValidatorUtils.validate(appSmsLoginBody); - SmsLoginBody loginBody = new SmsLoginBody(); - loginBody.setPhonenumber(appSmsLoginBody.getPhonenumber()); - loginBody.setSmsCode(appSmsLoginBody.getSmsCode()); - loginBody.setTenantId(appSmsLoginBody.getTenantId()); - loginBody.setClientId("ca839698e245d60aa2f0e59bd52b34f8"); - loginBody.setGrantType("appSms"); + String loginType = appSmsLoginBody.getLoginType(); + String body = ""; // 授权类型和客户端id - String clientId = loginBody.getClientId(); - String grantType = loginBody.getGrantType(); + String clientId = ""; + String grantType = ""; + String tenantId = appSmsLoginBody.getTenantId(); + ValidatorUtils.validate(appSmsLoginBody); + if("2".equals(loginType)){ + AppPasswordLoginBody loginBody = new AppPasswordLoginBody(); + loginBody.setUsername(appSmsLoginBody.getPhonenumber()); + loginBody.setPassword(appSmsLoginBody.getLoginPassword()); + loginBody.setClientId("835b15335d389c9fcfdf99421fa8019b"); + loginBody.setGrantType("appPassword"); + clientId = loginBody.getClientId(); + grantType = loginBody.getGrantType(); + body = JsonUtils.toJsonString(loginBody); + }else{ + SmsLoginBody loginBody = new SmsLoginBody(); + loginBody.setPhonenumber(appSmsLoginBody.getPhonenumber()); + loginBody.setSmsCode(appSmsLoginBody.getSmsCode()); + loginBody.setTenantId(appSmsLoginBody.getTenantId()); + loginBody.setClientId("ca839698e245d60aa2f0e59bd52b34f8"); + loginBody.setGrantType("appSms"); + clientId = loginBody.getClientId(); + grantType = loginBody.getGrantType(); + body = JsonUtils.toJsonString(loginBody); + } + SysClientVo client = clientService.queryByClientId(clientId); // 查询不到 client 或 client 内不包含 grantType if (ObjectUtil.isNull(client) || !StringUtils.contains(client.getGrantType(), grantType)) { @@ -85,9 +117,9 @@ public class AppAuthController { return R.fail(MessageUtils.message("auth.grant.type.blocked")); } // 校验租户 - loginService.checkTenant(loginBody.getTenantId()); +// loginService.checkTenant(loginBody.getTenantId()); + loginService.checkTenant(tenantId); // 登录 - String body = JsonUtils.toJsonString(loginBody); LoginVo loginVo = IAuthStrategy.login(body, client, grantType); return R.ok(loginVo); } @@ -113,16 +145,68 @@ public class AppAuthController { return R.ok("用户注销成功"); } + /** * 用户注册 */ - @ApiEncrypt @PostMapping("/register") - public R register(@Validated @RequestBody RegisterBody user) { - if (!configService.selectRegisterEnabled(user.getTenantId())) { - return R.fail("当前系统没有开启注册功能!"); + public R register(@Validated @RequestBody AppRegisterBody body) { + registerService.register(body); + return R.ok(); + } + + + /** + * 找回密码 + */ + @PostMapping("/forgetPassword") + public R forgetPassword(@Validated @RequestBody AppRegisterBody body) { + registerService.forgetPassword(body); + return R.ok(); + } + + + /** + * 用户注册短信验证码 + * + * @param phonenumber 用户手机号 + */ + @SaIgnore + @RateLimiter(key = "#phonenumber", time = 60, count = 1) + @GetMapping("/registerSmsCode") + public R registerSmsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) { + String key = GlobalConstants.REGISTER_CODE_KEY + phonenumber; + String code = RandomUtil.randomNumbers(4); + RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION)); + LinkedHashMap map = new LinkedHashMap<>(1); + map.put("code", code); + SmsBlend smsBlend = SmsFactory.getSmsBlend("config1"); + SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, map); + if (!smsResponse.isSuccess()) { + return R.fail(smsResponse.getData().toString()); + } + return R.ok(); + } + + /** + * 找回密码短信验证码 + * + * @param phonenumber 用户手机号 + */ + @SaIgnore + @RateLimiter(key = "#phonenumber", time = 60, count = 1) + @GetMapping("/forgetPasswordSmsCode") + public R forgetPasswordSmsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) { + String key = GlobalConstants.FORGET_PASSWORD_CODE_KEY + phonenumber; + String code = RandomUtil.randomNumbers(4); + RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION)); + LinkedHashMap map = new LinkedHashMap<>(1); + map.put("code", code); + SmsBlend smsBlend = SmsFactory.getSmsBlend("config1"); + SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, map); + if (!smsResponse.isSuccess()) { + return R.fail(smsResponse.getData().toString()); } - registerService.register(user); return R.ok(); } diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/model/AppRegisterBody.java b/fys-admin/src/main/java/com/fuyuanshen/app/model/AppRegisterBody.java new file mode 100644 index 00000000..73a73761 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/app/model/AppRegisterBody.java @@ -0,0 +1,30 @@ +package com.fuyuanshen.app.model; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class AppRegisterBody { + /** + * 手机号 + */ + @NotBlank(message = "{user.phonenumber.not.blank}") + private String phonenumber; + + /** + * 短信code + */ + private String smsCode; + + /** + * 租户ID + */ + @NotBlank(message = "租户ID不能为空") + private String tenantId; + + + /** + * 登录密码 + */ + private String password; +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/model/AppSmsLoginBody.java b/fys-admin/src/main/java/com/fuyuanshen/app/model/AppSmsLoginBody.java index ba91fe5a..1d1cb8b3 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/model/AppSmsLoginBody.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/model/AppSmsLoginBody.java @@ -14,7 +14,6 @@ public class AppSmsLoginBody { /** * 短信code */ - @NotBlank(message = "{sms.code.not.blank}") private String smsCode; /** @@ -23,4 +22,14 @@ public class AppSmsLoginBody { @NotBlank(message = "租户ID不能为空") private String tenantId; + /** + * 登录方式 1:手机验证码登录 2:密码登录 + */ + @NotBlank(message = "登录方式不能为空") + private String loginType; + + /** + * 登录密码 + */ + private String loginPassword; } diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/service/AppRegisterService.java b/fys-admin/src/main/java/com/fuyuanshen/app/service/AppRegisterService.java new file mode 100644 index 00000000..66361d77 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/app/service/AppRegisterService.java @@ -0,0 +1,145 @@ +package com.fuyuanshen.app.service; + +import cn.hutool.crypto.digest.BCrypt; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.fuyuanshen.app.domain.AppUser; +import com.fuyuanshen.app.mapper.AppUserMapper; +import com.fuyuanshen.app.model.AppRegisterBody; +import com.fuyuanshen.common.core.constant.Constants; +import com.fuyuanshen.common.core.constant.GlobalConstants; +import com.fuyuanshen.common.core.domain.model.RegisterBody; +import com.fuyuanshen.common.core.enums.UserType; +import com.fuyuanshen.common.core.exception.user.CaptchaException; +import com.fuyuanshen.common.core.exception.user.CaptchaExpireException; +import com.fuyuanshen.common.core.exception.user.UserException; +import com.fuyuanshen.common.core.utils.MessageUtils; +import com.fuyuanshen.common.core.utils.ServletUtils; +import com.fuyuanshen.common.core.utils.SpringUtils; +import com.fuyuanshen.common.core.utils.StringUtils; +import com.fuyuanshen.common.log.event.LogininforEvent; +import com.fuyuanshen.common.redis.utils.RedisUtils; +import com.fuyuanshen.common.tenant.helper.TenantHelper; +import com.fuyuanshen.common.web.config.properties.CaptchaProperties; +import com.fuyuanshen.system.domain.SysUser; +import com.fuyuanshen.system.domain.bo.SysUserBo; +import com.fuyuanshen.system.mapper.SysUserMapper; +import com.fuyuanshen.system.service.ISysUserService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Date; + +/** + * 注册校验方法 + * + * @author Lion Li + */ +@RequiredArgsConstructor +@Service +public class AppRegisterService { + + private final IAppUserService userService; + private final AppUserMapper userMapper; + + /** + * 注册 + */ + public void register(AppRegisterBody registerBody) { + String tenantId = registerBody.getTenantId(); + String username = registerBody.getPhonenumber(); + String password = registerBody.getPassword(); + + boolean codeValidate = validateRegisterSmsCode(tenantId, username, registerBody.getSmsCode()); + if (!codeValidate) { + throw new CaptchaException(); + } + + // 校验用户类型是否存在 + String userType = UserType.APP_USER.getUserType(); + + boolean exist = TenantHelper.dynamic(tenantId, () -> { + return userMapper.exists(new LambdaQueryWrapper() + .eq(AppUser::getUserName, username)); + }); + if (exist) { + throw new UserException("user.register.save.error", username); + } + AppUser appUser = new AppUser(); + appUser.setPhonenumber(username); + appUser.setUserName(username); + appUser.setStatus("0"); + appUser.setLoginDate(new Date()); + appUser.setLoginIp(ServletUtils.getClientIP()); + appUser.setTenantId(tenantId); + appUser.setPassword(password); + appUser.setUserType(userType); + userMapper.insert(appUser); + recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.register.success")); + } + + /** + * 注册校验短信验证码 + */ + private boolean validateRegisterSmsCode(String tenantId, String phonenumber, String smsCode) { + String code = RedisUtils.getCacheObject(GlobalConstants.REGISTER_CODE_KEY + phonenumber); + if (StringUtils.isBlank(code)) { + recordLogininfor(tenantId, phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")); + throw new CaptchaExpireException(); + } + return code.equals(smsCode); + } + + /** + * 忘记密码校验验证码 + */ + private boolean validateForgetPasswordCode(String tenantId, String phonenumber, String smsCode) { + String code = RedisUtils.getCacheObject(GlobalConstants.FORGET_PASSWORD_CODE_KEY + phonenumber); + if (StringUtils.isBlank(code)) { + recordLogininfor(tenantId, phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")); + throw new CaptchaExpireException(); + } + return code.equals(smsCode); + } + + /** + * 记录登录信息 + * + * @param tenantId 租户ID + * @param username 用户名 + * @param status 状态 + * @param message 消息内容 + * @return + */ + private void recordLogininfor(String tenantId, String username, String status, String message) { + LogininforEvent logininforEvent = new LogininforEvent(); + logininforEvent.setTenantId(tenantId); + logininforEvent.setUsername(username); + logininforEvent.setStatus(status); + logininforEvent.setMessage(message); + logininforEvent.setRequest(ServletUtils.getRequest()); + SpringUtils.context().publishEvent(logininforEvent); + } + + public void forgetPassword(AppRegisterBody registerBody) { + String tenantId = registerBody.getTenantId(); + String username = registerBody.getPhonenumber(); + String password = registerBody.getPassword(); + boolean codeValidate = validateForgetPasswordCode(tenantId, username, registerBody.getSmsCode()); + if (!codeValidate) { + throw new CaptchaException(); + } + boolean exist = TenantHelper.dynamic(tenantId, () -> { + return userMapper.exists(new LambdaQueryWrapper() + .eq(AppUser::getUserName, username)); + }); + if (!exist) { + throw new UserException("用户不存在"); + } + + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("user_name", username) + .set("password", password); + userMapper.update(updateWrapper); + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/AppPasswordAuthStrategy.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/AppPasswordAuthStrategy.java new file mode 100644 index 00000000..2d1b0468 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/AppPasswordAuthStrategy.java @@ -0,0 +1,144 @@ +package com.fuyuanshen.web.service.impl; + +import cn.dev33.satoken.stp.StpUtil; +import cn.dev33.satoken.stp.parameter.SaLoginParameter; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.crypto.digest.BCrypt; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.fuyuanshen.app.domain.AppUser; +import com.fuyuanshen.app.domain.vo.AppUserVo; +import com.fuyuanshen.app.mapper.AppUserMapper; +import com.fuyuanshen.app.service.AppLoginService; +import com.fuyuanshen.common.core.constant.Constants; +import com.fuyuanshen.common.core.constant.GlobalConstants; +import com.fuyuanshen.common.core.constant.SystemConstants; +import com.fuyuanshen.common.core.domain.model.AppLoginUser; +import com.fuyuanshen.common.core.domain.model.LoginUser; +import com.fuyuanshen.common.core.domain.model.PasswordLoginBody; +import com.fuyuanshen.common.core.enums.LoginType; +import com.fuyuanshen.common.core.enums.UserType; +import com.fuyuanshen.common.core.exception.user.CaptchaException; +import com.fuyuanshen.common.core.exception.user.CaptchaExpireException; +import com.fuyuanshen.common.core.exception.user.UserException; +import com.fuyuanshen.common.core.utils.MessageUtils; +import com.fuyuanshen.common.core.utils.StringUtils; +import com.fuyuanshen.common.core.utils.ValidatorUtils; +import com.fuyuanshen.common.json.utils.JsonUtils; +import com.fuyuanshen.common.redis.utils.RedisUtils; +import com.fuyuanshen.common.satoken.utils.AppLoginHelper; +import com.fuyuanshen.common.satoken.utils.LoginHelper; +import com.fuyuanshen.common.tenant.helper.TenantHelper; +import com.fuyuanshen.common.web.config.properties.CaptchaProperties; +import com.fuyuanshen.system.domain.SysUser; +import com.fuyuanshen.system.domain.vo.SysClientVo; +import com.fuyuanshen.system.domain.vo.SysUserVo; +import com.fuyuanshen.system.mapper.SysUserMapper; +import com.fuyuanshen.web.domain.vo.LoginVo; +import com.fuyuanshen.web.service.IAuthStrategy; +import com.fuyuanshen.web.service.SysLoginService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** + * APP密码认证策略 + * + * @author Michelle.Chung + */ +@Slf4j +@Service("appPassword" + IAuthStrategy.BASE_NAME) +@RequiredArgsConstructor +public class AppPasswordAuthStrategy implements IAuthStrategy { + + private final CaptchaProperties captchaProperties; + private final AppLoginService loginService; + private final AppUserMapper appUserMapper; + + @Override + public LoginVo login(String body, SysClientVo client) { + PasswordLoginBody loginBody = JsonUtils.parseObject(body, PasswordLoginBody.class); + ValidatorUtils.validate(loginBody); + String tenantId = loginBody.getTenantId(); + String username = loginBody.getUsername(); + String password = loginBody.getPassword(); + String code = loginBody.getCode(); + String uuid = loginBody.getUuid(); + +// boolean captchaEnabled = captchaProperties.getEnable(); +// // 验证码开关 +// if (captchaEnabled) { +// validateCaptcha(tenantId, username, code, uuid); +// } + AppLoginUser loginUser = TenantHelper.dynamic(tenantId, () -> { + AppUserVo user = loadUserByUsername(username); + loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> !StringUtils.equals(password, user.getPassword())); + // 此处可根据登录用户的数据不同 自行创建 loginUser + return loginService.buildLoginUser(user); + }); + loginUser.setClientKey(client.getClientKey()); + loginUser.setDeviceType(client.getDeviceType()); + SaLoginParameter model = new SaLoginParameter(); + model.setDeviceType(client.getDeviceType()); + // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置 + // 例如: 后台用户30分钟过期 app用户1天过期 + model.setTimeout(client.getTimeout()); + model.setActiveTimeout(client.getActiveTimeout()); + model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId()); + // 生成token + AppLoginHelper.login(loginUser, model); + + LoginVo loginVo = new LoginVo(); + loginVo.setAccessToken(StpUtil.getTokenValue()); + loginVo.setExpireIn(StpUtil.getTokenTimeout()); + loginVo.setClientId(client.getClientId()); + return loginVo; + } + + /** + * 校验验证码 + * + * @param username 用户名 + * @param code 验证码 + * @param uuid 唯一标识 + */ + private void validateCaptcha(String tenantId, String username, String code, String uuid) { + String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, ""); + String captcha = RedisUtils.getCacheObject(verifyKey); + RedisUtils.deleteObject(verifyKey); + if (captcha == null) { + loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")); + throw new CaptchaExpireException(); + } + if (!code.equalsIgnoreCase(captcha)) { + loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")); + throw new CaptchaException(); + } + } + + private AppUserVo loadUserByUsername(String username) { +// SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper().eq(SysUser::getUserName, username)); +// if (ObjectUtil.isNull(user)) { +// log.info("登录用户:{} 不存在.", username); +// throw new UserException("user.not.exists", username); +// } else if (SystemConstants.DISABLE.equals(user.getStatus())) { +// log.info("登录用户:{} 已被停用.", username); +// throw new UserException("user.blocked", username); +// } +// return user; + + AppUserVo user = appUserMapper.selectVoOne(new LambdaQueryWrapper() + .eq(AppUser::getPhonenumber, username) + .eq(AppUser::getUserType, UserType.APP_USER.getUserType())); + if (ObjectUtil.isNull(user)) { + log.info("登录用户:{} 不存在.", username); +// throw new UserException("user.not.exists", phonenumber); + // 新增AppUser用户 + return null; + } else if (SystemConstants.DISABLE.equals(user.getStatus())) { + log.info("登录用户:{} 已被停用.", username); + throw new UserException("user.blocked", username); + } + return user; + } + +} diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/constant/GlobalConstants.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/constant/GlobalConstants.java index 3eaad963..72deb006 100644 --- a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/constant/GlobalConstants.java +++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/constant/GlobalConstants.java @@ -17,6 +17,16 @@ public interface GlobalConstants { */ String CAPTCHA_CODE_KEY = GLOBAL_REDIS_KEY + "captcha_codes:"; + /** + * 忘记密码验证码 redis key + */ + String FORGET_PASSWORD_CODE_KEY = GLOBAL_REDIS_KEY + "forget_password_codes:"; + + /** + * 注册验证码 redis key + */ + String REGISTER_CODE_KEY = GLOBAL_REDIS_KEY + "register_codes:"; + /** * 设备分享验证码 redis key */ diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/AppPasswordLoginBody.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/AppPasswordLoginBody.java new file mode 100644 index 00000000..1a167799 --- /dev/null +++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/AppPasswordLoginBody.java @@ -0,0 +1,31 @@ +package com.fuyuanshen.common.core.domain.model; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.validator.constraints.Length; + +/** + * 密码登录对象 + * + * @author Lion Li + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class AppPasswordLoginBody extends LoginBody { + + /** + * 用户名 + */ + @NotBlank(message = "{user.username.not.blank}") + @Length(min = 2, max = 30, message = "{user.username.length.valid}") + private String username; + + /** + * 用户密码 + */ + @NotBlank(message = "{user.password.not.blank}") + @Length(min = 5, max = 30, message = "{user.password.length.valid}") + private String password; + +} From 540a6fc1da98a7eee7c3c19cfa405110db2282e1 Mon Sep 17 00:00:00 2001 From: chenyouting <514333061@qq.com> Date: Wed, 17 Sep 2025 17:43:17 +0800 Subject: [PATCH 063/160] =?UTF-8?q?=E5=AF=86=E7=A0=81=E9=95=BF=E5=BA=A6?= =?UTF-8?q?=E9=AA=8C=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/service/impl/AppPasswordAuthStrategy.java | 15 +++++---------- .../core/domain/model/AppPasswordLoginBody.java | 2 +- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/AppPasswordAuthStrategy.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/AppPasswordAuthStrategy.java index 2d1b0468..02cfa6e1 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/AppPasswordAuthStrategy.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/AppPasswordAuthStrategy.java @@ -13,6 +13,7 @@ import com.fuyuanshen.common.core.constant.Constants; import com.fuyuanshen.common.core.constant.GlobalConstants; import com.fuyuanshen.common.core.constant.SystemConstants; import com.fuyuanshen.common.core.domain.model.AppLoginUser; +import com.fuyuanshen.common.core.domain.model.AppPasswordLoginBody; import com.fuyuanshen.common.core.domain.model.LoginUser; import com.fuyuanshen.common.core.domain.model.PasswordLoginBody; import com.fuyuanshen.common.core.enums.LoginType; @@ -56,25 +57,19 @@ public class AppPasswordAuthStrategy implements IAuthStrategy { @Override public LoginVo login(String body, SysClientVo client) { - PasswordLoginBody loginBody = JsonUtils.parseObject(body, PasswordLoginBody.class); + AppPasswordLoginBody loginBody = JsonUtils.parseObject(body, AppPasswordLoginBody.class); ValidatorUtils.validate(loginBody); String tenantId = loginBody.getTenantId(); String username = loginBody.getUsername(); String password = loginBody.getPassword(); - String code = loginBody.getCode(); - String uuid = loginBody.getUuid(); - -// boolean captchaEnabled = captchaProperties.getEnable(); -// // 验证码开关 -// if (captchaEnabled) { -// validateCaptcha(tenantId, username, code, uuid); -// } AppLoginUser loginUser = TenantHelper.dynamic(tenantId, () -> { AppUserVo user = loadUserByUsername(username); - loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> !StringUtils.equals(password, user.getPassword())); + loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> !password.equals(user.getPassword())); + // 此处可根据登录用户的数据不同 自行创建 loginUser return loginService.buildLoginUser(user); }); + loginUser.setClientKey(client.getClientKey()); loginUser.setDeviceType(client.getDeviceType()); SaLoginParameter model = new SaLoginParameter(); diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/AppPasswordLoginBody.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/AppPasswordLoginBody.java index 1a167799..79159529 100644 --- a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/AppPasswordLoginBody.java +++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/domain/model/AppPasswordLoginBody.java @@ -25,7 +25,7 @@ public class AppPasswordLoginBody extends LoginBody { * 用户密码 */ @NotBlank(message = "{user.password.not.blank}") - @Length(min = 5, max = 30, message = "{user.password.length.valid}") +// @Length(min = 5, max = 30, message = "{user.password.length.valid}") private String password; } From 11e48acc8183091d332eb16f078cbde05bd0d5a9 Mon Sep 17 00:00:00 2001 From: chenyouting <514333061@qq.com> Date: Thu, 18 Sep 2025 08:54:29 +0800 Subject: [PATCH 064/160] =?UTF-8?q?=E4=B8=AA=E4=BA=BA=E4=B8=AD=E5=BF=83?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=AF=86=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/controller/AppAuthController.java | 11 ---- .../controller/AppUserCenterController.java | 54 +++++++++++++++++++ .../app/service/AppRegisterService.java | 10 ++++ 3 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 fys-admin/src/main/java/com/fuyuanshen/app/controller/AppUserCenterController.java diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppAuthController.java b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppAuthController.java index 980fd55f..938b214f 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppAuthController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppAuthController.java @@ -125,17 +125,6 @@ public class AppAuthController { } - - - /** - * 退出登录 - */ - @PostMapping("/logout") - public R logout() { - loginService.logout(); - return R.ok("退出成功"); - } - /** * 用户注销 */ diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppUserCenterController.java b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppUserCenterController.java new file mode 100644 index 00000000..d2250c82 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppUserCenterController.java @@ -0,0 +1,54 @@ +package com.fuyuanshen.app.controller; + +import com.fuyuanshen.app.model.AppRegisterBody; +import com.fuyuanshen.app.service.AppLoginService; +import com.fuyuanshen.app.service.AppRegisterService; +import com.fuyuanshen.common.core.domain.R; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * APP我的 + * + */ +@Slf4j +@RequiredArgsConstructor +@RestController +@RequestMapping("/app/userCenter") +public class AppUserCenterController { + + private final AppLoginService loginService; + private final AppRegisterService registerService; + /** + * 用户注销 + */ + @PostMapping("/cancelAccount") + public R cancelAccount() { + loginService.cancelAccount(); + return R.ok("用户注销成功"); + } + + + /** + * 修改密码 + */ + @PostMapping("/updatePassword") + public R updatePassword(@Validated @RequestBody AppRegisterBody body) { + registerService.updatePassword(body); + return R.ok(); + } + + /** + * 退出登录 + */ + @PostMapping("/logout") + public R logout() { + loginService.logout(); + return R.ok("退出成功"); + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/service/AppRegisterService.java b/fys-admin/src/main/java/com/fuyuanshen/app/service/AppRegisterService.java index 66361d77..282b15e7 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/service/AppRegisterService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/service/AppRegisterService.java @@ -19,6 +19,7 @@ import com.fuyuanshen.common.core.utils.SpringUtils; import com.fuyuanshen.common.core.utils.StringUtils; import com.fuyuanshen.common.log.event.LogininforEvent; import com.fuyuanshen.common.redis.utils.RedisUtils; +import com.fuyuanshen.common.satoken.utils.AppLoginHelper; import com.fuyuanshen.common.tenant.helper.TenantHelper; import com.fuyuanshen.common.web.config.properties.CaptchaProperties; import com.fuyuanshen.system.domain.SysUser; @@ -142,4 +143,13 @@ public class AppRegisterService { .set("password", password); userMapper.update(updateWrapper); } + + public void updatePassword(AppRegisterBody body) { + String username = AppLoginHelper.getUsername(); + String password = body.getPassword(); + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("user_name", username) + .set("password", password); + userMapper.update(updateWrapper); + } } From 630c6a8a3535934a8b6606b46bb8ac68b4f3839d Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Thu, 18 Sep 2025 11:25:40 +0800 Subject: [PATCH 065/160] =?UTF-8?q?=E8=A7=92=E8=89=B2=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=91=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/DeviceServiceImpl.java | 14 +++++++++++ .../service/impl/DeviceTypeServiceImpl.java | 23 +++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java index 4b77bda6..ecd0974e 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java @@ -40,7 +40,9 @@ import com.fuyuanshen.equipment.service.DeviceAssignmentsService; import com.fuyuanshen.equipment.service.DeviceService; import com.fuyuanshen.equipment.service.DeviceTypeGrantsService; import com.fuyuanshen.system.domain.vo.SysOssVo; +import com.fuyuanshen.system.domain.vo.SysRoleVo; import com.fuyuanshen.system.service.ISysOssService; +import com.fuyuanshen.system.service.ISysRoleService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; @@ -67,6 +69,8 @@ public class DeviceServiceImpl extends ServiceImpl impleme public static final String USER_ID_SEPARATOR = ":"; + private final ISysRoleService roleService; + @Value("${file.device.pic}") private String filePath; @Value("${file.device.ip}") @@ -109,6 +113,16 @@ public class DeviceServiceImpl extends ServiceImpl impleme if (username.equals("admin")) { criteria.setIsAdmin(true); } + // 角色管理员 + Long userId = LoginHelper.getUserId(); + List roles = roleService.selectRolesAuthByUserId(userId); + if (CollectionUtil.isNotEmpty(roles)) { + for (SysRoleVo role : roles) { + if (role.getRoleKey().equals("admin")) { + criteria.setIsAdmin(true); + } + } + } IPage devices = deviceMapper.findAll(criteria, page); diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceTypeServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceTypeServiceImpl.java index d3cb540b..dccaa495 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceTypeServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceTypeServiceImpl.java @@ -19,6 +19,8 @@ import com.fuyuanshen.equipment.mapper.DeviceMapper; import com.fuyuanshen.equipment.mapper.DeviceTypeGrantsMapper; import com.fuyuanshen.equipment.mapper.DeviceTypeMapper; import com.fuyuanshen.equipment.service.DeviceTypeService; +import com.fuyuanshen.system.domain.vo.SysRoleVo; +import com.fuyuanshen.system.service.ISysRoleService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -43,6 +45,8 @@ public class DeviceTypeServiceImpl extends ServiceImpl queryAll(DeviceTypeQueryCriteria criteria, Page page) { // 管理员 - String username = LoginHelper.getUsername(); - if (!username.equals("admin")) { + // String username = LoginHelper.getUsername(); + // if (!username.equals("admin")) { + // criteria.setCustomerId(LoginHelper.getUserId()); + // } + // 角色管理员 + Long userId = LoginHelper.getUserId(); + List roles = roleService.selectRolesAuthByUserId(userId); + boolean isAdmin = false; + if (CollectionUtil.isNotEmpty(roles)) { + for (SysRoleVo role : roles) { + if (role.getRoleKey().contains("admin")) { + isAdmin = true; + break; + } + } + } + if (!isAdmin) { criteria.setCustomerId(LoginHelper.getUserId()); } From 4d9038567f4069c4d8b261cbaa9873b7ad287979 Mon Sep 17 00:00:00 2001 From: DragonWenLong <552045633@qq.com> Date: Thu, 18 Sep 2025 11:40:12 +0800 Subject: [PATCH 066/160] =?UTF-8?q?=E6=A0=B9=E6=8D=AE=E6=8F=90=E4=BE=9B?= =?UTF-8?q?=E7=9A=84=E4=BB=A3=E7=A0=81=E5=B7=AE=E5=BC=82=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=EF=BC=8C=E4=BB=A5=E4=B8=8B=E6=98=AF=E7=AC=A6=E5=90=88Angular?= =?UTF-8?q?=20commit=E8=A7=84=E8=8C=83=E7=9A=84commit=20message=EF=BC=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` feat(app): 添加获取app版本接口及视频处理控制器 新增功能: - 在AppAuthController中添加了获取app版本信息的接口`/version`。- 新增AppVideoController用于处理视频上传和帧提取。 修改内容: - 在AppAuthController中引入了ISysDictTypeService服务。 - 在DeviceBizService中更新了设备通信模式的判断逻辑。 - 修改了DeviceAlarmMapper.xml和DeviceMapper.xml中的SQL查询语句以支持更多通信模式。 - 更新了DeviceXinghanBizService中的人员信息登记逻辑,并添加了获取设备详细信息的方法。 - 在DeviceXinghanController中添加了获取设备详细信息的接口`/info/{id}`。 - 更新了MqttXinghanJson类中的字段命名。 - 在pom.xml中添加了javacv相关的依赖。 修复问题: - 注释掉了AppSmsAuthStrategy中的登录检查逻辑。 ``` 这个commit message包含了类型(feat)、作用范围(app)以及简短的描述。同时在body部分详细说明了新增的功能、修改的内容以及修复的问题。 --- fys-admin/pom.xml | 11 + .../app/controller/AppAuthController.java | 26 ++ .../app/controller/AppVideoController.java | 249 ++++++++++++++++++ .../global/mqtt/base/MqttXinghanJson.java | 4 +- .../device/DeviceXinghanController.java | 15 ++ .../web/domain/vo/DeviceXinghanDetailVo.java | 103 ++++++++ .../web/service/device/DeviceBizService.java | 3 +- .../device/DeviceXinghanBizService.java | 145 ++++++++-- .../web/service/impl/AppSmsAuthStrategy.java | 2 +- .../mapper/equipment/DeviceAlarmMapper.xml | 2 +- .../mapper/equipment/DeviceMapper.xml | 2 +- 11 files changed, 541 insertions(+), 21 deletions(-) create mode 100644 fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java create mode 100644 fys-admin/src/main/java/com/fuyuanshen/web/domain/vo/DeviceXinghanDetailVo.java diff --git a/fys-admin/pom.xml b/fys-admin/pom.xml index 20f147ed..ff80564f 100644 --- a/fys-admin/pom.xml +++ b/fys-admin/pom.xml @@ -123,6 +123,17 @@ test + + org.bytedeco + javacv-platform + + + org.bytedeco + javacv + 1.5.7 + compile + + diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppAuthController.java b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppAuthController.java index d483e457..8a7328e7 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppAuthController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppAuthController.java @@ -17,9 +17,11 @@ import com.fuyuanshen.common.satoken.utils.LoginHelper; import com.fuyuanshen.common.tenant.helper.TenantHelper; import com.fuyuanshen.system.domain.bo.SysTenantBo; import com.fuyuanshen.system.domain.vo.SysClientVo; +import com.fuyuanshen.system.domain.vo.SysDictDataVo; import com.fuyuanshen.system.domain.vo.SysTenantVo; import com.fuyuanshen.system.service.ISysClientService; import com.fuyuanshen.system.service.ISysConfigService; +import com.fuyuanshen.system.service.ISysDictTypeService; import com.fuyuanshen.system.service.ISysTenantService; import com.fuyuanshen.web.domain.vo.LoginTenantVo; import com.fuyuanshen.web.domain.vo.LoginVo; @@ -36,6 +38,7 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.net.URL; +import java.util.ArrayList; import java.util.List; /** @@ -55,6 +58,7 @@ public class AppAuthController { private final ISysConfigService configService; private final ISysTenantService tenantService; private final ISysClientService clientService; + private final ISysDictTypeService dictTypeService; /** @@ -179,4 +183,26 @@ public class AppAuthController { SmsResponse smsResponse = smsBlend.sendMessage("18656573389", "123"); } + /** + * 获取app版本 + * @return + */ + @GetMapping("/version") + public R> getAppVersion() { + List list = dictTypeService.selectDictDataByType("app_version"); + list.forEach(d -> { + String[] arr = d.getRemark().split("\\|"); + d.setDictLabel(d.getDictLabel()); // ios/android + d.setDictValue(arr[0]); // 版本号 + d.setRemark(arr[1]); // 下载地址 + }); + // 只保留方法体:筛选 label=ios 且版本号 ≥ 2.5.0 的列表 +// List 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); + } + } diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java new file mode 100644 index 00000000..3b807b41 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java @@ -0,0 +1,249 @@ +package com.fuyuanshen.app.controller; + +import com.fuyuanshen.common.core.domain.R; +import com.fuyuanshen.common.web.core.BaseController; +import lombok.RequiredArgsConstructor; +import org.bytedeco.javacv.FFmpegFrameGrabber; +import org.bytedeco.javacv.Frame; +import org.bytedeco.javacv.Java2DFrameUtils; +import org.springframework.http.MediaType; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * APP 视频处理 + * @date 2025-09-15 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/app/video") +public class AppVideoController extends BaseController { + // 可配置项:建议从 application.yml 中读取 + private static final int MAX_VIDEO_SIZE = 10 * 1024 * 1024; // 10 MB + private static final int FRAME_RATE = 15; // 每秒抽15帧 + private static final int DURATION = 2; // 抽2秒 + private static final int TOTAL_FRAMES = FRAME_RATE * DURATION; + private static final int WIDTH = 160; + private static final int HEIGHT = 80; + private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); + + @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public R> upload(@RequestParam("file") MultipartFile file) { + if (file == null || file.isEmpty()) { + return R.fail("上传文件不能为空"); + } + + if (!isVideo(file.getOriginalFilename())) { + return R.fail("只允许上传视频文件"); + } + + if (file.getSize() > MAX_VIDEO_SIZE) { + return R.fail("视频大小不能超过10MB"); + } + + File tempFile = null; + try { + // 创建临时文件保存上传的视频 + tempFile = createTempVideoFile(file); + + + List frames = extractFramesFromVideo(tempFile); + if (frames.isEmpty()) { + return R.fail("无法提取任何帧"); + } + + // ✅ 新增:保存帧为图片 + //saveFramesToLocal(frames, "extracted_frame"); + + byte[] binaryData = convertFramesToRGB565(frames); +// String base64Data = Base64.getEncoder().encodeToString(binaryData); +// +// return R.ok(base64Data); + // 构造响应头 + // 将二进制数据转为 Hex 字符串 + // 转换为 Hex 字符串列表 + List hexList = bytesToHexList(binaryData); + + return R.ok(hexList); + + } catch (Exception e) { + return R.fail("视频处理失败:" + e.getMessage()); + } finally { + deleteTempFile(tempFile); + } + } + + /** + * rgb565 转 hex + */ + private List bytesToHexList(byte[] bytes) { + List hexList = new ArrayList<>(); + for (byte b : bytes) { + int value = b & 0xFF; + char high = HEX_ARRAY[value >>> 4]; + char low = HEX_ARRAY[value & 0x0F]; + hexList.add(String.valueOf(high) + low); + } + return hexList; + } + + /** + * 创建临时文件并保存上传的视频 + */ + private File createTempVideoFile(MultipartFile file) throws Exception { + File tempFile = Files.createTempFile("upload-", ".mp4").toFile(); + file.transferTo(tempFile); + return tempFile; + } + + /** + * 从视频中按时间均匀提取指定数量的帧 + */ + private List extractFramesFromVideo(File videoFile) throws Exception { + List frames = new ArrayList<>(); + + try (FFmpegFrameGrabber grabber = FFmpegFrameGrabber.createDefault(videoFile)) { + grabber.start(); + + // 获取视频总帧数和帧率 + long totalFramesInVideo = grabber.getLengthInFrames(); + int fps = (int) Math.round(grabber.getFrameRate()); + if (fps <= 0) fps = 30; + + double durationSeconds = (double) totalFramesInVideo / fps; + + if (durationSeconds < DURATION) { + throw new IllegalArgumentException("视频太短,至少需要 " + DURATION + " 秒"); + } + + // 计算每帧之间的间隔(浮点以实现更精确跳转) + double frameInterval = (double) totalFramesInVideo / TOTAL_FRAMES; + + for (int i = 0; i < TOTAL_FRAMES; i++) { + int targetFrameNumber = (int) Math.round(i * frameInterval); + + // 避免设置无效帧号 + if (targetFrameNumber >= totalFramesInVideo) { + throw new IllegalArgumentException("目标帧超出范围: " + targetFrameNumber + " "); + } + + grabber.setFrameNumber(targetFrameNumber); + Frame frame = grabber.grab(); + + if (frame != null && frame.image != null) { + BufferedImage bufferedImage = Java2DFrameUtils.toBufferedImage(frame); + frames.add(cropImage(bufferedImage, WIDTH, HEIGHT)); + } else { + throw new IllegalArgumentException("无法获取第 " + targetFrameNumber + "帧 "); + } + } + + grabber.stop(); + } + + return frames; + } + + /** + * 将抽取的帧保存到本地,用于调试 + */ + private void saveFramesToLocal(List frames, String prefix) { + // 指定输出目录 + File outputDir = new File("output_frames"); + if (!outputDir.exists()) { + outputDir.mkdirs(); + } + + int index = 0; + for (BufferedImage frame : frames) { + try { + File outputImage = new File(outputDir, prefix + "_" + (index++) + ".png"); + ImageIO.write(frame, "png", outputImage); + System.out.println("保存帧图片成功: " + outputImage.getAbsolutePath()); + } catch (Exception e) { + throw new IllegalArgumentException("保存帧图片失败 " + e); + } + } + } + + /** + * 将所有帧转换为 RGB565 格式字节数组 + */ + private byte[] convertFramesToRGB565(List frames) throws Exception { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + + for (BufferedImage image : frames) { + byte[] rgb565Bytes = convertToRGB565(image); + byteArrayOutputStream.write(rgb565Bytes); + } + + return byteArrayOutputStream.toByteArray(); + } + + /** + * 判断是否是支持的视频格式 + */ + private boolean isVideo(String filename) { + String ext = filename.substring(filename.lastIndexOf('.')).toLowerCase(); + return Arrays.asList(".mp4", ".avi", ".mov", ".mkv").contains(ext); + } + + /** + * 裁剪图像到目标尺寸 + */ + private BufferedImage cropImage(BufferedImage img, int targetWidth, int targetHeight) { + int w = Math.min(img.getWidth(), targetWidth); + int h = Math.min(img.getHeight(), targetHeight); + return img.getSubimage(0, 0, w, h); + } + + /** + * 将 BufferedImage 转换为 RGB565 格式的字节数组 + */ + private byte[] convertToRGB565(BufferedImage image) { + int width = image.getWidth(); + int height = image.getHeight(); + byte[] result = new byte[width * height * 2]; // RGB565: 2 bytes per pixel + int index = 0; + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int rgb = image.getRGB(x, y); + int r = ((rgb >> 16) & 0xFF) >> 3; + int g = ((rgb >> 8) & 0xFF) >> 2; + int b = (rgb & 0xFF) >> 3; + short pixel = (short) ((r << 11) | (g << 5) | b); + + result[index++] = (byte) (pixel >> 8); // High byte first + result[index++] = (byte) pixel; + } + } + + return result; + } + + /** + * 删除临时文件 + */ + private void deleteTempFile(File file) { + if (file != null && file.exists()) { + if (!file.delete()) { + throw new IllegalArgumentException("无法删除临时文件: " + file.getAbsolutePath()); + } + } + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/MqttXinghanJson.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/MqttXinghanJson.java index 2d49b468..3e0737f3 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/MqttXinghanJson.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/MqttXinghanJson.java @@ -39,8 +39,8 @@ public class MqttXinghanJson { /** * 第七键值对, 静止报警状态,0-未静止报警,1-正在静止报警。 */ - @JsonProperty("staShakeBit") - public Integer sta_ShakeBit; + @JsonProperty("sta_ShakeBit") + public Integer staShakeBit; /** * 第八键值对, 4G信号强度,0-32,数值越大,信号越强。 */ diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceXinghanController.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceXinghanController.java index 8600c788..4cc51d87 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceXinghanController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceXinghanController.java @@ -3,13 +3,16 @@ package com.fuyuanshen.web.controller.device; import com.fuyuanshen.app.domain.bo.AppPersonnelInfoBo; import com.fuyuanshen.app.domain.dto.AppDeviceLogoUploadDto; import com.fuyuanshen.app.domain.dto.DeviceInstructDto; +import com.fuyuanshen.app.domain.vo.AppDeviceDetailVo; 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.domain.vo.DeviceXinghanDetailVo; import com.fuyuanshen.web.service.device.DeviceXinghanBizService; +import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -26,6 +29,18 @@ public class DeviceXinghanController extends BaseController { private final DeviceXinghanBizService deviceXinghanBizService; + + /** + * 获取设备详细信息 + * + * @param id 主键 + */ + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(deviceXinghanBizService.getInfo(id)); + } + /** * 人员信息登记 */ diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/domain/vo/DeviceXinghanDetailVo.java b/fys-admin/src/main/java/com/fuyuanshen/web/domain/vo/DeviceXinghanDetailVo.java new file mode 100644 index 00000000..62c4e3e1 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/web/domain/vo/DeviceXinghanDetailVo.java @@ -0,0 +1,103 @@ +package com.fuyuanshen.web.domain.vo; + +import cn.idev.excel.annotation.ExcelProperty; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fuyuanshen.app.domain.vo.AppPersonnelInfoVo; +import lombok.Data; + +@Data +public class DeviceXinghanDetailVo { + /** + * 设备ID + */ + @ExcelProperty(value = "设备ID") + private Long deviceId; + + + /** + * 设备名称 + */ + private String deviceName; + + /** + * 设备IMEI + */ + private String deviceImei; + + /** + * 设备MAC + */ + private String deviceMac; + + /** + * 通讯方式 0:4G;1:蓝牙 + */ + private Integer communicationMode; + + /** + * 设备图片 + */ + private String devicePic; + + /** + * 设备类型 + */ + private String typeName; + + /** + * 蓝牙名称 + */ + private String bluetoothName; + + /** + * 设备状态 + * 0 失效 + * 1 正常 + */ + private Integer deviceStatus; + + + /** + * 人员信息 + */ + private AppPersonnelInfoVo personnelInfo; + + /** + * 在线状态(0离线,1在线) + */ + private Integer onlineStatus; + + // 经度 + private String longitude; + + // 纬度 + private String latitude; + + // 逆解析地址 + private String address; + + /** + * 第一键值对,静电预警档位:3,2,1,0,分别表示高档/中档/低挡/关闭. + */ + private Integer staDetectGrade; + /** + * 第二键值对,照明档位,2,1,0,分别表示弱光/强光/关闭 + */ + private Integer staLightGrade; + /** + * 第三键值对,SOS档位,2,1,0, 分别表示红蓝模式/爆闪模式/关闭 + */ + public Integer staSOSGrade; + /** + * 第四键值对,剩余照明时间,0-5999,单位分钟。 + */ + public Integer staPowerTime; + /** + * 第五键值对,剩余电量百分比,0-100 + */ + public Integer staPowerPercent; + /** + * 第六键值对, 近电预警级别, 0-无预警,1-弱预警,2-中预警,3-强预警,4-非常强预警。 + */ + public Integer staDetectResult; +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBizService.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBizService.java index 21cf14eb..df7785cb 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBizService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBizService.java @@ -36,6 +36,7 @@ import com.fuyuanshen.equipment.mapper.DeviceMapper; import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants; import com.fuyuanshen.web.service.device.status.base.DeviceStatusRule; import com.fuyuanshen.web.service.device.status.base.RealTimeStatusEngine; +import com.google.common.primitives.Ints; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -126,7 +127,7 @@ public class DeviceBizService { List records = result.getRecords(); if(records != null && !records.isEmpty()){ records.forEach(item -> { - if(item.getCommunicationMode()!=null && item.getCommunicationMode() == 0){ + if(item.getCommunicationMode()!=null && Ints.asList(0, 2).contains(item.getCommunicationMode())){ //设备在线状态 String onlineStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ item.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX); 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 8370ed9d..32254592 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 @@ -1,15 +1,22 @@ package com.fuyuanshen.web.service.device; +import cn.hutool.core.bean.BeanUtil; import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.fasterxml.jackson.databind.ObjectMapper; import com.fuyuanshen.app.domain.AppPersonnelInfo; +import com.fuyuanshen.app.domain.AppPersonnelInfoRecords; import com.fuyuanshen.app.domain.bo.AppPersonnelInfoBo; import com.fuyuanshen.app.domain.dto.AppDeviceLogoUploadDto; import com.fuyuanshen.app.domain.dto.DeviceInstructDto; +import com.fuyuanshen.app.domain.vo.AppDeviceDetailVo; import com.fuyuanshen.app.domain.vo.AppPersonnelInfoVo; import com.fuyuanshen.app.mapper.AppPersonnelInfoMapper; +import com.fuyuanshen.app.mapper.AppPersonnelInfoRecordsMapper; import com.fuyuanshen.common.core.constant.GlobalConstants; import com.fuyuanshen.common.core.exception.ServiceException; import com.fuyuanshen.common.core.utils.ImageToCArrayConverter; @@ -20,17 +27,21 @@ import com.fuyuanshen.common.json.utils.JsonUtils; import com.fuyuanshen.common.redis.utils.RedisUtils; import com.fuyuanshen.common.satoken.utils.AppLoginHelper; import com.fuyuanshen.equipment.domain.Device; +import com.fuyuanshen.equipment.domain.DeviceType; import com.fuyuanshen.equipment.domain.dto.AppDeviceSendMsgBo; import com.fuyuanshen.equipment.enums.LightModeEnum; import com.fuyuanshen.equipment.mapper.DeviceLogMapper; import com.fuyuanshen.equipment.mapper.DeviceMapper; import com.fuyuanshen.equipment.mapper.DeviceTypeMapper; +import com.fuyuanshen.global.mqtt.base.MqttXinghanJson; import com.fuyuanshen.global.mqtt.config.MqttGateway; import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants; import com.fuyuanshen.global.mqtt.constants.MqttConstants; import com.fuyuanshen.web.domain.Dto.DeviceDebugLogoUploadDto; +import com.fuyuanshen.web.domain.vo.DeviceXinghanDetailVo; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; @@ -56,6 +67,9 @@ public class DeviceXinghanBizService { private final DeviceTypeMapper deviceTypeMapper; private final MqttGateway mqttGateway; private final DeviceLogMapper deviceLogMapper; + private final AppPersonnelInfoRecordsMapper appPersonnelInfoRecordsMapper; + @Autowired + private ObjectMapper objectMapper; /** * 所有档位的描述表 @@ -106,6 +120,71 @@ public class DeviceXinghanBizService { sendCommand(dto, "ins_ShakeBit","强制报警"); } + public DeviceXinghanDetailVo getInfo(Long id) { + Device device = deviceMapper.selectById(id); + if (device == null) { + throw new RuntimeException("请先将设备入库!!!"); + } + + DeviceXinghanDetailVo vo = new DeviceXinghanDetailVo(); + vo.setDeviceId(device.getId()); + vo.setDeviceName(device.getDeviceName()); + vo.setDevicePic(device.getDevicePic()); + vo.setDeviceImei(device.getDeviceImei()); + vo.setDeviceMac(device.getDeviceMac()); + vo.setDeviceStatus(device.getDeviceStatus()); + DeviceType deviceType = deviceTypeMapper.selectById(device.getDeviceType()); + if (deviceType != null) { + vo.setCommunicationMode(Integer.valueOf(deviceType.getCommunicationMode())); + vo.setTypeName(deviceType.getTypeName()); + } + vo.setBluetoothName(device.getBluetoothName()); + + + QueryWrapper qw = new QueryWrapper() + .eq("device_id", device.getId()); + AppPersonnelInfo appPersonnelInfo = appPersonnelInfoMapper.selectOne(qw); + if (appPersonnelInfo != null) { + AppPersonnelInfoVo personnelInfoVo = MapstructUtils.convert(appPersonnelInfo, AppPersonnelInfoVo.class); + vo.setPersonnelInfo(personnelInfoVo); + } + //设备在线状态 + String onlineStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + device.getDeviceImei()+ DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX); + if(StringUtils.isNotBlank(onlineStatus)){ + vo.setOnlineStatus(1); + }else{ + vo.setOnlineStatus(0); + } + String deviceStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + device.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_STATUS_KEY_PREFIX); + // 获取设备上报的设备状态 + if(StringUtils.isNotBlank(deviceStatus)){ + try { + MqttXinghanJson deviceJson = objectMapper.readValue(deviceStatus, MqttXinghanJson.class); + vo.setStaLightGrade(deviceJson.getStaLightGrade()); + vo.setStaPowerPercent(deviceJson.getStaPowerPercent()); + vo.setStaSOSGrade(deviceJson.getStaSOSGrade()); + vo.setStaDetectGrade(deviceJson.getStaDetectGrade()); + vo.setStaPowerTime(deviceJson.getStaPowerTime()); + } catch (Exception e) { + throw new IllegalArgumentException("设备状态缓存格式非法", e); + } + }else{ + vo.setStaPowerPercent(0); + } + + // 获取经度纬度 + String locationKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + device.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_LOCATION_KEY_PREFIX; + String locationInfo = RedisUtils.getCacheObject(locationKey); + if(StringUtils.isNotBlank(locationInfo)){ + JSONObject jsonObject = JSONObject.parseObject(locationInfo); + vo.setLongitude(jsonObject.get("longitude").toString()); + vo.setLatitude(jsonObject.get("latitude").toString()); + vo.setAddress((String)jsonObject.get("address")); + } + + return vo; + } + /** * 上传设备logo */ @@ -212,18 +291,19 @@ public class DeviceXinghanBizService { * 人员登记 * @param bo */ + @Transactional(rollbackFor = Exception.class) // 1. 事务 public boolean registerPersonInfo(AppPersonnelInfoBo bo) { Long deviceId = bo.getDeviceId(); Device deviceObj = deviceMapper.selectById(deviceId); if (deviceObj == null) { throw new RuntimeException("请先将设备入库!!!"); } + if (deviceObj.getDeviceImei() == null) { + return saveOrUpdatePersonnelInfo(bo, deviceId); + } if (isDeviceOffline(deviceObj.getDeviceImei())) { throw new ServiceException("设备已断开连接:" + deviceObj.getDeviceName()); } - QueryWrapper qw = new QueryWrapper() - .eq("device_id", deviceId); - List appPersonnelInfoVos = appPersonnelInfoMapper.selectVoList(qw); List list = new ArrayList<>(); list.add(bo.getName()); @@ -247,18 +327,47 @@ public class DeviceXinghanBizService { log.info("发送人员信息登记到设备消息=>topic:{},payload:{}", MqttConstants.GLOBAL_PUB_KEY + deviceObj.getDeviceImei(), bo); recordDeviceLog(deviceId, deviceObj.getDeviceName(), "人员信息登记", JSON.toJSONString(bo), AppLoginHelper.getUserId()); - if (ObjectUtils.length(appPersonnelInfoVos) == 0) { - AppPersonnelInfo appPersonnelInfo = MapstructUtils.convert(bo, AppPersonnelInfo.class); - return appPersonnelInfoMapper.insertOrUpdate(appPersonnelInfo); + + return saveOrUpdatePersonnelInfo(bo, deviceId); + } + + /** + * 仅内部调用,已处于 @Transactional 内 + */ + private Boolean saveOrUpdatePersonnelInfo(AppPersonnelInfoBo bo, Long deviceId) { + // 1. 先查主表有没有 + AppPersonnelInfo main = appPersonnelInfoMapper.selectOne( + Wrappers.lambdaQuery() + .eq(AppPersonnelInfo::getDeviceId, deviceId)); + + if (main == null) { + /* ====== 新增场景 ====== */ + main = MapstructUtils.convert(bo, AppPersonnelInfo.class); + main.setDeviceId(deviceId); + // 1.1 主表插入 -> 主键回写 + appPersonnelInfoMapper.insert(main); // 执行后 main.getId() 一定有值 } else { - UpdateWrapper uw = new UpdateWrapper<>(); - uw.eq("device_id", deviceId) - .set("name", bo.getName()) - .set("position", bo.getPosition()) - .set("unit_name", bo.getUnitName()) - .set("code", bo.getCode()); - return appPersonnelInfoMapper.update(null, uw) > 0; + /* ====== 更新场景 ====== */ + // 1.2 把 bo 的新值刷到主表 + appPersonnelInfoMapper.update(null, + Wrappers.update() + .eq("device_id", deviceId) + .set("name", bo.getName()) + .set("position", bo.getPosition()) + .set("unit_name", bo.getUnitName()) + .set("code", bo.getCode()) + .set("update_time", new Date())); } + + // 2. 无论新增/更新,都插入一条履历 + AppPersonnelInfoRecords record = new AppPersonnelInfoRecords(); + // 只拷业务字段,不拷时间、id、操作人 + BeanUtil.copyProperties(main, record, "id", "createTime", "updateTime", "createBy", "updateBy"); + record.setId(null); // 让自增 + record.setPersonnelId(main.getId()); // 关键:主键已回写,不会为 null + appPersonnelInfoRecordsMapper.insert(record); + + return true; } /** @@ -273,6 +382,10 @@ public class DeviceXinghanBizService { if (deviceIds == null || deviceIds.isEmpty()) { throw new ServiceException("请选择设备"); } + if(!StringUtils.isNotEmpty(bo.getSendMsg())) + { + throw new ServiceException("请输入发送内容"); + } QueryWrapper queryWrapper = new QueryWrapper<>(); // 使用 in 语句根据id集合查询 queryWrapper.in("id", deviceIds); @@ -325,9 +438,11 @@ public class DeviceXinghanBizService { */ private void sendSingleAlarmMessage(Device device, String message) { String deviceImei = device.getDeviceImei(); - + int msgLen = message.length() / 2; + StringBuilder msgBuilder = new StringBuilder(message); + msgBuilder.insert(msgLen, '|'); // 缓存告警消息到Redis - RedisUtils.setCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + deviceImei + DEVICE_ALARM_MESSAGE_KEY_PREFIX, message, Duration.ofSeconds(5 * 60L)); + RedisUtils.setCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + deviceImei + DEVICE_ALARM_MESSAGE_KEY_PREFIX, msgBuilder.toString(), Duration.ofSeconds(5 * 60L)); // 构建并发送MQTT消息 Map payload = Map.of("ins_BreakNews", Collections.singletonList(0)); diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/AppSmsAuthStrategy.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/AppSmsAuthStrategy.java index 862cfbd9..eb9ea8d3 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/AppSmsAuthStrategy.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/AppSmsAuthStrategy.java @@ -54,7 +54,7 @@ public class AppSmsAuthStrategy implements IAuthStrategy { String phonenumber = loginBody.getPhonenumber(); String smsCode = loginBody.getSmsCode(); AppLoginUser loginUser = TenantHelper.dynamic(tenantId, () -> { - loginService.checkLogin(LoginType.SMS, tenantId, phonenumber, () -> !validateSmsCode(tenantId, phonenumber, smsCode)); +// loginService.checkLogin(LoginType.SMS, tenantId, phonenumber, () -> !validateSmsCode(tenantId, phonenumber, smsCode)); AppUserVo user = loadUserByPhonenumber(phonenumber); if (ObjectUtil.isNull(user)) { //新增Appuser diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceAlarmMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceAlarmMapper.xml index cdb76f8a..27d38895 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceAlarmMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceAlarmMapper.xml @@ -6,7 +6,7 @@ - SELECT (SELECT COUNT(1) FROM device_alarm WHERE treatment_state = 0) AS alarmsTotal + SELECT (SELECT COUNT(1) FROM device_alarm) AS alarmsTotal , (SELECT COUNT(1) FROM device_alarm - WHERE treatment_state = 0) AS processingAlarm + WHERE treatment_state = 0) AS processingAlarm , (SELECT COUNT(1) FROM device_alarm - WHERE treatment_state = 0 AND + WHERE DATE (create_time) = CURDATE()) AS alarmsTotalToday , ( SELECT COUNT (1) @@ -425,7 +425,9 @@ \ No newline at end of file From 5f36c255501c738470a83a76efa049987689dc2c Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Sat, 20 Sep 2025 14:35:23 +0800 Subject: [PATCH 070/160] =?UTF-8?q?=E5=86=85=E5=AE=B9=EF=BC=88=E6=8A=A5?= =?UTF-8?q?=E8=AD=A6=E4=BF=A1=E6=81=AF=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../equipment/domain/bo/DeviceAlarmBo.java | 4 ++-- .../resources/mapper/equipment/DeviceAlarmMapper.xml | 12 ++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceAlarmBo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceAlarmBo.java index ab50363b..2c298dc6 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceAlarmBo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceAlarmBo.java @@ -36,7 +36,7 @@ public class DeviceAlarmBo extends TenantEntity { /** * 报警事项 * device_action - * 0-强制报警,1-撞击闯入,2-自动报警,3-电子围栏告警 + * 0-强制报警,1-撞击闯入,2-自动报警,3-电子围栏告警 */ private Integer deviceAction; @@ -63,7 +63,7 @@ public class DeviceAlarmBo extends TenantEntity { private String dataSource; /** - * 内容 + * 内容(报警信息) */ private String content; diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceAlarmMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceAlarmMapper.xml index 39e312df..4e79b654 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceAlarmMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceAlarmMapper.xml @@ -6,17 +6,21 @@ SELECT r.id, - r.fence_id, - f.name AS fence_name, - r.device_id, - d.device_name, - r.user_id, - r.event_type, - r.latitude, - r.longitude, - r.accuracy, - r.event_time, - r.create_time + r.fence_id, + f.name AS fence_name, + r.device_id, + d.device_name, + r.user_id, + r.event_type, + r.latitude, + r.longitude, + r.accuracy, + r.event_time, + r.create_time FROM device_fence_access_record r - LEFT JOIN device_geo_fence f ON r.fence_id = f.id - LEFT JOIN device d ON r.device_id = d.id - ${ew.customSqlSegment} + LEFT JOIN device_geo_fence f ON r.fence_id = f.id + LEFT JOIN device d ON r.device_id = d.id + ${ew.customSqlSegment} + + + + AND f.name LIKE CONCAT('%', #{ew.params.fenceName}, '%') + + + + ORDER BY r.id ASC + + + + + From 5b3a92c80d46306091d3e32a5d56453170deb6b5 Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Mon, 22 Sep 2025 16:34:32 +0800 Subject: [PATCH 076/160] =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E6=89=80=E6=9C=89?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/DeviceTypeServiceImpl.java | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceTypeServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceTypeServiceImpl.java index dccaa495..d23572c9 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceTypeServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceTypeServiceImpl.java @@ -98,18 +98,35 @@ public class DeviceTypeServiceImpl extends ServiceImpl queryDeviceTypes() { DeviceTypeQueryCriteria criteria = new DeviceTypeQueryCriteria(); - // 管理员 - String username = LoginHelper.getUsername(); - if (!username.equals("admin")) { - criteria.setCustomerId(LoginHelper.getUserId()); + // // 管理员 + // String username = LoginHelper.getUsername(); + // if (!username.equals("admin")) { + // criteria.setCustomerId(LoginHelper.getUserId()); + // + // Long userId = LoginHelper.getUserId(); + // criteria.setCustomerId(userId); + // } - Long userId = LoginHelper.getUserId(); - criteria.setCustomerId(userId); + // 角色管理员 + Long userId = LoginHelper.getUserId(); + List roles = roleService.selectRolesAuthByUserId(userId); + boolean isAdmin = false; + if (CollectionUtil.isNotEmpty(roles)) { + for (SysRoleVo role : roles) { + if (role.getRoleKey().contains("admin")) { + isAdmin = true; + break; + } + } + } + if (!isAdmin) { + criteria.setCustomerId(LoginHelper.getUserId()); } return deviceTypeMapper.findAll(criteria); } + /** * 根据设备类型名称查询设备类型 * From e2821566c895c92edbc6e35d14d76fabc163ec87 Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Mon, 22 Sep 2025 18:18:05 +0800 Subject: [PATCH 077/160] =?UTF-8?q?=E4=BA=8B=E4=BB=B6=E5=9C=B0=E5=9D=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../equipment/domain/DeviceFenceAccessRecord.java | 5 +++++ .../equipment/domain/bo/DeviceFenceAccessRecordBo.java | 6 ++++++ .../equipment/domain/vo/DeviceFenceAccessRecordVo.java | 6 ++++++ .../equipment/service/impl/DeviceGeoFenceServiceImpl.java | 4 ++++ .../equipment/utils/map/GetAddressFromLatUtil.java | 3 +++ .../mapper/equipment/DeviceFenceAccessRecordMapper.xml | 4 ++-- 6 files changed, 26 insertions(+), 2 deletions(-) diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceFenceAccessRecord.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceFenceAccessRecord.java index 58ab238f..95c67d6c 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceFenceAccessRecord.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceFenceAccessRecord.java @@ -69,4 +69,9 @@ public class DeviceFenceAccessRecord extends BaseEntity { */ private Date eventTime; + /** + * 事件地址 + */ + private String eventAddress; + } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceFenceAccessRecordBo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceFenceAccessRecordBo.java index 2bbc33bb..41419765 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceFenceAccessRecordBo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceFenceAccessRecordBo.java @@ -1,5 +1,6 @@ package com.fuyuanshen.equipment.domain.bo; +import cn.idev.excel.annotation.ExcelProperty; import com.fuyuanshen.common.core.validate.AddGroup; import com.fuyuanshen.common.core.validate.EditGroup; import com.fuyuanshen.common.mybatis.core.domain.BaseEntity; @@ -81,6 +82,11 @@ public class DeviceFenceAccessRecordBo extends BaseEntity { @NotNull(message = "事件时间不能为空", groups = {AddGroup.class, EditGroup.class}) private Date eventTime; + /** + * 事件地址 + */ + private String eventAddress; + /** * 开始时间 */ diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceFenceAccessRecordVo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceFenceAccessRecordVo.java index 309158d0..b61e7505 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceFenceAccessRecordVo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceFenceAccessRecordVo.java @@ -97,6 +97,12 @@ public class DeviceFenceAccessRecordVo implements Serializable { @ExcelProperty(value = "事件时间") private Date eventTime; + /** + * 事件地址 + */ + @ExcelProperty(value = "事件地址") + private String eventAddress; + /** * 记录创建时间 */ diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceGeoFenceServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceGeoFenceServiceImpl.java index 2595b0ad..086d4734 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceGeoFenceServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceGeoFenceServiceImpl.java @@ -21,6 +21,7 @@ import com.fuyuanshen.equipment.service.IDeviceFenceAccessRecordService; import com.fuyuanshen.equipment.service.IDeviceFenceStatusService; import com.fuyuanshen.equipment.service.IDeviceGeoFenceService; import com.fuyuanshen.equipment.utils.map.GeoFenceChecker; +import com.fuyuanshen.equipment.utils.map.GetAddressFromLatUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -240,6 +241,9 @@ public class DeviceGeoFenceServiceImpl implements IDeviceGeoFenceService { recordBo.setFenceId(fence.getId()); recordBo.setLatitude(request.getLatitude()); recordBo.setLongitude(request.getLongitude()); + String address = GetAddressFromLatUtil.getAdd(request.getLongitude().toString(), request.getLatitude().toString()); + recordBo.setEventAddress(address); + recordBo.setEventTime(new Date()); // 1表示进入围栏,2表示离开围栏 recordBo.setEventType(currentStatus); diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/utils/map/GetAddressFromLatUtil.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/utils/map/GetAddressFromLatUtil.java index 2744977e..03da1cf7 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/utils/map/GetAddressFromLatUtil.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/utils/map/GetAddressFromLatUtil.java @@ -8,6 +8,8 @@ import org.slf4j.LoggerFactory; import java.net.URL; /** + * 根据经纬度获取地址信息工具类 + * * @author: 默苍璃 * @date: 2025-07-2615:59 */ @@ -74,5 +76,6 @@ public class GetAddressFromLatUtil { System.out.println("通过API获取到具体位置:" + res); return res; } + } diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceFenceAccessRecordMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceFenceAccessRecordMapper.xml index a1e7467e..27be7c7f 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceFenceAccessRecordMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceFenceAccessRecordMapper.xml @@ -46,7 +46,7 @@ r.latitude, r.longitude, r.accuracy, - r.event_time, + r.event_time, r.event_address, r.create_time FROM device_fence_access_record r LEFT JOIN device_geo_fence f ON r.fence_id = f.id @@ -90,7 +90,7 @@ AND r.event_time #{bo.endTime} - ORDER BY r.id ASC + ORDER BY r.event_time DESC \ No newline at end of file From 49e9066033a27176695efacf54a3d17bc7209107 Mon Sep 17 00:00:00 2001 From: DragonWenLong <552045633@qq.com> Date: Tue, 23 Sep 2025 11:31:11 +0800 Subject: [PATCH 078/160] =?UTF-8?q?feat(equipment):=20=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E7=BB=B4=E4=BF=AE=E8=AE=B0=E5=BD=95=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E7=AE=A1=E7=90=86=E5=92=8C=E6=8A=A5=E8=AD=A6=E5=A4=84?= =?UTF-8?q?=E7=90=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -重构文件哈希工具类路径并优化上传逻辑,支持秒传 - 新增维修记录图片ID列表字段及删除旧图片逻辑- 设备维修记录查询增加设备名称模糊搜索条件 -日期查询条件添加格式化注解支持 yyyy-MM-dd- MQTT规则中新增SOS与静止报警处理机制 - 实现报警生命周期管理(开始/结束)及Redis缓存控制 - 添加报警信息入库和位置解析功能 - 优化设备状态数据解析与经纬度异步存储逻辑 --- .../global/mqtt/base/MqttXinghanJson.java | 5 + .../rule/xinghan/XinghanDeviceDataRule.java | 132 +++++++++++++++++- .../service/device/DeviceDebugService.java | 5 +- .../domain/bo/DeviceRepairRecordsBo.java | 4 + .../DeviceRepairRecordsQueryCriteria.java | 3 + .../impl/DeviceRepairRecordsServiceImpl.java | 26 +++- .../equipment/utils}/FileHashUtil.java | 2 +- .../equipment/DeviceRepairRecordsMapper.xml | 3 + 8 files changed, 168 insertions(+), 12 deletions(-) rename {fys-admin/src/main/java/com/fuyuanshen/web/util => fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/utils}/FileHashUtil.java (95%) diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/MqttXinghanJson.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/MqttXinghanJson.java index 3e0737f3..4d9513f3 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/MqttXinghanJson.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/MqttXinghanJson.java @@ -61,5 +61,10 @@ public class MqttXinghanJson { */ @JsonProperty("sta_latitude") public String stalatitude; + /** + * 第十二键值对,系统现状,0关机,1仅充电,2开机未充电,3,开机且充电 + */ + @JsonProperty("sta_system") + public String stasystem; } diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanDeviceDataRule.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanDeviceDataRule.java index cf4fbd03..17dce0ed 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanDeviceDataRule.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanDeviceDataRule.java @@ -1,33 +1,41 @@ package com.fuyuanshen.global.mqtt.rule.xinghan; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson2.JSONObject; import com.fasterxml.jackson.databind.ObjectMapper; import com.fuyuanshen.common.core.constant.GlobalConstants; import com.fuyuanshen.common.core.utils.StringUtils; +import com.fuyuanshen.common.core.utils.date.DurationUtils; import com.fuyuanshen.common.json.utils.JsonUtils; import com.fuyuanshen.common.redis.utils.RedisUtils; +import com.fuyuanshen.equipment.domain.Device; +import com.fuyuanshen.equipment.domain.bo.DeviceAlarmBo; +import com.fuyuanshen.equipment.domain.vo.DeviceAlarmVo; +import com.fuyuanshen.equipment.mapper.DeviceAlarmMapper; +import com.fuyuanshen.equipment.service.DeviceService; +import com.fuyuanshen.equipment.service.IDeviceAlarmService; import com.fuyuanshen.equipment.utils.map.GetAddressFromLatUtil; import com.fuyuanshen.equipment.utils.map.LngLonUtil; import com.fuyuanshen.global.mqtt.base.MqttMessageRule; import com.fuyuanshen.global.mqtt.base.MqttRuleContext; import com.fuyuanshen.global.mqtt.base.MqttXinghanJson; import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants; -import com.fuyuanshen.global.mqtt.constants.LightingCommandTypeConstants; import com.fuyuanshen.global.mqtt.constants.XingHanCommandTypeConstants; import com.fuyuanshen.global.mqtt.listener.domain.FunctionAccessStatus; +import lombok.AllArgsConstructor; +import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.beans.factory.annotation.Autowired; import java.time.Duration; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; +import java.util.*; import java.util.concurrent.CompletableFuture; import static com.fuyuanshen.common.core.constant.GlobalConstants.FUNCTION_ACCESS_KEY; +import static com.fuyuanshen.common.core.constant.GlobalConstants.GLOBAL_REDIS_KEY; import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.*; import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX; @@ -57,6 +65,9 @@ public class XinghanDeviceDataRule implements MqttMessageRule { @Autowired private ObjectMapper objectMapper; + private final IDeviceAlarmService deviceAlarmService; + private final DeviceService deviceService; + private final DeviceAlarmMapper deviceAlarmMapper; @Override public void execute(MqttRuleContext context) { @@ -99,12 +110,123 @@ public class XinghanDeviceDataRule implements MqttMessageRule { // 异步发送经纬度到Redis asyncSendLocationToRedisWithFuture(deviceImei, deviceStatus.getStalatitude(), deviceStatus.getStalongitude()); + // 保存报警信息 + saveAlarm(deviceImei,deviceStatus); + } catch (Exception e) { log.error("异步发送设备信息到Redis时出错: device={}, error={}", deviceImei, e.getMessage(), e); } }); } + /** + * 入口:保存报警(SOS 与 Shake 完全独立) + */ + public void saveAlarm(String deviceImei, MqttXinghanJson status) { + int sos = Optional.ofNullable(status.getStaSOSGrade()).orElse(0); + // 1. 处理 SOS 报警 + handleSingleAlarm(deviceImei, + sos > 0, + AlarmType.SOS); + int shake = Optional.ofNullable(status.getStaShakeBit()).orElse(0); + // 2. 处理 Shake 报警 + handleSingleAlarm(deviceImei, + shake > 0, + AlarmType.SHAKE); + } + + /** + * 通用:对单个报警源的“开始/结束”生命周期管理 + */ + private void handleSingleAlarm(String deviceImei, boolean nowAlarming, AlarmType type) { + String redisKey = buildAlarmRedisKey(deviceImei, type); + + Long alarmId = RedisUtils.getCacheObject(redisKey); + + // ---------- 情况 1:当前正在报警 ---------- + if (nowAlarming) { + // 已存在未结束报警 -> 什么都不做(同一条报警) + if (alarmId != null) { + // key 还在 -> 同一条报警,只续期 + RedisUtils.setCacheObject(redisKey, alarmId, Duration.ofMinutes(10)); + return; + } + // 不存在 -> 新建 + DeviceAlarmBo bo = createAlarmBo(deviceImei, type); + deviceAlarmService.insertByBo(bo); + RedisUtils.setCacheObject(redisKey, bo.getId(), Duration.ofMinutes(10)); // 5分钟后结束过期 + return; + } + + // ---------- 情况 2:当前不报警 ---------- + if (alarmId != null) { + // 结束它 + finishAlarm(alarmId); + RedisUtils.deleteObject(redisKey); + } + } + + /** + * 结束报警:写库 + */ + private void finishAlarm(Long alarmId) { + DeviceAlarmVo vo = deviceAlarmService.queryById(alarmId); + if (vo == null || vo.getTreatmentState() == 0) { + return; // 已处理或已被删 + } + DeviceAlarmBo bo = BeanUtil.toBean(vo, DeviceAlarmBo.class); + bo.setFinishTime(new Date()); + bo.setDurationTime(DurationUtils.getDurationBetween(vo.getStartTime(), bo.getFinishTime())); + bo.setTreatmentState(0); // 已处理 + deviceAlarmService.updateByBo(bo); + } + + /** + * 新建报警 Bo + */ + private DeviceAlarmBo createAlarmBo(String deviceImei, AlarmType type) { + Device device = deviceService.selectDeviceByImei(deviceImei); + DeviceAlarmBo bo = new DeviceAlarmBo(); + bo.setDeviceId(device.getId()); + bo.setDeviceImei(deviceImei); + bo.setDeviceAction(2); // 自动报警 + bo.setStartTime(new Date()); + bo.setTreatmentState(1); // 未处理 + bo.setTenantId(device.getTenantId()); + + // 报警内容 + bo.setContent("自动报警:" + type.getDesc()); + + // 位置 + String location = RedisUtils.getCacheObject( + GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + deviceImei + DEVICE_LOCATION_KEY_PREFIX); + if (StrUtil.isNotBlank(location)) { + bo.setLocation(JSONObject.parseObject(location).getString("address")); + } + return bo; + } + + /** + * key 构建 + */ + private String buildAlarmRedisKey(String deviceImei, AlarmType type) { + return StrUtil.format("{}{}{}{}:alarm_id", + GLOBAL_REDIS_KEY, DEVICE_KEY_PREFIX, deviceImei, type.getSuffix()); + } + + /** + * 报警类型枚举 + */ + @AllArgsConstructor + @Getter + enum AlarmType { + SOS("_sos", "SOS报警"), + SHAKE("_shake", "静止报警"); + + private final String suffix; + private final String desc; + } + /** * 异步发送位置信息到Redis(使用CompletableFuture) * diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceDebugService.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceDebugService.java index 2f2e6b85..d4b875aa 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceDebugService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceDebugService.java @@ -10,18 +10,15 @@ import com.fuyuanshen.app.service.IAppBusinessFileService; import com.fuyuanshen.app.service.IAppOperationVideoService; import com.fuyuanshen.common.core.exception.ServiceException; import com.fuyuanshen.common.satoken.utils.AppLoginHelper; -import com.fuyuanshen.equipment.mapper.DeviceMapper; import com.fuyuanshen.equipment.service.DeviceService; import com.fuyuanshen.system.domain.vo.SysOssVo; import com.fuyuanshen.system.service.ISysOssService; import com.fuyuanshen.web.domain.vo.DeviceInfoVo; -import com.fuyuanshen.web.util.FileHashUtil; -import io.swagger.v3.oas.annotations.Operation; +import com.fuyuanshen.equipment.utils.FileHashUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceRepairRecordsBo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceRepairRecordsBo.java index e134543a..e4631729 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceRepairRecordsBo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/DeviceRepairRecordsBo.java @@ -11,6 +11,8 @@ import lombok.Data; import lombok.EqualsAndHashCode; import jakarta.validation.constraints.*; import java.util.Date; +import java.util.List; + import com.fasterxml.jackson.annotation.JsonFormat; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.web.multipart.MultipartFile; @@ -70,4 +72,6 @@ public class DeviceRepairRecordsBo extends BaseEntity { @Schema(title = "维修后图片") @JsonIgnore private MultipartFile afterFile; + + private List imageIds; } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/DeviceRepairRecordsQueryCriteria.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/DeviceRepairRecordsQueryCriteria.java index 4df5ec6d..bf9ba0f5 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/DeviceRepairRecordsQueryCriteria.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/query/DeviceRepairRecordsQueryCriteria.java @@ -7,6 +7,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; import org.springframework.web.multipart.MultipartFile; import java.util.Date; @@ -51,8 +52,10 @@ public class DeviceRepairRecordsQueryCriteria extends BaseEntity { private String repairPerson; @Schema(title = "维修开始时间") + @DateTimeFormat(pattern = "yyyy-MM-dd") private Date repairBeginTime; @Schema(title = "维修结束时间") + @DateTimeFormat(pattern = "yyyy-MM-dd") private Date repairEndTime; @Schema(title = "所属客户") diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceRepairRecordsServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceRepairRecordsServiceImpl.java index 3b838d88..80ff0fe5 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceRepairRecordsServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceRepairRecordsServiceImpl.java @@ -18,6 +18,7 @@ import com.fuyuanshen.equipment.domain.vo.DeviceRepairImagesVo; import com.fuyuanshen.equipment.enums.RepairImageType; import com.fuyuanshen.equipment.mapper.DeviceMapper; import com.fuyuanshen.equipment.mapper.DeviceRepairImagesMapper; +import com.fuyuanshen.equipment.utils.FileHashUtil; import com.fuyuanshen.system.domain.vo.SysOssVo; import com.fuyuanshen.system.service.ISysOssService; import lombok.RequiredArgsConstructor; @@ -32,6 +33,7 @@ import com.fuyuanshen.equipment.service.IDeviceRepairRecordsService; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; import java.util.*; /** @@ -178,6 +180,10 @@ public class DeviceRepairRecordsServiceImpl extends ServiceImpl images = new ArrayList<>(2); @@ -200,12 +206,28 @@ public class DeviceRepairRecordsServiceImpl extends ServiceImpl + + and d.device_name like concat('%', TRIM(#{criteria.searchValue}), '%') + and dr.device_id = #{criteria.deviceId} From 135e6d68998f26555206b7d449a92898a5e29eea Mon Sep 17 00:00:00 2001 From: chenyouting <514333061@qq.com> Date: Tue, 23 Sep 2025 11:42:44 +0800 Subject: [PATCH 079/160] =?UTF-8?q?=E8=AE=BE=E5=A4=87=E5=88=86=E4=BA=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/fuyuanshen/app/domain/vo/AppDeviceShareVo.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/vo/AppDeviceShareVo.java b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/vo/AppDeviceShareVo.java index 3e1d6179..92d515fe 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/vo/AppDeviceShareVo.java +++ b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/vo/AppDeviceShareVo.java @@ -114,4 +114,9 @@ public class AppDeviceShareVo implements Serializable { * 创建时间 */ private String createTime; + + /** + * 设备详情页面 + */ + private String detailPageUrl; } From 89f08c2d911cf954715e066ab6884f6fdca9fb13 Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Tue, 23 Sep 2025 13:56:34 +0800 Subject: [PATCH 080/160] =?UTF-8?q?=E5=AF=BC=E5=85=A5=E5=AF=BC=E5=87=BA?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../converter/IgnoreFailedImageConverter.java | 56 +++++++++++++++++-- .../domain/dto/DeviceExcelExportDTO.java | 34 +++++------ .../domain/dto/DeviceExcelImportDTO.java | 49 ++++++++-------- .../equipment/domain/form/DeviceForm.java | 1 - .../equipment/domain/vo/DeviceAlarmVo.java | 4 +- .../domain/vo/DeviceFenceAccessRecordVo.java | 4 +- .../excel/UploadDeviceDataListener.java | 6 +- .../service/impl/DeviceExportService.java | 32 ++++++++--- .../service/impl/DeviceServiceImpl.java | 21 ++++++- .../service/impl/DeviceTypeServiceImpl.java | 18 +++++- .../mapper/equipment/DeviceTypeMapper.xml | 11 +++- 11 files changed, 168 insertions(+), 68 deletions(-) diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/converter/IgnoreFailedImageConverter.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/converter/IgnoreFailedImageConverter.java index 650bec9c..6795efa5 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/converter/IgnoreFailedImageConverter.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/converter/IgnoreFailedImageConverter.java @@ -9,6 +9,7 @@ import org.slf4j.LoggerFactory; import java.io.ByteArrayOutputStream; import java.io.InputStream; +import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; @@ -33,23 +34,62 @@ public class IgnoreFailedImageConverter implements Converter { @Override public WriteCellData convertToExcelData(URL value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { if (value == null) { - return null; + logger.debug("图片URL为空"); + return new WriteCellData<>(new byte[0]); } try { + logger.debug("开始加载图片: {}", value); URLConnection conn = value.openConnection(); - conn.setConnectTimeout(2000); // 2秒超时 - conn.setReadTimeout(3000); // 3秒超时 + // 增加连接和读取超时时间 + conn.setConnectTimeout(10000); // 10秒连接超时 + conn.setReadTimeout(30000); // 30秒读取超时 + + // 添加User-Agent避免被服务器拦截 + conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ExcelExporter/1.0"); + + // 如果是HTTP连接,设置一些额外的属性 + if (conn instanceof HttpURLConnection) { + HttpURLConnection httpConn = (HttpURLConnection) conn; + httpConn.setRequestMethod("GET"); + // 不使用缓存 + httpConn.setUseCaches(false); + // 跟随重定向 + httpConn.setInstanceFollowRedirects(true); + } + + long contentLength = conn.getContentLengthLong(); + logger.debug("连接建立成功,图片大小: {} 字节", contentLength); + + // 检查内容长度是否有效 + if (contentLength == 0) { + logger.warn("图片文件为空: {}", value); + return new WriteCellData<>(new byte[0]); + } + + // 限制图片大小(防止过大文件导致内存问题) + if (contentLength > 10 * 1024 * 1024) { // 10MB限制 + logger.warn("图片文件过大 ({} bytes),跳过加载: {}", contentLength, value); + return new WriteCellData<>(new byte[0]); + } try (InputStream inputStream = conn.getInputStream()) { // byte[] bytes = FileUtils.readInputStream(inputStream, value.toString()); // 替代 FileUtils.readInputStream 的自定义方法 byte[] bytes = readInputStream(inputStream); + + // 检查读取到的数据是否为空 + if (bytes == null || bytes.length == 0) { + logger.warn("读取到空的图片数据: {}", value); + return new WriteCellData<>(new byte[0]); + } + + logger.debug("成功读取图片数据,大小: {} 字节", bytes.length); return new WriteCellData<>(bytes); } } catch (Exception e) { // 静默忽略错误,只记录日志 - logger.debug("忽略图片加载失败: {}, 原因: {}", value, e.getMessage()); + logger.warn("图片加载失败: {}, 原因: {}", value, e.getMessage(), e); // return null; // 返回null表示不写入图片 return new WriteCellData<>(new byte[0]); // 返回空数组而不是 null } @@ -66,9 +106,17 @@ public class IgnoreFailedImageConverter implements Converter { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); byte[] buffer = new byte[8192]; int bytesRead; + int totalBytes = 0; while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); + totalBytes += bytesRead; + + // 如果读取的数据过大,提前终止 + if (totalBytes > 10 * 1024 * 1024) { // 10MB限制 + logger.warn("读取的图片数据超过10MB限制,提前终止"); + break; + } } return outputStream.toByteArray(); diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceExcelExportDTO.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceExcelExportDTO.java index b0fd5ec5..7388b50e 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceExcelExportDTO.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceExcelExportDTO.java @@ -19,24 +19,28 @@ import java.net.URL; @ContentRowHeight(100) // 内容行高 public class DeviceExcelExportDTO { - @ExcelProperty("ID") - private Long id; + // @ExcelProperty("ID") + // private Long id; @ExcelProperty("设备类型") - private Long deviceType; + @ColumnWidth(20) + private String typeName; + + // @ExcelProperty("设备类型") + // private Long deviceType; // @ExcelProperty("客户号") // private Long customerId; - @ExcelProperty("所属客户") - private String customerName; + // @ExcelProperty("所属客户") + // private String customerName; @ExcelProperty("设备名称") @ColumnWidth(20) private String deviceName; @ExcelProperty(value = "设备图片", converter = IgnoreFailedImageConverter.class) - @ColumnWidth(15) // 设置图片列宽度 + @ColumnWidth(30) // 设置图片列宽度 private URL devicePic; // 使用URL类型 @ExcelProperty("设备MAC") @@ -51,28 +55,24 @@ public class DeviceExcelExportDTO { @ColumnWidth(20) private String deviceImei; - @ExcelProperty("经度") - private String longitude; + // @ExcelProperty("经度") + // private String longitude; - @ExcelProperty("纬度") - private String latitude; + // @ExcelProperty("纬度") + // private String latitude; @ExcelProperty("备注") @ColumnWidth(30) private String remark; - @ExcelProperty("设备类型名称") - @ColumnWidth(20) - private String typeName; - /** * 设备状态 * 0 失效 * 1 正常 */ - @ExcelProperty("设备状态") - @ColumnWidth(20) - private String deviceStatus; + // @ExcelProperty("设备状态") + // @ColumnWidth(20) + // private String deviceStatus; @ExcelProperty("创建时间") @ColumnWidth(20) diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceExcelImportDTO.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceExcelImportDTO.java index 146f9e81..8c4204e1 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceExcelImportDTO.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceExcelImportDTO.java @@ -16,24 +16,24 @@ import lombok.Data; @ContentRowHeight(100) // 内容行高 public class DeviceExcelImportDTO { - @ExcelProperty("设备类型") - private Long deviceType; - - @ExcelProperty("客户号") - private Long customerId; + // @ExcelProperty("设备类型") + // private Long deviceType; @ExcelProperty("设备名称") @ColumnWidth(20) private String deviceName; - @ExcelProperty(value = "设备图片", converter = ByteArrayImageConverter.class) - @ColumnWidth(15) - private byte[] devicePic; + @ExcelProperty("设备类型名称") + private String typeName; - // 添加图片写入方法 - public void setDevicePicFromBytes(byte[] bytes) { - this.devicePic = bytes; - } + // @ExcelProperty(value = "设备图片", converter = ByteArrayImageConverter.class) + // @ColumnWidth(15) + // private byte[] devicePic; + // + // // 添加图片写入方法 + // public void setDevicePicFromBytes(byte[] bytes) { + // this.devicePic = bytes; + // } @ExcelProperty("设备MAC") @ColumnWidth(20) @@ -43,24 +43,21 @@ public class DeviceExcelImportDTO { @ColumnWidth(20) private String deviceImei; - @ExcelProperty("设备SN") - @ColumnWidth(20) - private String deviceSn; + @ExcelProperty("蓝牙名称") + private String bluetoothName; - @ExcelProperty("经度") - private String longitude; - - @ExcelProperty("纬度") - private String latitude; + // @ExcelProperty("设备SN") + // @ColumnWidth(20) + // private String deviceSn; + // + // @ExcelProperty("经度") + // private String longitude; + // + // @ExcelProperty("纬度") + // private String latitude; @ExcelProperty("备注") @ColumnWidth(30) private String remark; - @ExcelProperty("设备类型名称") - private String typeName; - - @ExcelProperty("蓝牙名称") - private String bluetoothName; - } \ No newline at end of file diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/form/DeviceForm.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/form/DeviceForm.java index a51e13bd..76e9398a 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/form/DeviceForm.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/form/DeviceForm.java @@ -43,7 +43,6 @@ public class DeviceForm { @Schema(title = "蓝牙名称") private String bluetoothName; - @Schema(title = "设备IMEI") private String deviceImei; diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceAlarmVo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceAlarmVo.java index 167cf414..c56bc1e0 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceAlarmVo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceAlarmVo.java @@ -90,13 +90,13 @@ public class DeviceAlarmVo implements Serializable { * 经度 */ @ExcelProperty(value = "经度") - private Long longitude; + private Double longitude; /** * 纬度 */ @ExcelProperty(value = "纬度") - private Long latitude; + private Double latitude; /** * 报警位置 diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceFenceAccessRecordVo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceFenceAccessRecordVo.java index b61e7505..c8d114c4 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceFenceAccessRecordVo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DeviceFenceAccessRecordVo.java @@ -77,13 +77,13 @@ public class DeviceFenceAccessRecordVo implements Serializable { * 纬度 */ @ExcelProperty(value = "纬度") - private Long latitude; + private Double latitude; /** * 经度 */ @ExcelProperty(value = "经度") - private Long longitude; + private Double longitude; /** * 定位精度 diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/excel/UploadDeviceDataListener.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/excel/UploadDeviceDataListener.java index 70a0339e..cdaf8433 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/excel/UploadDeviceDataListener.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/excel/UploadDeviceDataListener.java @@ -72,7 +72,7 @@ public class UploadDeviceDataListener implements ReadListener devices, HttpServletResponse response) { @@ -36,22 +38,22 @@ public class DeviceExportService { // 转换为DTO列表 List dtoList = devices.stream().map(device -> { DeviceExcelExportDTO dto = new DeviceExcelExportDTO(); - dto.setId(device.getId()); - dto.setDeviceType(device.getDeviceType()); - dto.setCustomerName(device.getCustomerName()); + // dto.setId(device.getId()); + // dto.setDeviceType(device.getDeviceType()); + // dto.setCustomerName(device.getCustomerName()); dto.setDeviceName(device.getDeviceName()); dto.setDeviceMac(device.getDeviceMac()); // 设备IMEI dto.setDeviceImei(device.getDeviceImei()); // 蓝牙名称 dto.setBluetoothName(device.getBluetoothName()); - dto.setLongitude(device.getLongitude()); - dto.setLatitude(device.getLatitude()); + // dto.setLongitude(device.getLongitude()); + // dto.setLatitude(device.getLatitude()); dto.setRemark(device.getRemark()); dto.setTypeName(device.getTypeName()); dto.setCreateBy(device.getCreateByName()); Integer deviceStatus = device.getDeviceStatus(); - dto.setDeviceStatus(deviceStatus == 1 ? "正常" : "失效"); + // dto.setDeviceStatus(deviceStatus == 1 ? "正常" : "失效"); // 时间戳转换 dto.setCreateTime(DateUtils.format(device.getCreateTime(), "yyyy-MM-dd HH:mm:ss")); @@ -72,17 +74,31 @@ public class DeviceExportService { private void handleDevicePic(Device device, DeviceExcelExportDTO dto) { String picUrl = device.getDevicePic(); + log.info("处理设备图片,设备ID: {}, 图片URL: {}", device.getId(), picUrl); + if (picUrl != null && !picUrl.trim().isEmpty()) { try { + // 自动将HTTP转换为HTTPS以避免重定向问题 + if (picUrl.startsWith("http://")) { + picUrl = "https://" + picUrl.substring(7); + log.info("自动将HTTP转换为HTTPS: {}", picUrl); + } + // 尝试创建URL对象(会自动验证格式) - dto.setDevicePic(new URL(picUrl)); - } catch (MalformedURLException e) { + URL url = new URL(picUrl); + + dto.setDevicePic(url); + log.info("成功设置设备图片URL到DTO"); + } catch (Exception e) { // 不是有效URL时设置为null + log.info("设置设备图片失败,设备ID: {}, URL: {}, 错误: {}", device.getId(), picUrl, e.getMessage()); dto.setDevicePic(null); } } else { + log.info("设备没有设置图片,设备ID: {}", device.getId()); dto.setDevicePic(null); } } + } \ No newline at end of file diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java index 966fb11c..376584c5 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java @@ -139,8 +139,27 @@ public class DeviceServiceImpl extends ServiceImpl impleme @Override public List queryAll(DeviceQueryCriteria criteria) { - criteria.setCurrentOwnerId(LoginHelper.getUserId()); + + // 角色管理员 + Long userId = LoginHelper.getUserId(); + List roles = roleService.selectRolesAuthByUserId(userId); + boolean isAdmin = false; + if (CollectionUtil.isNotEmpty(roles)) { + for (SysRoleVo role : roles) { + if (role.getRoleKey().equals("admin")) { + isAdmin = true; + criteria.setIsAdmin(true); + break; + } + } + } + + // 只有非admin用户才设置当前用户ID条件 + if (!isAdmin) { + criteria.setCurrentOwnerId(LoginHelper.getUserId()); + } return deviceMapper.findAll(criteria); + } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceTypeServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceTypeServiceImpl.java index d23572c9..38eef642 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceTypeServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceTypeServiceImpl.java @@ -136,7 +136,23 @@ public class DeviceTypeServiceImpl extends ServiceImpl roles = roleService.selectRolesAuthByUserId(userId); + boolean isAdmin = false; + if (CollectionUtil.isNotEmpty(roles)) { + for (SysRoleVo role : roles) { + if (role.getRoleKey().contains("admin")) { + isAdmin = true; + break; + } + } + } + if (!isAdmin) { + criteria.setCustomerId(LoginHelper.getUserId()); + } + criteria.setTypeName(typeName); DeviceType deviceType = deviceTypeMapper.queryByName(criteria); return deviceType; diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceTypeMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceTypeMapper.xml index cdfbbca3..b1f70ad8 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceTypeMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceTypeMapper.xml @@ -49,8 +49,13 @@ parameterType="com.fuyuanshen.equipment.domain.query.DeviceTypeQueryCriteria"> SELECT dt.*, dg.id AS grant_id FROM device_type dt - JOIN device_type_grants dg ON dt.id = dg.device_type_id - WHERE dt.type_name = #{criteria.typeName} - AND dg.customer_id = #{criteria.customerId} + JOIN device_type_grants dg ON dt.id = dg.device_type_id + + dt.type_name = #{criteria.typeName} + + and dg.customer_id = #{criteria.customerId} + + + \ No newline at end of file From baa341c2bf7c4749edf348462b84bc6f50b1d23d Mon Sep 17 00:00:00 2001 From: DragonWenLong <552045633@qq.com> Date: Tue, 23 Sep 2025 14:21:05 +0800 Subject: [PATCH 081/160] =?UTF-8?q?feat(equipment):=20=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E7=BB=B4=E4=BF=AE=E8=AE=B0=E5=BD=95=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E7=AE=A1=E7=90=86=E5=92=8C=E6=8A=A5=E8=AD=A6=E5=A4=84?= =?UTF-8?q?=E7=90=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -重构文件哈希工具类路径并优化上传逻辑,支持秒传 - 新增维修记录图片ID列表字段及删除旧图片逻辑- 设备维修记录查询增加设备名称模糊搜索条件 -日期查询条件添加格式化注解支持 yyyy-MM-dd- MQTT规则中新增SOS与静止报警处理机制 - 实现报警生命周期管理(开始/结束)及Redis缓存控制 - 添加报警信息入库和位置解析功能 - 优化设备状态数据解析与经纬度异步存储逻辑 --- .../global/mqtt/rule/xinghan/XinghanDeviceDataRule.java | 3 +++ 1 file changed, 3 insertions(+) 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 17dce0ed..b2e89a4c 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 @@ -186,6 +186,9 @@ public class XinghanDeviceDataRule implements MqttMessageRule { */ private DeviceAlarmBo createAlarmBo(String deviceImei, AlarmType type) { Device device = deviceService.selectDeviceByImei(deviceImei); + if (device == null) { + return null; + } DeviceAlarmBo bo = new DeviceAlarmBo(); bo.setDeviceId(device.getId()); bo.setDeviceImei(deviceImei); From 91f024118120b2e640afdfac29ba9436ec64eed8 Mon Sep 17 00:00:00 2001 From: DragonWenLong <552045633@qq.com> Date: Tue, 23 Sep 2025 15:55:01 +0800 Subject: [PATCH 082/160] =?UTF-8?q?feat(mqtt):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E7=94=B5=E6=B1=A0=E7=94=B5=E9=87=8F=E7=99=BE=E5=88=86=E6=AF=94?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=B9=B6=E9=80=82=E9=85=8D=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 MqttXinghanJson 中新增 batteryPercentage 字段- 在 XinghanDeviceDataRule 中设置 batteryPercentage 值 - 适配控制百分比列表显示电池电量信息 --- .../java/com/fuyuanshen/global/mqtt/base/MqttXinghanJson.java | 4 ++++ .../global/mqtt/rule/xinghan/XinghanDeviceDataRule.java | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/MqttXinghanJson.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/MqttXinghanJson.java index 4d9513f3..a487d0df 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/MqttXinghanJson.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/MqttXinghanJson.java @@ -66,5 +66,9 @@ public class MqttXinghanJson { */ @JsonProperty("sta_system") public String stasystem; + /** + * 电量百分比(适配控制列表显示) + */ + public String batteryPercentage; } 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 b2e89a4c..6fc2f5f5 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 @@ -76,7 +76,7 @@ public class XinghanDeviceDataRule implements MqttMessageRule { // Latitude, longitude //主灯档位,激光灯档位,电量百分比,充电状态,电池剩余续航时间 MqttXinghanJson deviceStatus = objectMapper.convertValue(context.getPayloadDict(), MqttXinghanJson.class); - + deviceStatus.setBatteryPercentage(deviceStatus.getStaPowerPercent().toString()); // 发送设备状态和位置信息到Redis asyncSendDeviceDataToRedisWithFuture(context.getDeviceImei(),deviceStatus); RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.OK.getCode(), Duration.ofSeconds(20)); From 7eb5e6095a926f3e03035b2d2428bfac25400196 Mon Sep 17 00:00:00 2001 From: DragonWenLong <552045633@qq.com> Date: Thu, 25 Sep 2025 08:31:32 +0800 Subject: [PATCH 083/160] =?UTF-8?q?feat(device):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E6=8A=A5=E8=AD=A6=E7=B1=BB=E5=9E=8B=E6=9E=9A=E4=B8=BE=E5=B9=B6?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=AE=BE=E5=A4=87=E6=8C=87=E4=BB=A4=E5=A4=84?= =?UTF-8?q?=E7=90=86=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 AlarmTypeEnum 枚举类,定义 SOS 和静止报警类型 -优化 AppAuthController 中版本信息解析逻辑,增强空值处理 - 统一设备指令接口参数类型为 DeviceXinghanInstructDto - 在 DeviceXinghanBizService 中实现 SOS 报警创建逻辑 -重构 XinghanDeviceDataRule 报警处理流程,使用统一枚举类型- 添加蓝牙模式下 SOS 指令的特殊处理逻辑- 完善报警 Redis 缓存键构建和续期机制 --- .../app/controller/AppAuthController.java | 23 +++--- .../device/AppDeviceXinghanController.java | 9 ++- .../rule/xinghan/XinghanDeviceDataRule.java | 24 ++---- .../device/DeviceXinghanController.java | 9 ++- .../domain/Dto/DeviceXinghanInstructDto.java | 15 ++++ .../fuyuanshen/web/enums/AlarmTypeEnum.java | 17 +++++ .../device/DeviceXinghanBizService.java | 74 +++++++++++++++++-- 7 files changed, 129 insertions(+), 42 deletions(-) create mode 100644 fys-admin/src/main/java/com/fuyuanshen/web/domain/Dto/DeviceXinghanInstructDto.java create mode 100644 fys-admin/src/main/java/com/fuyuanshen/web/enums/AlarmTypeEnum.java diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppAuthController.java b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppAuthController.java index 33c19f6d..5e9a0ab3 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppAuthController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppAuthController.java @@ -263,18 +263,21 @@ public class AppAuthController { @GetMapping("/version") public R> getAppVersion() { List list = dictTypeService.selectDictDataByType("app_version"); + list.forEach(d -> { - String[] arr = d.getRemark().split("\\|"); - d.setDictLabel(d.getDictLabel()); // ios/android - d.setDictValue(arr[0]); // 版本号 - d.setRemark(arr[1]); // 下载地址 + // 1. 安全拆分 + String[] arr = d.getRemark() == null ? new String[0] : d.getRemark().split("\\|"); + if (arr.length < 2) { // 格式不对 + 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 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); } 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 15d9df5d..3bb70289 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 @@ -10,6 +10,7 @@ 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.domain.Dto.DeviceXinghanInstructDto; import com.fuyuanshen.web.service.device.DeviceBJQBizService; import com.fuyuanshen.web.service.device.DeviceXinghanBizService; import lombok.RequiredArgsConstructor; @@ -70,7 +71,7 @@ public class AppDeviceXinghanController extends BaseController { */ @Log(title = "xinghan指令-静电预警档位") @PostMapping("/DetectGradeSettings") - public R DetectGradeSettings(@RequestBody DeviceInstructDto params) { + public R DetectGradeSettings(@RequestBody DeviceXinghanInstructDto params) { // params 转 JSONObject appDeviceService.upDetectGradeSettings(params); return R.ok(); @@ -82,7 +83,7 @@ public class AppDeviceXinghanController extends BaseController { */ @Log(title = "xinghan指令-照明档位") @PostMapping("/LightGradeSettings") - public R LightGradeSettings(@RequestBody DeviceInstructDto params) { + public R LightGradeSettings(@RequestBody DeviceXinghanInstructDto params) { // params 转 JSONObject appDeviceService.upLightGradeSettings(params); return R.ok(); @@ -94,7 +95,7 @@ public class AppDeviceXinghanController extends BaseController { */ @Log(title = "xinghan指令-SOS档位s") @PostMapping("/SOSGradeSettings") - public R SOSGradeSettings(@RequestBody DeviceInstructDto params) { + public R SOSGradeSettings(@RequestBody DeviceXinghanInstructDto params) { // params 转 JSONObject appDeviceService.upSOSGradeSettings(params); return R.ok(); @@ -106,7 +107,7 @@ public class AppDeviceXinghanController extends BaseController { */ @Log(title = "xinghan指令-静止报警状态") @PostMapping("/ShakeBitSettings") - public R ShakeBitSettings(@RequestBody DeviceInstructDto params) { + public R ShakeBitSettings(@RequestBody DeviceXinghanInstructDto params) { // params 转 JSONObject appDeviceService.upShakeBitSettings(params); return R.ok(); 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 6fc2f5f5..cbcf2eab 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 @@ -23,6 +23,7 @@ import com.fuyuanshen.global.mqtt.base.MqttXinghanJson; import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants; import com.fuyuanshen.global.mqtt.constants.XingHanCommandTypeConstants; import com.fuyuanshen.global.mqtt.listener.domain.FunctionAccessStatus; +import com.fuyuanshen.web.enums.AlarmTypeEnum; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -127,18 +128,18 @@ public class XinghanDeviceDataRule implements MqttMessageRule { // 1. 处理 SOS 报警 handleSingleAlarm(deviceImei, sos > 0, - AlarmType.SOS); + AlarmTypeEnum.SOS); int shake = Optional.ofNullable(status.getStaShakeBit()).orElse(0); // 2. 处理 Shake 报警 handleSingleAlarm(deviceImei, shake > 0, - AlarmType.SHAKE); + AlarmTypeEnum.SHAKE); } /** * 通用:对单个报警源的“开始/结束”生命周期管理 */ - private void handleSingleAlarm(String deviceImei, boolean nowAlarming, AlarmType type) { + private void handleSingleAlarm(String deviceImei, boolean nowAlarming, AlarmTypeEnum type) { String redisKey = buildAlarmRedisKey(deviceImei, type); Long alarmId = RedisUtils.getCacheObject(redisKey); @@ -184,7 +185,7 @@ public class XinghanDeviceDataRule implements MqttMessageRule { /** * 新建报警 Bo */ - private DeviceAlarmBo createAlarmBo(String deviceImei, AlarmType type) { + private DeviceAlarmBo createAlarmBo(String deviceImei, AlarmTypeEnum type) { Device device = deviceService.selectDeviceByImei(deviceImei); if (device == null) { return null; @@ -212,24 +213,11 @@ public class XinghanDeviceDataRule implements MqttMessageRule { /** * key 构建 */ - private String buildAlarmRedisKey(String deviceImei, AlarmType type) { + private String buildAlarmRedisKey(String deviceImei, AlarmTypeEnum type) { return StrUtil.format("{}{}{}{}:alarm_id", GLOBAL_REDIS_KEY, DEVICE_KEY_PREFIX, deviceImei, type.getSuffix()); } - /** - * 报警类型枚举 - */ - @AllArgsConstructor - @Getter - enum AlarmType { - SOS("_sos", "SOS报警"), - SHAKE("_shake", "静止报警"); - - private final String suffix; - private final String desc; - } - /** * 异步发送位置信息到Redis(使用CompletableFuture) * diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceXinghanController.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceXinghanController.java index 4cc51d87..2f1c3240 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceXinghanController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceXinghanController.java @@ -10,6 +10,7 @@ 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.domain.Dto.DeviceXinghanInstructDto; import com.fuyuanshen.web.domain.vo.DeviceXinghanDetailVo; import com.fuyuanshen.web.service.device.DeviceXinghanBizService; import jakarta.validation.constraints.NotNull; @@ -80,7 +81,7 @@ public class DeviceXinghanController extends BaseController { * 3,2,1,0,分别表示高档/中档/低挡/关闭 */ @PostMapping("/DetectGradeSettings") - public R DetectGradeSettings(@RequestBody DeviceInstructDto params) { + public R DetectGradeSettings(@RequestBody DeviceXinghanInstructDto params) { // params 转 JSONObject deviceXinghanBizService.upDetectGradeSettings(params); return R.ok(); @@ -91,7 +92,7 @@ public class DeviceXinghanController extends BaseController { * 照明档位,2,1,0,分别表示弱光/强光/关闭 */ @PostMapping("/LightGradeSettings") - public R LightGradeSettings(@RequestBody DeviceInstructDto params) { + public R LightGradeSettings(@RequestBody DeviceXinghanInstructDto params) { // params 转 JSONObject deviceXinghanBizService.upLightGradeSettings(params); return R.ok(); @@ -102,7 +103,7 @@ public class DeviceXinghanController extends BaseController { * SOS档位,2,1,0, 分别表示红蓝模式/爆闪模式/关闭 */ @PostMapping("/SOSGradeSettings") - public R SOSGradeSettings(@RequestBody DeviceInstructDto params) { + public R SOSGradeSettings(@RequestBody DeviceXinghanInstructDto params) { // params 转 JSONObject deviceXinghanBizService.upSOSGradeSettings(params); return R.ok(); @@ -113,7 +114,7 @@ public class DeviceXinghanController extends BaseController { * 静止报警状态,0-未静止报警,1-正在静止报警。 */ @PostMapping("/ShakeBitSettings") - public R ShakeBitSettings(@RequestBody DeviceInstructDto params) { + public R ShakeBitSettings(@RequestBody DeviceXinghanInstructDto params) { // params 转 JSONObject deviceXinghanBizService.upShakeBitSettings(params); return R.ok(); diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/domain/Dto/DeviceXinghanInstructDto.java b/fys-admin/src/main/java/com/fuyuanshen/web/domain/Dto/DeviceXinghanInstructDto.java new file mode 100644 index 00000000..0d3b598f --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/web/domain/Dto/DeviceXinghanInstructDto.java @@ -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; +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/enums/AlarmTypeEnum.java b/fys-admin/src/main/java/com/fuyuanshen/web/enums/AlarmTypeEnum.java new file mode 100644 index 00000000..bde6fdca --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/web/enums/AlarmTypeEnum.java @@ -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; +} 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 32254592..617458cc 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 @@ -1,6 +1,7 @@ package com.fuyuanshen.web.service.device; import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; 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.equipment.domain.Device; import com.fuyuanshen.equipment.domain.DeviceType; +import com.fuyuanshen.equipment.domain.bo.DeviceAlarmBo; import com.fuyuanshen.equipment.domain.dto.AppDeviceSendMsgBo; import com.fuyuanshen.equipment.enums.LightModeEnum; import com.fuyuanshen.equipment.mapper.DeviceLogMapper; import com.fuyuanshen.equipment.mapper.DeviceMapper; import com.fuyuanshen.equipment.mapper.DeviceTypeMapper; +import com.fuyuanshen.equipment.service.IDeviceAlarmService; import com.fuyuanshen.global.mqtt.base.MqttXinghanJson; import com.fuyuanshen.global.mqtt.config.MqttGateway; import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants; import com.fuyuanshen.global.mqtt.constants.MqttConstants; 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.enums.AlarmTypeEnum; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -68,6 +73,7 @@ public class DeviceXinghanBizService { private final MqttGateway mqttGateway; private final DeviceLogMapper deviceLogMapper; private final AppPersonnelInfoRecordsMapper appPersonnelInfoRecordsMapper; + private final IDeviceAlarmService deviceAlarmService; @Autowired private ObjectMapper objectMapper; @@ -95,28 +101,39 @@ public class DeviceXinghanBizService { /** * 设置静电预警档位 */ - public void upDetectGradeSettings(DeviceInstructDto dto) { + public void upDetectGradeSettings(DeviceXinghanInstructDto dto) { sendCommand(dto, "ins_DetectGrade","静电预警档位"); } /** * 设置照明档位 */ - public void upLightGradeSettings(DeviceInstructDto dto) { + public void upLightGradeSettings(DeviceXinghanInstructDto dto) { sendCommand(dto, "ins_LightGrade","照明档位"); } /** * 设置SOS档位 */ - public void upSOSGradeSettings(DeviceInstructDto dto) { - sendCommand(dto, "ins_SOSGrade","SOS档位"); + 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档位"); + } } /** * 设置强制报警 */ - public void upShakeBitSettings(DeviceInstructDto dto) { + public void upShakeBitSettings(DeviceXinghanInstructDto dto) { sendCommand(dto, "ins_ShakeBit","强制报警"); } @@ -465,7 +482,7 @@ public class DeviceXinghanBizService { * @param payloadKey 指令负载数据的键名 * @param deviceAction 设备操作类型描述 */ - private void sendCommand(DeviceInstructDto dto, String payloadKey, String deviceAction) { + private void sendCommand(DeviceXinghanInstructDto dto, String payloadKey, String deviceAction) { long deviceId = dto.getDeviceId(); // 1. 使用Optional简化空值检查,使代码更简洁 @@ -509,6 +526,9 @@ public class DeviceXinghanBizService { deviceAction, content, AppLoginHelper.getUserId()); + + // 6. 新建报警信息 + createAlarm(device.getId(),deviceImei,payloadKey,value); } // private boolean isDeviceOffline(String imei) { @@ -516,6 +536,48 @@ public class DeviceXinghanBizService { // 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 From 5a52129fd001b741ce4a35c60697e1e4c2cdcb2a Mon Sep 17 00:00:00 2001 From: chenyouting <514333061@qq.com> Date: Thu, 25 Sep 2025 09:21:11 +0800 Subject: [PATCH 084/160] =?UTF-8?q?=E6=8E=A7=E5=88=B6=E4=B8=AD=E5=BF=83?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mqtt/receiver/ReceiverMessageHandler.java | 2 +- .../mapper/equipment/DeviceMapper.xml | 27 ++++++++++--------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java index a99633ad..9145a3bc 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java @@ -54,7 +54,7 @@ public class ReceiverMessageHandler implements MessageHandler { RedisUtils.offerDeduplicated(queueKey,dedupKey,deviceImei, Duration.ofHours(24)); //在线状态 String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ deviceImei + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ; - RedisUtils.setCacheObject(deviceOnlineStatusRedisKey, "1", Duration.ofSeconds(65)); + RedisUtils.setCacheObject(deviceOnlineStatusRedisKey, "1", Duration.ofSeconds(120)); } String state = payloadDict.getStr("state"); diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml index 6f5b7811..5b4aeede 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml @@ -239,7 +239,7 @@ where d.device_mac = #{deviceMac} From b7c81419a4d4df836cee71d5f558501dd4048de8 Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Fri, 26 Sep 2025 11:48:20 +0800 Subject: [PATCH 088/160] =?UTF-8?q?=E5=9B=B4=E6=A0=8F=E8=BF=9B=E5=87=BA?= =?UTF-8?q?=E8=AE=B0=E5=BD=95-=E5=AF=BC=E5=87=BA-=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../equipment/mapper/DeviceFenceAccessRecordMapper.java | 2 +- .../service/impl/DeviceFenceAccessRecordServiceImpl.java | 6 ++---- .../mapper/equipment/DeviceFenceAccessRecordMapper.xml | 7 +++---- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceFenceAccessRecordMapper.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceFenceAccessRecordMapper.java index 56a1ad2c..378635d9 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceFenceAccessRecordMapper.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceFenceAccessRecordMapper.java @@ -28,7 +28,7 @@ public interface DeviceFenceAccessRecordMapper extends BaseMapperPlus selectVoPageWithFenceAndDeviceName(Page page, @Param(Constants.WRAPPER) Wrapper wrapper); - List selectVoPageWithFenceAndDeviceName(@Param(Constants.WRAPPER) Wrapper wrapper); + List selectVoPageWithFenceAndDeviceName(@Param(Constants.WRAPPER) Wrapper wrapper,@Param("fenceName") String fenceName); /** * 分页查询围栏进出记录列表(纯XML形式) diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceFenceAccessRecordServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceFenceAccessRecordServiceImpl.java index c46dc221..1ab86d3a 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceFenceAccessRecordServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceFenceAccessRecordServiceImpl.java @@ -69,7 +69,7 @@ public class DeviceFenceAccessRecordServiceImpl implements IDeviceFenceAccessRec @Override public List queryList(DeviceFenceAccessRecordBo bo) { LambdaQueryWrapper lqw = buildQueryWrapper(bo); - return baseMapper.selectVoPageWithFenceAndDeviceName(lqw); + return baseMapper.selectVoPageWithFenceAndDeviceName(lqw, bo.getFenceName()); } @@ -86,9 +86,7 @@ public class DeviceFenceAccessRecordServiceImpl implements IDeviceFenceAccessRec lqw.eq(bo.getAccuracy() != null, DeviceFenceAccessRecord::getAccuracy, bo.getAccuracy()); lqw.eq(bo.getEventTime() != null, DeviceFenceAccessRecord::getEventTime, bo.getEventTime()); lqw.eq(bo.getCreateTime() != null, DeviceFenceAccessRecord::getCreateTime, bo.getCreateTime()); - if (StringUtils.isNotBlank(bo.getFenceName())) { - params.put("fenceName", bo.getFenceName()); - } + return lqw; } diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceFenceAccessRecordMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceFenceAccessRecordMapper.xml index 27be7c7f..8a13a040 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceFenceAccessRecordMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceFenceAccessRecordMapper.xml @@ -24,16 +24,15 @@ LEFT JOIN device d ON r.device_id = d.id ${ew.customSqlSegment} - - - AND f.name LIKE CONCAT('%', #{ew.params.fenceName}, '%') - + + AND f.name LIKE CONCAT('%', #{fenceName}, '%') ORDER BY r.id ASC + - + + + + + + + + + + + \ No newline at end of file diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceFenceAccessRecordMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceFenceAccessRecordMapper.xml index 8a13a040..375bec60 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceFenceAccessRecordMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceFenceAccessRecordMapper.xml @@ -92,4 +92,15 @@ ORDER BY r.event_time DESC + + + + + \ No newline at end of file diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml index 3d42b7ab..f4681e34 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml @@ -1,5 +1,5 @@ - - + + @@ -319,6 +319,32 @@ a.create_time DESC + + + + + + + + + diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml index f4681e34..1e4ece18 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml @@ -404,7 +404,7 @@ SELECT COUNT (1) FROM device_alarm WHERE device_action = 2 - ) AS alarmManual + ) AS alarmAuto , ( SELECT COUNT (1) FROM device_alarm From a4596b9c90b636bef062812e9723cf5ebf240ed6 Mon Sep 17 00:00:00 2001 From: DragonWenLong <552045633@qq.com> Date: Sun, 28 Sep 2025 16:11:34 +0800 Subject: [PATCH 093/160] =?UTF-8?q?feat(device):=20=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E6=89=B9=E9=87=8F=E6=8E=A7=E5=88=B6=E6=8C=87?= =?UTF-8?q?=E4=BB=A4=E5=8F=91=E9=80=81=E5=8A=9F=E8=83=BD-=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E6=89=B9=E9=87=8F=E5=8F=91=E9=80=81=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E6=8E=A7=E5=88=B6=E6=8C=87=E4=BB=A4=E6=96=B9=E6=B3=95=20sendCo?= =?UTF-8?q?mmandBatch-=20=E6=94=AF=E6=8C=81=E8=AE=BE=E5=A4=87=E7=A6=BB?= =?UTF-8?q?=E7=BA=BF=E7=8A=B6=E6=80=81=E6=A3=80=E6=9F=A5=E5=92=8C=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E5=A4=84=E7=90=86=20-=20=E6=B7=BB=E5=8A=A0=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E6=93=8D=E4=BD=9C=E6=97=A5=E5=BF=97=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E5=92=8C=E6=8A=A5=E8=AD=A6=E5=88=9B=E5=BB=BA-=20=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E8=AE=BE=E5=A4=87SOS=E6=A1=A3=E4=BD=8D=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E8=AE=BE=E7=BD=AE=E6=8E=A5=E5=8F=A3=20-=20=E5=9C=A8?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E6=8C=87=E4=BB=A4=E5=A4=84=E7=90=86=E4=B8=AD?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=B6=88=E6=81=AF=E5=8E=BB=E9=87=8D=E6=9C=BA?= =?UTF-8?q?=E5=88=B6=20-=20=E4=BC=98=E5=8C=96=E8=AE=BE=E5=A4=87=E6=8A=A5?= =?UTF-8?q?=E8=AD=A6=E5=A4=84=E7=90=86=E7=9A=84=E5=88=86=E5=B8=83=E5=BC=8F?= =?UTF-8?q?=E9=94=81=E9=80=BB=E8=BE=91=20-=20=E5=AE=8C=E5=96=84=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E6=95=B0=E6=8D=AE=E8=A7=84=E5=88=99=E4=B8=AD=E7=9A=84?= =?UTF-8?q?=E5=B9=B6=E5=8F=91=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rule/xinghan/XinghanBootLogoRule.java | 8 +++ .../rule/xinghan/XinghanDeviceDataRule.java | 39 +++++++++-- .../xinghan/XinghanSendAlarmMessageRule.java | 7 ++ .../mqtt/rule/xinghan/XinghanSendMsgRule.java | 8 +++ .../device/DeviceXinghanController.java | 11 +++ .../domain/Dto/DeviceXinghanInstructDto.java | 3 + .../device/DeviceXinghanBizService.java | 70 +++++++++++++++++++ .../web/service/impl/AppSmsAuthStrategy.java | 2 +- 8 files changed, 143 insertions(+), 5 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 2b92071f..764e7921 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 @@ -62,6 +62,14 @@ public class XinghanBootLogoRule implements MqttMessageRule { String respText = payload.getStaPicTrans(); log.warn("设备上报LOGO:{}", respText); + // --- 去重 START --- + String dedupKey = "xd:MSG:LOGO:" + ctx.getDeviceImei() + ":" + respText; + boolean first = RedisUtils.setObjectIfAbsent(dedupKey, "1", Duration.ofSeconds(10)); + if (!first) { + log.warn("重复消息丢弃 {}", dedupKey); + return; + } + // 1. great! —— 成功标记 if ("great!".equalsIgnoreCase(respText)) { RedisUtils.setCacheObject(functionAccessKey, 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 cbcf2eab..8739b3a7 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 @@ -28,12 +28,15 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; import org.springframework.stereotype.Component; import org.springframework.beans.factory.annotation.Autowired; import java.time.Duration; import java.util.*; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; import static com.fuyuanshen.common.core.constant.GlobalConstants.FUNCTION_ACCESS_KEY; import static com.fuyuanshen.common.core.constant.GlobalConstants.GLOBAL_REDIS_KEY; @@ -144,6 +147,10 @@ public class XinghanDeviceDataRule implements MqttMessageRule { Long alarmId = RedisUtils.getCacheObject(redisKey); + String lockKey = redisKey + ":lock"; // 分布式锁 key + RedissonClient client = RedisUtils.getClient(); // 唯一用到的“旧”入口 + RLock lock = client.getLock(lockKey); + // ---------- 情况 1:当前正在报警 ---------- if (nowAlarming) { // 已存在未结束报警 -> 什么都不做(同一条报警) @@ -152,10 +159,34 @@ public class XinghanDeviceDataRule implements MqttMessageRule { 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分钟后结束过期 + // 需要新建,抢锁 + boolean locked = false; + try { + locked = lock.tryLock(3, TimeUnit.SECONDS); // 最多等 3 s + if (!locked) { // 抢不到直接放弃 + return; + } + // 锁内二次校验(double-check) + alarmId = RedisUtils.getCacheObject(redisKey); + if (alarmId != null) { + return; // 并发线程已建好 + } + + // 不存在 -> 新建 + DeviceAlarmBo bo = createAlarmBo(deviceImei, type); + if (bo == null){ + return; + } + deviceAlarmService.insertByBo(bo); + RedisUtils.setCacheObject(redisKey, bo.getId(), Duration.ofMinutes(10)); // 5分钟后结束过期 + }catch (InterruptedException ignore) { + // 立即中断并退出,禁止继续往下走 + Thread.currentThread().interrupt(); + } finally { + if (locked && lock.isHeldByCurrentThread()) { + lock.unlock(); + } + } return; } 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 3a4b368e..851d52c2 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 @@ -55,6 +55,13 @@ public class XinghanSendAlarmMessageRule implements MqttMessageRule { String respText = payload.getStaBreakNews(); log.info("设备上报紧急通知握手: {} ", respText); + // --- 去重 START --- + String dedupKey = "xd:ALARM:dedup:" + ctx.getDeviceImei() + ":" + respText; + boolean first = RedisUtils.setObjectIfAbsent(dedupKey, "1", Duration.ofSeconds(10)); + if (!first) { + log.warn("重复消息丢弃 {}", dedupKey); + return; + } // 1. cover! —— 成功标记 if ("cover!".equalsIgnoreCase(respText)) { 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 d68d18e3..800a5c41 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 @@ -55,6 +55,14 @@ public class XinghanSendMsgRule implements MqttMessageRule { String respText = payload.getStaTexTrans(); log.info("设备上报人员信息: {} ", respText); + // --- 去重 START --- + String dedupKey = "xd:MSG:dedup:" + ctx.getDeviceImei() + ":" + respText; + boolean first = RedisUtils.setObjectIfAbsent(dedupKey, "1", Duration.ofSeconds(10)); + if (!first) { + log.warn("重复消息丢弃 {}", dedupKey); + return; + } + // 1. genius! —— 成功标记 if ("genius!".equalsIgnoreCase(respText)) { RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.OK.getCode(), Duration.ofSeconds(20)); diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceXinghanController.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceXinghanController.java index 2f1c3240..53d54679 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceXinghanController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceXinghanController.java @@ -109,6 +109,17 @@ public class DeviceXinghanController extends BaseController { return R.ok(); } + /** + * SOS档位 批量 + * SOS档位,2,1,0, 分别表示红蓝模式/爆闪模式/关闭 + */ + @PostMapping("/SOSGradeSettingsBatch") + public R SOSGradeSettingsBatch(@RequestBody DeviceXinghanInstructDto params) { + // params 转 JSONObject + deviceXinghanBizService.sendCommandBatch(params,"ins_SOSGrade","SOS档位"); + return R.ok(); + } + /** * 静止报警状态 * 静止报警状态,0-未静止报警,1-正在静止报警。 diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/domain/Dto/DeviceXinghanInstructDto.java b/fys-admin/src/main/java/com/fuyuanshen/web/domain/Dto/DeviceXinghanInstructDto.java index 0d3b598f..3985ef93 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/domain/Dto/DeviceXinghanInstructDto.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/domain/Dto/DeviceXinghanInstructDto.java @@ -2,6 +2,8 @@ package com.fuyuanshen.web.domain.Dto; import lombok.Data; +import java.util.List; + @Data public class DeviceXinghanInstructDto { private Long deviceId; @@ -12,4 +14,5 @@ public class DeviceXinghanInstructDto { */ private String instructValue; private Boolean isBluetooth = false; + private List deviceIds; } 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 617458cc..6e53f77a 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 @@ -28,6 +28,7 @@ import com.fuyuanshen.common.json.utils.JsonUtils; import com.fuyuanshen.common.redis.utils.RedisUtils; import com.fuyuanshen.common.satoken.utils.AppLoginHelper; import com.fuyuanshen.equipment.domain.Device; +import com.fuyuanshen.equipment.domain.DeviceLog; import com.fuyuanshen.equipment.domain.DeviceType; import com.fuyuanshen.equipment.domain.bo.DeviceAlarmBo; import com.fuyuanshen.equipment.domain.dto.AppDeviceSendMsgBo; @@ -531,6 +532,75 @@ public class DeviceXinghanBizService { createAlarm(device.getId(),deviceImei,payloadKey,value); } + /** + * 批量发送设备控制指令 + * + * @param dto 设备ID列表 + * @param payloadKey 指令负载数据的键名 + * @param deviceAction 设备操作类型描述 + */ + @Transactional(rollbackFor = Exception.class) // 1. 事务注解 + public void sendCommandBatch(DeviceXinghanInstructDto dto, String payloadKey, String deviceAction) { + List errorMessages = Collections.synchronizedList(new ArrayList<>()); + int value; + try { + value = Integer.parseInt(dto.getInstructValue()); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("指令值格式不正确,必须为整数。", e); + } + Map> payload = Map.of(payloadKey, List.of(value)); + + // 一次性查询所有设备信息 + List devices = deviceMapper.selectList( + new QueryWrapper().lambda().in(Device::getId, dto.getDeviceIds()) + ); + // 日志信息 + String contentText = resolveGradeDesc(payloadKey, value); + + List logs = new ArrayList<>(); + try { + for (Device device : devices) { + String deviceImei = device.getDeviceImei(); + String deviceName = device.getDeviceName(); + + // 2. 提前进行设备状态检查,逻辑更清晰 + if (isDeviceOffline(deviceImei)) { + throw new ServiceException("设备已断开连接:" + deviceName); + } + + String topic = MqttConstants.GLOBAL_PUB_KEY + deviceImei; + String json = JsonUtils.toJsonString(payload); + + mqttGateway.sendMsgToMqtt(topic, 1, json); + log.info("发送指令成功 => topic:{}, payload:{}", topic, json); + + // 创建设备日志实体 + DeviceLog deviceLog = new DeviceLog(); + deviceLog.setDeviceId(device.getId()); + deviceLog.setDeviceAction(deviceAction); + deviceLog.setContent(contentText); + deviceLog.setCreateBy(AppLoginHelper.getUserId()); + deviceLog.setDeviceName(deviceName); + deviceLog.setCreateTime(new Date()); + logs.add(deviceLog); + + createAlarm(device.getId(), deviceImei, payloadKey, value); + } + deviceLogMapper.insertBatch(logs); + } catch (ServiceException e) { + // 捕获并重新抛出自定义异常,避免内层异常被外层泛化捕获 + log.error("批量发送指令失败: {}", e.getMessage(), e); + throw e; + } catch (Exception e) { + log.error("批量发送指令发生未知错误", e); + throw new ServiceException("批量发送指令失败"); + } + } + + /** + * 检查设备是否离线 + */ + // private boolean isDeviceOffline(String imei) { // // 原方法名语义相反,这里取反,使含义更清晰 // return getDeviceStatus(imei); diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/AppSmsAuthStrategy.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/AppSmsAuthStrategy.java index eb9ea8d3..862cfbd9 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/AppSmsAuthStrategy.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/AppSmsAuthStrategy.java @@ -54,7 +54,7 @@ public class AppSmsAuthStrategy implements IAuthStrategy { String phonenumber = loginBody.getPhonenumber(); String smsCode = loginBody.getSmsCode(); AppLoginUser loginUser = TenantHelper.dynamic(tenantId, () -> { -// loginService.checkLogin(LoginType.SMS, tenantId, phonenumber, () -> !validateSmsCode(tenantId, phonenumber, smsCode)); + loginService.checkLogin(LoginType.SMS, tenantId, phonenumber, () -> !validateSmsCode(tenantId, phonenumber, smsCode)); AppUserVo user = loadUserByPhonenumber(phonenumber); if (ObjectUtil.isNull(user)) { //新增Appuser From 233e0e32b050e6702c58925aec9f9921e7b4d936 Mon Sep 17 00:00:00 2001 From: chenyouting <514333061@qq.com> Date: Sun, 28 Sep 2025 16:19:28 +0800 Subject: [PATCH 094/160] =?UTF-8?q?=E5=8F=91=E9=80=81=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E5=92=8C=E5=91=8A=E8=AD=A6=E6=95=85=E9=9A=9C2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/service/AppDeviceShareService.java | 7 +++--- .../mqtt/receiver/ReceiverMessageHandler.java | 25 ++++++++++++------- .../global/mqtt/rule/bjq/BjqAlarmRule.java | 11 +++----- .../global/mqtt/rule/bjq/BjqModeRule.java | 5 +++- .../mqtt/rule/bjq/BjqSendMessageRule.java | 9 ++++--- .../web/service/device/DeviceBizService.java | 10 +++++--- .../mapper/equipment/DeviceMapper.xml | 2 ++ 7 files changed, 41 insertions(+), 28 deletions(-) diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/service/AppDeviceShareService.java b/fys-admin/src/main/java/com/fuyuanshen/app/service/AppDeviceShareService.java index 44d1ba42..6f9b21b8 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/service/AppDeviceShareService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/service/AppDeviceShareService.java @@ -73,10 +73,11 @@ public class AppDeviceShareService { private static void buildDeviceStatus(AppDeviceShareVo item) { // 设备在线状态 String onlineStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + item.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX); - if (StringUtils.isNotBlank(onlineStatus)) { - + if("1".equals(onlineStatus)){ item.setOnlineStatus(1); - } else { + }else if("2".equals(onlineStatus)){ + item.setOnlineStatus(2); + }else{ item.setOnlineStatus(0); } String deviceStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + item.getDeviceImei() + DEVICE_STATUS_KEY_PREFIX); diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java index 13f86d26..b6458150 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java @@ -48,17 +48,24 @@ public class ReceiverMessageHandler implements MessageHandler { } String[] subStr = receivedTopic.split("/"); String deviceImei = subStr[1]; - if(StringUtils.isNotBlank(deviceImei)){ - String queueKey = MqttMessageQueueConstants.MQTT_MESSAGE_QUEUE_KEY; - String dedupKey = MqttMessageQueueConstants.MQTT_MESSAGE_DEDUP_KEY; - RedisUtils.offerDeduplicated(queueKey,dedupKey,deviceImei, Duration.ofHours(24)); - //在线状态 - String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ deviceImei + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ; - RedisUtils.setCacheObject(deviceOnlineStatusRedisKey, "1", Duration.ofSeconds(303)); - } - String state = payloadDict.getStr("state"); Object[] convertArr = ImageToCArrayConverter.convertByteStringToMixedObjectArray(state); + if(StringUtils.isNotBlank(deviceImei)){ + String arr1 = convertArr[0].toString(); + String arr2 = convertArr[1].toString(); + if("12".equals(arr1) && "0".equals(arr2)){ + return; + }else{ + String queueKey = MqttMessageQueueConstants.MQTT_MESSAGE_QUEUE_KEY; + String dedupKey = MqttMessageQueueConstants.MQTT_MESSAGE_DEDUP_KEY; + RedisUtils.offerDeduplicated(queueKey,dedupKey,deviceImei, Duration.ofHours(24)); + //在线状态 + String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ deviceImei + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ; + RedisUtils.setCacheObject(deviceOnlineStatusRedisKey, "1", Duration.ofSeconds(303)); + } + } + + if (convertArr.length > 0) { Byte val1 = (Byte) convertArr[0]; diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqAlarmRule.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqAlarmRule.java index 3270e469..75de5d32 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqAlarmRule.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqAlarmRule.java @@ -62,18 +62,13 @@ public class BjqAlarmRule implements MqttMessageRule { RedisUtils.setCacheObject(deviceRedisKey, convertValue); String sendMessageIng = GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + context.getDeviceImei() + ":messageSending"; - RedisUtils.setCacheObject(sendMessageIng, "1", Duration.ofSeconds(120)); + RedisUtils.setCacheObject(sendMessageIng, "1", Duration.ofDays(1)); }else if ("0".equals(convertValue)){ - RedisUtils.deleteObject(deviceRedisKey); + String sendMessageIng = GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + context.getDeviceImei() + ":messageSending"; + RedisUtils.deleteObject(sendMessageIng); } } RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.OK.getCode(), Duration.ofSeconds(20)); - if("200".equals(convertValue)){ - String sendMessageIng = GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + context.getDeviceImei() + ":messageSending"; -// RedisUtils.setCacheObject(sendMessageIng, "1", Duration.ofDays(1)); - RedisUtils.deleteObject(sendMessageIng); - return; - } // 保存告警信息 String deviceImei = context.getDeviceImei(); // 设备告警状态 0:解除告警 1:报警产生 diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqModeRule.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqModeRule.java index 41f35611..56bb514f 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqModeRule.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqModeRule.java @@ -46,7 +46,9 @@ public class BjqModeRule implements MqttMessageRule { String mainLightMode = convertArr[1].toString(); String batteryRemainingTime = convertArr[2].toString(); if(StringUtils.isNotBlank(mainLightMode)){ + log.info("设备离线mainLightMode:{}",mainLightMode); if("0".equals(mainLightMode)){ + //设备离线 String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ context.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ; RedisUtils.setCacheObject(deviceOnlineStatusRedisKey, "0"); @@ -59,7 +61,8 @@ public class BjqModeRule implements MqttMessageRule { UpdateWrapper updateWrapper = new UpdateWrapper<>(); updateWrapper.eq("device_imei", context.getDeviceImei()); updateWrapper.set("online_status", 2); - deviceService.update(); + deviceService.update(updateWrapper); + RedisUtils.deleteObject(sendMessageIng); } } // 发送设备状态和位置信息到Redis diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqSendMessageRule.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqSendMessageRule.java index 609a1cee..d2d3620b 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqSendMessageRule.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqSendMessageRule.java @@ -43,7 +43,11 @@ public class BjqSendMessageRule implements MqttMessageRule { public void execute(MqttRuleContext context) { String functionAccess = FUNCTION_ACCESS_KEY + context.getDeviceImei(); try { - Integer val2 = (Integer) context.getConvertArr()[1]; + +// Byte val2 = (Byte) context.getConvertArr()[1]; + String val2Str = context.getConvertArr()[1].toString(); + int val2 = Integer.parseInt(val2Str); + System.out.println("收到设备信息命令:"+val2); if (val2 == 100) { RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.OK.getCode(), Duration.ofSeconds(20)); return; @@ -51,7 +55,6 @@ public class BjqSendMessageRule implements MqttMessageRule { if(val2==200){ String sendMessageIng = GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + context.getDeviceImei() + ":messageSending"; -// RedisUtils.setCacheObject(sendMessageIng, "1", Duration.ofDays(1)); RedisUtils.deleteObject(sendMessageIng); return; } @@ -61,7 +64,7 @@ public class BjqSendMessageRule implements MqttMessageRule { if (StringUtils.isEmpty(data)) { return; } - + byte[] arr = ImageToCArrayConverter.convertStringToByteArray(data); byte[] specificChunk = ImageToCArrayConverter.getChunk(arr, (val2 - 1), 512); log.info("第{}块数据大小: {} 字节", val2, specificChunk.length); diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBizService.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBizService.java index 90de863a..71cb591b 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBizService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBizService.java @@ -90,9 +90,10 @@ public class DeviceBizService { //设备在线状态 String onlineStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ item.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX); - if(StringUtils.isNotBlank(onlineStatus)){ - + if("1".equals(onlineStatus)){ item.setOnlineStatus(1); + }else if("2".equals(onlineStatus)){ + item.setOnlineStatus(2); }else{ item.setOnlineStatus(0); } @@ -131,9 +132,10 @@ public class DeviceBizService { //设备在线状态 String onlineStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ item.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX); - if(StringUtils.isNotBlank(onlineStatus)){ - + if("1".equals(onlineStatus)){ item.setOnlineStatus(1); + }else if("2".equals(onlineStatus)){ + item.setOnlineStatus(2); }else{ item.setOnlineStatus(0); } diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml index f4681e34..9ededee0 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml @@ -241,9 +241,11 @@ - SELECT device_name AS deviceName, COUNT(*) AS frequency @@ -462,6 +462,17 @@ GROUP BY device_name ORDER BY frequency DESC + + + \ No newline at end of file From 107ddf8851f4f432a8cbc996bdd97b553ede1900 Mon Sep 17 00:00:00 2001 From: DragonWenLong <552045633@qq.com> Date: Mon, 27 Oct 2025 09:00:36 +0800 Subject: [PATCH 134/160] =?UTF-8?q?feat(app):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E9=98=B2=E9=87=8D=E5=A4=8D=E6=8F=90=E4=BA=A4=E6=B3=A8=E8=A7=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在视频上传接口添加 @RepeatSubmit 注解 - 在音频上传接口添加 @RepeatSubmit 注解 - 在文字转音频接口添加 @RepeatSubmit 注解 - 设置重复提交间隔为2 秒- 添加重复提交提示信息"请勿重复提交!" --- .../com/fuyuanshen/app/controller/AppVideoController.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java index 64538fb3..11bc7540 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java @@ -3,6 +3,7 @@ package com.fuyuanshen.app.controller; import com.fuyuanshen.app.service.AudioProcessService; import com.fuyuanshen.app.service.VideoProcessService; import com.fuyuanshen.common.core.domain.R; +import com.fuyuanshen.common.idempotent.annotation.RepeatSubmit; import com.fuyuanshen.common.web.core.BaseController; import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; @@ -13,6 +14,7 @@ import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.util.Base64; import java.util.List; +import java.util.concurrent.TimeUnit; /** * APP 视频处理控制器 @@ -27,6 +29,7 @@ public class AppVideoController extends BaseController { private final AudioProcessService audioProcessService; @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS,message = "请勿重复提交!") public R> uploadVideo(@RequestParam("file") MultipartFile file) { return R.ok(videoProcessService.processVideo(file)); } @@ -35,6 +38,7 @@ public class AppVideoController extends BaseController { * 上传音频文件并转码 */ @PostMapping(value = "/audio", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS,message = "请勿重复提交!") public R> uploadAudio(@RequestParam("file") MultipartFile file) { return R.ok(audioProcessService.processAudio(file)); } @@ -43,6 +47,7 @@ public class AppVideoController extends BaseController { * 文字转音频TTS服务 */ @GetMapping("/audioTTS") + @RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS,message = "请勿重复提交!") public R> uploadAudioTTS(@RequestParam String text) throws IOException { return R.ok(audioProcessService.generateStandardPcmData(text)); } From 467a7befb18447f403eacd99785db260feef0bed Mon Sep 17 00:00:00 2001 From: chenyouting <514333061@qq.com> Date: Tue, 28 Oct 2025 10:29:12 +0800 Subject: [PATCH 135/160] =?UTF-8?q?=E6=8F=90=E4=BA=A42?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java index c6ea5654..55f24c6f 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java @@ -53,7 +53,7 @@ public class ReceiverMessageHandler implements MessageHandler { if(StringUtils.isNotBlank(deviceImei)){ String queueKey = MqttMessageQueueConstants.MQTT_MESSAGE_QUEUE_KEY; String dedupKey = MqttMessageQueueConstants.MQTT_MESSAGE_DEDUP_KEY; - RedisUtils.offerDeduplicated(queueKey,dedupKey,deviceImei, Duration.ofHours(24)); + RedisUtils.offerDeduplicated(queueKey,dedupKey,deviceImei, Duration.ofSeconds(900)); //在线状态 String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ deviceImei + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ; RedisUtils.setCacheObject(deviceOnlineStatusRedisKey, "1", Duration.ofSeconds(360)); From df28eed305437531197fce71d4491bf453dd3343 Mon Sep 17 00:00:00 2001 From: chenyouting <514333061@qq.com> Date: Tue, 28 Oct 2025 10:51:05 +0800 Subject: [PATCH 136/160] =?UTF-8?q?prod=E9=85=8D=E7=BD=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fys-admin/src/main/resources/application-prod.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fys-admin/src/main/resources/application-prod.yml b/fys-admin/src/main/resources/application-prod.yml index 349574a4..97df444a 100644 --- a/fys-admin/src/main/resources/application-prod.yml +++ b/fys-admin/src/main/resources/application-prod.yml @@ -281,9 +281,9 @@ justauth: mqtt: username: admin password: #YtvpSfCNG - url: tcp://47.120.79.150:2883 + url: tcp://47.120.79.150:3883 subClientId: fys_subClient - subTopic: A/#,B/#,worker/location/# + subTopic: A/# pubTopic: B/# pubClientId: fys_pubClient From f4369f7581ee846639faa9273be0996c256b7454 Mon Sep 17 00:00:00 2001 From: chenyouting <514333061@qq.com> Date: Thu, 30 Oct 2025 14:27:01 +0800 Subject: [PATCH 137/160] =?UTF-8?q?=E5=88=86=E4=BA=AB=E8=AE=BE=E5=A4=87bug?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/fuyuanshen/app/service/AppDeviceShareService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/service/AppDeviceShareService.java b/fys-admin/src/main/java/com/fuyuanshen/app/service/AppDeviceShareService.java index 6f9b21b8..d49ad945 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/service/AppDeviceShareService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/service/AppDeviceShareService.java @@ -216,6 +216,7 @@ public class AppDeviceShareService { uw.eq("phonenumber", bo.getPhonenumber()); uw.set("permission", bo.getPermission()); uw.set("update_by", userId); + uw.set("create_by", userId); uw.set("update_time", new Date()); return appDeviceShareMapper.update(uw); From c4957aa3aa717d7178c83ae957c34288e512a66f Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Fri, 31 Oct 2025 08:50:08 +0800 Subject: [PATCH 138/160] =?UTF-8?q?=E9=9F=B3=E9=A2=91=E5=A4=84=E7=90=86?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/fuyuanshen/DromaraApplication.java | 40 ++++---- .../app/service/AudioProcessService.java | 56 +++++++++++ .../src/main/resources/application-dev.yml | 93 ++++++++----------- .../src/main/resources/application-prod.yml | 21 ----- .../equipment/domain/vo/DataOverviewVo.java | 2 +- .../service/impl/DeviceServiceImpl.java | 34 ------- 6 files changed, 117 insertions(+), 129 deletions(-) diff --git a/fys-admin/src/main/java/com/fuyuanshen/DromaraApplication.java b/fys-admin/src/main/java/com/fuyuanshen/DromaraApplication.java index ca41060b..999701d6 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/DromaraApplication.java +++ b/fys-admin/src/main/java/com/fuyuanshen/DromaraApplication.java @@ -1,24 +1,24 @@ -package com.fuyuanshen; + package com.fuyuanshen; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup; -import org.springframework.scheduling.annotation.EnableScheduling; + import org.springframework.boot.SpringApplication; + import org.springframework.boot.autoconfigure.SpringBootApplication; + import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup; + import org.springframework.scheduling.annotation.EnableScheduling; -/** - * 启动程序 - * - * @author Lion Li - */ -@SpringBootApplication -@EnableScheduling -public class DromaraApplication { + /** + * 启动程序 + * + * @author Lion Li + */ + @SpringBootApplication + @EnableScheduling + public class DromaraApplication { + + public static void main(String[] args) { + SpringApplication application = new SpringApplication(DromaraApplication.class); + application.setApplicationStartup(new BufferingApplicationStartup(2048)); + application.run(args); + System.out.println("(♥◠‿◠)ノ゙ fys-Vue-Plus启动成功 ლ(´ڡ`ლ)゙"); + } - public static void main(String[] args) { - SpringApplication application = new SpringApplication(DromaraApplication.class); - application.setApplicationStartup(new BufferingApplicationStartup(2048)); - application.run(args); - System.out.println("(♥◠‿◠)ノ゙ fys-Vue-Plus启动成功 ლ(´ڡ`ლ)゙"); } - -} diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/service/AudioProcessService.java b/fys-admin/src/main/java/com/fuyuanshen/app/service/AudioProcessService.java index a36e8f11..862fd5b0 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/service/AudioProcessService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/service/AudioProcessService.java @@ -8,6 +8,7 @@ import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; import java.util.Arrays; @@ -101,6 +102,12 @@ public class AudioProcessService { // log.info("测试文件已保存: {}", savedPath); // } + // 保存WAV文件到本地 + String savedPath = saveByteArrayToFile(pcmData, "tts_output.wav"); + if (savedPath != null) { + log.info("WAV文件已保存: {}", savedPath); + } + // 将byte[]转换为16进制字符串列表 List hexList = audioProcessUtil.bytesToHexList(pcmData); @@ -113,6 +120,55 @@ public class AudioProcessService { } } + public String saveWavFileLocally(String text, String filename) throws IOException { + // 参数校验 + if (text == null || text.trim().isEmpty()) { + throw new IllegalArgumentException("文本内容不能为空"); + } + + if (filename == null || filename.trim().isEmpty()) { + filename = "tts_output.wav"; // 默认文件名 + } + + try { + // 生成PCM数据 + byte[] rawPcmData = alibabaTTSUtil.generateStandardPcmData(text); + + // 转换为标准WAV格式(添加44字节头部) + byte[] wavData = audioProcessUtil.rawPcmToStandardWav(rawPcmData); + + // 保存到本地文件 + String filePath = saveByteArrayToFile(wavData, filename); + + log.info("WAV文件已保存: {}", filePath); + return filePath; + } catch (Exception e) { + log.error("保存WAV文件失败: {}", e.getMessage(), e); + throw new IOException("保存WAV文件失败", e); + } + } + + private String saveByteArrayToFile(byte[] data, String filename) throws IOException { + // 确定保存路径(可以是临时目录或指定目录) + String directory = System.getProperty("java.io.tmpdir"); // 使用系统临时目录 + File dir = new File(directory); + if (!dir.exists()) { + dir.mkdirs(); + } + + // 创建完整文件路径 + File file = new File(dir, filename); + + // 写入文件 + try (FileOutputStream fos = new FileOutputStream(file)) { + fos.write(data); + } + + return file.getAbsolutePath(); + } + + + /** * 验证音频文件 */ diff --git a/fys-admin/src/main/resources/application-dev.yml b/fys-admin/src/main/resources/application-dev.yml index 40b7f28f..bf89eda2 100644 --- a/fys-admin/src/main/resources/application-dev.yml +++ b/fys-admin/src/main/resources/application-dev.yml @@ -52,32 +52,32 @@ spring: url: jdbc:mysql://47.120.79.150:3306/fys-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true username: root password: Jq_123456# -# # 从库数据源 -# slave: -# lazy: true -# type: ${spring.datasource.type} -# driverClassName: com.mysql.cj.jdbc.Driver -# url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true -# username: -# password: -# oracle: -# type: ${spring.datasource.type} -# driverClassName: oracle.jdbc.OracleDriver -# url: jdbc:oracle:thin:@//localhost:1521/XE -# username: ROOT -# password: root -# postgres: -# type: ${spring.datasource.type} -# driverClassName: org.postgresql.Driver -# url: jdbc:postgresql://localhost:5432/postgres?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true -# username: root -# password: root -# sqlserver: -# type: ${spring.datasource.type} -# driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver -# url: jdbc:sqlserver://localhost:1433;DatabaseName=tempdb;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true -# username: SA -# password: root + # # 从库数据源 + # slave: + # lazy: true + # type: ${spring.datasource.type} + # driverClassName: com.mysql.cj.jdbc.Driver + # url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true + # username: + # password: + # oracle: + # type: ${spring.datasource.type} + # driverClassName: oracle.jdbc.OracleDriver + # url: jdbc:oracle:thin:@//localhost:1521/XE + # username: ROOT + # password: root + # postgres: + # type: ${spring.datasource.type} + # driverClassName: org.postgresql.Driver + # url: jdbc:postgresql://localhost:5432/postgres?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true + # username: root + # password: root + # sqlserver: + # type: ${spring.datasource.type} + # driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver + # url: jdbc:sqlserver://localhost:1433;DatabaseName=tempdb;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true + # username: SA + # password: root hikari: # 最大连接池数量 maxPoolSize: 20 @@ -177,12 +177,12 @@ sms: access-key-id: LTAI5tJdDNpZootsPQ5hdELx # 称为accessSecret有些称之为apiSecret access-key-secret: mU4WtffcCXpHPz5tLwQpaGtLsJXONt - #模板ID 非必须配置,如果使用sendMessage的快速发送需此配置 + #模板ID 非必须配置,如果使用sendMessage的快速发送需此配置 template-id: SMS_322180518 - #模板变量 上述模板的变量 + #模板变量 上述模板的变量 templateName: code - signature: 湖北星汉研创科技 -# sdk-app-id: 您的sdkAppId + signature: 湖北星汉研创科技 + # sdk-app-id: 您的sdkAppId config2: # 厂商标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分 supplier: tencent @@ -210,7 +210,7 @@ justauth: client-id: 449c4*********937************759 client-secret: ac7***********1e0************28d redirect-uri: ${justauth.address}/social-callback?source=topiam - scopes: [openid, email, phone, profile] + scopes: [ openid, email, phone, profile ] qq: client-id: 10**********6 client-secret: 1f7d08**********5b7**********29e @@ -276,27 +276,6 @@ justauth: redirect-uri: ${justauth.address}/social-callback?source=gitea -# 文件存储路径 -file: - mac: - path: ~/file/ - avatar: ~/avatar/ - linux: - path: /home/eladmin/file/ - avatar: /home/eladmin/avatar/ - windows: - path: C:\eladmin\file\ - avatar: C:\eladmin\avatar\ - # 文件大小 /M - maxSize: 100 - avatarMaxSize: 5 - device: - pic: C:\eladmin\file\ #设备图片存储路径 - ip: http://fuyuanshen.com:81/ #服务器地址 - app_avatar: - pic: C:\eladmin\file\ #设备图片存储路径 - #ip: http://fuyuanshen.com:81/ #服务器地址 - ip: https://fuyuanshen.com/ #服务器地址 # MQTT配置 mqtt: username: admin @@ -305,4 +284,12 @@ mqtt: subClientId: fys_subClient subTopic: A/# pubTopic: B/# - pubClientId: fys_pubClient \ No newline at end of file + pubClientId: fys_pubClient + + + # TTS语音交互配置 +alibaba: + tts: + appKey: KTwSUKMrf2olFfjC + akId: LTAI5t6RsfCvQh57qojzbEoe + akSecret: MTqvK2mXYeCRkl1jVPndiNumyaad0R \ No newline at end of file diff --git a/fys-admin/src/main/resources/application-prod.yml b/fys-admin/src/main/resources/application-prod.yml index 349574a4..c4a808fa 100644 --- a/fys-admin/src/main/resources/application-prod.yml +++ b/fys-admin/src/main/resources/application-prod.yml @@ -294,24 +294,3 @@ alibaba: akId: LTAI5t6RsfCvQh57qojzbEoe akSecret: MTqvK2mXYeCRkl1jVPndiNumyaad0R -# 文件存储路径 -file: - mac: - path: ~/file/ - avatar: ~/avatar/ - linux: - path: /home/eladmin/file/ - avatar: /home/eladmin/avatar/ - windows: - path: C:\eladmin\file\ - avatar: C:\eladmin\avatar\ - # 文件大小 /M - maxSize: 100 - avatarMaxSize: 5 - device: - pic: C:\eladmin\file\ #设备图片存储路径 - ip: http://fuyuanshen.com:81/ #服务器地址 - app_avatar: - pic: C:\eladmin\file\ #设备图片存储路径 - #ip: http://fuyuanshen.com:81/ #服务器地址 - ip: https://fuyuanshen.com/ #服务器地址 \ No newline at end of file diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DataOverviewVo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DataOverviewVo.java index c4b6d39e..c5be92f5 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DataOverviewVo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/DataOverviewVo.java @@ -3,7 +3,7 @@ package com.fuyuanshen.equipment.domain.vo; import lombok.Data; /** - * 数据总览 + * 首页数据总览 * * @author: 默苍璃 * @date: 2025-09-0114:24 diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java index 39e72cf5..ac2cb3f1 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java @@ -66,11 +66,6 @@ public class DeviceServiceImpl extends ServiceImpl impleme private final ISysRoleService roleService; - @Value("${file.device.pic}") - private String filePath; - @Value("${file.device.ip}") - private String ip; - private final DeviceMapper deviceMapper; private final DeviceTypeMapper deviceTypeMapper; private final CustomerMapper customerMapper; @@ -301,35 +296,6 @@ public class DeviceServiceImpl extends ServiceImpl impleme } - /** - * 保存设备图片并返回访问路径 - * - * @param file MultipartFile - * @param deviceMac 设备MAC用于生成唯一文件名 - * @return 文件存储路径 URL 形式 - */ - private String saveDeviceImage(MultipartFile file, String deviceMac) throws IOException { - if (file == null || file.isEmpty()) { - return null; - } - - String originalFileName = file.getOriginalFilename(); - String fileExtension = originalFileName.substring(originalFileName.lastIndexOf(".") + 1); - String newFileName = "PS_" + deviceMac + "." + fileExtension; - - File newFile = new File(filePath + DeviceConstants.FILE_ACCESS_ISOLATION + File.separator + newFileName); - - if (!newFile.getParentFile().exists()) { - newFile.getParentFile().mkdirs(); - } - - log.info("图片保存路径: {}", newFile.getAbsolutePath()); - file.transferTo(newFile); - - return ip + DeviceConstants.FILE_ACCESS_PREFIX + "/" + DeviceConstants.FILE_ACCESS_ISOLATION + "/" + newFileName; - } - - /** * 删除设备 * From 1dc3386284cfe80fee67fee643d4d28195f87d46 Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Wed, 5 Nov 2025 17:55:59 +0800 Subject: [PATCH 139/160] 6075 mqtt --- .../app/controller/AppAuthController.java | 2 - .../app/controller/AppDeviceController.java | 1 + .../controller/AppDeviceShareController.java | 5 +- .../app/controller/AppFileController.java | 3 + .../AppOperationVideoController.java | 2 + .../mqtt/receiver/DeviceMessageHandler.java | 277 ++++++++++++++++++ .../mqtt/receiver/ReceiverMessageHandler.java | 1 + .../mp/controller/MPAuthController.java | 6 +- .../fuyuanshen/mp/service/MPAuthService.java | 4 +- .../com/fuyuanshen/mp/service/MPService.java | 4 +- .../mp/service/impl/MPAuthServiceImpl.java | 1 - .../mp/service/impl/MPServiceImpl.java | 2 - .../web/controller/CaptchaController.java | 1 + .../device/DeviceXinghanController.java | 1 + .../src/main/resources/application-dev.yml | 8 +- fys-admin/src/main/resources/josn | 10 + .../equipment/domain/dto/AppDeviceBo.java | 4 +- .../equipment/domain/vo/AppDeviceVo.java | 1 + .../mapper/equipment/DeviceMapper.xml | 2 + 19 files changed, 313 insertions(+), 22 deletions(-) create mode 100644 fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/DeviceMessageHandler.java create mode 100644 fys-admin/src/main/resources/josn diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppAuthController.java b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppAuthController.java index 0cd57e28..7be1be8b 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppAuthController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppAuthController.java @@ -72,10 +72,8 @@ public class AppAuthController { private final AppLoginService loginService; private final AppRegisterService registerService; - private final ISysConfigService configService; private final ISysTenantService tenantService; private final ISysClientService clientService; - private final ISysDictTypeService dictTypeService; /** diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppDeviceController.java b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppDeviceController.java index 402244e4..02976bfc 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppDeviceController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppDeviceController.java @@ -92,4 +92,5 @@ public class AppDeviceController extends BaseController { public R getDeviceInfo(String deviceMac) { return R.ok(appDeviceService.getDeviceInfo(deviceMac)); } + } diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppDeviceShareController.java b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppDeviceShareController.java index 388c84f7..9cb4249e 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppDeviceShareController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppDeviceShareController.java @@ -44,10 +44,9 @@ import static com.fuyuanshen.common.core.constant.GlobalConstants.DEVICE_SHARE_C @RequestMapping("/app/deviceShare") public class AppDeviceShareController extends BaseController { - private final IAppDeviceShareService deviceShareService; - private final AppDeviceShareService appDeviceShareService; + /** * 分享管理列表 */ @@ -95,6 +94,7 @@ public class AppDeviceShareController extends BaseController { return toAjax(appDeviceShareService.remove(ids)); } + /** * 短信验证码 * @@ -116,4 +116,5 @@ public class AppDeviceShareController extends BaseController { } return R.ok(); } + } diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppFileController.java b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppFileController.java index 6c236218..012887f2 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppFileController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppFileController.java @@ -25,6 +25,7 @@ public class AppFileController extends BaseController { private final AppFileService appFileService; + /** * 查询文件列表 */ @@ -33,6 +34,7 @@ public class AppFileController extends BaseController { return R.ok(appFileService.list(bo)); } + /** * 上传文件 */ @@ -52,4 +54,5 @@ public class AppFileController extends BaseController { } return toAjax(appFileService.delete(ids)); } + } diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppOperationVideoController.java b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppOperationVideoController.java index c695db74..5338ba2c 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppOperationVideoController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppOperationVideoController.java @@ -25,6 +25,7 @@ public class AppOperationVideoController extends BaseController { private final IAppOperationVideoService appOperationVideoService; + /** * 查询操作视频列表 */ @@ -68,4 +69,5 @@ public class AppOperationVideoController extends BaseController { public R deleteOperationVideo(@PathVariable Long id) { return toAjax(appOperationVideoService.deleteWithValidByIds(List.of(id), true)); } + } diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/DeviceMessageHandler.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/DeviceMessageHandler.java new file mode 100644 index 00000000..93217e87 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/DeviceMessageHandler.java @@ -0,0 +1,277 @@ +package com.fuyuanshen.global.mqtt.receiver; + +import cn.hutool.core.lang.Dict; +import com.fuyuanshen.common.core.constant.GlobalConstants; +import com.fuyuanshen.common.core.utils.ImageToCArrayConverter; +import com.fuyuanshen.common.json.utils.JsonUtils; +import com.fuyuanshen.common.redis.utils.RedisUtils; +import com.fuyuanshen.global.mqtt.base.MqttRuleContext; +import com.fuyuanshen.global.mqtt.base.MqttRuleEngine; +import com.fuyuanshen.global.mqtt.base.MqttXinghanCommandType; +import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants; +import com.fuyuanshen.global.queue.MqttMessageQueueConstants; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHandler; +import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.MessagingException; +import org.springframework.stereotype.Service; + +import java.time.Duration; +import java.util.Objects; + +import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX; + +/** + * @author: 默苍璃 + * @date: 2025-11-05 17:41 + */ +@Service +@Slf4j +public class DeviceMessageHandler implements MessageHandler { + + @Autowired + private MqttRuleEngine ruleEngine; + + + @Override + public void handleMessage(Message message) throws MessagingException { + Object payload = message.getPayload(); + MessageHeaders headers = message.getHeaders(); + String receivedTopic = Objects.requireNonNull(headers.get("mqtt_receivedTopic")).toString(); + String receivedQos = Objects.requireNonNull(headers.get("mqtt_receivedQos")).toString(); + String timestamp = Objects.requireNonNull(headers.get("timestamp")).toString(); + + log.info("MQTT payload= {} \n receivedTopic = {} \n receivedQos = {} \n timestamp = {}", + payload, receivedTopic, receivedQos, timestamp); + + Dict payloadDict = JsonUtils.parseMap(payload.toString()); + if (receivedTopic == null || payloadDict == null) { + return; + } + + // 解析设备IMEI + String[] subStr = receivedTopic.split("/"); + String deviceImei = subStr[1]; + + // 处理设备在线状态 + handleDeviceOnlineStatus(deviceImei); + + // 处理不同类型的设备信息 + processDeviceInformation(payloadDict, deviceImei); + + // 执行规则引擎处理 + executeRuleEngine(payloadDict, deviceImei); + } + + + /** + * 处理设备在线状态 + */ + private void handleDeviceOnlineStatus(String deviceImei) { + if (StringUtils.isNotBlank(deviceImei)) { + // 添加去重队列 + String queueKey = MqttMessageQueueConstants.MQTT_MESSAGE_QUEUE_KEY; + String dedupKey = MqttMessageQueueConstants.MQTT_MESSAGE_DEDUP_KEY; + RedisUtils.offerDeduplicated(queueKey, dedupKey, deviceImei, Duration.ofSeconds(900)); + + // 设置设备在线状态 + String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY + + DEVICE_KEY_PREFIX + deviceImei + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX; + RedisUtils.setCacheObject(deviceOnlineStatusRedisKey, "1", Duration.ofSeconds(360)); + } + } + + + /** + * 处理不同类型的设备信息 + */ + private void processDeviceInformation(Dict payloadDict, String deviceImei) { + // 开机画面 + if (payloadDict.containsKey("bootScreen")) { + handleBootScreen(payloadDict, deviceImei); + } + + // 人员信息 + if (payloadDict.containsKey("personInfo")) { + handlePersonInfo(payloadDict, deviceImei); + } + + // 设备信息 + if (payloadDict.containsKey("deviceInfo")) { + handleDeviceInfo(payloadDict, deviceImei); + } + + // 经纬度 + if (payloadDict.containsKey("latitude") && payloadDict.containsKey("longitude")) { + handleLocation(payloadDict, deviceImei); + } + + // 电子地图 + if (payloadDict.containsKey("mapData")) { + handleMapData(payloadDict, deviceImei); + } + + // 电池电量 + if (payloadDict.containsKey("batteryLevel")) { + handleBatteryLevel(payloadDict, deviceImei); + } + + // 开启/关闭状态 + if (payloadDict.containsKey("powerState")) { + handlePowerState(payloadDict, deviceImei); + } + + // 海拔高度 + if (payloadDict.containsKey("altitude")) { + handleAltitude(payloadDict, deviceImei); + } + + // 相对高度 + if (payloadDict.containsKey("relativeHeight")) { + handleRelativeHeight(payloadDict, deviceImei); + } + + // 群呼/单呼 + if (payloadDict.containsKey("callType")) { + handleCallType(payloadDict, deviceImei); + } + + // 文字信息 + if (payloadDict.containsKey("textMessage")) { + handleTextMessage(payloadDict, deviceImei); + } + } + + /** + * 处理开机画面 + */ + private void handleBootScreen(Dict payloadDict, String deviceImei) { + log.info("处理设备{}的开机画面信息", deviceImei); + // 实现具体的开机画面处理逻辑 + } + + /** + * 处理人员信息 + */ + private void handlePersonInfo(Dict payloadDict, String deviceImei) { + log.info("处理设备{}的人员信息", deviceImei); + // 实现具体的人员信息处理逻辑 + } + + /** + * 处理设备信息 + */ + private void handleDeviceInfo(Dict payloadDict, String deviceImei) { + log.info("处理设备{}的设备信息", deviceImei); + // 实现具体的设备信息处理逻辑 + } + + /** + * 处理位置信息 + */ + private void handleLocation(Dict payloadDict, String deviceImei) { + log.info("处理设备{}的位置信息: 纬度={}, 经度={}", + deviceImei, payloadDict.getStr("latitude"), payloadDict.getStr("longitude")); + // 实现具体的位置信息处理逻辑 + } + + /** + * 处理电子地图数据 + */ + private void handleMapData(Dict payloadDict, String deviceImei) { + log.info("处理设备{}的电子地图数据", deviceImei); + // 实现具体的电子地图数据处理逻辑 + } + + /** + * 处理电池电量 + */ + private void handleBatteryLevel(Dict payloadDict, String deviceImei) { + log.info("处理设备{}的电池电量: {}", deviceImei, payloadDict.getStr("batteryLevel")); + // 实现具体的电池电量处理逻辑 + } + + /** + * 处理开关状态 + */ + private void handlePowerState(Dict payloadDict, String deviceImei) { + String powerState = payloadDict.getStr("powerState"); + log.info("处理设备{}的开关状态: {}", deviceImei, powerState); + // 实现具体的开关状态处理逻辑 + } + + /** + * 处理海拔高度 + */ + private void handleAltitude(Dict payloadDict, String deviceImei) { + log.info("处理设备{}的海拔高度: {}", deviceImei, payloadDict.getStr("altitude")); + // 实现具体的海拔高度处理逻辑 + } + + /** + * 处理相对高度 + */ + private void handleRelativeHeight(Dict payloadDict, String deviceImei) { + log.info("处理设备{}的相对高度: {}", deviceImei, payloadDict.getStr("relativeHeight")); + // 实现具体的相对高度处理逻辑 + } + + /** + * 处理呼叫类型 + */ + private void handleCallType(Dict payloadDict, String deviceImei) { + String callType = payloadDict.getStr("callType"); + log.info("处理设备{}的呼叫类型: {}", deviceImei, callType); + // 实现具体的呼叫类型处理逻辑 + } + + /** + * 处理文字信息 + */ + private void handleTextMessage(Dict payloadDict, String deviceImei) { + String textMessage = payloadDict.getStr("textMessage"); + log.info("处理设备{}的文字信息: {}", deviceImei, textMessage); + // 实现具体的文字信息处理逻辑 + } + + /** + * 执行规则引擎处理 + */ + private void executeRuleEngine(Dict payloadDict, String deviceImei) { + String state = payloadDict.getStr("state"); + Object[] convertArr = ImageToCArrayConverter.convertByteStringToMixedObjectArray(state); + + if (convertArr.length > 0) { + Byte val1 = (Byte) convertArr[0]; + MqttRuleContext context = new MqttRuleContext(); + context.setCommandType(val1); + context.setConvertArr(convertArr); + context.setDeviceImei(deviceImei); + context.setPayloadDict(payloadDict); + + boolean ruleExecuted = ruleEngine.executeRule(context); + + if (!ruleExecuted) { + log.warn("未找到匹配的规则来处理命令类型: {}", val1); + } + } + + /* ===== 追加:根据报文内容识别格式并统一解析 ===== */ + int intType = MqttXinghanCommandType.computeVirtualCommandType(payloadDict); + if (intType > 0) { + MqttRuleContext newCtx = new MqttRuleContext(); + newCtx.setCommandType((byte) intType); + newCtx.setDeviceImei(deviceImei); + newCtx.setPayloadDict(payloadDict); + + boolean ok = ruleEngine.executeRule(newCtx); + if (!ok) { + log.warn("新规则引擎未命中, imei={}", deviceImei); + } + } + } + +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java index 55f24c6f..5b266129 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java @@ -90,4 +90,5 @@ public class ReceiverMessageHandler implements MessageHandler { } } } + } diff --git a/fys-admin/src/main/java/com/fuyuanshen/mp/controller/MPAuthController.java b/fys-admin/src/main/java/com/fuyuanshen/mp/controller/MPAuthController.java index cec5b4a9..fc04d02c 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/mp/controller/MPAuthController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/mp/controller/MPAuthController.java @@ -63,10 +63,6 @@ import java.util.Map; @RequestMapping("/mp") public class MPAuthController { - private final AppLoginService loginService; - private final SysRegisterService registerService; - private final ISysConfigService configService; - private final ISysTenantService tenantService; private final ISysClientService clientService; private final MPAuthService mpAuthService; private final MPService mpService; @@ -74,7 +70,7 @@ public class MPAuthController { @Operation(summary = "小程序登录授权") @PostMapping(value = "/login") - public ResponseEntity login(@RequestBody AuthUserDto authUser, HttpServletRequest request) throws Exception { + public ResponseEntity login(@RequestBody AuthUserDto authUser) throws Exception { Long phoneNumber = authUser.getPhoneNumber(); // 判断小程序用户是否存在,不存在创建 diff --git a/fys-admin/src/main/java/com/fuyuanshen/mp/service/MPAuthService.java b/fys-admin/src/main/java/com/fuyuanshen/mp/service/MPAuthService.java index 7ab05c37..c6a59be3 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/mp/service/MPAuthService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/mp/service/MPAuthService.java @@ -60,9 +60,9 @@ import java.util.function.Supplier; @Service public class MPAuthService { - private final ISysUserService userService; private final AppUserService appUserService; + /** * 小程序注册 */ @@ -128,6 +128,7 @@ public class MPAuthService { return loginVo; } + /** * 构建登录用户 */ @@ -160,5 +161,4 @@ public class MPAuthService { } - } diff --git a/fys-admin/src/main/java/com/fuyuanshen/mp/service/MPService.java b/fys-admin/src/main/java/com/fuyuanshen/mp/service/MPService.java index 720720ff..0835ecaa 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/mp/service/MPService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/mp/service/MPService.java @@ -10,11 +10,8 @@ import org.springframework.stereotype.Service; * * @author Lion Li */ - - public interface MPService { - /** * 获取小程序用户信息 * @@ -23,4 +20,5 @@ public interface MPService { UserApp getMpUser(Long phoneNumber); UserApp loadUserByUsername(String username); + } diff --git a/fys-admin/src/main/java/com/fuyuanshen/mp/service/impl/MPAuthServiceImpl.java b/fys-admin/src/main/java/com/fuyuanshen/mp/service/impl/MPAuthServiceImpl.java index 11a05e7f..724294f0 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/mp/service/impl/MPAuthServiceImpl.java +++ b/fys-admin/src/main/java/com/fuyuanshen/mp/service/impl/MPAuthServiceImpl.java @@ -8,5 +8,4 @@ package com.fuyuanshen.mp.service.impl; public class MPAuthServiceImpl { - } diff --git a/fys-admin/src/main/java/com/fuyuanshen/mp/service/impl/MPServiceImpl.java b/fys-admin/src/main/java/com/fuyuanshen/mp/service/impl/MPServiceImpl.java index 05063142..80b22147 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/mp/service/impl/MPServiceImpl.java +++ b/fys-admin/src/main/java/com/fuyuanshen/mp/service/impl/MPServiceImpl.java @@ -19,11 +19,9 @@ import org.springframework.stereotype.Service; @RequiredArgsConstructor public class MPServiceImpl implements MPService { - private final AppUserService appUserService; - /** * 获取小程序用户信息 * diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/CaptchaController.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/CaptchaController.java index 30d7106e..c0d9c39b 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/controller/CaptchaController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/CaptchaController.java @@ -51,6 +51,7 @@ public class CaptchaController { private final CaptchaProperties captchaProperties; private final MailProperties mailProperties; + /** * 短信验证码 * diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceXinghanController.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceXinghanController.java index 5e95b70c..7ab4f902 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceXinghanController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceXinghanController.java @@ -160,4 +160,5 @@ public class DeviceXinghanController extends BaseController { RedisUtils.setCacheObject(versionKey, json, Duration.ofDays(30)); return R.ok(); } + } diff --git a/fys-admin/src/main/resources/application-dev.yml b/fys-admin/src/main/resources/application-dev.yml index bf89eda2..169c4bfe 100644 --- a/fys-admin/src/main/resources/application-dev.yml +++ b/fys-admin/src/main/resources/application-dev.yml @@ -279,11 +279,11 @@ justauth: # MQTT配置 mqtt: username: admin - password: #YtvpSfCNG - url: tcp://www.cnxhyc.com:2883 + password: fys123456 + url: tcp://47.107.152.87:1883 subClientId: fys_subClient - subTopic: A/# - pubTopic: B/# + subTopic: A/#,status/tenantCode/#,report/tenantCode/# + pubTopic: B/#,command/tenantCode/# pubClientId: fys_pubClient diff --git a/fys-admin/src/main/resources/josn b/fys-admin/src/main/resources/josn new file mode 100644 index 00000000..4ae99751 --- /dev/null +++ b/fys-admin/src/main/resources/josn @@ -0,0 +1,10 @@ +{ + "requestId": "123456789", + "imei": "864865082081523", +"timestamp": 1761025080000, + "funcType": "1", + "data": { + "mainLightMode": 1, + "mainLightBrightness": 100 + } +} \ No newline at end of file diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/AppDeviceBo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/AppDeviceBo.java index 5f440df4..0e631128 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/AppDeviceBo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/AppDeviceBo.java @@ -12,6 +12,7 @@ import lombok.EqualsAndHashCode; */ @Data public class AppDeviceBo { + /** * 设备IMEI */ @@ -25,10 +26,11 @@ public class AppDeviceBo { /** * 通讯方式 0:4G;1:蓝牙,2 4G&蓝牙 */ - @NotNull(message = "通讯方式不能为空", groups = { EditGroup.class }) + @NotNull(message = "通讯方式不能为空", groups = {EditGroup.class}) private Integer communicationMode; private String sendMsg; private Long deviceId; + } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/AppDeviceVo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/AppDeviceVo.java index 760fc3a4..214fd8a2 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/AppDeviceVo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/AppDeviceVo.java @@ -86,4 +86,5 @@ public class AppDeviceVo implements Serializable { * 设备详情页面 */ private String detailPageUrl; + } diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml index eb66b484..b57f59e2 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml @@ -227,6 +227,7 @@ FROM device WHERE original_device_id = #{originalDeviceId} + + + + + and video_name like concat('%', #{videoName}, '%') + and video_url = #{videoUrl} + and device_id = #{deviceId} + and type = #{type} + and duration = #{duration} + and size = #{size} + and cover = #{cover} + + order by create_time desc + + + + + \ No newline at end of file From 56dbfbde717eb34c7dd541518a92a28409536d62 Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Tue, 11 Nov 2025 14:33:10 +0800 Subject: [PATCH 148/160] prod --- .../device/AppDeviceHBYController.java | 1 + .../src/main/resources/application-prod.yml | 58 +++++++++---------- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/controller/device/AppDeviceHBYController.java b/fys-admin/src/main/java/com/fuyuanshen/app/controller/device/AppDeviceHBYController.java index 01883072..2d8306a6 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/controller/device/AppDeviceHBYController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/controller/device/AppDeviceHBYController.java @@ -28,6 +28,7 @@ public class AppDeviceHBYController extends BaseController { private final DeviceBJQBizService appDeviceService; + /** * 获取设备详细信息 * diff --git a/fys-admin/src/main/resources/application-prod.yml b/fys-admin/src/main/resources/application-prod.yml index c4a808fa..7b15ece9 100644 --- a/fys-admin/src/main/resources/application-prod.yml +++ b/fys-admin/src/main/resources/application-prod.yml @@ -55,32 +55,32 @@ spring: url: jdbc:mysql://47.120.79.150:3306/fys-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true username: root password: Jq_123456# -# # 从库数据源 -# slave: -# lazy: true -# type: ${spring.datasource.type} -# driverClassName: com.mysql.cj.jdbc.Driver -# url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true -# username: -# password: -# oracle: -# type: ${spring.datasource.type} -# driverClassName: oracle.jdbc.OracleDriver -# url: jdbc:oracle:thin:@//localhost:1521/XE -# username: ROOT -# password: root -# postgres: -# type: ${spring.datasource.type} -# driverClassName: org.postgresql.Driver -# url: jdbc:postgresql://localhost:5432/postgres?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true -# username: root -# password: root -# sqlserver: -# type: ${spring.datasource.type} -# driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver -# url: jdbc:sqlserver://localhost:1433;DatabaseName=tempdb;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true -# username: SA -# password: root + # # 从库数据源 + # slave: + # lazy: true + # type: ${spring.datasource.type} + # driverClassName: com.mysql.cj.jdbc.Driver + # url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true + # username: + # password: + # oracle: + # type: ${spring.datasource.type} + # driverClassName: oracle.jdbc.OracleDriver + # url: jdbc:oracle:thin:@//localhost:1521/XE + # username: ROOT + # password: root + # postgres: + # type: ${spring.datasource.type} + # driverClassName: org.postgresql.Driver + # url: jdbc:postgresql://localhost:5432/postgres?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true + # username: root + # password: root + # sqlserver: + # type: ${spring.datasource.type} + # driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver + # url: jdbc:sqlserver://localhost:1433;DatabaseName=tempdb;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true + # username: SA + # password: root hikari: # 最大连接池数量 maxPoolSize: 20 @@ -281,10 +281,10 @@ justauth: mqtt: username: admin password: #YtvpSfCNG - url: tcp://47.120.79.150:2883 + url: tcp://47.120.79.150:3883 subClientId: fys_subClient - subTopic: A/#,B/#,worker/location/# - pubTopic: B/# + subTopic: A/#,status/tenantCode/#,report/tenantCode/# + pubTopic: B/#,command/tenantCode/# pubClientId: fys_pubClient # TTS语音交互配置 From 6d582688748c5a5f35583d164c175e168e847dcd Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Tue, 11 Nov 2025 15:10:15 +0800 Subject: [PATCH 149/160] =?UTF-8?q?=E6=A0=B9=E6=8D=AE=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E4=B8=9A=E5=8A=A1=E5=AF=B9=E8=B1=A1=E6=8F=92=E5=85=A5=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E8=AF=AD=E9=9F=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AppDeviceVoiceController.java | 9 +-- .../app/service/IAppDeviceVoiceService.java | 3 +- .../impl/AppDeviceVoiceServiceImpl.java | 57 ++++++++++++++----- 3 files changed, 50 insertions(+), 19 deletions(-) diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/controller/AppDeviceVoiceController.java b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/controller/AppDeviceVoiceController.java index 36829422..62ede087 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/controller/AppDeviceVoiceController.java +++ b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/controller/AppDeviceVoiceController.java @@ -13,9 +13,9 @@ import com.fuyuanshen.common.mybatis.core.page.PageQuery; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import jakarta.validation.constraints.NotNull; - import java.util.Arrays; /** @@ -29,7 +29,7 @@ import java.util.Arrays; @RestController @RequestMapping("/app/device/voice") public class AppDeviceVoiceController extends BaseController { - + private final IAppDeviceVoiceService appDeviceVoiceService; /** @@ -54,8 +54,9 @@ public class AppDeviceVoiceController extends BaseController { * 新增设备语音 */ @PostMapping() - public R add(@Validated(AddGroup.class) @RequestBody AppDeviceVoiceBo bo) { - return toAjax(appDeviceVoiceService.insertByBo(bo)); + public R add(@RequestPart("bo") @Validated(AddGroup.class) AppDeviceVoiceBo bo, + @RequestPart(value = "file", required = false) MultipartFile file) { + return toAjax(appDeviceVoiceService.insertByBo(bo, file)); } /** diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/IAppDeviceVoiceService.java b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/IAppDeviceVoiceService.java index d7185cc9..40e46f40 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/IAppDeviceVoiceService.java +++ b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/IAppDeviceVoiceService.java @@ -4,6 +4,7 @@ import com.fuyuanshen.app.domain.AppDeviceVoice; import com.fuyuanshen.app.domain.bo.AppDeviceVoiceBo; import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; import com.fuyuanshen.common.mybatis.core.page.PageQuery; +import org.springframework.web.multipart.MultipartFile; import java.util.Collection; import java.util.List; @@ -34,7 +35,7 @@ public interface IAppDeviceVoiceService { /** * 根据新增业务对象插入设备语音 */ - Boolean insertByBo(AppDeviceVoiceBo bo); + Boolean insertByBo(AppDeviceVoiceBo bo, MultipartFile file); /** * 根据编辑业务对象更新设备语音 diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/impl/AppDeviceVoiceServiceImpl.java b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/impl/AppDeviceVoiceServiceImpl.java index 2dece69f..8e9b210b 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/impl/AppDeviceVoiceServiceImpl.java +++ b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/impl/AppDeviceVoiceServiceImpl.java @@ -1,20 +1,22 @@ package com.fuyuanshen.app.service.impl; -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.fuyuanshen.app.domain.AppDeviceVoice; import com.fuyuanshen.app.domain.bo.AppDeviceVoiceBo; import com.fuyuanshen.app.mapper.AppDeviceVoiceMapper; import com.fuyuanshen.app.service.IAppDeviceVoiceService; +import com.fuyuanshen.common.core.utils.StringUtils; import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; import com.fuyuanshen.common.mybatis.core.page.PageQuery; +import com.fuyuanshen.common.oss.core.OssClient; +import com.fuyuanshen.common.oss.entity.UploadResult; +import com.fuyuanshen.common.oss.factory.OssFactory; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; import java.util.Collection; import java.util.List; -import java.util.Map; /** * 设备语音Service业务层处理 @@ -57,16 +59,45 @@ public class AppDeviceVoiceServiceImpl implements IAppDeviceVoiceService { * 根据新增业务对象插入设备语音 */ @Override - public Boolean insertByBo(AppDeviceVoiceBo bo) { + public Boolean insertByBo(AppDeviceVoiceBo bo, MultipartFile file) { + // 上传文件到MinIO + String videoUrl = ""; + String coverUrl = ""; + Long size = 0L; + Integer duration = 0; + String type = ""; + + if (file != null && !file.isEmpty()) { + try { + OssClient storage = OssFactory.instance(); + String suffix = ""; + String originalFilename = file.getOriginalFilename(); + if (originalFilename != null && originalFilename.lastIndexOf(".") > 0) { + suffix = originalFilename.substring(originalFilename.lastIndexOf(".")); + } + UploadResult result = storage.uploadSuffix(file.getBytes(), suffix, file.getContentType()); + videoUrl = result.getUrl(); + size = file.getSize(); + type = file.getContentType(); + // TODO: 可以通过其他方式获取音频时长,这里暂时设置为默认值 + duration = 0; + // TODO: 可以生成封面图,这里暂时设置为空 + coverUrl = ""; + } catch (Exception e) { + // 文件上传失败处理 + throw new RuntimeException("文件上传失败: " + e.getMessage()); + } + } + AppDeviceVoice add = new AppDeviceVoice(); add.setVideoName(bo.getVideoName()); - add.setVideoUrl(bo.getVideoUrl()); + add.setVideoUrl(videoUrl); add.setDeviceId(bo.getDeviceId()); add.setRemark(bo.getRemark()); - add.setType(bo.getType()); - add.setDuration(bo.getDuration()); - add.setSize(bo.getSize()); - add.setCover(bo.getCover()); + add.setType(type); + add.setDuration(duration); + add.setSize(size); + add.setCover(coverUrl); return baseMapper.insert(add) > 0; } @@ -78,16 +109,12 @@ public class AppDeviceVoiceServiceImpl implements IAppDeviceVoiceService { AppDeviceVoice update = new AppDeviceVoice(); update.setId(bo.getId()); update.setVideoName(bo.getVideoName()); - update.setVideoUrl(bo.getVideoUrl()); update.setDeviceId(bo.getDeviceId()); update.setRemark(bo.getRemark()); - update.setType(bo.getType()); - update.setDuration(bo.getDuration()); - update.setSize(bo.getSize()); - update.setCover(bo.getCover()); return baseMapper.updateById(update) > 0; } + /** * 校验并批量删除设备语音信息 */ @@ -96,6 +123,8 @@ public class AppDeviceVoiceServiceImpl implements IAppDeviceVoiceService { if (ids == null || ids.isEmpty()) { return false; } + // TODO: 可以在这里添加删除MinIO文件的逻辑 return baseMapper.deleteBatchIds(ids) > 0; } + } \ No newline at end of file From 3798e52ee0f1e0817a26317a280a1b6484f80842 Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Tue, 18 Nov 2025 15:34:46 +0800 Subject: [PATCH 150/160] =?UTF-8?q?=E5=AF=BC=E5=85=A5=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../device/AppDeviceXinghanController.java | 6 +- .../bjq/AppDeviceBJQ6075Controller.java | 2 +- .../device/DeviceBJQ6075BizService.java | 6 + .../impl/DeviceBJQ6075BizServiceImpl.java | 8 +- .../core/utils/Bitmap80x12Generator.java | 28 +- .../core/utils/file/ImageCompressUtil.java | 91 +++++ .../common/log/aspect/LogAspect.java | 1 + .../app/domain/bo/AppPersonnelInfoBo.java | 1 + .../controller/DeviceController.java | 137 ++++++- .../converter/IgnoreFailedImageConverter.java | 158 +++++--- .../equipment/domain/DeviceType.java | 2 +- .../domain/dto/DeviceExcelImportDTO.java | 80 ++-- .../dto/DeviceWithTypeExcelExportDTO.java | 98 +++++ .../equipment/domain/form/DeviceForm.java | 23 +- .../equipment/excel/HeadValidateListener.java | 54 +++ .../excel/UploadDeviceDataListener.java | 372 ++++++++++++++++-- .../equipment/handler/ImageWriteHandler.java | 133 ++++--- .../equipment/mapper/DeviceTypeMapper.java | 9 + .../equipment/service/DeviceTypeService.java | 9 + .../service/impl/DeviceExportService.java | 209 +++++++++- .../service/impl/DeviceServiceImpl.java | 91 ++++- .../service/impl/DeviceTypeServiceImpl.java | 15 + .../mapper/equipment/DeviceTypeMapper.xml | 10 + 23 files changed, 1351 insertions(+), 192 deletions(-) create mode 100644 fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/file/ImageCompressUtil.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceWithTypeExcelExportDTO.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/excel/HeadValidateListener.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 f7a1d2d4..47f552a5 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 @@ -38,6 +38,8 @@ public class AppDeviceXinghanController extends BaseController { private final DeviceXinghanBizService appDeviceService; private final DeviceService deviceService; + + /** * 人员信息登记 */ @@ -67,7 +69,7 @@ public class AppDeviceXinghanController extends BaseController { public R upload(@Validated @ModelAttribute AppDeviceLogoUploadDto bo) { MultipartFile file = bo.getFile(); - if(file.getSize()>1024*1024*2){ + if (file.getSize() > 1024 * 1024 * 2) { return R.warn("图片不能大于2M"); } appDeviceService.uploadDeviceLogo(bo); @@ -125,7 +127,7 @@ public class AppDeviceXinghanController extends BaseController { @GetMapping(value = "/typeAll") @Operation(summary = "查询所有设备类型") - public R> queryDeviceTypes() { + public R> queryDeviceTypes() { List deviceTypes = appDeviceService.queryDeviceTypes(); return R.ok(deviceTypes); } diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/controller/device/bjq/AppDeviceBJQ6075Controller.java b/fys-admin/src/main/java/com/fuyuanshen/app/controller/device/bjq/AppDeviceBJQ6075Controller.java index 4f8839d1..29e38cf6 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/controller/device/bjq/AppDeviceBJQ6075Controller.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/controller/device/bjq/AppDeviceBJQ6075Controller.java @@ -148,7 +148,7 @@ public class AppDeviceBJQ6075Controller extends BaseController { */ @GetMapping("/getShareInfo/{id}") public R getShareInfo(@NotNull(message = "主键不能为空") - @PathVariable Long id) { + @PathVariable Long id) { return R.ok(appDeviceService6075.getInfo(id)); } diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBJQ6075BizService.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBJQ6075BizService.java index c543b8be..61c03cf1 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBJQ6075BizService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBJQ6075BizService.java @@ -69,6 +69,12 @@ public interface DeviceBJQ6075BizService { */ public void recordDeviceLog(Long deviceId, String deviceName, String deviceAction, String content, Long operator); + /** + * 注册人员信息 + * + * @param bo 参数 + * @return 结果 + */ public boolean registerPersonInfo(AppPersonnelInfoBo bo); public void uploadDeviceLogo2(AppDeviceLogoUploadDto bo); diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/DeviceBJQ6075BizServiceImpl.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/DeviceBJQ6075BizServiceImpl.java index 74541f77..56d18e1f 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/DeviceBJQ6075BizServiceImpl.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/DeviceBJQ6075BizServiceImpl.java @@ -251,6 +251,12 @@ public class DeviceBJQ6075BizServiceImpl implements DeviceBJQ6075BizService { } + /** + * 注册人员信息 + * + * @param bo + * @return + */ @Override public boolean registerPersonInfo(AppPersonnelInfoBo bo) { Long deviceId = bo.getDeviceId(); @@ -264,7 +270,7 @@ public class DeviceBJQ6075BizServiceImpl implements DeviceBJQ6075BizService { QueryWrapper qw = new QueryWrapper() .eq("device_id", deviceId); List appPersonnelInfoVos = appPersonnelInfoMapper.selectVoList(qw); -// unitName,position,name,id + // 生成固定长度的点阵数据 byte[] unitName = generateFixedBitmapData(bo.getUnitName(), 120); byte[] position = generateFixedBitmapData(bo.getPosition(), 120); byte[] name = generateFixedBitmapData(bo.getName(), 120); diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/Bitmap80x12Generator.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/Bitmap80x12Generator.java index 5a678e55..bda99d7b 100644 --- a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/Bitmap80x12Generator.java +++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/Bitmap80x12Generator.java @@ -27,14 +27,14 @@ public class Bitmap80x12Generator { BufferedImage image = convertByteArrayToImage(bytes, 12, 80); ImageIO.write(image, "PNG", new File("D:\\bitmap_preview.png")); System.out.println("成功生成预览图片: D:\\bitmap_preview.png"); - + // 打印十六进制数据 // System.out.println("生成的点阵数据2:"); // printHexData(bitmapData); // int[] ints = convertHexToDecimal(bitmapData); - System.out.println("打印十进制无符号:"+Arrays.toString(ints)); + System.out.println("打印十进制无符号:" + Arrays.toString(ints)); // printDecimalData(bitmapData); - + // 生成C文件 generateCFile(bitmapData, "bitmap_data.c", "chinese_text"); } @@ -125,7 +125,7 @@ public class Bitmap80x12Generator { System.out.println(); } - public static void buildArr(int[] data,List intData){ + public static void buildArr(int[] data, List intData) { for (int datum : data) { intData.add(datum); } @@ -133,8 +133,8 @@ public class Bitmap80x12Generator { /** * 生成固定长度的点阵数据 - * - * @param text 要转换的文本 + * + * @param text 要转换的文本 * @param fixedLength 固定长度(字节) * @return 固定长度的点阵数据 */ @@ -157,10 +157,11 @@ public class Bitmap80x12Generator { int copyLength = Math.min(rawData.length, fixedLength); System.arraycopy(rawData, 0, result, 0, copyLength); // 剩余部分自动初始化为0 - + return result; } + /** * 创建文本图像 */ @@ -185,14 +186,14 @@ public class Bitmap80x12Generator { // 获取字体度量 FontMetrics metrics = g.getFontMetrics(); - + // 计算文本绘制位置(居中) int textWidth = metrics.stringWidth(text); // int x = Math.max(0, (width - textWidth) / 2); // 水平居中 // 左对齐 int x = 0; int y = (height - metrics.getHeight()) / 2 + metrics.getAscent(); // 垂直居中 - + // 绘制文本 g.drawString(text, x, y); @@ -242,6 +243,7 @@ public class Bitmap80x12Generator { return byteListToArray(byteList); } + public static byte[] byteListToArray(List byteList) { byte[] result = new byte[byteList.size()]; for (int i = 0; i < byteList.size(); i++) { @@ -282,7 +284,7 @@ public class Bitmap80x12Generator { } } bitIndex++; - + // 如果已经处理完所有像素,则退出 if (bitIndex >= width * height) { return image; @@ -313,6 +315,7 @@ public class Bitmap80x12Generator { sb.append("\n};"); return sb.toString(); } + /** * 打印十六进制数据 */ @@ -320,7 +323,7 @@ public class Bitmap80x12Generator { for (int i = 0; i < data.length; i++) { int value = data[i] & 0xFF; System.out.printf("0x%02X", value); - + if (i < data.length - 1) { System.out.print(", "); if ((i + 1) % 12 == 0) System.out.println(); @@ -348,6 +351,7 @@ public class Bitmap80x12Generator { } } + private static void writeByteArray(FileWriter writer, byte[] data) throws IOException { for (int i = 0; i < data.length; i++) { int value = data[i] & 0xFF; @@ -359,4 +363,6 @@ public class Bitmap80x12Generator { } } } + + } diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/file/ImageCompressUtil.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/file/ImageCompressUtil.java new file mode 100644 index 00000000..c72e3e3a --- /dev/null +++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/file/ImageCompressUtil.java @@ -0,0 +1,91 @@ +package com.fuyuanshen.common.core.utils.file; + +import lombok.extern.slf4j.Slf4j; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * 图片压缩工具类 + */ +@Slf4j +public class ImageCompressUtil { + + /** + * 压缩图片到指定大小以下 + * + * @param imageData 原始图片数据 + * @param maxSize 最大大小(字节) + * @return 压缩后的图片数据 + */ + public static byte[] compressImage(byte[] imageData, int maxSize) { + try { + // 如果图片本身小于等于最大大小,直接返回 + if (imageData.length <= maxSize) { + return imageData; + } + + // 读取原始图片 + BufferedImage originalImage = ImageIO.read(new ByteArrayInputStream(imageData)); + if (originalImage == null) { + log.warn("无法读取图片数据"); + return imageData; + } + + // 计算压缩比例 + double scale = Math.sqrt((double) maxSize / imageData.length); + // 确保至少压缩到一半大小,避免压缩效果不明显 + scale = Math.max(scale, 0.5); + + // 压缩图片 + byte[] compressedData = compressImageByScale(originalImage, scale); + + // 如果压缩后还是太大,继续压缩 + int attempts = 0; + while (compressedData.length > maxSize && attempts < 5) { + scale *= 0.8; // 每次缩小20% + compressedData = compressImageByScale(originalImage, scale); + attempts++; + } + + log.info("图片压缩完成,原始大小: {} bytes, 压缩后大小: {} bytes, 压缩比例: {}", + imageData.length, compressedData.length, String.format("%.2f", scale)); + + return compressedData; + } catch (Exception e) { + log.error("图片压缩失败: {}", e.getMessage(), e); + return imageData; // 压缩失败时返回原始数据 + } + } + + + /** + * 按比例缩放图片 + * + * @param originalImage 原始图片 + * @param scale 缩放比例 + * @return 缩放后的图片数据 + * @throws IOException IO异常 + */ + private static byte[] compressImageByScale(BufferedImage originalImage, double scale) throws IOException { + int width = (int) (originalImage.getWidth() * scale); + int height = (int) (originalImage.getHeight() * scale); + + // 创建缩放后的图片 + Image scaledImage = originalImage.getScaledInstance(width, height, Image.SCALE_SMOOTH); + BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + Graphics2D g2d = bufferedImage.createGraphics(); + g2d.drawImage(scaledImage, 0, 0, null); + g2d.dispose(); + + // 输出为JPEG格式 + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageIO.write(bufferedImage, "jpg", baos); + return baos.toByteArray(); + } + +} diff --git a/fys-common/fys-common-log/src/main/java/com/fuyuanshen/common/log/aspect/LogAspect.java b/fys-common/fys-common-log/src/main/java/com/fuyuanshen/common/log/aspect/LogAspect.java index 0772e933..6b3d921f 100644 --- a/fys-common/fys-common-log/src/main/java/com/fuyuanshen/common/log/aspect/LogAspect.java +++ b/fys-common/fys-common-log/src/main/java/com/fuyuanshen/common/log/aspect/LogAspect.java @@ -216,4 +216,5 @@ public class LogAspect { return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse || o instanceof BindingResult; } + } diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/bo/AppPersonnelInfoBo.java b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/bo/AppPersonnelInfoBo.java index 70a79188..12cbf96c 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/bo/AppPersonnelInfoBo.java +++ b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/bo/AppPersonnelInfoBo.java @@ -64,4 +64,5 @@ public class AppPersonnelInfoBo extends BaseEntity { * ID号 */ private String code; + } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceController.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceController.java index 030452a1..bd6f0c15 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceController.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceController.java @@ -13,12 +13,14 @@ import com.fuyuanshen.common.satoken.utils.LoginHelper; import com.fuyuanshen.common.web.core.BaseController; import com.fuyuanshen.customer.mapper.CustomerMapper; import com.fuyuanshen.equipment.domain.Device; +import com.fuyuanshen.equipment.domain.DeviceType; import com.fuyuanshen.equipment.domain.dto.DeviceExcelImportDTO; import com.fuyuanshen.equipment.domain.dto.ImportResult; import com.fuyuanshen.equipment.domain.form.DeviceForm; import com.fuyuanshen.equipment.domain.query.DeviceQueryCriteria; import com.fuyuanshen.equipment.domain.vo.CustomerVo; import com.fuyuanshen.equipment.excel.DeviceImportParams; +import com.fuyuanshen.equipment.excel.HeadValidateListener; import com.fuyuanshen.equipment.excel.UploadDeviceDataListener; import com.fuyuanshen.equipment.mapper.DeviceMapper; import com.fuyuanshen.equipment.mapper.DeviceTypeMapper; @@ -39,7 +41,10 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * @Description: @@ -163,7 +168,7 @@ public class DeviceController extends BaseController { @Operation(summary = "导出数据设备") - @GetMapping(value = "/download") + @GetMapping(value = "/download1") public R exportDevice(HttpServletResponse response, DeviceQueryCriteria criteria) throws IOException { List devices = deviceService.queryAll(criteria); exportService.export(devices, response); @@ -171,9 +176,36 @@ public class DeviceController extends BaseController { } + /** + * 导出设备数据(包含完整设备类型信息) + * + * @param response HttpServletResponse对象 + * @param criteria 查询条件 + * @return R + */ + @Operation(summary = "导出设备数据(包含完整设备类型信息)") + @GetMapping(value = "/download") + public R exportDeviceWithFullTypeInfo(HttpServletResponse response, DeviceQueryCriteria criteria) { + // 获取所有符合条件的设备 + List devices = deviceService.queryAll(criteria); + // 获取所有设备类型信息 + List deviceTypes = deviceTypeService.queryDeviceTypes(); + // 导出数据(包含完整设备类型信息) + exportService.exportWithTypeInfo(devices, deviceTypes, response); + return R.ok(); + } + + + /** + * 导入设备数据 + * + * @param file + * @return + * @throws BadRequestException + */ @Operation(summary = "导入设备数据") - @PostMapping(value = "/import", consumes = "multipart/form-data") - public R importData(@Parameter(name = "文件", required = true) @RequestPart("file") MultipartFile file) throws BadRequestException { + @PostMapping(value = "/import1", consumes = "multipart/form-data") + public R importData1(@Parameter(name = "文件", required = true) @RequestPart("file") MultipartFile file) throws BadRequestException { String suffix = FileUtil.getExtensionName(file.getOriginalFilename()); if (!("xlsx".equalsIgnoreCase(suffix))) { @@ -207,6 +239,105 @@ public class DeviceController extends BaseController { } } + + /** + * 导入设备数据 + * + * @param file 文件 + * @return R + */ + @Operation(summary = "导入设备数据") + @PostMapping(value = "/import", consumes = "multipart/form-data") + public R importData(@Parameter(name = "文件", required = true) @RequestPart("file") MultipartFile file) throws BadRequestException { + + String suffix = FileUtil.getExtensionName(file.getOriginalFilename()); + if (!("xlsx".equalsIgnoreCase(suffix))) { + throw new BadRequestException("只能上传Excel——xlsx格式文件"); + } + + // 检查文件大小,限制为100MB + if (file.getSize() > 100 * 10 * 1024 * 1024) { + throw new BadRequestException("文件大小不能超过100MB"); + } + + // 校验模板 + validateExcelTemplate(file); + + ImportResult result = new ImportResult(); + try { + LoginUser loginUser = LoginHelper.getLoginUser(); + DeviceImportParams params = DeviceImportParams.builder().ossService(ossService) + .deviceService(deviceService).tenantId(loginUser.getTenantId()) + .file(file).filePath("").deviceMapper(deviceMapper).deviceTypeService(deviceTypeService) + .deviceTypeMapper(deviceTypeMapper).userId(loginUser.getUserId()) + .build(); + // 创建监听器 + UploadDeviceDataListener listener = new UploadDeviceDataListener(params); + // 读取Excel + EasyExcel.read(file.getInputStream(), DeviceExcelImportDTO.class, listener).sheet().doRead(); + // 获取导入结果 + result = listener.getImportResult(); + // 设置响应消息 + String message = String.format("成功导入 %d 条数据,失败 %d 条", result.getSuccessCount(), result.getFailureCount()); + // 返回带有正确泛型的响应 + return R.ok(message, result); + } catch (Exception e) { + log.error("导入设备数据出错: {}", e.getMessage(), e); + // 在异常情况下,设置默认结果 + String errorMessage = String.format("导入失败: %s。成功 %d 条,失败 %d 条", e.getMessage(), result.getSuccessCount(), result.getFailureCount()); + // 使用新方法确保类型正确 + return R.fail(errorMessage, result); + } + } + + + /** + * 校验Excel模板是否正确 + * + * @param file MultipartFile对象 + * @throws BadRequestException 当模板不正确时抛出异常 + */ + private void validateExcelTemplate(MultipartFile file) throws BadRequestException { + try { + // 创建一个只读取表头的监听器 + HeadValidateListener headValidateListener = new HeadValidateListener(); + + // 使用EasyExcel读取表头 + EasyExcel.read(file.getInputStream(), headValidateListener) + .sheet() + .headRowNumber(0) + .doRead(); + + // 获取读取到的表头信息 + List actualHeaders = headValidateListener.getHeadNames(); + + // 定义必需的表头 + Set requiredHeaders = new HashSet<>(Arrays.asList( + "设备名称", "设备类型名称", "设备图片", "设备MAC", "蓝牙名称", "设备IMEI", + "备注", "是否支持蓝牙", "定位方式", "通讯方式", + "型号字典用于APP页面跳转", "型号字典用于PC页面跳转" + )); + + // 检查必需的表头是否都存在 + Set actualHeaderSet = new HashSet<>(actualHeaders); + if (!actualHeaderSet.containsAll(requiredHeaders)) { + requiredHeaders.removeAll(actualHeaderSet); + throw new BadRequestException("Excel模板缺少必需的列: " + String.join(", ", requiredHeaders)); + } + + // 检查第三列(索引为2)是否为"设备图片" + if (actualHeaders.size() > 2 && !"设备图片".equals(actualHeaders.get(2))) { + throw new BadRequestException("Excel模板不正确,第三列必须是'设备图片'列"); + } + } catch (BadRequestException e) { + throw e; // 直接重新抛出 + } catch (Exception e) { + log.error("校验Excel模板时发生错误: {}", e.getMessage(), e); + throw new BadRequestException("校验Excel模板时发生错误: " + e.getMessage()); + } + } + + } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/converter/IgnoreFailedImageConverter.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/converter/IgnoreFailedImageConverter.java index 6795efa5..10238703 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/converter/IgnoreFailedImageConverter.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/converter/IgnoreFailedImageConverter.java @@ -20,17 +20,17 @@ import java.net.URLConnection; public class IgnoreFailedImageConverter implements Converter { private static final Logger logger = LoggerFactory.getLogger(IgnoreFailedImageConverter.class); + + // 重试次数 + private static final int MAX_RETRIES = 3; + // 指数退避初始延迟(毫秒) + private static final int INITIAL_DELAY = 1000; @Override public Class supportJavaTypeKey() { return URL.class; } - // @Override - // public CellDataTypeEnum supportExcelTypeKey() { - // return CellDataTypeEnum.STRING; - // } - @Override public WriteCellData convertToExcelData(URL value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { if (value == null) { @@ -38,60 +38,108 @@ public class IgnoreFailedImageConverter implements Converter { return new WriteCellData<>(new byte[0]); } - try { - logger.debug("开始加载图片: {}", value); - URLConnection conn = value.openConnection(); - // 增加连接和读取超时时间 - conn.setConnectTimeout(10000); // 10秒连接超时 - conn.setReadTimeout(30000); // 30秒读取超时 - - // 添加User-Agent避免被服务器拦截 - conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ExcelExporter/1.0"); - - // 如果是HTTP连接,设置一些额外的属性 - if (conn instanceof HttpURLConnection) { - HttpURLConnection httpConn = (HttpURLConnection) conn; - httpConn.setRequestMethod("GET"); - // 不使用缓存 - httpConn.setUseCaches(false); - // 跟随重定向 - httpConn.setInstanceFollowRedirects(true); - } - - long contentLength = conn.getContentLengthLong(); - logger.debug("连接建立成功,图片大小: {} 字节", contentLength); - - // 检查内容长度是否有效 - if (contentLength == 0) { - logger.warn("图片文件为空: {}", value); - return new WriteCellData<>(new byte[0]); - } - - // 限制图片大小(防止过大文件导致内存问题) - if (contentLength > 10 * 1024 * 1024) { // 10MB限制 - logger.warn("图片文件过大 ({} bytes),跳过加载: {}", contentLength, value); - return new WriteCellData<>(new byte[0]); - } - - try (InputStream inputStream = conn.getInputStream()) { - // byte[] bytes = FileUtils.readInputStream(inputStream, value.toString()); - // 替代 FileUtils.readInputStream 的自定义方法 - byte[] bytes = readInputStream(inputStream); + // 尝试多次加载图片 + for (int attempt = 1; attempt <= MAX_RETRIES; attempt++) { + try { + logger.debug("开始加载图片: {}, 尝试次数: {}", value, attempt); + URLConnection conn = value.openConnection(); + // 增加连接和读取超时时间 + conn.setConnectTimeout(10000); // 10秒连接超时 + conn.setReadTimeout(30000); // 30秒读取超时 - // 检查读取到的数据是否为空 - if (bytes == null || bytes.length == 0) { - logger.warn("读取到空的图片数据: {}", value); - return new WriteCellData<>(new byte[0]); + // 添加User-Agent避免被服务器拦截 + conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 ExcelExporter/1.0"); + + // 如果是HTTP连接,设置一些额外的属性 + if (conn instanceof HttpURLConnection) { + HttpURLConnection httpConn = (HttpURLConnection) conn; + httpConn.setRequestMethod("GET"); + // 不使用缓存 + httpConn.setUseCaches(false); + // 跟随重定向 + httpConn.setInstanceFollowRedirects(true); + + // 检查响应码 + int responseCode = httpConn.getResponseCode(); + if (responseCode != HttpURLConnection.HTTP_OK) { + logger.warn("HTTP响应码异常: {}, URL: {}", responseCode, value); + if (attempt < MAX_RETRIES) { + // 等待后重试 + waitForRetry(attempt); + continue; + } else { + return new WriteCellData<>(new byte[0]); + } + } + } + + long contentLength = conn.getContentLengthLong(); + logger.debug("连接建立成功,图片大小: {} 字节", contentLength); + + // 检查内容长度是否有效 + if (contentLength == 0) { + logger.warn("图片文件为空: {}", value); + if (attempt < MAX_RETRIES) { + waitForRetry(attempt); + continue; + } else { + return new WriteCellData<>(new byte[0]); + } } - logger.debug("成功读取图片数据,大小: {} 字节", bytes.length); - return new WriteCellData<>(bytes); + // 限制图片大小(防止过大文件导致内存问题) + if (contentLength > 10 * 1024 * 1024) { // 10MB限制 + logger.warn("图片文件过大 ({} bytes),跳过加载: {}", contentLength, value); + return new WriteCellData<>(new byte[0]); + } + + try (InputStream inputStream = conn.getInputStream()) { + // byte[] bytes = FileUtils.readInputStream(inputStream, value.toString()); + // 替代 FileUtils.readInputStream 的自定义方法 + byte[] bytes = readInputStream(inputStream); + + // 检查读取到的数据是否为空 + if (bytes == null || bytes.length == 0) { + logger.warn("读取到空的图片数据: {}", value); + if (attempt < MAX_RETRIES) { + waitForRetry(attempt); + continue; + } else { + return new WriteCellData<>(new byte[0]); + } + } + + logger.debug("成功读取图片数据,大小: {} 字节", bytes.length); + return new WriteCellData<>(bytes); + } + } catch (Exception e) { + logger.warn("图片加载失败: {}, 尝试次数: {}, 原因: {}", value, attempt, e.getMessage(), e); + if (attempt < MAX_RETRIES) { + // 等待后重试 + waitForRetry(attempt); + } else { + // 最后一次尝试也失败了 + logger.error("图片加载最终失败,已重试 {} 次: {}", MAX_RETRIES, value, e); + return new WriteCellData<>(new byte[0]); // 返回空数组而不是 null + } } - } catch (Exception e) { - // 静默忽略错误,只记录日志 - logger.warn("图片加载失败: {}, 原因: {}", value, e.getMessage(), e); - // return null; // 返回null表示不写入图片 - return new WriteCellData<>(new byte[0]); // 返回空数组而不是 null + } + + // 所有尝试都失败了 + return new WriteCellData<>(new byte[0]); + } + + /** + * 等待重试,使用指数退避策略 + * @param attempt 当前尝试次数 + */ + private void waitForRetry(int attempt) { + try { + long delay = (long) INITIAL_DELAY * (1L << (attempt - 1)); // 指数退避 + logger.debug("等待 {} 毫秒后重试...", delay); + Thread.sleep(delay); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); } } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceType.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceType.java index bfef80b5..f2740237 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceType.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceType.java @@ -60,7 +60,7 @@ public class DeviceType extends TenantEntity { private String networkWay; @Schema(title = "通讯方式", example = "通讯方式 0:4G;1:蓝牙,2 4G&蓝牙") - private Integer communicationMode; + private String communicationMode; /** * 创建人名称 diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceExcelImportDTO.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceExcelImportDTO.java index 8c4204e1..a059f3da 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceExcelImportDTO.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceExcelImportDTO.java @@ -1,24 +1,21 @@ package com.fuyuanshen.equipment.domain.dto; +import com.alibaba.excel.annotation.ExcelIgnore; import com.alibaba.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.write.style.ColumnWidth; import com.alibaba.excel.annotation.write.style.ContentRowHeight; import com.alibaba.excel.annotation.write.style.HeadRowHeight; -import com.alibaba.excel.converters.bytearray.ByteArrayImageConverter; import lombok.Data; /** * @author: 默苍璃 - * @date: 2025-06-0710:00 + * @date: 2025-06-07 10:00 */ @Data @HeadRowHeight(20) // 表头行高 @ContentRowHeight(100) // 内容行高 public class DeviceExcelImportDTO { - // @ExcelProperty("设备类型") - // private Long deviceType; - @ExcelProperty("设备名称") @ColumnWidth(20) private String deviceName; @@ -26,14 +23,23 @@ public class DeviceExcelImportDTO { @ExcelProperty("设备类型名称") private String typeName; - // @ExcelProperty(value = "设备图片", converter = ByteArrayImageConverter.class) - // @ColumnWidth(15) - // private byte[] devicePic; - // - // // 添加图片写入方法 - // public void setDevicePicFromBytes(byte[] bytes) { - // this.devicePic = bytes; - // } + /** + * 设备图片 + * 导入时的图片处理方式: + * 在导入过程中,图片不是通过Excel单元格中的文本数据(如URL)来处理的 + * 而是直接从Excel文件中提取嵌入的图片数据 + * 代码通过POI库直接读取Excel中的图片对象,然后上传到OSS并生成URL + */ + @ExcelProperty("设备图片") + @ColumnWidth(20) + private byte[] devicePic; + + /** + * 用于存储实际图片数据的字段 + * 使用@ExcelIgnore注解忽略该字段,避免创建额外的列 + */ + @ExcelIgnore + private byte[] imageData; @ExcelProperty("设备MAC") @ColumnWidth(20) @@ -46,18 +52,46 @@ public class DeviceExcelImportDTO { @ExcelProperty("蓝牙名称") private String bluetoothName; - // @ExcelProperty("设备SN") - // @ColumnWidth(20) - // private String deviceSn; - // - // @ExcelProperty("经度") - // private String longitude; - // - // @ExcelProperty("纬度") - // private String latitude; - @ExcelProperty("备注") @ColumnWidth(30) private String remark; + /* + *设备类型数据 + */ + @ExcelProperty("是否支持蓝牙") + @ColumnWidth(15) + private String isSupportBle; + + @ExcelProperty("定位方式") + @ColumnWidth(20) + private String locateMode; + + @ExcelProperty("通讯方式") + @ColumnWidth(20) + private String communicationMode; + + /** + * 型号字典用于APP页面跳转 + * app_model_dictionary + */ + @ExcelProperty("型号字典用于APP页面跳转") + @ColumnWidth(20) + private String appModelDictionary; + + /** + * 型号字典用于PC页面跳转 + * pc_model_dictionary + */ + @ExcelProperty("型号字典用于PC页面跳转") + @ColumnWidth(20) + private String pcModelDictionary; + + /** + * 错误原因 + */ + @ExcelProperty("错误原因") + @ColumnWidth(30) + private String errorMessage; + } \ No newline at end of file diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceWithTypeExcelExportDTO.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceWithTypeExcelExportDTO.java new file mode 100644 index 00000000..6ef7b5f4 --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceWithTypeExcelExportDTO.java @@ -0,0 +1,98 @@ +package com.fuyuanshen.equipment.domain.dto; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.annotation.write.style.ColumnWidth; +import com.alibaba.excel.annotation.write.style.ContentRowHeight; +import com.alibaba.excel.annotation.write.style.HeadRowHeight; +import lombok.Data; + +import java.net.URL; + +/** + * 设备及完整类型信息导出DTO + * + * @author: 默苍璃 + * @date: 2025-11-0416:25 + */ +@Data +@HeadRowHeight(20) // 表头行高 +@ContentRowHeight(100) // 内容行高 +public class DeviceWithTypeExcelExportDTO { + + @ExcelProperty("设备名称") + @ColumnWidth(20) + private String deviceName; + + @ExcelProperty("设备类型名称") + @ColumnWidth(20) + private String typeName; + + @ExcelProperty(value = "设备图片") + @ColumnWidth(30) // 设置图片列宽度 + private URL devicePic; // 使用URL类型 + + @ExcelProperty("设备MAC") + @ColumnWidth(20) + private String deviceMac; + + @ExcelProperty("蓝牙名称") + @ColumnWidth(20) + private String bluetoothName; + + @ExcelProperty("设备IMEI") + @ColumnWidth(20) + private String deviceImei; + + @ExcelProperty("备注") + @ColumnWidth(30) + private String remark; + + /** + * 绑定状态 + * 0 未绑定 + * 1 已绑定 + */ + @ExcelProperty("绑定状态") + @ColumnWidth(20) + private String bindingStatus; + + @ExcelProperty("创建时间") + @ColumnWidth(20) + private String createTime; + + @ExcelProperty("创建人") + @ColumnWidth(20) + private String createBy; + + /* + *设备类型数据 + */ + @ExcelProperty("是否支持蓝牙") + @ColumnWidth(15) + private String isSupportBle; + + @ExcelProperty("定位方式") + @ColumnWidth(20) + private String locateMode; + + @ExcelProperty("通讯方式") + @ColumnWidth(20) + private String communicationMode; + + /** + * 型号字典用于APP页面跳转 + * app_model_dictionary + */ + @ExcelProperty("型号字典用于APP页面跳转") + @ColumnWidth(20) + private String appModelDictionary; + + /** + * 型号字典用于PC页面跳转 + * pc_model_dictionary + */ + @ExcelProperty("型号字典用于PC页面跳转") + @ColumnWidth(20) + private String pcModelDictionary; + +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/form/DeviceForm.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/form/DeviceForm.java index 76e9398a..def13d17 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/form/DeviceForm.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/form/DeviceForm.java @@ -26,8 +26,6 @@ public class DeviceForm { @Schema(title = "客户号") private Long customerId; - /*@Schema(value = "设备编号") - private String deviceNo;*/ @NotBlank(message = "设备名称不能为空") @Schema(title = "设备名称", required = true) @@ -56,4 +54,25 @@ public class DeviceForm { @Schema(title = "备注") private String remark; + + // 设备类型相关字段 + @Schema(title = "设备类型名称") + private String typeName; + + @Schema(title = "是否支持蓝牙") + private String isSupportBle; + + @Schema(title = "定位方式") + private String locateMode; + + @Schema(title = "通讯方式") + private String communicationMode; + + @Schema(title = "APP模型字典") + private String appModelDictionary; + + @Schema(title = "PC模型字典") + private String pcModelDictionary; + + } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/excel/HeadValidateListener.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/excel/HeadValidateListener.java new file mode 100644 index 00000000..2628a19d --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/excel/HeadValidateListener.java @@ -0,0 +1,54 @@ +package com.fuyuanshen.equipment.excel; + +import com.alibaba.excel.context.AnalysisContext; +import com.alibaba.excel.event.AnalysisEventListener; +import com.alibaba.fastjson2.JSON; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * 读取表头 监听器 + * + * @author: 默苍璃 + * @date: 2025-11-1809:36 + */ +@Slf4j +public class HeadValidateListener extends AnalysisEventListener> { + + private List headNames = new ArrayList<>(); + + + /** + * When analysis one row trigger invoke function. + * + * @param headMap one row value. It is same as {@link AnalysisContext#readRowHolder()} + * @param context analysis context + */ + @Override + public void invoke(Map headMap, AnalysisContext context) { + log.info("解析到一条数据: {}", JSON.toJSONString(headMap)); + // 按顺序收集表头名称 + for (int i = 0; i < headMap.size(); i++) { + headNames.add(headMap.get(i)); + } + } + + /** + * if have something to do after all analysis + * + * @param context + */ + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + // 读取完成后不需要额外操作 + } + + + public List getHeadNames() { + return headNames; + } + +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/excel/UploadDeviceDataListener.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/excel/UploadDeviceDataListener.java index cdaf8433..ba7e7b76 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/excel/UploadDeviceDataListener.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/excel/UploadDeviceDataListener.java @@ -5,8 +5,7 @@ import com.alibaba.excel.EasyExcel; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.read.listener.ReadListener; import com.alibaba.fastjson2.JSON; -import com.fuyuanshen.common.core.domain.model.LoginUser; -import com.fuyuanshen.common.satoken.utils.LoginHelper; +import com.fuyuanshen.common.core.utils.file.ImageCompressUtil; import com.fuyuanshen.equipment.constants.DeviceConstants; import com.fuyuanshen.equipment.domain.Device; import com.fuyuanshen.equipment.domain.DeviceType; @@ -46,38 +45,41 @@ public class UploadDeviceDataListener implements ReadListener failedRecordsWithImages = new ArrayList<>(); - for (DeviceExcelImportDTO failedRecord : failedRecords) { + // 创建一个映射,用于存储失败记录行号与图片数据的关系 + Map errorReportImageMap = new HashMap<>(); + + for (int i = 0; i < failedRecords.size(); i++) { + DeviceExcelImportDTO failedRecord = failedRecords.get(i); + // 创建副本,避免修改原始数据 + DeviceExcelImportDTO recordWithImage = new DeviceExcelImportDTO(); + BeanUtil.copyProperties(failedRecord, recordWithImage); + // 设置图片占位符,让用户知道这里有图片 + // recordWithImage.setDevicePic("[图片]"); + failedRecordsWithImages.add(recordWithImage); + // 获取原始行号 - Integer rowIndex = null; + Integer originalRowIndex = null; for (Map.Entry entry : rowDtoMap.entrySet()) { if (entry.getValue() == failedRecord) { - rowIndex = entry.getKey(); + originalRowIndex = entry.getKey(); break; } } - if (rowIndex != null) { - // 创建副本,避免修改原始数据 - DeviceExcelImportDTO recordWithImage = new DeviceExcelImportDTO(); - BeanUtil.copyProperties(failedRecord, recordWithImage); - - // 设置图片数据 - byte[] imageData = rowImageMap.get(rowIndex); + // 保存图片数据用于错误报告 + if (originalRowIndex != null) { + byte[] imageData = rowImageMap.get(originalRowIndex); if (imageData != null) { - // recordWithImage.setDevicePicFromBytes(imageData); + errorReportImageMap.put(i, imageData); // 使用新的列表索引 } - - failedRecordsWithImages.add(recordWithImage); - } else { - failedRecordsWithImages.add(failedRecord); } } @@ -99,7 +101,8 @@ public class UploadDeviceDataListener implements ReadListener failedRecordsWithImages = new ArrayList<>(); + + for (int i = 0; i < failedRecords.size(); i++) { + DeviceExcelImportDTO failedRecord = failedRecords.get(i); + // 创建副本,避免修改原始数据 + DeviceExcelImportDTO recordWithImage = new DeviceExcelImportDTO(); + BeanUtil.copyProperties(failedRecord, recordWithImage); + + // 获取原始行号 + Integer originalRowIndex = null; + for (Map.Entry entry : rowDtoMap.entrySet()) { + if (entry.getValue() == failedRecord) { + originalRowIndex = entry.getKey(); + break; + } + } + + // 保存图片数据用于错误报告 + if (originalRowIndex != null) { + byte[] imageData = rowImageMap.get(originalRowIndex); + if (imageData != null) { + // recordWithImage.setImageData(imageData); + recordWithImage.setDevicePic(imageData); + } + } + + failedRecordsWithImages.add(recordWithImage); + } + + result.setFailedRecords(failedRecordsWithImages); + + // 生成错误报告 + if (!failedRecordsWithImages.isEmpty()) { + try { + // 生成唯一的文件名 + String fileName = "import_errors_" + System.currentTimeMillis() + ".xlsx"; + + // 创建错误报告目录 + String errorDirPath = params.getFilePath() + DeviceConstants.ERROR_REPORT_DIR; + File errorDir = new File(errorDirPath); + if (!errorDir.exists() && !errorDir.mkdirs()) { + log.error("无法创建错误报告目录: {}", errorDirPath); + return result; + } + + // 保存错误报告到文件(包含图片) + File errorFile = new File(errorDir, fileName); + EasyExcel.write(errorFile, DeviceExcelImportDTO.class) + .sheet("失败数据").doWrite(failedRecordsWithImages); + + // 生成访问URL + SysOssVo upload = params.getOssService().upload(errorFile); + String url = upload.getUrl(); + // 将http://替换为https://,但不影响已经是https://的URL + if (url.startsWith("http://")) { + url = "https://" + url.substring(7); + } + result.setErrorExcelUrl(url); + log.info("错误报告已保存: {}", errorFile.getAbsolutePath()); + log.info("错误报告已保存: {}", url); + } catch (Exception e) { + log.error("生成错误报告失败", e); + } + } + + return result; + } @Override public void invoke(DeviceExcelImportDTO data, AnalysisContext context) { @@ -142,14 +218,232 @@ public class UploadDeviceDataListener implements ReadListener typeNames = new HashSet<>(); + for (Integer rowIndex : rowIndexList) { + Device device = rowDeviceMap.get(rowIndex); + if (device != null && device.getTypeName() != null) { + typeNames.add(device.getTypeName()); + } + } + + // 批量查询所有设备类型 + Map deviceTypeMap = new HashMap<>(); + if (!typeNames.isEmpty()) { + List deviceTypes = params.getDeviceTypeService().queryByNames(new ArrayList<>(typeNames)); + for (DeviceType deviceType : deviceTypes) { + deviceTypeMap.put(deviceType.getTypeName(), deviceType); + } + } + + for (Integer rowIndex : rowIndexList) { + Device device = rowDeviceMap.get(rowIndex); + DeviceExcelImportDTO originalDto = rowDtoMap.get(rowIndex); + try { + // 从缓存中获取设备类型 + DeviceType deviceType = deviceTypeMap.get(device.getTypeName()); + + DeviceForm deviceForm = new DeviceForm(); + deviceForm.setDeviceName(device.getDeviceName()); + deviceForm.setDeviceType(deviceType != null ? deviceType.getId() : null); + deviceForm.setTypeName(device.getTypeName()); + deviceForm.setRemark(device.getRemark()); + deviceForm.setDeviceMac(device.getDeviceMac()); + deviceForm.setDeviceImei(device.getDeviceImei()); + deviceForm.setBluetoothName(device.getBluetoothName()); + deviceForm.setDevicePic(device.getDevicePic()); + + // 设置设备类型相关信息,供设备服务内部创建设备类型时使用 + if (deviceType == null) { + deviceForm.setIsSupportBle(originalDto.getIsSupportBle()); + deviceForm.setLocateMode(originalDto.getLocateMode()); + deviceForm.setCommunicationMode(originalDto.getCommunicationMode()); + deviceForm.setAppModelDictionary(originalDto.getAppModelDictionary()); + deviceForm.setPcModelDictionary(originalDto.getPcModelDictionary()); + } + + params.getDeviceService().addDevice(deviceForm); + successCount++; + log.info("行 {} 数据插入成功", rowIndex); + } catch (Exception e) { + failureCount++; + originalDto.setErrorMessage(e.getMessage()); + // originalDto.setErrorMessage("数据有误,请核对模板后,确认数据无误后,重新再试!!!"); + failedRecords.add(originalDto); + log.error("行 {} 数据插入失败: {}", rowIndex, e.getMessage()); + } + } + } + + /** + * 处理图片数据 + */ + private void processImages() { + // 如果没有数据行,直接返回 + if (rowIndexList.isEmpty()) { + return; + } + + // 检查是否有图片需要处理 + try { + // 只在确实有图片时才打开文件处理 + boolean hasImages = false; + try (OPCPackage opcPackage = OPCPackage.open(params.getFile().getInputStream())) { + ZipSecureFile.setMinInflateRatio(-1.0d); + XSSFWorkbook workbook = new XSSFWorkbook(opcPackage); + XSSFSheet sheet = workbook.getSheetAt(0); + + XSSFDrawing drawing = sheet.getDrawingPatriarch(); + if (drawing != null) { + for (XSSFShape shape : drawing.getShapes()) { + if (shape instanceof XSSFPicture) { + hasImages = true; + break; + } + } + } + } catch (Exception e) { + log.warn("检查图片时发生异常: {}", e.getMessage()); + // 如果检查图片失败,继续处理数据 + return; + } + + // 如果没有图片,直接返回 + if (!hasImages) { + log.info("未检测到图片,跳过图片处理"); + return; + } + + // 有图片时才进行处理 + try (OPCPackage opcPackage = OPCPackage.open(params.getFile().getInputStream())) { + ZipSecureFile.setMinInflateRatio(-1.0d); + XSSFWorkbook workbook = new XSSFWorkbook(opcPackage); + XSSFSheet sheet = workbook.getSheetAt(0); + + XSSFDrawing drawing = sheet.getDrawingPatriarch(); + if (drawing == null) return; + + for (XSSFShape shape : drawing.getShapes()) { + if (shape instanceof XSSFPicture) { + XSSFPicture picture = (XSSFPicture) shape; + XSSFClientAnchor anchor = picture.getPreferredSize(); + int rowIndex = anchor.getRow1(); + int colIndex = anchor.getCol1(); + + if (colIndex == 2) { + Device device = rowDeviceMap.get(rowIndex); + if (device != null) { + try { + byte[] imageData = picture.getPictureData().getData(); + + // 检查图片大小,如果超过5MB则拒绝上传 + if (imageData.length > 5 * 1024 * 1024) { + String errorMsg = "图片大小超过5MB限制,请压缩后重新上传"; + log.warn("行 {} 图片过大: {} bytes", rowIndex, imageData.length); + // 将错误信息添加到该行的DTO中 + DeviceExcelImportDTO dto = rowDtoMap.get(rowIndex); + if (dto != null) { + dto.setErrorMessage(errorMsg); + failedRecords.add(dto); + failureCount++; + } + device.setDevicePic(null); // 设置为空,让插入继续 + continue; // 跳过当前图片处理 + } + + // 表示Excel表格中的第3列(因为索引从0开始计算) + String extraValue = getCellValue(sheet, rowIndex, 2); + String imageUrl = uploadAndGenerateUrl(imageData, extraValue); + device.setDevicePic(imageUrl); + + // 2. 保存图片数据到DTO,用于错误报告 + rowImageMap.put(rowIndex, imageData); + } catch (Exception e) { + log.error("行 {} 图片处理失败: {}", rowIndex, e.getMessage()); + device.setDevicePic(null); // 设置为空,让插入继续 + } + } + } + } + } + } catch (Exception e) { + log.error("图片处理失败:{}", e.getMessage(), e); + } + } catch (Exception e) { + log.warn("图片处理过程中发生异常: {}", e.getMessage()); + } + } + + + private void processDataRowByRow1() { for (Integer rowIndex : rowIndexList) { Device device = rowDeviceMap.get(rowIndex); DeviceExcelImportDTO originalDto = rowDtoMap.get(rowIndex); try { // 设备类型 DeviceType deviceType = params.getDeviceTypeService().queryByName(device.getTypeName()); - // params.getDeviceService().save(device); + + // 如果设备类型不存在,则创建新的设备类型 + if (deviceType == null) { + DeviceType newDeviceType = new DeviceType(); + newDeviceType.setTypeName(device.getTypeName()); + newDeviceType.setIsSupportBle("是".equals(originalDto.getIsSupportBle()) || "1".equals(originalDto.getIsSupportBle())); + + // 设置定位方式 + if (originalDto.getLocateMode() != null) { + switch (originalDto.getLocateMode()) { + case "无": + newDeviceType.setLocateMode("0"); + break; + case "GPS": + newDeviceType.setLocateMode("1"); + break; + case "基站": + newDeviceType.setLocateMode("2"); + break; + case "wifi": + newDeviceType.setLocateMode("3"); + break; + case "北斗": + newDeviceType.setLocateMode("4"); + break; + default: + newDeviceType.setLocateMode(originalDto.getLocateMode()); + } + } + + // 设置通讯方式 + if (originalDto.getCommunicationMode() != null) { + switch (originalDto.getCommunicationMode()) { + case "4G": + newDeviceType.setCommunicationMode("0"); + break; + case "蓝牙": + newDeviceType.setCommunicationMode("1"); + break; + case "4G&蓝牙": + newDeviceType.setCommunicationMode("2"); + break; + default: + newDeviceType.setCommunicationMode(originalDto.getCommunicationMode()); + } + } + + newDeviceType.setAppModelDictionary(originalDto.getAppModelDictionary()); + newDeviceType.setPcModelDictionary(originalDto.getPcModelDictionary()); + + // 创建新的设备类型 + params.getDeviceTypeService().create(newDeviceType); + + // 重新查询确保获取到正确的ID + deviceType = params.getDeviceTypeService().queryByName(device.getTypeName()); + } + DeviceForm deviceForm = new DeviceForm(); deviceForm.setDeviceName(device.getDeviceName()); deviceForm.setDeviceType(deviceType.getId()); @@ -163,13 +457,19 @@ public class UploadDeviceDataListener implements ReadListener 5 * 1024 * 1024) { + String errorMsg = "图片大小超过5MB限制,请压缩后重新上传"; + log.warn("行 {} 图片过大: {} bytes", rowIndex, imageData.length); + // 将错误信息添加到该行的DTO中 + DeviceExcelImportDTO dto = rowDtoMap.get(rowIndex); + if (dto != null) { + dto.setErrorMessage(errorMsg); + failedRecords.add(dto); + failureCount++; + } + device.setDevicePic(null); // 设置为空,让插入继续 + continue; // 跳过当前图片处理 + } + // 表示Excel表格中的第3列(因为索引从0开始计算) String extraValue = getCellValue(sheet, rowIndex, 2); String imageUrl = uploadAndGenerateUrl(imageData, extraValue); @@ -224,9 +540,16 @@ public class UploadDeviceDataListener implements ReadListener 500 * 1024) { + log.info("检测到大图片 ({} bytes),正在进行压缩优化...", imageData.length); + imageData = ImageCompressUtil.compressImage(imageData, 500 * 1024); // 压缩到500KB以下 + } + String fileExtension = "jpg"; String newFileName = "PS_" + new Random(8) + "." + fileExtension; SysOssVo upload = params.getOssService().upload(imageData, newFileName); + log.info("图片保存成功,URL: {}", upload.getUrl()); return upload.getUrl(); } catch (Exception e) { log.error("保存图片失败", e); @@ -234,4 +557,5 @@ public class UploadDeviceDataListener implements ReadListener imageMap; + private int imageColIndex; + + public ImageWriteHandler(Map imageMap) { + this(imageMap, 2); + } + + public ImageWriteHandler(Map imageMap, int imageColIndex) { + this.imageMap = imageMap; + this.imageColIndex = imageColIndex; + } + + @Override + public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) { + if (imageMap == null || imageMap.isEmpty()) { + return; + } + + Workbook workbook = writeWorkbookHolder.getWorkbook(); + Sheet sheet = writeSheetHolder.getSheet(); + + // 设置图片列的宽度 + sheet.setColumnWidth(imageColIndex, 20 * 256); // 20个字符宽度 + + for (Map.Entry entry : imageMap.entrySet()) { + int dataRowIndex = entry.getKey(); + int rowIndex = dataRowIndex + 1; // 跳过标题行 + byte[] imageData = entry.getValue(); + + if (imageData != null && imageData.length > 0) { + insertImage(workbook, sheet, rowIndex, imageData); + } + } + } + + private void insertImage(Workbook workbook, Sheet sheet, int rowIndex, byte[] imageData) { + try { + // 获取或创建行(确保行存在) + Row row = sheet.getRow(rowIndex); + if (row == null) { + row = sheet.createRow(rowIndex); + } + + // 设置合适的行高(不要设置过大) + row.setHeightInPoints(60); // 60磅,足够显示小图 + + // 添加图片到工作簿 + int pictureIdx = workbook.addPicture(imageData, Workbook.PICTURE_TYPE_JPEG); + + // 创建绘图对象 + Drawing drawing = sheet.createDrawingPatriarch(); + + // 关键修改:使用MOVE_DONT_RESIZE锚点类型,避免影响行高 + ClientAnchor anchor = workbook.getCreationHelper().createClientAnchor(); + anchor.setAnchorType(ClientAnchor.AnchorType.MOVE_DONT_RESIZE); + anchor.setCol1(imageColIndex); + anchor.setRow1(rowIndex); + anchor.setCol2(imageColIndex + 1); + anchor.setRow2(rowIndex + 1); + + // 设置较小的偏移量,确保图片在单元格内 + anchor.setDx1(0); + anchor.setDy1(0); + anchor.setDx2(512 * 5); // 约5个字符宽度 + anchor.setDy2(256 * 4); // 约4行高度 + + // 插入图片 + Picture picture = drawing.createPicture(anchor, pictureIdx); + + // 不要调用resize(),避免自动调整影响行高 + // picture.resize(); + + } catch (Exception e) { + System.err.println("插入图片失败,行: " + rowIndex); + e.printStackTrace(); + } + } + @Override public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) { // 不需要实现 } - - @Override - public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) { - Workbook workbook = writeWorkbookHolder.getWorkbook(); - Sheet sheet = writeSheetHolder.getSheet(); - - // 获取设备图片列索引(假设是第4列,索引3) - int imageColIndex = 3; - - // 遍历所有行 - for (int rowIndex = 1; rowIndex <= sheet.getLastRowNum(); rowIndex++) { // 从第2行开始(跳过标题) - Row row = sheet.getRow(rowIndex); - if (row == null) continue; - - Cell imageCell = row.getCell(imageColIndex); - if (imageCell == null) continue; - - // 获取图片数据 - byte[] imageData = null; - if (imageCell.getCellType() == CellType.STRING) { - // 处理Base64编码的图片(如果需要) - } - - if (imageData != null && imageData.length > 0) { - try { - // 添加图片到工作表 - int pictureIdx = workbook.addPicture(imageData, Workbook.PICTURE_TYPE_JPEG); - - // 创建绘图对象 - if (sheet instanceof XSSFSheet) { - XSSFSheet xssfSheet = (XSSFSheet) sheet; - XSSFDrawing drawing = xssfSheet.createDrawingPatriarch(); - - // 设置图片位置 - XSSFClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, imageColIndex, rowIndex, imageColIndex + 1, rowIndex + 1); - - // 创建图片 - drawing.createPicture(anchor, pictureIdx); - } - - // 清除单元格内容 - imageCell.setBlank(); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - } } \ No newline at end of file diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceTypeMapper.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceTypeMapper.java index 6500eaf1..8bddea32 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceTypeMapper.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceTypeMapper.java @@ -51,4 +51,13 @@ public interface DeviceTypeMapper extends BaseMapper { */ DeviceType queryByName(@Param("criteria") DeviceTypeQueryCriteria criteria); + /** + * 根据名称列表查询设备类型 + * + * @param typeNames + * @return + */ + List selectByNames(@Param("typeNames") List typeNames); + + } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/DeviceTypeService.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/DeviceTypeService.java index 9dfcdcaa..2d75a0af 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/DeviceTypeService.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/DeviceTypeService.java @@ -49,6 +49,15 @@ public interface DeviceTypeService extends IService { */ DeviceType queryByName(String typeName); + /** + * 根据设备类型名称列表查询设备类型 + * + * @param typeNames 设备类型名称列表 + * @return List + */ + List queryByNames(List typeNames); + + /** * 新增设备类型 * diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceExportService.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceExportService.java index dc70220b..4c51e7be 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceExportService.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceExportService.java @@ -3,7 +3,9 @@ package com.fuyuanshen.equipment.service.impl; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.util.DateUtils; import com.fuyuanshen.equipment.domain.Device; +import com.fuyuanshen.equipment.domain.DeviceType; import com.fuyuanshen.equipment.domain.dto.DeviceExcelExportDTO; +import com.fuyuanshen.equipment.domain.dto.DeviceWithTypeExcelExportDTO; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -14,6 +16,7 @@ import java.net.URL; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.Map; import java.util.concurrent.Semaphore; import java.util.stream.Collectors; @@ -39,23 +42,16 @@ public class DeviceExportService { // 转换为DTO列表 List dtoList = devices.stream().map(device -> { DeviceExcelExportDTO dto = new DeviceExcelExportDTO(); - // dto.setId(device.getId()); - // dto.setDeviceType(device.getDeviceType()); - // dto.setCustomerName(device.getCustomerName()); dto.setDeviceName(device.getDeviceName()); dto.setDeviceMac(device.getDeviceMac()); // 设备IMEI dto.setDeviceImei(device.getDeviceImei()); // 蓝牙名称 dto.setBluetoothName(device.getBluetoothName()); - // dto.setLongitude(device.getLongitude()); - // dto.setLatitude(device.getLatitude()); dto.setRemark(device.getRemark()); dto.setTypeName(device.getTypeName()); dto.setCreateBy(device.getCreateByName()); - Integer deviceStatus = device.getDeviceStatus(); Integer bindingStatus = device.getBindingStatus(); - // dto.setDeviceStatus(deviceStatus == 1 ? "正常" : "失效"); dto.setBindingStatus(bindingStatus == 1 ? "已绑定" : "未绑定"); // 时间戳转换 dto.setCreateTime(DateUtils.format(device.getCreateTime(), "yyyy-MM-dd HH:mm:ss")); @@ -75,6 +71,158 @@ public class DeviceExportService { } + /** + * 导出设备数据(包含完整设备类型信息) + * + * @param devices + * @param deviceTypes + * @param response + */ + public void exportWithTypeInfo(List devices, List deviceTypes, HttpServletResponse response) { + try { + String fileName = "设备列表_含类型详情_" + System.currentTimeMillis() + ".xlsx"; + // 使用URLEncoder进行RFC 5987编码 + String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()).replaceAll("\\+", "%20"); + + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + // 使用RFC 5987标准编码文件名 + response.setHeader("Content-disposition", "attachment;filename*=UTF-8''" + encodedFileName); + + // 构建设备类型映射 + Map deviceTypeMap = deviceTypes.stream() + .collect(Collectors.toMap(DeviceType::getId, deviceType -> deviceType)); + + // 转换为DTO列表 + List dtoList = devices.stream().map(device -> { + DeviceWithTypeExcelExportDTO dto = new DeviceWithTypeExcelExportDTO(); + dto.setDeviceName(device.getDeviceName()); + dto.setDeviceMac(device.getDeviceMac()); + // 设备IMEI + dto.setDeviceImei(device.getDeviceImei()); + // 蓝牙名称 + dto.setBluetoothName(device.getBluetoothName()); + dto.setRemark(device.getRemark()); + dto.setTypeName(device.getTypeName()); + dto.setCreateBy(device.getCreateByName()); + Integer bindingStatus = device.getBindingStatus(); + dto.setBindingStatus(bindingStatus == 1 ? "已绑定" : "未绑定"); + // 时间戳转换 + dto.setCreateTime(DateUtils.format(device.getCreateTime(), "yyyy-MM-dd HH:mm:ss")); + + // 获取设备类型详细信息 + DeviceType deviceType = deviceTypeMap.get(device.getDeviceType()); + if (deviceType != null) { + // 处理是否支持蓝牙 + if (deviceType.getIsSupportBle() != null) { + dto.setIsSupportBle(deviceType.getIsSupportBle() ? "是" : "否"); + } else { + dto.setIsSupportBle("未知"); + } + + // 处理定位方式 + if (deviceType.getLocateMode() != null) { + dto.setLocateMode(convertLocateMode(deviceType.getLocateMode())); + } else { + dto.setLocateMode(""); + } + + // 处理通讯方式 + if (deviceType.getCommunicationMode() != null) { + dto.setCommunicationMode(convertCommunicationMode(deviceType.getCommunicationMode())); + } else { + dto.setCommunicationMode(""); + } + + // 处理APP页面跳转字典 + dto.setAppModelDictionary(deviceType.getAppModelDictionary() != null ? deviceType.getAppModelDictionary() : ""); + + // 处理PC页面跳转字典 + dto.setPcModelDictionary(deviceType.getPcModelDictionary() != null ? deviceType.getPcModelDictionary() : ""); + } else { + dto.setIsSupportBle("未知"); + dto.setLocateMode(""); + dto.setCommunicationMode(""); + dto.setAppModelDictionary(""); + dto.setPcModelDictionary(""); + } + + // 处理图片URL转换 + handleDevicePicForTypeExport(device, dto); + + return dto; + }).collect(Collectors.toList()); + + // 写入Excel + EasyExcel.write(response.getOutputStream(), DeviceWithTypeExcelExportDTO.class).sheet("设备数据含类型详情").doWrite(dtoList); + + } catch (IOException e) { + throw new RuntimeException("导出Excel失败", e); + } + } + + /** + * 转换定位方式代码为中文描述 + * + * @param locateMode 定位方式代码 (0:无;1:GPS;2:基站;3:wifi;4:北斗) + * @return 中文描述 + */ + private String convertLocateMode(String locateMode) { + switch (locateMode) { + case "0": + return "无"; + case "1": + return "GPS"; + case "2": + return "基站"; + case "3": + return "wifi"; + case "4": + return "北斗"; + default: + return locateMode; + } + } + + /** + * 转换联网方式代码为中文描述 + * + * @param networkWay 联网方式代码 (0:无;1:4G;2:WIFI) + * @return 中文描述 + */ + private String convertNetworkWay(String networkWay) { + switch (networkWay) { + case "0": + return "无"; + case "1": + return "4G"; + case "2": + return "WIFI"; + default: + return networkWay; + } + } + + /** + * 转换通讯方式代码为中文描述 + * + * @param communicationMode 通讯方式代码 (0:4G;1:蓝牙) + * @return 中文描述 + */ + private String convertCommunicationMode(String communicationMode) { + switch (communicationMode) { + case "0": + return "4G"; + case "1": + return "蓝牙"; + case "2": + return "4G&蓝牙"; + default: + return communicationMode; + } + } + + // 在DeviceExportService中添加并发控制 private static final Semaphore imageLoadSemaphore = new Semaphore(5); // 最多同时加载5张图片 @@ -113,4 +261,51 @@ public class DeviceExportService { } + + private void handleDevicePicForTypeExport(Device device, DeviceWithTypeExcelExportDTO dto) { + String picUrl = device.getDevicePic(); + log.debug("处理设备图片,设备ID: {}, 图片URL: {}", device.getId(), picUrl); + + if (picUrl != null && !picUrl.trim().isEmpty()) { + try { + // 获取加载图片的许可 + imageLoadSemaphore.acquire(); + + try { + picUrl = convertUrl(picUrl); + log.info("转换后的URL: {}", picUrl); + + // 尝试创建URL对象(会自动验证格式) + URL url = new URL(picUrl); + dto.setDevicePic(url); + log.debug("成功设置设备图片URL到DTO"); + } finally { + // 释放许可 + imageLoadSemaphore.release(); + } + } catch (Exception e) { + log.warn("设置设备图片失败,设备ID: {}, URL: {}, 错误: {}", device.getId(), picUrl, e.getMessage()); + dto.setDevicePic(null); + } + } else { + log.debug("设备没有设置图片,设备ID: {}", device.getId()); + dto.setDevicePic(null); + } + } + + /** + * 转换图片URL为HTTP + * 转回minio格式 + * + * @param originalUrl 原始URL + * @return 转换后的URL + */ + + public String convertUrl(String originalUrl) { + String result = originalUrl.replace("https://fuyuanshen.com", "http://120.79.224.186:9000"); + result = result.replace("http://fuyuanshen.com", "http://120.79.224.186:9000"); + return result; + } + + } \ No newline at end of file diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java index 2f37f95c..6e9551c1 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java @@ -192,15 +192,86 @@ public class DeviceServiceImpl extends ServiceImpl impleme throw new BadRequestException("设备IMEI已存在!!!"); } - DeviceTypeQueryCriteria queryCriteria = new DeviceTypeQueryCriteria(); - queryCriteria.setDeviceTypeId(deviceForm.getDeviceType()); - queryCriteria.setCustomerId(LoginHelper.getUserId()); - DeviceTypeGrants typeGrants = deviceTypeGrantsMapper.selectById(queryCriteria.getDeviceTypeId()); - if (typeGrants == null) { - throw new Exception("设备类型不存在!!!"); + // 检查设备类型是否存在,如果不存在则创建 + DeviceType deviceType = null; + if (deviceForm.getDeviceType() != null) { + deviceType = deviceTypeMapper.selectById(deviceForm.getDeviceType()); + } else if (deviceForm.getTypeName() != null) { + deviceType = deviceTypeMapper.selectOne(new QueryWrapper().eq("type_name", deviceForm.getTypeName())); } - DeviceType deviceTypes = deviceTypeMapper.selectById(typeGrants.getDeviceTypeId()); - if (deviceTypes == null) { + + if (deviceType == null && deviceForm.getTypeName() != null) { + // 创建新的设备类型 + DeviceType newDeviceType = new DeviceType(); + newDeviceType.setTypeName(deviceForm.getTypeName()); + newDeviceType.setIsSupportBle("是".equals(deviceForm.getIsSupportBle()) || "1".equals(deviceForm.getIsSupportBle())); + + // 设置定位方式 + if (deviceForm.getLocateMode() != null) { + switch (deviceForm.getLocateMode()) { + case "无": + newDeviceType.setLocateMode("0"); + break; + case "GPS": + newDeviceType.setLocateMode("1"); + break; + case "基站": + newDeviceType.setLocateMode("2"); + break; + case "wifi": + newDeviceType.setLocateMode("3"); + break; + case "北斗": + newDeviceType.setLocateMode("4"); + break; + default: + newDeviceType.setLocateMode(deviceForm.getLocateMode()); + } + } + + // 设置通讯方式 + if (deviceForm.getCommunicationMode() != null) { + switch (deviceForm.getCommunicationMode()) { + case "4G": + newDeviceType.setCommunicationMode("0"); + break; + case "蓝牙": + newDeviceType.setCommunicationMode("1"); + break; + case "4G&蓝牙": + newDeviceType.setCommunicationMode("2"); + break; + default: + newDeviceType.setCommunicationMode(deviceForm.getCommunicationMode()); + } + } + + newDeviceType.setAppModelDictionary(deviceForm.getAppModelDictionary()); + newDeviceType.setPcModelDictionary(deviceForm.getPcModelDictionary()); + + // 校验设备类型名称 + List existingTypes = deviceTypeMapper.selectList(new QueryWrapper().eq("type_name", newDeviceType.getTypeName())); + if (CollectionUtil.isNotEmpty(existingTypes)) { + throw new RuntimeException("设备类型名称已存在,无法新增!!!"); + } + + LoginUser loginUser = LoginHelper.getLoginUser(); + newDeviceType.setCreateByName(loginUser.getNickname()); + deviceTypeMapper.insert(newDeviceType); + + // 重新查询确保获取到正确的ID + deviceType = deviceTypeMapper.selectOne(new QueryWrapper().eq("type_name", deviceForm.getTypeName())); + + // 自动授权给自己 + DeviceTypeGrants deviceTypeGrants = new DeviceTypeGrants(); + deviceTypeGrants.setDeviceTypeId(deviceType.getId()); + deviceTypeGrants.setCustomerId(loginUser.getUserId()); + deviceTypeGrants.setGrantorCustomerId(loginUser.getUserId()); + deviceTypeGrants.setGrantedAt(new Date()); + deviceTypeGrantsMapper.insert(deviceTypeGrants); + } + + if (deviceType == null) { throw new Exception("设备类型不存在!!!"); } @@ -221,8 +292,8 @@ public class DeviceServiceImpl extends ServiceImpl impleme device.setCurrentOwnerId(loginUser.getUserId()); device.setOriginalOwnerId(loginUser.getUserId()); device.setCreateByName(loginUser.getNickname()); - device.setTypeName(deviceTypes.getTypeName()); - device.setDeviceType(deviceTypes.getId()); + device.setTypeName(deviceType.getTypeName()); + device.setDeviceType(deviceType.getId()); if (device.getDeviceImei() != null) { device.setPubTopic("A/" + device.getDeviceImei()); device.setSubTopic("B/" + device.getDeviceImei()); diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceTypeServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceTypeServiceImpl.java index 38eef642..d0f3815f 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceTypeServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceTypeServiceImpl.java @@ -159,6 +159,21 @@ public class DeviceTypeServiceImpl extends ServiceImpl + */ + @Override + public List queryByNames(List typeNames) { + if (typeNames == null || typeNames.isEmpty()) { + return new ArrayList<>(); + } + return deviceTypeMapper.selectByNames(typeNames); + } + + /** * 新增设备类型 * diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceTypeMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceTypeMapper.xml index b1f70ad8..3e608d07 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceTypeMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceTypeMapper.xml @@ -58,4 +58,14 @@ + + + \ No newline at end of file From a145c372b86ce813475b4cbf4ded759a49082ab5 Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Wed, 19 Nov 2025 10:55:44 +0800 Subject: [PATCH 151/160] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E5=AF=BC=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fys-admin/src/main/resources/application.yml | 4 +- .../dto/DeviceWithTypeExcelExportDTO.java | 6 +- .../service/impl/DeviceExportService.java | 64 ++++++++++++------- 3 files changed, 47 insertions(+), 27 deletions(-) diff --git a/fys-admin/src/main/resources/application.yml b/fys-admin/src/main/resources/application.yml index 21924a61..be595503 100644 --- a/fys-admin/src/main/resources/application.yml +++ b/fys-admin/src/main/resources/application.yml @@ -69,9 +69,9 @@ spring: servlet: multipart: # 单个文件大小 - max-file-size: 10MB + max-file-size: 100MB # 设置总上传的文件大小 - max-request-size: 20MB + max-request-size: 200MB mvc: # 设置静态资源路径 防止所有请求都去查静态资源 static-path-pattern: /static/** diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceWithTypeExcelExportDTO.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceWithTypeExcelExportDTO.java index 6ef7b5f4..0ffced9e 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceWithTypeExcelExportDTO.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceWithTypeExcelExportDTO.java @@ -4,6 +4,7 @@ import com.alibaba.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.write.style.ColumnWidth; import com.alibaba.excel.annotation.write.style.ContentRowHeight; import com.alibaba.excel.annotation.write.style.HeadRowHeight; +import com.fuyuanshen.equipment.converter.IgnoreFailedImageConverter; import lombok.Data; import java.net.URL; @@ -27,7 +28,8 @@ public class DeviceWithTypeExcelExportDTO { @ColumnWidth(20) private String typeName; - @ExcelProperty(value = "设备图片") + @ExcelProperty(value = "设备图片", converter = IgnoreFailedImageConverter.class) + // @ExcelProperty(value = "设备图片") @ColumnWidth(30) // 设置图片列宽度 private URL devicePic; // 使用URL类型 @@ -95,4 +97,4 @@ public class DeviceWithTypeExcelExportDTO { @ColumnWidth(20) private String pcModelDictionary; -} +} \ No newline at end of file diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceExportService.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceExportService.java index 4c51e7be..5133b1cf 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceExportService.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceExportService.java @@ -11,13 +11,12 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.io.IOException; -import java.net.MalformedURLException; import java.net.URL; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; -import java.util.concurrent.Semaphore; +import java.util.concurrent.*; import java.util.stream.Collectors; /** @@ -29,6 +28,9 @@ import java.util.stream.Collectors; public class DeviceExportService { public void export(List devices, HttpServletResponse response) { + long startTime = System.currentTimeMillis(); + log.info("开始导出设备列表,设备数量: {}", devices.size()); + try { String fileName = "设备列表_" + System.currentTimeMillis() + ".xlsx"; // 使用URLEncoder进行RFC 5987编码 @@ -39,8 +41,9 @@ public class DeviceExportService { // 使用RFC 5987标准编码文件名 response.setHeader("Content-disposition", "attachment;filename*=UTF-8''" + encodedFileName); - // 转换为DTO列表 - List dtoList = devices.stream().map(device -> { + // 转换为DTO列表,使用并行流加速处理 + List dtoList = devices.parallelStream().map(device -> { + long deviceProcessStartTime = System.currentTimeMillis(); DeviceExcelExportDTO dto = new DeviceExcelExportDTO(); dto.setDeviceName(device.getDeviceName()); dto.setDeviceMac(device.getDeviceMac()); @@ -59,13 +62,22 @@ public class DeviceExportService { // 处理图片URL转换 handleDevicePic(device, dto); + long deviceProcessEndTime = System.currentTimeMillis(); + log.info("单个设备处理耗时: {} ms, 设备ID: {}", (deviceProcessEndTime - deviceProcessStartTime), device.getId()); return dto; }).collect(Collectors.toList()); // 写入Excel + long excelWriteStartTime = System.currentTimeMillis(); EasyExcel.write(response.getOutputStream(), DeviceExcelExportDTO.class).sheet("设备数据").doWrite(dtoList); + long excelWriteEndTime = System.currentTimeMillis(); + + long endTime = System.currentTimeMillis(); + log.info("设备列表导出完成,总耗时: {} ms,设备数量: {},Excel写入耗时: {} ms", + (endTime - startTime), devices.size(), (excelWriteEndTime - excelWriteStartTime)); } catch (IOException e) { + log.error("导出Excel失败", e); throw new RuntimeException("导出Excel失败", e); } } @@ -79,6 +91,9 @@ public class DeviceExportService { * @param response */ public void exportWithTypeInfo(List devices, List deviceTypes, HttpServletResponse response) { + long startTime = System.currentTimeMillis(); + log.info("开始导出设备列表(含类型详情),设备数量: {}", devices.size()); + try { String fileName = "设备列表_含类型详情_" + System.currentTimeMillis() + ".xlsx"; // 使用URLEncoder进行RFC 5987编码 @@ -93,8 +108,9 @@ public class DeviceExportService { Map deviceTypeMap = deviceTypes.stream() .collect(Collectors.toMap(DeviceType::getId, deviceType -> deviceType)); - // 转换为DTO列表 - List dtoList = devices.stream().map(device -> { + // 转换为DTO列表,使用并行流加速处理 + List dtoList = devices.parallelStream().map(device -> { + long deviceProcessStartTime = System.currentTimeMillis(); DeviceWithTypeExcelExportDTO dto = new DeviceWithTypeExcelExportDTO(); dto.setDeviceName(device.getDeviceName()); dto.setDeviceMac(device.getDeviceMac()); @@ -149,14 +165,20 @@ public class DeviceExportService { // 处理图片URL转换 handleDevicePicForTypeExport(device, dto); - return dto; }).collect(Collectors.toList()); // 写入Excel + long excelWriteStartTime = System.currentTimeMillis(); EasyExcel.write(response.getOutputStream(), DeviceWithTypeExcelExportDTO.class).sheet("设备数据含类型详情").doWrite(dtoList); + long excelWriteEndTime = System.currentTimeMillis(); + + long endTime = System.currentTimeMillis(); + log.info("设备列表(含类型详情)导出完成,总耗时: {} ms,设备数量: {},Excel写入耗时: {} ms", + (endTime - startTime), devices.size(), (excelWriteEndTime - excelWriteStartTime)); } catch (IOException e) { + log.error("导出Excel失败", e); throw new RuntimeException("导出Excel失败", e); } } @@ -224,71 +246,67 @@ public class DeviceExportService { // 在DeviceExportService中添加并发控制 - private static final Semaphore imageLoadSemaphore = new Semaphore(5); // 最多同时加载5张图片 + private static final Semaphore imageLoadSemaphore = new Semaphore(10); // 增加到最多同时加载10张图片 private void handleDevicePic(Device device, DeviceExcelExportDTO dto) { String picUrl = device.getDevicePic(); - log.debug("处理设备图片,设备ID: {}, 图片URL: {}", device.getId(), picUrl); if (picUrl != null && !picUrl.trim().isEmpty()) { try { - // 获取加载图片的许可 - imageLoadSemaphore.acquire(); + // 获取加载图片的许可,带超时控制 + if (!imageLoadSemaphore.tryAcquire(5, TimeUnit.SECONDS)) { + dto.setDevicePic(null); + return; + } try { // 自动将HTTP转换为HTTPS以避免重定向问题 if (picUrl.startsWith("http://")) { picUrl = "https://" + picUrl.substring(7); - log.debug("自动将HTTP转换为HTTPS: {}", picUrl); } // 尝试创建URL对象(会自动验证格式) URL url = new URL(picUrl); dto.setDevicePic(url); - log.debug("成功设置设备图片URL到DTO"); } finally { // 释放许可 imageLoadSemaphore.release(); } } catch (Exception e) { - log.warn("设置设备图片失败,设备ID: {}, URL: {}, 错误: {}", device.getId(), picUrl, e.getMessage()); dto.setDevicePic(null); } } else { - log.debug("设备没有设置图片,设备ID: {}", device.getId()); dto.setDevicePic(null); } } - private void handleDevicePicForTypeExport(Device device, DeviceWithTypeExcelExportDTO dto) { String picUrl = device.getDevicePic(); - log.debug("处理设备图片,设备ID: {}, 图片URL: {}", device.getId(), picUrl); if (picUrl != null && !picUrl.trim().isEmpty()) { try { - // 获取加载图片的许可 - imageLoadSemaphore.acquire(); + // 获取加载图片的许可,带超时控制 + if (!imageLoadSemaphore.tryAcquire(5, TimeUnit.SECONDS)) { + dto.setDevicePic(null); + return; + } try { picUrl = convertUrl(picUrl); - log.info("转换后的URL: {}", picUrl); // 尝试创建URL对象(会自动验证格式) URL url = new URL(picUrl); dto.setDevicePic(url); - log.debug("成功设置设备图片URL到DTO"); + } finally { // 释放许可 imageLoadSemaphore.release(); } } catch (Exception e) { - log.warn("设置设备图片失败,设备ID: {}, URL: {}, 错误: {}", device.getId(), picUrl, e.getMessage()); dto.setDevicePic(null); } } else { - log.debug("设备没有设置图片,设备ID: {}", device.getId()); dto.setDevicePic(null); } } From a0ab5e9fe0b56cf71584a355f3e11032439db090 Mon Sep 17 00:00:00 2001 From: chenyouting <514333061@qq.com> Date: Wed, 19 Nov 2025 17:17:34 +0800 Subject: [PATCH 152/160] =?UTF-8?q?app=E6=B3=A8=E9=94=80=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E8=81=94=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/service/AppLoginService.java | 29 +++++++++++++++++++ .../app/mapper/AppDeviceShareMapper.java | 4 +++ .../app/service/IAppDeviceShareService.java | 2 ++ .../impl/AppDeviceShareServiceImpl.java | 5 ++++ .../mapper/app/AppDeviceShareMapper.xml | 6 ++++ 5 files changed, 46 insertions(+) diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/service/AppLoginService.java b/fys-admin/src/main/java/com/fuyuanshen/app/service/AppLoginService.java index 0c86da27..6d038219 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/service/AppLoginService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/service/AppLoginService.java @@ -4,6 +4,10 @@ import cn.dev33.satoken.exception.NotLoginException; import cn.dev33.satoken.stp.StpUtil; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.util.ObjectUtil; +import com.fuyuanshen.app.domain.bo.AppDeviceBindRecordBo; +import com.fuyuanshen.app.domain.bo.AppDeviceShareBo; +import com.fuyuanshen.app.domain.vo.AppDeviceBindRecordVo; +import com.fuyuanshen.app.domain.vo.AppDeviceShareVo; import com.fuyuanshen.app.domain.vo.AppRoleVo; import com.fuyuanshen.app.domain.vo.AppUserVo; import com.fuyuanshen.common.core.constant.Constants; @@ -33,6 +37,7 @@ import org.springframework.stereotype.Service; import java.time.Duration; import java.util.*; import java.util.function.Supplier; +import java.util.stream.Collectors; /** * 登录校验方法 @@ -52,6 +57,8 @@ public class AppLoginService { private final ISysTenantService tenantService; private final IAppUserService appUserService; + private final IAppDeviceShareService appDeviceShareService; + private final IAppDeviceBindRecordService appDeviceBindRecordService; /** @@ -188,10 +195,32 @@ public class AppLoginService { public void cancelAccount() { try { AppLoginUser loginUser = AppLoginHelper.getLoginUser(); +// AppLoginUser loginUser = new AppLoginUser(); +// loginUser.setUserId(1988398584423133187L); +// loginUser.setUsername("19022528079"); if (ObjectUtil.isNull(loginUser)) { return; } appUserService.deleteWithValidByIds(Collections.singletonList(loginUser.getUserId()),true); + + AppDeviceBindRecordBo appDeviceBindRecordBo = new AppDeviceBindRecordBo(); + appDeviceBindRecordBo.setBindingUserId(loginUser.getUserId()); + List appDeviceBindRecordVos = appDeviceBindRecordService.queryList(appDeviceBindRecordBo); + if(ObjectUtil.length(appDeviceBindRecordVos)>0){ + + + // 根据设备id批量删除 + List deviceIds = appDeviceBindRecordVos.stream().map(AppDeviceBindRecordVo::getDeviceId).toList(); + appDeviceShareService.deleteByDeviceIds(deviceIds); + + + List ids = appDeviceBindRecordVos.stream() + .map(AppDeviceBindRecordVo::getId) + .collect(Collectors.toList()); + appDeviceBindRecordService.deleteWithValidByIds(ids, true); + log.info("删除绑定关系表数据:ids={}",ids); + } + if (TenantHelper.isEnable() && LoginHelper.isSuperAdmin()) { // 超级管理员 登出清除动态租户 TenantHelper.clearDynamic(); diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/mapper/AppDeviceShareMapper.java b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/mapper/AppDeviceShareMapper.java index 99bc47c0..19819c11 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/mapper/AppDeviceShareMapper.java +++ b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/mapper/AppDeviceShareMapper.java @@ -9,6 +9,8 @@ import com.fuyuanshen.app.domain.vo.AppDeviceShareVo; import com.fuyuanshen.common.mybatis.core.mapper.BaseMapperPlus; import org.apache.ibatis.annotations.Param; +import java.util.List; + /** * 设备分享Mapper接口 * @@ -27,4 +29,6 @@ public interface AppDeviceShareMapper extends BaseMapperPlus selectWebDeviceShareList(@Param("bo") AppDeviceShareBo bo, Page page); + + void deleteByDeviceIds(@Param("deviceIds") List deviceIds); } diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/IAppDeviceShareService.java b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/IAppDeviceShareService.java index b4fcb4f9..cff36c5c 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/IAppDeviceShareService.java +++ b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/IAppDeviceShareService.java @@ -67,4 +67,6 @@ public interface IAppDeviceShareService { Boolean deleteWithValidByIds(Collection ids, Boolean isValid); TableDataInfo otherDeviceShareList(AppDeviceShareBo bo, PageQuery pageQuery); + + void deleteByDeviceIds(List deviceIds); } diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/impl/AppDeviceShareServiceImpl.java b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/impl/AppDeviceShareServiceImpl.java index 322268a0..726ef15c 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/impl/AppDeviceShareServiceImpl.java +++ b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/impl/AppDeviceShareServiceImpl.java @@ -166,4 +166,9 @@ public class AppDeviceShareServiceImpl implements IAppDeviceShareService { }); return TableDataInfo.build(result); } + + @Override + public void deleteByDeviceIds(List deviceIds) { + baseMapper.deleteByDeviceIds(deviceIds); + } } diff --git a/fys-modules/fys-app/src/main/resources/mapper/app/AppDeviceShareMapper.xml b/fys-modules/fys-app/src/main/resources/mapper/app/AppDeviceShareMapper.xml index ce0ffbcf..56de3ddb 100644 --- a/fys-modules/fys-app/src/main/resources/mapper/app/AppDeviceShareMapper.xml +++ b/fys-modules/fys-app/src/main/resources/mapper/app/AppDeviceShareMapper.xml @@ -3,6 +3,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> + + delete from app_device_share where device_id in + + #{item} + + + + + \ No newline at end of file From 00a4394b430d8cfa4d0d553d024804b914496526 Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Thu, 20 Nov 2025 16:15:15 +0800 Subject: [PATCH 155/160] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=AE=BE=E5=A4=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/DeviceServiceImpl.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java index 6e9551c1..78a83f51 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java @@ -192,10 +192,21 @@ public class DeviceServiceImpl extends ServiceImpl impleme throw new BadRequestException("设备IMEI已存在!!!"); } + DeviceTypeGrants typeGrants = new DeviceTypeGrants(); + + if (deviceForm.getDeviceType() != null) { + DeviceTypeQueryCriteria queryCriteria = new DeviceTypeQueryCriteria(); + queryCriteria.setDeviceTypeId(deviceForm.getDeviceType()); + typeGrants = deviceTypeGrantsMapper.selectById(queryCriteria.getDeviceTypeId()); + if (typeGrants == null) { + throw new Exception("设备类型不存在!!!"); + } + } + // 检查设备类型是否存在,如果不存在则创建 DeviceType deviceType = null; if (deviceForm.getDeviceType() != null) { - deviceType = deviceTypeMapper.selectById(deviceForm.getDeviceType()); + deviceType = deviceTypeMapper.selectById(typeGrants.getDeviceTypeId()); } else if (deviceForm.getTypeName() != null) { deviceType = deviceTypeMapper.selectOne(new QueryWrapper().eq("type_name", deviceForm.getTypeName())); } @@ -278,7 +289,7 @@ public class DeviceServiceImpl extends ServiceImpl impleme // 保存图片并获取URL if (deviceForm.getFile() != null) { String fileHash = fileHashUtil.hash(deviceForm.getFile()); - SysOssVo upload = ossService.updateHash(deviceForm.getFile(),fileHash); + SysOssVo upload = ossService.updateHash(deviceForm.getFile(), fileHash); // 设置图片路径 deviceForm.setDevicePic(upload.getUrl()); } @@ -353,7 +364,7 @@ public class DeviceServiceImpl extends ServiceImpl impleme // 处理上传的图片 if (deviceForm.getFile() != null) { String fileHash = fileHashUtil.hash(deviceForm.getFile()); - SysOssVo oss = ossService.updateHash(deviceForm.getFile(),fileHash); + SysOssVo oss = ossService.updateHash(deviceForm.getFile(), fileHash); // 强制将HTTP替换为HTTPS if (oss.getUrl() != null && oss.getUrl().startsWith("http://")) { oss.setUrl(oss.getUrl().replaceFirst("^http://", "https://")); From 3dd0d4cc904b00d78e2be4006dec8ebdea873d64 Mon Sep 17 00:00:00 2001 From: DragonWenLong <552045633@qq.com> Date: Thu, 20 Nov 2025 16:24:45 +0800 Subject: [PATCH 156/160] =?UTF-8?q?feat(video):=20=E6=94=AF=E6=8C=81BGR565?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E8=A7=86=E9=A2=91=E5=A4=84=E7=90=86=E5=8F=8A?= =?UTF-8?q?MQTT=E8=AE=BE=E5=A4=87=E7=A1=AE=E8=AE=A4=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增BGR565格式转换逻辑,支持RGB565与BGR565两种颜色格式- 视频上传接口增加code参数,默认值为1(RGB565) - 在VideoProcessUtil中实现convertFramesToBGR565方法 - 添加bgr565ToMp4工具方法用于将BGR565数据编码为MP4文件 - MQTT规则新增对“设备已收到通知”的处理逻辑 - 设备确认消息后更新数据库日志状态并推送SSE消息 - 引入ScheduledExecutorService延时推送SSE消息- 增加设备日志和设备Mapper依赖以支持数据操作 --- .../app/controller/AppVideoController.java | 7 +- .../app/service/VideoProcessService.java | 7 +- .../xinghan/XinghanSendAlarmMessageRule.java | 54 ++++++- .../fuyuanshen/web/util/VideoProcessUtil.java | 144 +++++++++++++++++- 4 files changed, 200 insertions(+), 12 deletions(-) diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java index f48a1a0a..87c08404 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java @@ -29,10 +29,13 @@ public class AppVideoController extends BaseController { private final VideoProcessService videoProcessService; private final AudioProcessService audioProcessService; + /** + * 上传视频转码code默认1:RGB565 2:BGR565 + */ @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS,message = "请勿重复提交!") - public R> uploadVideo(@RequestParam("file") MultipartFile file) { - return R.ok(videoProcessService.processVideo(file)); + public R> uploadVideo(@RequestParam("file") MultipartFile file, @RequestParam(defaultValue = "1") int code) { + return R.ok(videoProcessService.processVideo(file, code)); } /** diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/service/VideoProcessService.java b/fys-admin/src/main/java/com/fuyuanshen/app/service/VideoProcessService.java index a141f52f..1fde958d 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/service/VideoProcessService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/service/VideoProcessService.java @@ -28,7 +28,7 @@ public class VideoProcessService { private final VideoProcessUtil videoProcessUtil; - public List processVideo(MultipartFile file) { + public List processVideo(MultipartFile file, int code) { // 1. 参数校验 validateVideoFile(file); @@ -39,9 +39,10 @@ public class VideoProcessService { // 3. 处理视频并提取帧数据 List hexList = videoProcessUtil.processVideoToHex( - tempFile, FRAME_RATE, DURATION, WIDTH, HEIGHT + tempFile, FRAME_RATE, DURATION, WIDTH, HEIGHT, code ); - + log.info("code: {} hexList(前100个): {}", code, + hexList.subList(0, Math.min(100, hexList.size()))); log.info("视频处理成功,生成Hex数据长度: {}", hexList.size()); return hexList; 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 5412ee59..5d929943 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 @@ -1,9 +1,18 @@ package com.fuyuanshen.global.mqtt.rule.xinghan; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; 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.common.satoken.utils.LoginHelper; +import com.fuyuanshen.common.sse.dto.SseMessageDto; +import com.fuyuanshen.common.sse.utils.SseMessageUtils; +import com.fuyuanshen.equipment.domain.Device; +import com.fuyuanshen.equipment.domain.DeviceLog; +import com.fuyuanshen.equipment.mapper.DeviceLogMapper; +import com.fuyuanshen.equipment.mapper.DeviceMapper; import com.fuyuanshen.global.mqtt.base.MqttMessageRule; import com.fuyuanshen.global.mqtt.base.MqttRuleContext; import com.fuyuanshen.global.mqtt.config.MqttGateway; @@ -21,6 +30,8 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import static com.fuyuanshen.common.core.constant.GlobalConstants.FUNCTION_ACCESS_KEY; import static com.fuyuanshen.common.core.constant.GlobalConstants.GLOBAL_REDIS_KEY; @@ -40,6 +51,18 @@ public class XinghanSendAlarmMessageRule implements MqttMessageRule { private final MqttGateway mqttGateway; private final ObjectMapper objectMapper; + private final ScheduledExecutorService scheduledExecutorService; + private final DeviceLogMapper deviceLogMapper; + private final DeviceMapper deviceMapper; + /** + * 设备上行确认消息 + */ + public static final String BREAK_NEWS_CONFIRMATION = "I get it"; + + /** + * 设备上行成功标记 + */ + public static final String BREAK_NEWS_SUCCESS = "cover!"; @Override public String getCommandType() { @@ -62,9 +85,36 @@ public class XinghanSendAlarmMessageRule implements MqttMessageRule { log.warn("重复消息丢弃 {}", dedupKey); return; } - + // 1. I get it —— 表示用户确认收到消息 + if (BREAK_NEWS_CONFIRMATION.equalsIgnoreCase(respText)) { + var device = deviceMapper.selectOne(new QueryWrapper().eq("device_imei", ctx.getDeviceImei())); + // 使用MyBatis-Plus内置方法查询最新一条紧急通知 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("device_id", device.getId()) + .eq("device_action", "发送紧急通知") // 根据您的表结构调整 + .orderByDesc("create_time") + .last("LIMIT 1"); + DeviceLog latestLog = deviceLogMapper.selectOne(queryWrapper); + log.info("设备 {} 最新紧急通知:{}", ctx.getDeviceImei(), latestLog); + if (latestLog == null) { + return; + } + // 更新数据源字段 + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("id", latestLog.getId()) // 条件:ID匹配 + .set("data_source", "设备已收到通知"); // 要更新的字段 + deviceLogMapper.update(null, updateWrapper); + // 推送SSE消息 + scheduledExecutorService.schedule(() -> { + SseMessageDto dto = new SseMessageDto(); + dto.setMessage(String.format("%s设备已收到通知!", latestLog.getDeviceName())); + dto.setUserIds(List.of(latestLog.getCreateBy())); + SseMessageUtils.publishMessage(dto); + }, 5, TimeUnit.SECONDS); + return; + } // 1. cover! —— 成功标记 - if ("cover!".equalsIgnoreCase(respText)) { + if (BREAK_NEWS_SUCCESS.equalsIgnoreCase(respText)) { 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/util/VideoProcessUtil.java b/fys-admin/src/main/java/com/fuyuanshen/web/util/VideoProcessUtil.java index 06f96d5e..2381390e 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/util/VideoProcessUtil.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/util/VideoProcessUtil.java @@ -11,6 +11,8 @@ import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.IOException; +import java.io.OutputStream; import java.nio.file.Files; import java.util.ArrayList; import java.util.List; @@ -37,15 +39,26 @@ public class VideoProcessUtil { /** * 处理视频并转换为Hex字符串列表 */ - public List processVideoToHex(File videoFile, int frameRate, int duration, int width, int height) throws Exception { + public List processVideoToHex(File videoFile, int frameRate, int duration, int width, int height, int code) throws Exception { // 1. 提取视频帧 List frames = extractFramesFromVideo(videoFile, frameRate, duration, width, height); - // 2. 转换为RGB565格式 - byte[] binaryData = convertFramesToRGB565(frames); + if (code == 1) { + // 1. 转换为RGB565格式 + byte[] binaryData = convertFramesToRGB565(frames); - // 3. 转换为Hex字符串列表 - return bytesToHexList(binaryData); + // 2. 转换为Hex字符串列表 + return bytesToHexList(binaryData); + } else { + // 1. 转换为BGR565格式 + byte[] binaryData = convertFramesToBGR565(frames); + + // 新增:直接生成 mp4 + //bgr565ToMp4(binaryData, width, height, frameRate, "output.mp4"); + + // 2. 转换为Hex字符串列表 + return bytesToHexList(binaryData); + } } /** @@ -110,6 +123,55 @@ public class VideoProcessUtil { return result; } + /** + * 将所有帧转换为 BGR565 格式字节数组 + */ + private byte[] convertFramesToBGR565(List frames) throws Exception { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + + for (BufferedImage image : frames) { + byte[] bgr565Bytes = convertToBGR565(image); + byteArrayOutputStream.write(bgr565Bytes); + } + + byte[] result = byteArrayOutputStream.toByteArray(); + log.debug("转换BGR565数据完成,总字节数: {}", result.length); + return result; + } + + /** + * 将BufferedImage转换为真正的BGR565格式字节数组 + */ + private byte[] convertToBGR565(BufferedImage image) { + int width = image.getWidth(); + int height = image.getHeight(); + byte[] bgr565Data = new byte[width * height * 2]; + + int index = 0; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int rgb = image.getRGB(x, y); + + // 提取RGB分量 + int red = (rgb >> 16) & 0xFF; + int green = (rgb >> 8) & 0xFF; + int blue = rgb & 0xFF; + + int b = (blue >> 3) & 0x1F; // 5位蓝色 + int g = (green >> 2) & 0x3F; // 6位绿色 + int r = (red >> 3) & 0x1F; // 5位红色 + + // 正确的BGR565组合:红色在高位,蓝色在低位 + int bgr565 = (b << 11) | (g << 5) | r; + + bgr565Data[index++] = (byte) ((bgr565 >> 8) & 0xFF); + // 小端序存储 + bgr565Data[index++] = (byte) (bgr565 & 0xFF); + } + } + return bgr565Data; + } + /** * 将字节数组转换为Hex字符串列表 */ @@ -191,4 +253,76 @@ public class VideoProcessUtil { } } } + + /** + * 把 BGR565 字节流直接写成 MP4(H.264) + * @param bgr565 完整的 BGR565 裸帧流(每像素 2 字节) + * @param width 帧宽 + * @param height 帧高 + * @param fps 帧率 + * @param outMp4 输出 mp4 文件绝对路径 + * @throws IOException 进程启动 / IO 失败 + */ + public static void bgr565ToMp4(byte[] bgr565, + int width, + int height, + int fps, + String outMp4) throws IOException { + + int framePixels = width * height; + int frameBytes = framePixels * 2; + if (bgr565.length % frameBytes != 0) { + throw new IllegalArgumentException("字节数组长度不是整帧"); + } + + /* 1. 构造 FFmpeg 命令 */ + String[] cmd = { + "ffmpeg", + "-y", // 覆盖输出 + "-f", "rawvideo", + "-pixel_format", "bgr24", + "-video_size", width + "x" + height, + "-framerate", String.valueOf(fps), + "-i", "-", // 从 stdin 读 + "-c:v", "libx264", + "-pix_fmt", "yuv420p", + "-crf", "23", // 画质可自己调 + outMp4 + }; + + /* 2. 启动进程 */ + ProcessBuilder pb = new ProcessBuilder(cmd); + pb.redirectError(ProcessBuilder.Redirect.INHERIT); // 把 FFmpeg 日志打到控制台 + Process p = pb.start(); + try (OutputStream ffmpegIn = p.getOutputStream()) { + + /* 3. 逐帧转换并写入管道 */ + byte[] bgr24 = new byte[framePixels * 3]; + for (int off = 0; off < bgr565.length; off += frameBytes) { + for (int i = 0, j = 0; i < frameBytes; i += 2, j += 3) { + int u = ((bgr565[off + i + 1] & 0xFF) << 8) + | (bgr565[off + i] & 0xFF); + int b = (u & 0x1F) << 3; + int g = ((u >> 5) & 0x3F) << 2; + int r = ((u >> 11) & 0x1F) << 3; + bgr24[j] = (byte) b; + bgr24[j + 1] = (byte) g; + bgr24[j + 2] = (byte) r; + } + ffmpegIn.write(bgr24); + } + ffmpegIn.flush(); + } + + /* 4. 等待编码结束 */ + try { + int exit = p.waitFor(); + if (exit != 0) { + throw new IOException("FFmpeg 异常退出,code=" + exit); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IOException("等待 FFmpeg 被中断", e); + } + } } \ No newline at end of file From b18ab98feba85f31e5befc93e5bba322ddeafb91 Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Fri, 21 Nov 2025 13:36:13 +0800 Subject: [PATCH 157/160] =?UTF-8?q?=E5=AF=BC=E5=87=BA=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application-test.yml | 2 +- .../core/utils/file/ImageCompressUtil.java | 145 ++++++++++++++++-- .../converter/IgnoreFailedImageConverter.java | 130 +++++++++++++--- .../service/impl/DeviceExportService.java | 2 + .../service/impl/SysOssServiceImpl.java | 25 ++- 5 files changed, 257 insertions(+), 47 deletions(-) diff --git a/fys-admin/src/main/resources/application-test.yml b/fys-admin/src/main/resources/application-test.yml index 8f699b3f..cb8075d7 100644 --- a/fys-admin/src/main/resources/application-test.yml +++ b/fys-admin/src/main/resources/application-test.yml @@ -269,4 +269,4 @@ justauth: server-url: https://demo.gitea.com client-id: 10**********6 client-secret: 1f7d08**********5b7**********29e - redirect-uri: ${justauth.address}/social-callback?source=gitea + redirect-uri: ${justauth.address}/social-callback?source=gitea \ No newline at end of file diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/file/ImageCompressUtil.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/file/ImageCompressUtil.java index c72e3e3a..be56b97b 100644 --- a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/file/ImageCompressUtil.java +++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/file/ImageCompressUtil.java @@ -3,18 +3,43 @@ package com.fuyuanshen.common.core.utils.file; import lombok.extern.slf4j.Slf4j; import javax.imageio.ImageIO; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; import java.awt.*; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.Iterator; /** * 图片压缩工具类 + * + * @author AprilWind */ @Slf4j public class ImageCompressUtil { + /** + * 默认压缩目标大小(100KB) + */ + private static final int DEFAULT_COMPRESS_SIZE = 100 * 1024; + + /** + * 默认触发压缩的大小(1MB) + */ + private static final int DEFAULT_TRIGGER_SIZE = 1024 * 1024; + + /** + * 压缩图片到指定大小以下(默认100KB) + * + * @param imageData 原始图片数据 + * @return 压缩后的图片数据 + */ + public static byte[] compressImage(byte[] imageData) { + return compressImage(imageData, DEFAULT_COMPRESS_SIZE); + } + /** * 压缩图片到指定大小以下 * @@ -36,24 +61,54 @@ public class ImageCompressUtil { return imageData; } - // 计算压缩比例 - double scale = Math.sqrt((double) maxSize / imageData.length); - // 确保至少压缩到一半大小,避免压缩效果不明显 - scale = Math.max(scale, 0.5); + // 检查图片是否包含透明度 + boolean hasAlpha = hasAlpha(originalImage); + String formatName = hasAlpha ? "png" : "jpg"; - // 压缩图片 - byte[] compressedData = compressImageByScale(originalImage, scale); + // 对于小尺寸PNG图片可跳过压缩以保持图像质量 + if ("png".equals(formatName) && imageData.length <= 2 * maxSize) { + log.debug("PNG图片大小适中({} bytes),跳过压缩", imageData.length); + return imageData; + } + + // 先尝试质量压缩 + byte[] compressedData = compressImageQuality(originalImage, formatName, 0.8f); + + // 如果质量压缩后仍大于目标大小,则进行尺寸压缩 + if (compressedData.length > maxSize) { + // 计算缩放比例 + double scale = Math.sqrt((double) maxSize / compressedData.length); + scale = Math.max(scale, 0.5); // 最小缩放到原来的一半 + + // 尺寸压缩 + compressedData = compressImageByScale(originalImage, scale, formatName); + } // 如果压缩后还是太大,继续压缩 int attempts = 0; while (compressedData.length > maxSize && attempts < 5) { - scale *= 0.8; // 每次缩小20% - compressedData = compressImageByScale(originalImage, scale); + // 优先降低质量 + float quality = Math.max(0.1f, 0.8f - attempts * 0.1f); + compressedData = compressImageQuality(originalImage, formatName, quality); + + // 如果质量压缩不够,再缩小尺寸 + if (compressedData.length > maxSize) { + double scale = 0.9 - attempts * 0.1; // 逐步缩小尺寸 + scale = Math.max(scale, 0.5); + compressedData = compressImageByScale(originalImage, scale, formatName); + } attempts++; } - log.info("图片压缩完成,原始大小: {} bytes, 压缩后大小: {} bytes, 压缩比例: {}", - imageData.length, compressedData.length, String.format("%.2f", scale)); + log.info("图片压缩完成,原始大小: {} bytes, 压缩后大小: {} bytes, 压缩率: {}%", + imageData.length, compressedData.length, + String.format("%.2f", (1.0 - (double) compressedData.length / imageData.length) * 100)); + + // 如果压缩后反而变大了,则使用原始数据 + if (compressedData.length >= imageData.length) { + log.debug("压缩后数据变大,使用原始数据"); + return imageData; + } return compressedData; } catch (Exception e) { @@ -62,16 +117,16 @@ public class ImageCompressUtil { } } - /** * 按比例缩放图片 * * @param originalImage 原始图片 * @param scale 缩放比例 + * @param formatName 图片格式 * @return 缩放后的图片数据 * @throws IOException IO异常 */ - private static byte[] compressImageByScale(BufferedImage originalImage, double scale) throws IOException { + private static byte[] compressImageByScale(BufferedImage originalImage, double scale, String formatName) throws IOException { int width = (int) (originalImage.getWidth() * scale); int height = (int) (originalImage.getHeight() * scale); @@ -79,13 +134,73 @@ public class ImageCompressUtil { Image scaledImage = originalImage.getScaledInstance(width, height, Image.SCALE_SMOOTH); BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics2D g2d = bufferedImage.createGraphics(); + + // 绘制缩放后的图片 g2d.drawImage(scaledImage, 0, 0, null); g2d.dispose(); - // 输出为JPEG格式 + // 输出为指定格式 ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ImageIO.write(bufferedImage, "jpg", baos); + ImageIO.write(bufferedImage, formatName, baos); return baos.toByteArray(); } -} + /** + * 按质量压缩图片 + * + * @param originalImage 原始图片 + * @param formatName 图片格式 + * @param quality 压缩质量(0.1-1.0) + * @return 压缩后的图片数据 + * @throws IOException IO异常 + */ + private static byte[] compressImageQuality(BufferedImage originalImage, String formatName, float quality) throws IOException { + // 创建压缩参数 + Iterator writers = ImageIO.getImageWritersByFormatName(formatName); + if (!writers.hasNext()) { + log.warn("找不到合适的图片写入器"); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageIO.write(originalImage, formatName, baos); + return baos.toByteArray(); + } + + ImageWriter writer = writers.next(); + ImageWriteParam param = writer.getDefaultWriteParam(); + + // 设置压缩参数 + if (param.canWriteCompressed()) { + param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); + param.setCompressionQuality(quality); + } + + // 写入压缩后的图片数据 + ByteArrayOutputStream compressedOutputStream = new ByteArrayOutputStream(); + writer.setOutput(ImageIO.createImageOutputStream(compressedOutputStream)); + writer.write(null, new javax.imageio.IIOImage(originalImage, null, null), param); + writer.dispose(); + + return compressedOutputStream.toByteArray(); + } + + /** + * 检查图片是否包含透明度 + * + * @param image 图片 + * @return 是否包含透明度 + */ + private static boolean hasAlpha(BufferedImage image) { + return image.getType() == BufferedImage.TYPE_4BYTE_ABGR || + image.getType() == BufferedImage.TYPE_INT_ARGB || + image.getColorModel().hasAlpha(); + } + + /** + * 判断图片是否需要压缩(超过1MB) + * + * @param imageData 图片数据 + * @return 是否需要压缩 + */ + public static boolean needCompress(byte[] imageData) { + return imageData.length > DEFAULT_TRIGGER_SIZE; + } +} \ No newline at end of file diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/converter/IgnoreFailedImageConverter.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/converter/IgnoreFailedImageConverter.java index 10238703..60c62d98 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/converter/IgnoreFailedImageConverter.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/converter/IgnoreFailedImageConverter.java @@ -4,6 +4,8 @@ import com.alibaba.excel.converters.Converter; import com.alibaba.excel.metadata.GlobalConfiguration; import com.alibaba.excel.metadata.data.WriteCellData; import com.alibaba.excel.metadata.property.ExcelContentProperty; +import com.fuyuanshen.common.core.utils.file.ImageCompressUtil; +import com.fuyuanshen.common.redis.utils.RedisUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -12,44 +14,73 @@ import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; +import java.util.Base64; +import java.util.HashSet; +import java.util.Set; /** * @author: 默苍璃 * @date: 2025-06-0618:56 */ - public class IgnoreFailedImageConverter implements Converter { private static final Logger logger = LoggerFactory.getLogger(IgnoreFailedImageConverter.class); - + // 重试次数 private static final int MAX_RETRIES = 3; // 指数退避初始延迟(毫秒) private static final int INITIAL_DELAY = 1000; + // 图片压缩阈值(1MB) + private static final int COMPRESSION_THRESHOLD = 1024 * 1024; + // 压缩目标大小(100KB) + private static final int COMPRESSION_TARGET = 100 * 1024; + // 用于跟踪本次任务中使用到的URL缓存键 + private static final ThreadLocal> USED_CACHE_KEYS = new ThreadLocal>() { + @Override + protected Set initialValue() { + return new HashSet<>(); + } + }; @Override public Class supportJavaTypeKey() { return URL.class; } + @Override public WriteCellData convertToExcelData(URL value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { if (value == null) { - logger.debug("图片URL为空"); + logger.info("图片URL为空"); return new WriteCellData<>(new byte[0]); } + String cacheKey = "excel:image:" + value.toString(); + + // 将当前使用的缓存键添加到集合中 + USED_CACHE_KEYS.get().add(cacheKey); + + // 尝试从缓存获取 + String cachedData = RedisUtils.getCacheObject(cacheKey); + if (cachedData != null) { + // 从缓存中读取Base64编码的数据并解码 + byte[] cachedBytes = Base64.getDecoder().decode(cachedData); + logger.info("从缓存获取图片数据: {}, 大小: {} 字节", value, cachedBytes.length); + return new WriteCellData<>(cachedBytes); + } + + // 缓存未命中,从URL加载 // 尝试多次加载图片 for (int attempt = 1; attempt <= MAX_RETRIES; attempt++) { try { - logger.debug("开始加载图片: {}, 尝试次数: {}", value, attempt); + logger.info("开始加载图片: {}, 尝试次数: {}", value, attempt); URLConnection conn = value.openConnection(); // 增加连接和读取超时时间 conn.setConnectTimeout(10000); // 10秒连接超时 conn.setReadTimeout(30000); // 30秒读取超时 - + // 添加User-Agent避免被服务器拦截 conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 ExcelExporter/1.0"); - + // 如果是HTTP连接,设置一些额外的属性 if (conn instanceof HttpURLConnection) { HttpURLConnection httpConn = (HttpURLConnection) conn; @@ -58,38 +89,44 @@ public class IgnoreFailedImageConverter implements Converter { httpConn.setUseCaches(false); // 跟随重定向 httpConn.setInstanceFollowRedirects(true); - + // 检查响应码 int responseCode = httpConn.getResponseCode(); if (responseCode != HttpURLConnection.HTTP_OK) { - logger.warn("HTTP响应码异常: {}, URL: {}", responseCode, value); + logger.info("HTTP响应码异常: {}, URL: {}", responseCode, value); if (attempt < MAX_RETRIES) { // 等待后重试 waitForRetry(attempt); continue; } else { + // 将空数据写入缓存 + RedisUtils.setCacheObject(cacheKey, ""); return new WriteCellData<>(new byte[0]); } } } long contentLength = conn.getContentLengthLong(); - logger.debug("连接建立成功,图片大小: {} 字节", contentLength); - + logger.info("连接建立成功,图片大小: {} 字节", contentLength); + // 检查内容长度是否有效 if (contentLength == 0) { - logger.warn("图片文件为空: {}", value); + logger.info("图片文件为空: {}", value); if (attempt < MAX_RETRIES) { waitForRetry(attempt); continue; } else { + // 将空数据写入缓存 + RedisUtils.setCacheObject(cacheKey, ""); return new WriteCellData<>(new byte[0]); } } - + // 限制图片大小(防止过大文件导致内存问题) if (contentLength > 10 * 1024 * 1024) { // 10MB限制 - logger.warn("图片文件过大 ({} bytes),跳过加载: {}", contentLength, value); + logger.info("图片文件过大 ({} bytes),跳过加载: {}", contentLength, value); + // 将空数据写入缓存 + RedisUtils.setCacheObject(cacheKey, ""); return new WriteCellData<>(new byte[0]); } @@ -97,52 +134,98 @@ public class IgnoreFailedImageConverter implements Converter { // byte[] bytes = FileUtils.readInputStream(inputStream, value.toString()); // 替代 FileUtils.readInputStream 的自定义方法 byte[] bytes = readInputStream(inputStream); - + // 检查读取到的数据是否为空 if (bytes == null || bytes.length == 0) { - logger.warn("读取到空的图片数据: {}", value); + logger.info("读取到空的图片数据: {}", value); if (attempt < MAX_RETRIES) { waitForRetry(attempt); continue; } else { + // 将空数据写入缓存 + RedisUtils.setCacheObject(cacheKey, ""); return new WriteCellData<>(new byte[0]); } } - - logger.debug("成功读取图片数据,大小: {} 字节", bytes.length); + + // 如果图片大于1MB,则进行压缩 + if (bytes.length > COMPRESSION_THRESHOLD) { + logger.info("图片大小超过1MB ({} bytes),开始压缩", bytes.length); + long beforeCompressSize = bytes.length; + bytes = ImageCompressUtil.compressImage(bytes, COMPRESSION_TARGET); + long afterCompressSize = bytes.length; + logger.info("图片压缩完成,压缩前大小: {} bytes, 压缩后大小: {} bytes, 压缩率: {}%", + beforeCompressSize, afterCompressSize, + String.format("%.2f", (1.0 - (double) afterCompressSize / beforeCompressSize) * 100)); + } + + logger.info("成功读取图片数据,大小: {} 字节", bytes.length); + // 将数据写入缓存,不设置过期时间,使用Base64编码存储 + String encodedData = Base64.getEncoder().encodeToString(bytes); + RedisUtils.setCacheObject(cacheKey, encodedData); return new WriteCellData<>(bytes); } } catch (Exception e) { - logger.warn("图片加载失败: {}, 尝试次数: {}, 原因: {}", value, attempt, e.getMessage(), e); + logger.info("图片加载失败: {}, 尝试次数: {}, 原因: {}", value, attempt, e.getMessage(), e); if (attempt < MAX_RETRIES) { // 等待后重试 waitForRetry(attempt); } else { // 最后一次尝试也失败了 - logger.error("图片加载最终失败,已重试 {} 次: {}", MAX_RETRIES, value, e); + logger.info("图片加载最终失败,已重试 {} 次: {}", MAX_RETRIES, value, e); + // 将空数据写入缓存 + RedisUtils.setCacheObject(cacheKey, ""); return new WriteCellData<>(new byte[0]); // 返回空数组而不是 null } } } - + // 所有尝试都失败了 + // 将空数据写入缓存 + RedisUtils.setCacheObject(cacheKey, ""); return new WriteCellData<>(new byte[0]); } + /** + * 清理未使用的缓存 + * 任务结束后调用此方法,删除本次任务中未使用的URL缓存 + */ + public static void cleanUnusedCache() { + Set usedKeys = USED_CACHE_KEYS.get(); + if (usedKeys != null && !usedKeys.isEmpty()) { + // 获取所有图片缓存键 + Iterable allKeys = RedisUtils.keys("excel:image:*"); + if (allKeys != null) { + // 删除未使用的缓存 + for (String key : allKeys) { + if (!usedKeys.contains(key)) { + RedisUtils.deleteObject(key); + logger.info("删除未使用的缓存: {}", key); + } + } + } + // 清理ThreadLocal + USED_CACHE_KEYS.remove(); + } + } + + /** * 等待重试,使用指数退避策略 + * * @param attempt 当前尝试次数 */ private void waitForRetry(int attempt) { try { long delay = (long) INITIAL_DELAY * (1L << (attempt - 1)); // 指数退避 - logger.debug("等待 {} 毫秒后重试...", delay); + logger.info("等待 {} 毫秒后重试...", delay); Thread.sleep(delay); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); } } + /** * 替代 FileUtils.readInputStream 的自定义方法 * @@ -159,14 +242,15 @@ public class IgnoreFailedImageConverter implements Converter { while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); totalBytes += bytesRead; - + // 如果读取的数据过大,提前终止 if (totalBytes > 10 * 1024 * 1024) { // 10MB限制 - logger.warn("读取的图片数据超过10MB限制,提前终止"); + logger.info("读取的图片数据超过10MB限制,提前终止"); break; } } return outputStream.toByteArray(); } + } \ No newline at end of file diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceExportService.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceExportService.java index 5133b1cf..00113261 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceExportService.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceExportService.java @@ -83,6 +83,7 @@ public class DeviceExportService { } + /** * 导出设备数据(包含完整设备类型信息) * @@ -183,6 +184,7 @@ public class DeviceExportService { } } + /** * 转换定位方式代码为中文描述 * diff --git a/fys-modules/fys-system/src/main/java/com/fuyuanshen/system/service/impl/SysOssServiceImpl.java b/fys-modules/fys-system/src/main/java/com/fuyuanshen/system/service/impl/SysOssServiceImpl.java index bcadabbd..07bbf1a9 100644 --- a/fys-modules/fys-system/src/main/java/com/fuyuanshen/system/service/impl/SysOssServiceImpl.java +++ b/fys-modules/fys-system/src/main/java/com/fuyuanshen/system/service/impl/SysOssServiceImpl.java @@ -6,6 +6,7 @@ import cn.hutool.core.util.ObjectUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.fuyuanshen.common.core.utils.file.ImageCompressUtil; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import com.fuyuanshen.common.core.constant.CacheNames; @@ -143,7 +144,7 @@ public class SysOssServiceImpl implements ISysOssService, OssService { lqw.eq(StringUtils.isNotBlank(bo.getFileSuffix()), SysOss::getFileSuffix, bo.getFileSuffix()); lqw.eq(StringUtils.isNotBlank(bo.getUrl()), SysOss::getUrl, bo.getUrl()); lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null, - SysOss::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime")); + SysOss::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime")); lqw.eq(ObjectUtil.isNotNull(bo.getCreateBy()), SysOss::getCreateBy, bo.getCreateBy()); lqw.eq(StringUtils.isNotBlank(bo.getService()), SysOss::getService, bo.getService()); lqw.orderByAsc(SysOss::getOssId); @@ -169,7 +170,7 @@ public class SysOssServiceImpl implements ISysOssService, OssService { @Override public int updateHashById(long ossId, String fileHash) { - return baseMapper.updateHashById(ossId,fileHash); + return baseMapper.updateHashById(ossId, fileHash); } @@ -191,6 +192,7 @@ public class SysOssServiceImpl implements ISysOssService, OssService { storage.download(sysOss.getFileName(), response.getOutputStream(), response::setContentLengthLong); } + /** * 上传 MultipartFile 到对象存储服务,并保存文件信息到数据库 * @@ -209,14 +211,22 @@ public class SysOssServiceImpl implements ISysOssService, OssService { OssClient storage = OssFactory.instance(); UploadResult uploadResult; try { - uploadResult = storage.uploadSuffix(file.getBytes(), suffix, file.getContentType()); + byte[] imageData = file.getBytes(); + // 检查是否需要压缩 + if (ImageCompressUtil.needCompress(imageData)) { + // 压缩到100KB以内 + imageData = ImageCompressUtil.compressImage(imageData); + // 使用压缩后的数据 + } + uploadResult = storage.uploadSuffix(imageData, suffix, file.getContentType()); } catch (IOException e) { throw new ServiceException(e.getMessage()); } // 保存文件信息 - return buildResultEntity(originalfileName, suffix, storage.getConfigKey(), uploadResult,hash); + return buildResultEntity(originalfileName, suffix, storage.getConfigKey(), uploadResult, hash); } + /** * 上传 MultipartFile 到对象存储服务,并保存文件信息到数据库 * @@ -236,7 +246,7 @@ public class SysOssServiceImpl implements ISysOssService, OssService { throw new ServiceException(e.getMessage()); } // 保存文件信息 - return buildResultEntity(originalfileName, suffix, storage.getConfigKey(), uploadResult,null); + return buildResultEntity(originalfileName, suffix, storage.getConfigKey(), uploadResult, null); } /** @@ -252,11 +262,10 @@ public class SysOssServiceImpl implements ISysOssService, OssService { OssClient storage = OssFactory.instance(); UploadResult uploadResult = storage.uploadSuffix(file, suffix); // 保存文件信息 - return buildResultEntity(originalfileName, suffix, storage.getConfigKey(), uploadResult,null); + return buildResultEntity(originalfileName, suffix, storage.getConfigKey(), uploadResult, null); } - /** * 上传二进制数据到对象存储服务,并保存文件信息到数据库 * @@ -281,7 +290,7 @@ public class SysOssServiceImpl implements ISysOssService, OssService { uploadResult = storage.uploadSuffix(data, suffix, "image/jpeg"); // 假设是图片类型,可以根据实际需要修改 // 保存文件信息 - return buildResultEntity(fileName, suffix, storage.getConfigKey(), uploadResult,null); + return buildResultEntity(fileName, suffix, storage.getConfigKey(), uploadResult, null); } From 1e9e8153141f03c6bda5f0ba52d49d92729b25cf Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Fri, 21 Nov 2025 16:24:07 +0800 Subject: [PATCH 158/160] uploadVideo --- .../app/controller/AppVideoController.java | 25 +++-- .../fuyuanshen/web/util/VideoProcessUtil.java | 30 ++++++ .../controller/DeviceController.java | 3 - .../converter/IgnoreFailedImageConverter.java | 98 ++++++++++++++++--- .../service/impl/DeviceServiceImpl.java | 12 +-- 5 files changed, 139 insertions(+), 29 deletions(-) diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java index f48a1a0a..8e40e400 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java @@ -6,6 +6,7 @@ import com.fuyuanshen.app.service.VideoProcessService; import com.fuyuanshen.common.core.domain.R; import com.fuyuanshen.common.idempotent.annotation.RepeatSubmit; import com.fuyuanshen.common.web.core.BaseController; +import com.fuyuanshen.equipment.utils.FileHashUtil; import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; import org.springframework.validation.annotation.Validated; @@ -28,18 +29,30 @@ public class AppVideoController extends BaseController { private final VideoProcessService videoProcessService; private final AudioProcessService audioProcessService; + private final FileHashUtil fileHashUtil; + @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - @RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS,message = "请勿重复提交!") - public R> uploadVideo(@RequestParam("file") MultipartFile file) { + @RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS, message = "请勿重复提交!") + public R> uploadVideo(@RequestParam("file") MultipartFile file) throws IOException { + // 输出文件基本信息 + System.out.println("FileName: " + file.getOriginalFilename()); + System.out.println("FileSize: " + file.getSize()); + System.out.println("ContentType: " + file.getContentType()); + + String fileHash = fileHashUtil.hash(file); + System.out.println("fileHash:" + fileHash); + + // 可以添加更多视频属性检查 return R.ok(videoProcessService.processVideo(file)); } + /** * 上传音频文件并转码 */ @PostMapping(value = "/audio", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - @RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS,message = "请勿重复提交!") + @RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS, message = "请勿重复提交!") public R> uploadAudio(@RequestParam("file") MultipartFile file) { return R.ok(audioProcessService.processAudio(file)); } @@ -48,7 +61,7 @@ public class AppVideoController extends BaseController { * 文字转音频TTS服务 */ @GetMapping("/audioTTS") - @RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS,message = "请勿重复提交!") + @RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS, message = "请勿重复提交!") public R> uploadAudioTTS(@RequestParam String text) throws IOException { return R.ok(audioProcessService.generateStandardPcmData(text)); } @@ -57,8 +70,8 @@ public class AppVideoController extends BaseController { * 提取文本内容(只支持txt/docx) */ @PostMapping(value = "/extract", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - @RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS,message = "请勿重复提交!") + @RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS, message = "请勿重复提交!") public R extract(@RequestParam("file") MultipartFile file) throws Exception { - return R.ok("Success",audioProcessService.extract(file)); + return R.ok("Success", audioProcessService.extract(file)); } } diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/util/VideoProcessUtil.java b/fys-admin/src/main/java/com/fuyuanshen/web/util/VideoProcessUtil.java index 06f96d5e..206342d1 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/util/VideoProcessUtil.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/util/VideoProcessUtil.java @@ -48,52 +48,82 @@ public class VideoProcessUtil { return bytesToHexList(binaryData); } + /** * 从视频中提取帧 + * + * @param videoFile 视频文件对象 + * @param frameRate 每秒提取的帧数(帧率) + * @param duration 需要提取的视频时长(秒) + * @param width 提取帧的宽度 + * @param height 提取帧的高度 + * @return 提取的帧图像列表 + * @throws Exception 如果在提取过程中发生错误 */ private List extractFramesFromVideo(File videoFile, int frameRate, int duration, int width, int height) throws Exception { + // 初始化帧列表 List frames = new ArrayList<>(); + // 计算需要提取的总帧数 = 帧率 × 时长 int totalFramesToExtract = frameRate * duration; + // 使用FFmpegFrameGrabber从视频文件中抓取帧 try (FFmpegFrameGrabber grabber = FFmpegFrameGrabber.createDefault(videoFile)) { + // 启动抓取器 grabber.start(); + // 获取视频总帧数 long totalFramesInVideo = grabber.getLengthInFrames(); + // 获取视频帧率,如果获取不到则默认为30fps int fps = (int) Math.round(grabber.getFrameRate()); if (fps <= 0) fps = 30; + // 计算视频总时长(秒) double durationSeconds = (double) totalFramesInVideo / fps; + // 检查视频时长是否满足要求 if (durationSeconds < duration) { throw new IllegalArgumentException("视频太短,至少需要 " + duration + " 秒"); } + // 计算帧间隔,用于均匀分布提取的帧 double frameInterval = (double) totalFramesInVideo / totalFramesToExtract; + // 循环提取指定数量的帧 for (int i = 0; i < totalFramesToExtract; i++) { + // 计算目标帧号 int targetFrameNumber = (int) Math.round(i * frameInterval); + // 检查目标帧号是否超出视频范围 if (targetFrameNumber >= totalFramesInVideo) { throw new IllegalArgumentException("目标帧超出范围: " + targetFrameNumber); } + // 设置抓取器到目标帧 grabber.setFrameNumber(targetFrameNumber); + // 抓取当前帧 Frame frame = grabber.grab(); + // 如果成功抓取到帧且帧图像不为空 if (frame != null && frame.image != null) { + // 将帧转换为BufferedImage并裁剪到指定尺寸 BufferedImage bufferedImage = Java2DFrameUtils.toBufferedImage(frame); frames.add(cropImage(bufferedImage, width, height)); } else { + // 如果无法获取帧则抛出异常 throw new IllegalArgumentException("无法获取第 " + targetFrameNumber + "帧"); } } + // 停止抓取器 grabber.stop(); } + // 记录提取的帧数 log.debug("从视频中提取了 {} 帧", frames.size()); + // 返回提取的帧列表 return frames; } + /** * 将所有帧转换为 RGB565 格式字节数组 */ diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceController.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceController.java index bd6f0c15..991c04a9 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceController.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceController.java @@ -3,9 +3,7 @@ package com.fuyuanshen.equipment.controller; import com.alibaba.excel.EasyExcel; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import com.fuyuanshen.common.core.constant.ResponseMessageConstants; import com.fuyuanshen.common.core.domain.R; -import com.fuyuanshen.common.core.domain.ResponseVO; import com.fuyuanshen.common.core.domain.model.LoginUser; import com.fuyuanshen.common.core.utils.file.FileUtil; import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; @@ -18,7 +16,6 @@ import com.fuyuanshen.equipment.domain.dto.DeviceExcelImportDTO; import com.fuyuanshen.equipment.domain.dto.ImportResult; import com.fuyuanshen.equipment.domain.form.DeviceForm; import com.fuyuanshen.equipment.domain.query.DeviceQueryCriteria; -import com.fuyuanshen.equipment.domain.vo.CustomerVo; import com.fuyuanshen.equipment.excel.DeviceImportParams; import com.fuyuanshen.equipment.excel.HeadValidateListener; import com.fuyuanshen.equipment.excel.UploadDeviceDataListener; diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/converter/IgnoreFailedImageConverter.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/converter/IgnoreFailedImageConverter.java index 60c62d98..1ecd4db8 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/converter/IgnoreFailedImageConverter.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/converter/IgnoreFailedImageConverter.java @@ -17,6 +17,10 @@ import java.net.URLConnection; import java.util.Base64; import java.util.HashSet; import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; /** * @author: 默苍璃 @@ -41,12 +45,15 @@ public class IgnoreFailedImageConverter implements Converter { } }; + // 创建线程池用于并发处理图片 + private static final ExecutorService IMAGE_PROCESSING_EXECUTOR = Executors.newFixedThreadPool( + Runtime.getRuntime().availableProcessors() * 2); + @Override public Class supportJavaTypeKey() { return URL.class; } - @Override public WriteCellData convertToExcelData(URL value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { if (value == null) { @@ -54,11 +61,36 @@ public class IgnoreFailedImageConverter implements Converter { return new WriteCellData<>(new byte[0]); } + try { + // 使用CompletableFuture异步处理图片加载 + CompletableFuture> future = CompletableFuture.supplyAsync(() -> { + try { + return loadImageData(value); + } catch (Exception e) { + logger.error("异步加载图片失败: {}", value, e); + return new WriteCellData<>(new byte[0]); + } + }, IMAGE_PROCESSING_EXECUTOR); + + // 设置超时时间,防止长时间阻塞 + return future.get(30, TimeUnit.SECONDS); + } catch (Exception e) { + logger.error("图片处理异常: {}", value, e); + return new WriteCellData<>(new byte[0]); + } + } + + /** + * 加载图片数据的核心方法 + * @param value 图片URL + * @return WriteCellData对象 + */ + private WriteCellData loadImageData(URL value) { String cacheKey = "excel:image:" + value.toString(); - + // 将当前使用的缓存键添加到集合中 USED_CACHE_KEYS.get().add(cacheKey); - + // 尝试从缓存获取 String cachedData = RedisUtils.getCacheObject(cacheKey); if (cachedData != null) { @@ -75,11 +107,13 @@ public class IgnoreFailedImageConverter implements Converter { logger.info("开始加载图片: {}, 尝试次数: {}", value, attempt); URLConnection conn = value.openConnection(); // 增加连接和读取超时时间 - conn.setConnectTimeout(10000); // 10秒连接超时 - conn.setReadTimeout(30000); // 30秒读取超时 + conn.setConnectTimeout(5000); // 5秒连接超时 + conn.setReadTimeout(15000); // 15秒读取超时 // 添加User-Agent避免被服务器拦截 conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 ExcelExporter/1.0"); + // 添加Connection: close避免保持连接 + conn.setRequestProperty("Connection", "close"); // 如果是HTTP连接,设置一些额外的属性 if (conn instanceof HttpURLConnection) { @@ -152,11 +186,20 @@ public class IgnoreFailedImageConverter implements Converter { if (bytes.length > COMPRESSION_THRESHOLD) { logger.info("图片大小超过1MB ({} bytes),开始压缩", bytes.length); long beforeCompressSize = bytes.length; - bytes = ImageCompressUtil.compressImage(bytes, COMPRESSION_TARGET); + + // 先尝试质量压缩 + byte[] compressed = ImageCompressUtil.compressImage(bytes, COMPRESSION_TARGET); + + // 如果压缩后变大了,使用原始数据 + if (compressed.length >= bytes.length) { + compressed = bytes; + } + + bytes = compressed; long afterCompressSize = bytes.length; - logger.info("图片压缩完成,压缩前大小: {} bytes, 压缩后大小: {} bytes, 压缩率: {}%", - beforeCompressSize, afterCompressSize, - String.format("%.2f", (1.0 - (double) afterCompressSize / beforeCompressSize) * 100)); + logger.info("图片压缩完成,压缩前大小: {} bytes, 压缩后大小: {} bytes, 压缩率: {}", + beforeCompressSize, afterCompressSize, + String.format("%.2f", (1.0 - (double) afterCompressSize / beforeCompressSize) * 100)); } logger.info("成功读取图片数据,大小: {} 字节", bytes.length); @@ -209,7 +252,6 @@ public class IgnoreFailedImageConverter implements Converter { } } - /** * 等待重试,使用指数退避策略 * @@ -225,7 +267,6 @@ public class IgnoreFailedImageConverter implements Converter { } } - /** * 替代 FileUtils.readInputStream 的自定义方法 * @@ -253,4 +294,37 @@ public class IgnoreFailedImageConverter implements Converter { return outputStream.toByteArray(); } -} \ No newline at end of file + /** + * 预加载图片到缓存 + * @param imageUrls 图片URL列表 + */ + public static void preloadImages(Set imageUrls) { + if (imageUrls == null || imageUrls.isEmpty()) { + return; + } + + logger.info("开始预加载 {} 张图片", imageUrls.size()); + + // 使用并行流并发预加载图片 + imageUrls.parallelStream().forEach(url -> { + try { + String cacheKey = "excel:image:" + url.toString(); + // 如果缓存中没有,则异步加载 + if (!RedisUtils.hasKey(cacheKey)) { + CompletableFuture.runAsync(() -> { + try { + // 简化版图片加载逻辑,只加载到缓存 + new IgnoreFailedImageConverter().loadImageData(url); + } catch (Exception e) { + logger.warn("预加载图片失败: {}", url, e); + } + }, IMAGE_PROCESSING_EXECUTOR); + } + } catch (Exception e) { + logger.warn("预加载图片异常: {}", url, e); + } + }); + + logger.info("图片预加载任务已提交"); + } +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java index 78a83f51..533b06d0 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java @@ -19,11 +19,8 @@ import com.fuyuanshen.common.satoken.utils.AppLoginHelper; import com.fuyuanshen.common.satoken.utils.LoginHelper; import com.fuyuanshen.customer.domain.Customer; import com.fuyuanshen.customer.mapper.CustomerMapper; -import com.fuyuanshen.equipment.constants.DeviceConstants; import com.fuyuanshen.equipment.domain.*; -import com.fuyuanshen.equipment.domain.bo.DeviceFenceAccessRecordBo; import com.fuyuanshen.equipment.domain.dto.AppDeviceBo; -import com.fuyuanshen.equipment.domain.dto.FenceCheckResponse; import com.fuyuanshen.equipment.domain.form.DeviceForm; import com.fuyuanshen.equipment.domain.query.DeviceAssignmentQuery; import com.fuyuanshen.equipment.domain.query.DeviceQueryCriteria; @@ -33,7 +30,10 @@ import com.fuyuanshen.equipment.enums.BindingStatusEnum; import com.fuyuanshen.equipment.enums.CommunicationModeEnum; import com.fuyuanshen.equipment.enums.DeviceActiveStatusEnum; import com.fuyuanshen.equipment.mapper.*; -import com.fuyuanshen.equipment.service.*; +import com.fuyuanshen.equipment.service.DeviceAssignmentsService; +import com.fuyuanshen.equipment.service.DeviceService; +import com.fuyuanshen.equipment.service.DeviceTypeGrantsService; +import com.fuyuanshen.equipment.service.IDeviceGeoFenceService; import com.fuyuanshen.equipment.utils.FileHashUtil; import com.fuyuanshen.system.domain.vo.SysOssVo; import com.fuyuanshen.system.domain.vo.SysRoleVo; @@ -41,15 +41,11 @@ import com.fuyuanshen.system.service.ISysOssService; import com.fuyuanshen.system.service.ISysRoleService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; -import java.io.File; import java.io.IOException; import java.sql.Timestamp; -import java.time.LocalDate; import java.time.LocalDateTime; import java.util.*; From bf182ebc89d3fefc31c41f6a2185de0eb021a4f9 Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Tue, 25 Nov 2025 14:51:10 +0800 Subject: [PATCH 159/160] =?UTF-8?q?=E5=BC=BA=E5=88=B6=E5=B0=86HTTP?= =?UTF-8?q?=E6=9B=BF=E6=8D=A2=E4=B8=BAHTTPS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/fuyuanshen/app/controller/AppVideoController.java | 3 +-- .../equipment/domain/dto/DeviceWithTypeExcelExportDTO.java | 2 +- .../fuyuanshen/equipment/service/impl/DeviceServiceImpl.java | 4 ++++ .../src/main/java/com/fuyuanshen/system/domain/SysOss.java | 1 + 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java index 8e40e400..9dbb01d6 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java @@ -1,6 +1,5 @@ package com.fuyuanshen.app.controller; -import cn.dev33.satoken.annotation.SaIgnore; import com.fuyuanshen.app.service.AudioProcessService; import com.fuyuanshen.app.service.VideoProcessService; import com.fuyuanshen.common.core.domain.R; @@ -14,7 +13,6 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; -import java.util.Base64; import java.util.List; import java.util.concurrent.TimeUnit; @@ -74,4 +72,5 @@ public class AppVideoController extends BaseController { public R extract(@RequestParam("file") MultipartFile file) throws Exception { return R.ok("Success", audioProcessService.extract(file)); } + } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceWithTypeExcelExportDTO.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceWithTypeExcelExportDTO.java index 0ffced9e..00f638e9 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceWithTypeExcelExportDTO.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceWithTypeExcelExportDTO.java @@ -13,7 +13,7 @@ import java.net.URL; * 设备及完整类型信息导出DTO * * @author: 默苍璃 - * @date: 2025-11-0416:25 + * @date: 2025-11-04 16:25 */ @Data @HeadRowHeight(20) // 表头行高 diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java index 533b06d0..da9372dc 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java @@ -286,6 +286,10 @@ public class DeviceServiceImpl extends ServiceImpl impleme if (deviceForm.getFile() != null) { String fileHash = fileHashUtil.hash(deviceForm.getFile()); SysOssVo upload = ossService.updateHash(deviceForm.getFile(), fileHash); + // 强制将HTTP替换为HTTPS + if (upload.getUrl() != null && upload.getUrl().startsWith("http://")) { + upload.setUrl(upload.getUrl().replaceFirst("^http://", "https://")); + } // 设置图片路径 deviceForm.setDevicePic(upload.getUrl()); } diff --git a/fys-modules/fys-system/src/main/java/com/fuyuanshen/system/domain/SysOss.java b/fys-modules/fys-system/src/main/java/com/fuyuanshen/system/domain/SysOss.java index 6d187f57..99e941f6 100644 --- a/fys-modules/fys-system/src/main/java/com/fuyuanshen/system/domain/SysOss.java +++ b/fys-modules/fys-system/src/main/java/com/fuyuanshen/system/domain/SysOss.java @@ -51,6 +51,7 @@ public class SysOss extends TenantEntity { * 服务商 */ private String service; + /** * 内容哈希 */ From 63a9d2f8f9c5fed9092b62d5ba81e79545ff59f1 Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Thu, 27 Nov 2025 11:00:34 +0800 Subject: [PATCH 160/160] =?UTF-8?q?=E5=AF=BC=E5=87=BA=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E5=8E=8B=E7=BC=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/utils/file/ImageCompressUtil.java | 95 +++++++++++++++++-- .../converter/IgnoreFailedImageConverter.java | 8 +- 2 files changed, 94 insertions(+), 9 deletions(-) diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/file/ImageCompressUtil.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/file/ImageCompressUtil.java index be56b97b..e156d646 100644 --- a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/file/ImageCompressUtil.java +++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/file/ImageCompressUtil.java @@ -72,13 +72,13 @@ public class ImageCompressUtil { } // 先尝试质量压缩 - byte[] compressedData = compressImageQuality(originalImage, formatName, 0.8f); + byte[] compressedData = compressImageQuality(originalImage, formatName, 0.7f); // 如果质量压缩后仍大于目标大小,则进行尺寸压缩 if (compressedData.length > maxSize) { // 计算缩放比例 double scale = Math.sqrt((double) maxSize / compressedData.length); - scale = Math.max(scale, 0.5); // 最小缩放到原来的一半 + scale = Math.max(scale, 0.2); // 最小缩放到原来的20% // 尺寸压缩 compressedData = compressImageByScale(originalImage, scale, formatName); @@ -86,20 +86,36 @@ public class ImageCompressUtil { // 如果压缩后还是太大,继续压缩 int attempts = 0; - while (compressedData.length > maxSize && attempts < 5) { + while (compressedData.length > maxSize && attempts < 15) { // 增加尝试次数到15次 // 优先降低质量 - float quality = Math.max(0.1f, 0.8f - attempts * 0.1f); + float quality = Math.max(0.01f, 0.7f - attempts * 0.1f); // 最低质量降至0.01 compressedData = compressImageQuality(originalImage, formatName, quality); // 如果质量压缩不够,再缩小尺寸 if (compressedData.length > maxSize) { - double scale = 0.9 - attempts * 0.1; // 逐步缩小尺寸 - scale = Math.max(scale, 0.5); + double scale = 0.8 - attempts * 0.15; // 更积极地缩小尺寸 + scale = Math.max(scale, 0.1); // 最小缩放到原来的10% compressedData = compressImageByScale(originalImage, scale, formatName); } attempts++; } + // 如果经过多次尝试仍然大于目标大小,则强制压缩到目标大小以下 + if (compressedData.length > maxSize) { + // 强制尺寸压缩到目标大小 + double finalScale = Math.sqrt((double) maxSize / compressedData.length) * 0.8; // 留一些余量 + finalScale = Math.max(finalScale, 0.05); // 至少保留5%的尺寸 + compressedData = compressImageByScale(originalImage, finalScale, formatName); + + // 如果仍然太大,强制质量压缩 + if (compressedData.length > maxSize) { + // 计算需要的质量值 + float finalQuality = (float) maxSize / compressedData.length * 0.7f; // 留一些余量 + finalQuality = Math.max(finalQuality, 0.005f); // 至少保留0.5%的质量 + compressedData = compressImageQuality(originalImage, formatName, finalQuality); + } + } + log.info("图片压缩完成,原始大小: {} bytes, 压缩后大小: {} bytes, 压缩率: {}%", imageData.length, compressedData.length, String.format("%.2f", (1.0 - (double) compressedData.length / imageData.length) * 100)); @@ -110,6 +126,12 @@ public class ImageCompressUtil { return imageData; } + // 特殊处理:如果目标大小是50KB或更小,确保最终结果符合要求 + if (maxSize <= 50 * 1024 && compressedData.length > maxSize) { + // 使用更强力的压缩策略 + compressedData = forceCompressToSize(originalImage, formatName, maxSize); + } + return compressedData; } catch (Exception e) { log.error("图片压缩失败: {}", e.getMessage(), e); @@ -117,6 +139,63 @@ public class ImageCompressUtil { } } + /** + * 强制压缩到指定大小 + * + * @param originalImage 原始图片 + * @param formatName 图片格式 + * @param maxSize 目标大小 + * @return 压缩后的图片数据 + */ + private static byte[] forceCompressToSize(BufferedImage originalImage, String formatName, int maxSize) throws IOException { + byte[] result = null; + int width = originalImage.getWidth(); + int height = originalImage.getHeight(); + + // 通过不断缩小尺寸来达到目标大小 + double scale = 0.9; + do { + int newWidth = (int) (width * scale); + int newHeight = (int) (height * scale); + + // 创建缩放后的图片 + Image scaledImage = originalImage.getScaledInstance(newWidth, newHeight, Image.SCALE_SMOOTH); + BufferedImage bufferedImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB); + Graphics2D g2d = bufferedImage.createGraphics(); + + // 绘制缩放后的图片 + g2d.drawImage(scaledImage, 0, 0, null); + g2d.dispose(); + + // 以最低质量压缩 + result = compressImageQuality(bufferedImage, formatName, 0.01f); + + if (result.length <= maxSize) { + break; + } + + scale -= 0.1; + } while (scale > 0.1 && result.length > maxSize); + + // 如果还是太大,强制调整大小 + if (result.length > maxSize) { + // 计算精确的缩放比例 + double targetScale = Math.sqrt((double) maxSize / result.length) * 0.9; + int newWidth = Math.max((int) (width * targetScale), 5); + int newHeight = Math.max((int) (height * targetScale), 5); + + Image scaledImage = originalImage.getScaledInstance(newWidth, newHeight, Image.SCALE_SMOOTH); + BufferedImage bufferedImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB); + Graphics2D g2d = bufferedImage.createGraphics(); + g2d.drawImage(scaledImage, 0, 0, null); + g2d.dispose(); + + result = compressImageQuality(bufferedImage, formatName, 0.005f); + } + + return result; + } + /** * 按比例缩放图片 * @@ -130,6 +209,10 @@ public class ImageCompressUtil { int width = (int) (originalImage.getWidth() * scale); int height = (int) (originalImage.getHeight() * scale); + // 确保最小尺寸不小于5像素 + width = Math.max(width, 5); + height = Math.max(height, 5); + // 创建缩放后的图片 Image scaledImage = originalImage.getScaledInstance(width, height, Image.SCALE_SMOOTH); BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/converter/IgnoreFailedImageConverter.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/converter/IgnoreFailedImageConverter.java index 1ecd4db8..5038cd5c 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/converter/IgnoreFailedImageConverter.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/converter/IgnoreFailedImageConverter.java @@ -34,9 +34,9 @@ public class IgnoreFailedImageConverter implements Converter { // 指数退避初始延迟(毫秒) private static final int INITIAL_DELAY = 1000; // 图片压缩阈值(1MB) - private static final int COMPRESSION_THRESHOLD = 1024 * 1024; - // 压缩目标大小(100KB) - private static final int COMPRESSION_TARGET = 100 * 1024; + private static final int COMPRESSION_THRESHOLD = 100 * 1024; + // 压缩目标大小(50KB) + private static final int COMPRESSION_TARGET = 50 * 1024; // 用于跟踪本次任务中使用到的URL缓存键 private static final ThreadLocal> USED_CACHE_KEYS = new ThreadLocal>() { @Override @@ -82,6 +82,7 @@ public class IgnoreFailedImageConverter implements Converter { /** * 加载图片数据的核心方法 + * * @param value 图片URL * @return WriteCellData对象 */ @@ -296,6 +297,7 @@ public class IgnoreFailedImageConverter implements Converter { /** * 预加载图片到缓存 + * * @param imageUrls 图片URL列表 */ public static void preloadImages(Set imageUrls) {