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] =?UTF-8?q?feat(web):=20=E6=96=B0=E5=A2=9E=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E8=81=94=E8=B0=83=E4=B8=AD=E5=BF=83=E5=8A=9F=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} +