hxd 2 mesiacov pred
rodič
commit
18fefb537f
19 zmenil súbory, kde vykonal 370 pridanie a 119 odobranie
  1. 15 11
      portal-service-application/src/main/java/com/hfln/portal/application/controller/pub/PubController.java
  2. 6 6
      portal-service-application/src/main/java/com/hfln/portal/application/controller/wap/HomeController.java
  3. 1 1
      portal-service-application/src/main/java/com/hfln/portal/application/controller/web/WebDictController.java
  4. 9 0
      portal-service-application/src/main/java/com/hfln/portal/application/controller/web/WebSystemController.java
  5. 2 2
      portal-service-common/src/main/java/com/hfln/portal/common/dto/data/oss/OssFileDTO.java
  6. 36 0
      portal-service-common/src/main/java/com/hfln/portal/common/dto/data/oss/OtaFileDTO.java
  7. 22 0
      portal-service-common/src/main/java/com/hfln/portal/common/request/web/UpdateOTAParams.java
  8. 1 0
      portal-service-domain/src/main/java/com/hfln/portal/domain/exception/ErrorEnum.java
  9. 9 1
      portal-service-domain/src/main/java/com/hfln/portal/domain/gateway/PubGateway.java
  10. 1 1
      portal-service-domain/src/main/java/com/hfln/portal/domain/gateway/UserGateway.java
  11. 2 0
      portal-service-domain/src/main/java/com/hfln/portal/domain/gateway/WebGateway.java
  12. 7 2
      portal-service-infrastructure/src/main/java/com/hfln/portal/infrastructure/gateway/impl/DeviceGatewayImpl.java
  13. 56 30
      portal-service-infrastructure/src/main/java/com/hfln/portal/infrastructure/gateway/impl/PubGatewayImpl.java
  14. 112 62
      portal-service-infrastructure/src/main/java/com/hfln/portal/infrastructure/gateway/impl/UserGatewayImpl.java
  15. 86 0
      portal-service-infrastructure/src/main/java/com/hfln/portal/infrastructure/gateway/impl/WebGatewayImpl.java
  16. 0 1
      portal-service-infrastructure/src/main/java/com/hfln/portal/infrastructure/service/OssFileService.java
  17. 2 1
      portal-service-server/src/main/resources/bootstrap-dev.yml
  18. 1 0
      portal-service-server/src/main/resources/bootstrap-local.yml
  19. 2 1
      portal-service-server/src/main/resources/bootstrap-test.yml

+ 15 - 11
portal-service-application/src/main/java/com/hfln/portal/application/controller/pub/PubController.java

@@ -4,16 +4,15 @@ package com.hfln.portal.application.controller.pub;
 
 import cn.hfln.framework.catchlog.CatchAndLog;
 import cn.hfln.framework.dto.ApiResult;
+import com.hfln.portal.common.dto.data.oss.OtaFileDTO;
 import com.hfln.portal.common.dto.data.pub.DicItemDto;
+import com.hfln.portal.common.request.web.UpdateOTAParams;
 import com.hfln.portal.domain.gateway.PubGateway;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.GetMapping;
-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.bind.annotation.*;
 
 import javax.validation.Valid;
 import java.util.List;
@@ -34,11 +33,16 @@ public class PubController {
         return ApiResult.success(pubGateway.query(dicType));
     }
 
-//
-//    @PostMapping(value = "/uploadWJ")
-//    @Operation(summary = "上传文件")
-//    public ApiResult<Void> uploadCommonFile(@Valid @RequestParam MultipartFile file , String fileType) throws IOException {
-//        pubGateway.uploadWJ(file, fileType);
-//        return ApiResult.success();
-//    }
+    @GetMapping("/OTA/query")
+    @Operation(summary = "OTA升级固件列表查询")
+    public ApiResult<List<OtaFileDTO>> queryOtaFiles() {
+        return ApiResult.success(pubGateway.queryOtaFiles());
+    }
+
+    @PostMapping("/OTA/update")
+    @Operation(summary = "设备OTA升级")
+    public ApiResult<Void> updateOTA(@RequestBody @Valid UpdateOTAParams params) {
+        pubGateway.updateOTA(params);
+        return ApiResult.success();
+    }
 }

+ 6 - 6
portal-service-application/src/main/java/com/hfln/portal/application/controller/wap/HomeController.java

@@ -36,12 +36,12 @@ public class HomeController {
         return ApiResult.success(deviceGateway.queryHomeInfo(userId));
     }
 
-    @PostMapping(value = "/uploadCarousel")
-    @Operation(summary = "上传轮播图", description = "上传轮播图片文件,支持PNG和JPEG格式")
-    public ApiResult<Void> uploadCarousel(@Valid @RequestParam MultipartFile file) throws IOException {
-        userGateway.uploadCarousel(file);
-        return ApiResult.success();
-    }
+//    @PostMapping(value = "/uploadCarousel")
+//    @Operation(summary = "上传轮播图", description = "上传轮播图片文件,支持PNG和JPEG格式")
+//    public ApiResult<Void> uploadCarousel(@Valid @RequestParam MultipartFile file) throws IOException {
+//        userGateway.uploadCarousel(file);
+//        return ApiResult.success();
+//    }
 
     @GetMapping("/getFileList")
     @Operation(summary = "获取后台文件列表", description = "获取后台指定业务类型下所有的文件,包含访问地址,目前后台有三种类型:1.FLOORPLAN-户型图,2.CAROUSEL-轮播图,3.USERPROTOCOL-用户协议")

+ 1 - 1
portal-service-application/src/main/java/com/hfln/portal/application/controller/web/DictController.java → portal-service-application/src/main/java/com/hfln/portal/application/controller/web/WebDictController.java

@@ -25,7 +25,7 @@ import java.util.List;
 @Tag(name = "web端字典相关")
 @Slf4j
 @RequestMapping("/web/dic")
-public class DictController {
+public class WebDictController {
 
     @Autowired
     private PubGateway pubGateway;

+ 9 - 0
portal-service-application/src/main/java/com/hfln/portal/application/controller/web/WebSystemController.java

@@ -17,8 +17,10 @@ import io.swagger.v3.oas.annotations.tags.Tag;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
 
 import javax.validation.Valid;
+import java.io.IOException;
 import java.util.List;
 
 @RestController
@@ -93,4 +95,11 @@ public class WebSystemController {
     public ApiResult<RoleMenuTreeDTO> getRoleSelectedMenu(@RequestParam Long roleId) {
         return ApiResult.success(webGateway.getRoleSelectedMenu(roleId));
     }
+
+    @PostMapping("/OTAUpload")
+    @Operation(summary = "OTA升级文件上传")
+    public ApiResult<Void> OTAUpload(@RequestParam("file") MultipartFile file) throws IOException {
+        webGateway.OTAUpload(file);
+        return ApiResult.success();
+    }
 }

+ 2 - 2
portal-service-common/src/main/java/com/hfln/portal/common/dto/data/oss/OssFileDTO.java

@@ -24,7 +24,7 @@ public class OssFileDTO extends BaseVO {
      * 业务主键
      */
     @Schema(description = "业务主键")
-    private Long busiId;
+    private Long busiKey;
 
     /**
      * 文件名称
@@ -36,6 +36,6 @@ public class OssFileDTO extends BaseVO {
      * 文件地址
      */
     @Schema(description = "文件地址")
-    private String fileUrl;
+    private String ossUrl;
 
 }

+ 36 - 0
portal-service-common/src/main/java/com/hfln/portal/common/dto/data/oss/OtaFileDTO.java

@@ -0,0 +1,36 @@
+package com.hfln.portal.common.dto.data.oss;
+
+import com.hfln.portal.common.vo.BaseVO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.time.LocalDateTime;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class OtaFileDTO extends BaseVO {
+    /**
+     * 主键ID
+     */
+    @Schema(description = "主键ID")
+    private Long fileId;
+
+    /**
+     * 文件名称
+     */
+    @Schema(description = "文件名称")
+    private String fileName;
+
+    /**
+     * OSS文件地址
+     */
+    @Schema(description = "OSS文件地址")
+    private String ossUrl;
+
+    /**
+     * 创建时间
+     */
+    @Schema(description = "创建时间")
+    private LocalDateTime createTime;
+} 

+ 22 - 0
portal-service-common/src/main/java/com/hfln/portal/common/request/web/UpdateOTAParams.java

@@ -0,0 +1,22 @@
+package com.hfln.portal.common.request.web;
+
+import com.hfln.portal.common.vo.BaseVO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@Data
+@Schema(description = "设备OTA升级参数")
+public class UpdateOTAParams extends BaseVO {
+
+    @NotEmpty(message = "设备ID列表不能为空")
+    @Schema(description = "设备ID列表", required = true, example = "[\"device001\", \"device002\"]")
+    private List<String> clientIds;
+
+    @NotNull(message = "固件URL不能为空")
+    @Schema(description = "固件文件URL", required = true, example = "https://lnxx.oss-cn-shanghai.aliyuncs.com/ota/20241201/abc12345_firmware_v1.2.3.bin")
+    private String ossUrl;
+}

+ 1 - 0
portal-service-domain/src/main/java/com/hfln/portal/domain/exception/ErrorEnum.java

@@ -94,6 +94,7 @@ public enum ErrorEnum implements ErrorEnumInterface{
     FILE_DEL_OR_NOT_EXIST("80005", "文件已删除或不存在!"),
     FILE_EXIST_MULTIPLE("80006", "存在多个文件!"),
     FILE_TYPE_IS_NULL("80007", "文件类型不能为空!"),
+    FILE_ONLY_BIN_HEX_FW("80008", "文件格式仅支持 BIN/HEX/FW!"),
 
 
     /**

+ 9 - 1
portal-service-domain/src/main/java/com/hfln/portal/domain/gateway/PubGateway.java

@@ -1,6 +1,8 @@
 package com.hfln.portal.domain.gateway;
 
+import com.hfln.portal.common.dto.data.oss.OtaFileDTO;
 import com.hfln.portal.common.dto.data.pub.DicItemDto;
+import com.hfln.portal.common.request.web.UpdateOTAParams;
 
 import java.util.List;
 
@@ -9,6 +11,12 @@ public interface PubGateway {
 
     List<DicItemDto> query(String dicType);
 
-//    void uploadWJ(MultipartFile file,  String fileType) throws IOException;
+
+    List<OtaFileDTO> queryOtaFiles();
+
+
+    void updateOTA(UpdateOTAParams params);
+
+
 }
 

+ 1 - 1
portal-service-domain/src/main/java/com/hfln/portal/domain/gateway/UserGateway.java

@@ -56,7 +56,7 @@ public interface UserGateway {
      * @param file 图片文件
      * @throws IOException IO异常
      */
-    void uploadCarousel(MultipartFile file) throws IOException;
+//    void uploadCarousel(MultipartFile file) throws IOException;
 
     /**
      * 获取文件列表

+ 2 - 0
portal-service-domain/src/main/java/com/hfln/portal/domain/gateway/WebGateway.java

@@ -71,6 +71,8 @@ public interface WebGateway {
 
     RoleMenuTreeDTO getRoleSelectedMenu(Long roleId);
 
+    void OTAUpload(MultipartFile file) throws IOException;
+
     /**
      * web字典相关
      */

+ 7 - 2
portal-service-infrastructure/src/main/java/com/hfln/portal/infrastructure/gateway/impl/DeviceGatewayImpl.java

@@ -557,14 +557,19 @@ public class DeviceGatewayImpl implements DeviceGateway {
     @Override
     public Boolean saveRoom(RoomParam param) {
 
+        //查询设备信息
+        DevInfo devInfo = devInfoService.getById(param.getDevId());
+        if (devInfo == null){
+            throw new BizException(ErrorEnum.DEVICE_IS_NOT_EXIST.getErrorCode(), ErrorEnum.DEVICE_IS_NOT_EXIST.getErrorMessage());
+        }
+
         //判断屏蔽子区域≤6个
         List<SubRegionInfo> subRegions = param.getSubRegions();
         if (subRegions != null && subRegions.size() > 6) {
             throw new BizException(ErrorEnum.SUB_REGION_TOO_MANY.getErrorCode(),ErrorEnum.SUB_REGION_TOO_MANY.getErrorMessage());
         }
 
-        //查询设备信息
-        DevInfo devInfo = devInfoService.getById(param.getDevId());
+
 
         // 更新dev_room表
         devRoomService.saveOrUpdate(param);

+ 56 - 30
portal-service-infrastructure/src/main/java/com/hfln/portal/infrastructure/gateway/impl/PubGatewayImpl.java

@@ -1,10 +1,15 @@
 package com.hfln.portal.infrastructure.gateway.impl;
 
 import cn.hfln.framework.extension.BizException;
+import com.alibaba.fastjson2.JSONObject;
+import com.hfln.portal.common.constant.mqtt.topic.TopicConstants;
+import com.hfln.portal.common.dto.data.oss.OtaFileDTO;
 import com.hfln.portal.common.dto.data.pub.DicItemDto;
+import com.hfln.portal.common.request.web.UpdateOTAParams;
 import com.hfln.portal.domain.customer.util.CopyUtils;
 import com.hfln.portal.domain.exception.ErrorEnum;
 import com.hfln.portal.domain.gateway.PubGateway;
+import com.hfln.portal.infrastructure.mqtt.MqttClient;
 import com.hfln.portal.infrastructure.po.TblOssFile;
 import com.hfln.portal.infrastructure.service.OssFileService;
 import com.hfln.portal.infrastructure.service.TblDicItemService;
@@ -12,9 +17,8 @@ import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
-import org.springframework.web.multipart.MultipartFile;
 
-import java.io.IOException;
+import java.util.Comparator;
 import java.util.List;
 
 @Slf4j
@@ -29,41 +33,63 @@ public class PubGatewayImpl implements PubGateway {
     @Autowired
     private OssFileService ossFileService;
 
+    @Autowired
+    private MqttClient mqttClient;
+
 
     @Override
     public List<DicItemDto> query(String dicType) {
         return CopyUtils.copyList(tblDicItemService.queryByDicType(dicType), DicItemDto.class);
     }
 
+    @Override
+    public List<OtaFileDTO> queryOtaFiles() {
+        // 查询所有OTA类型的文件,按创建时间倒序排列
+        List<TblOssFile> otaFiles = ossFileService.queryFile("OTA", null);
+        
+        // 使用Comparator.comparing()简化排序,nullsLast()处理null值
+        otaFiles.sort(Comparator.comparing(TblOssFile::getCreateTime, Comparator.nullsLast(Comparator.reverseOrder())));
+        
+        // 转换为DTO并返回
+        return CopyUtils.copyList(otaFiles, OtaFileDTO.class);
+    }
 
-//    @Override
-//    public void uploadWJ(MultipartFile file, String fileType) throws IOException {
-//
-//        // 1. 校验文件非空
-//        if (file.isEmpty()) {
-//            throw new BizException(ErrorEnum.FILE_IS_EMPTY.getErrorCode(), ErrorEnum.FILE_IS_EMPTY.getErrorMessage());
-//        }
-//
-//        // 2. 校验文件类型参数
-//        if (StringUtils.isEmpty(fileType)) {
-//            throw new BizException(ErrorEnum.FILE_TYPE_IS_NULL.getErrorCode(), ErrorEnum.FILE_TYPE_IS_NULL.getErrorMessage());
-//        }
-//
-//        // 3. 获取原始文件名
-//        String originalFilename = file.getOriginalFilename();
-//
-//        String objectName = originalFilename;
-//        // 4. 调用封装好的上传方法
-//        String fileUrl = ossFileService.uploadFile(file, objectName);  // <<< 调用你的封装方法
-//
-//        // 5. 保存数据库记录
-//        TblOssFile ossFile = new TblOssFile();
-//        ossFile.setBusiType(fileType);
-//        ossFile.setBusiKey("0");
-//        ossFile.setFileName(originalFilename);
-//        ossFile.setOssUrl(fileUrl);  // <<< 存完整外链地址
-//        ossFileService.save(ossFile);
-//    }
+    @Override
+    public void updateOTA(UpdateOTAParams params) {
+        // 校验参数
+        if (params.getClientIds() == null || params.getClientIds().isEmpty()) {
+            throw new BizException(ErrorEnum.CLIENT_ID_ID_NULL.getErrorCode(), ErrorEnum.CLIENT_ID_ID_NULL.getErrorMessage());
+        }
+        
+        if (StringUtils.isEmpty(params.getOssUrl())) {
+            throw new BizException(ErrorEnum.FILE_IS_EMPTY.getErrorCode(), ErrorEnum.FILE_IS_EMPTY.getErrorMessage());
+        }
+
+        // 遍历设备列表,为每个设备发送OTA升级消息
+        for (String clientId : params.getClientIds()) {
+            try {
+                // 构建MQTT主题
+                String topic = String.format(TopicConstants.TOPIC_DEV_UPDATEOTA, clientId);
+                
+                // 构建消息内容,包含固件URL
+                JSONObject message = new JSONObject();
+                message.put("url", params.getOssUrl());
+                
+                // 发送MQTT消息
+                if (mqttClient != null) {
+                    mqttClient.sendMessage(topic, message.toJSONString());
+                    log.info("OTA升级消息已发送到设备: clientId={}, firmwareUrl={}", clientId, params.getOssUrl());
+                } else {
+                    log.warn("MQTT客户端不可用,无法发送OTA升级消息到设备: {}", clientId);
+                }
+            } catch (Exception e) {
+                log.error("发送OTA升级消息到设备失败: clientId={}, error={}", clientId, e.getMessage(), e);
+                // 继续处理下一个设备,不中断整个批量操作
+            }
+        }
+        
+        log.info("批量OTA升级操作完成,共处理{}个设备", params.getClientIds().size());
+    }
 
 }
 

+ 112 - 62
portal-service-infrastructure/src/main/java/com/hfln/portal/infrastructure/gateway/impl/UserGatewayImpl.java

@@ -18,7 +18,6 @@ import com.hfln.portal.common.request.user.*;
 import com.hfln.portal.common.response.user.UserInfoWxRes;
 import com.hfln.portal.common.response.user.UserTokenInfo;
 import com.hfln.portal.domain.customer.DeviceType;
-import com.hfln.portal.domain.customer.OssBusiType;
 import com.hfln.portal.domain.customer.util.CopyUtils;
 import com.hfln.portal.domain.customer.util.PasswordUtil;
 import com.hfln.portal.domain.customer.util.WxOfficeAccountClient;
@@ -27,13 +26,7 @@ import com.hfln.portal.domain.gateway.UserGateway;
 import com.hfln.portal.infrastructure.oss.OssClient;
 import com.hfln.portal.infrastructure.oss.OssUtils;
 import com.hfln.portal.infrastructure.po.*;
-import com.hfln.portal.infrastructure.service.DevGroupService;
-import com.hfln.portal.infrastructure.service.DevInfoService;
-import com.hfln.portal.infrastructure.service.DevShareService;
-import com.hfln.portal.infrastructure.service.GroupShareService;
-import com.hfln.portal.infrastructure.service.OssFileService;
-import com.hfln.portal.infrastructure.service.UserService;
-import com.hfln.portal.infrastructure.service.WxRelationService;
+import com.hfln.portal.infrastructure.service.*;
 import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
 import me.chanjar.weixin.common.bean.WxOAuth2UserInfo;
@@ -54,7 +47,8 @@ import org.springframework.web.client.RestTemplate;
 import org.springframework.web.multipart.MultipartFile;
 
 import java.io.IOException;
-import java.io.InputStream;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
@@ -94,6 +88,9 @@ public class UserGatewayImpl implements UserGateway {
     @Value("${lnxx.wechat.bind.callbackUrl}")
     private String callbackUrl;
 
+    @Value("${oss.download-url-pre}")
+    private String downloadUrlPre;
+
     @Autowired
     private WxMpService wxMpService;
 
@@ -518,48 +515,48 @@ public class UserGatewayImpl implements UserGateway {
         return false;
     }
 
-    @Override
-    public void uploadCarousel(MultipartFile file) throws IOException {
-        // 校验文件非空
-        if (file.isEmpty()) {
-            throw new BizException(ErrorEnum.FILE_IS_EMPTY.getErrorCode(), ErrorEnum.FILE_IS_EMPTY.getErrorMessage());
-        }
-
-        // 校验文件类型
-        String contentType = file.getContentType();
-        if (contentType == null || (!contentType.equalsIgnoreCase("image/png") && !contentType.equalsIgnoreCase("image/jpeg"))) {
-            throw new BizException(ErrorEnum.FILE_ONLY_PNG_JPG.getErrorCode(), ErrorEnum.FILE_ONLY_PNG_JPG.getErrorMessage());
-        }
-
-        // 校验文件头
-        byte[] fileHeader = new byte[8];
-        try (InputStream is = file.getInputStream()) {
-            int readBytes = is.read(fileHeader);
-            if (readBytes < 8) {
-                throw new BizException(ErrorEnum.FILE_NOT_COMPLETE.getErrorCode(), ErrorEnum.FILE_NOT_COMPLETE.getErrorMessage());
-            }
-        }
-        boolean isPng = checkPngMagicNumber(fileHeader);
-        boolean isJpeg = checkJpegMagicNumber(fileHeader);
-
-        if (!isPng && !isJpeg) {
-            throw new BizException(ErrorEnum.FILE_ONLY_PNG_JPG02.getErrorCode(), ErrorEnum.FILE_ONLY_PNG_JPG02.getErrorMessage());
-        }
-
-        // 生成OSS对象名称
-        String objectName = OssUtils.getObjectName(OssBusiType.CAROUSEL.getCode(), file.getOriginalFilename());
-        
-        // 上传文件到OSS
-        ossClient.upload(file.getInputStream(), OssUtils.BUCKET_NAME, objectName);
-
-        // 保存文件记录到数据库
-        TblOssFile ossFile = new TblOssFile();
-        ossFile.setBusiType(OssBusiType.CAROUSEL.getCode());
-        ossFile.setBusiKey("0");
-        ossFile.setFileName(file.getOriginalFilename());
-        ossFile.setOssUrl(OssUtils.BUCKET_NAME + "/" + objectName);
-        ossFileService.save(ossFile);
-    }
+//    @Override
+//    public void uploadCarousel(MultipartFile file) throws IOException {
+//        // 校验文件非空
+//        if (file.isEmpty()) {
+//            throw new BizException(ErrorEnum.FILE_IS_EMPTY.getErrorCode(), ErrorEnum.FILE_IS_EMPTY.getErrorMessage());
+//        }
+//
+//        // 校验文件类型
+//        String contentType = file.getContentType();
+//        if (contentType == null || (!contentType.equalsIgnoreCase("image/png") && !contentType.equalsIgnoreCase("image/jpeg"))) {
+//            throw new BizException(ErrorEnum.FILE_ONLY_PNG_JPG.getErrorCode(), ErrorEnum.FILE_ONLY_PNG_JPG.getErrorMessage());
+//        }
+//
+//        // 校验文件头
+//        byte[] fileHeader = new byte[8];
+//        try (InputStream is = file.getInputStream()) {
+//            int readBytes = is.read(fileHeader);
+//            if (readBytes < 8) {
+//                throw new BizException(ErrorEnum.FILE_NOT_COMPLETE.getErrorCode(), ErrorEnum.FILE_NOT_COMPLETE.getErrorMessage());
+//            }
+//        }
+//        boolean isPng = checkPngMagicNumber(fileHeader);
+//        boolean isJpeg = checkJpegMagicNumber(fileHeader);
+//
+//        if (!isPng && !isJpeg) {
+//            throw new BizException(ErrorEnum.FILE_ONLY_PNG_JPG02.getErrorCode(), ErrorEnum.FILE_ONLY_PNG_JPG02.getErrorMessage());
+//        }
+//
+//        // 生成OSS对象名称
+//        String objectName = OssUtils.getObjectName(OssBusiType.CAROUSEL.getCode(), file.getOriginalFilename());
+//
+//        // 上传文件到OSS
+//        ossClient.upload(file.getInputStream(), OssUtils.BUCKET_NAME, objectName);
+//
+//        // 保存文件记录到数据库
+//        TblOssFile ossFile = new TblOssFile();
+//        ossFile.setBusiType(OssBusiType.CAROUSEL.getCode());
+//        ossFile.setBusiKey("0");
+//        ossFile.setFileName(file.getOriginalFilename());
+//        ossFile.setOssUrl(OssUtils.BUCKET_NAME + "/" + objectName);
+//        ossFileService.save(ossFile);
+//    }
 
     @Override
     public List<OssFileDTO> getFileList(String fileType) {
@@ -574,7 +571,7 @@ public class UserGatewayImpl implements UserGateway {
                     dto.setBusiType(ossFile.getBusiType());
                     dto.setFileName(ossFile.getFileName());
                     // 获取可访问的URL
-                    dto.setFileUrl(ossClient.getDownloadUrl(ossFile.getOssUrl()));
+                    dto.setOssUrl(ossFile.getOssUrl());
                     return dto;
                 })
                 .collect(Collectors.toList());
@@ -618,22 +615,75 @@ public class UserGatewayImpl implements UserGateway {
             throw new BizException(ErrorEnum.FILE_TYPE_IS_NULL.getErrorCode(), ErrorEnum.FILE_TYPE_IS_NULL.getErrorMessage());
         }
 
-        // 生成OSS对象名称,直接使用文件类型作为业务类型
-        String objectName = OssUtils.getObjectName(fileType, file.getOriginalFilename());
+        // 获取原始文件名
+        String originalFilename = file.getOriginalFilename();
+        if (originalFilename == null) {
+            throw new BizException(ErrorEnum.FILE_IS_EMPTY.getErrorCode(), "文件名不能为空");
+        }
+
+        // 生成OSS对象名称,使用原始文件名
+        String objectName = generateCommonObjectName(fileType, originalFilename);
         
         // 上传文件到OSS
         ossClient.upload(file.getInputStream(), OssUtils.BUCKET_NAME, objectName);
 
-        // 保存文件记录到数据库
-        TblOssFile ossFile = new TblOssFile();
-        ossFile.setBusiType(fileType); // 直接使用前端传入的文件类型作为业务类型
-        ossFile.setBusiKey("0"); // 业务主键设为默认值
-        ossFile.setFileName(file.getOriginalFilename());
-        ossFile.setOssUrl(OssUtils.BUCKET_NAME + "/" + objectName);
-        ossFileService.save(ossFile);
+        // 查询是否已存在相同文件名和业务类型的记录
+        TblOssFile existingFile = ossFileService.queryOneFile(fileType, "0");
+        if (existingFile != null && originalFilename.equals(existingFile.getFileName())) {
+            // 如果存在相同文件名和业务类型的记录,则更新现有记录
+            log.info("发现相同文件名和业务类型的记录,执行覆盖操作:文件名={}, 业务类型={}", originalFilename, fileType);
+            
+            // 删除旧的OSS文件
+            try {
+                String oldOssUrl = existingFile.getOssUrl();
+                if (oldOssUrl != null && oldOssUrl.contains("/")) {
+                    // 从完整URL中提取objectName
+                    String oldObjectName = oldOssUrl.substring(oldOssUrl.indexOf("/") + 1);
+                    // 使用bucketName/objectName格式删除
+                    String deleteUrl = OssUtils.BUCKET_NAME + "/" + oldObjectName;
+                    ossClient.delete(deleteUrl);
+                    log.info("已删除旧的OSS文件:{}", oldObjectName);
+                }
+            } catch (Exception e) {
+                log.warn("删除旧OSS文件失败,继续执行覆盖操作:{}", e.getMessage());
+            }
+            
+            // 更新数据库记录
+            String fullOssUrl = downloadUrlPre + objectName;
+            existingFile.setOssUrl(fullOssUrl);
+            ossFileService.updateById(existingFile);
+            
+            log.info("文件覆盖成功:文件名={}, 业务类型={}", originalFilename, fileType);
+        } else {
+            // 如果不存在,则创建新记录
+            TblOssFile ossFile = new TblOssFile();
+            ossFile.setBusiType(fileType); // 直接使用前端传入的文件类型作为业务类型
+            ossFile.setBusiKey("0"); // 业务主键设为默认值
+            ossFile.setFileName(originalFilename); // 使用原始文件名
+            String fullOssUrl = downloadUrlPre + objectName;
+            ossFile.setOssUrl(fullOssUrl);
+            ossFileService.save(ossFile);
+            
+            log.info("新文件上传成功:文件名={}, OSS路径={}", originalFilename, objectName);
+        }
     }
 
-
+    /**
+     * 生成通用文件的OSS对象名称,保留原始文件名
+     * @param fileType 文件类型
+     * @param originalFilename 原始文件名
+     * @return OSS对象名称
+     */
+    private String generateCommonObjectName(String fileType, String originalFilename) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(fileType)
+          .append("/")
+          .append(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")))
+          .append("/")
+          .append(originalFilename); // 直接使用原始文件名
+
+        return sb.toString();
+    }
 
     private boolean checkPngMagicNumber(byte[] fileHeader) {
         byte[] pngMagicNumber = {(byte) 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};

+ 86 - 0
portal-service-infrastructure/src/main/java/com/hfln/portal/infrastructure/gateway/impl/WebGatewayImpl.java

@@ -33,9 +33,13 @@ import com.hfln.portal.domain.gateway.sms.SmsGateway;
 import com.hfln.portal.infrastructure.config.UserAuthService;
 import com.hfln.portal.infrastructure.po.*;
 import com.hfln.portal.infrastructure.service.*;
+import com.hfln.portal.infrastructure.oss.OssClient;
+import com.hfln.portal.infrastructure.oss.OssUtils;
+import com.hfln.portal.infrastructure.po.TblOssFile;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
@@ -45,6 +49,7 @@ import org.springframework.web.multipart.MultipartFile;
 import java.io.IOException;
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -52,6 +57,9 @@ import java.util.stream.Collectors;
 @Service
 public class WebGatewayImpl implements WebGateway {
 
+    @Value("${oss.download-url-pre}")
+    private String downloadUrlPre;
+
     @Autowired
     private DevInfoService devInfoService;
 
@@ -78,15 +86,25 @@ public class WebGatewayImpl implements WebGateway {
 
     @Autowired
     private TblRoleMenuMapService tblRoleMenuMapService;
+
     @Autowired
     private TblDicService tblDicService;
+
     @Autowired
     private TblDicItemService tblDicItemService;
+
     @Autowired
     private UserService userService;
+
     @Autowired
     private TblTenantService tblTenantService;
 
+    @Autowired
+    private OssFileService ossFileService;
+
+    @Autowired
+    private OssClient ossClient;
+
     @Override
     public UploadRes uploadDev(MultipartFile file) throws IOException {
 
@@ -625,4 +643,72 @@ public class WebGatewayImpl implements WebGateway {
         tblDicItemService.removeById(tblDicItem);
     }
 
+
+    @Override
+    public void OTAUpload(MultipartFile file) throws IOException {
+        // 校验文件非空
+        if (file.isEmpty()) {
+            throw new BizException(ErrorEnum.FILE_IS_EMPTY.getErrorCode(), ErrorEnum.FILE_IS_EMPTY.getErrorMessage());
+        }
+
+        // 校验文件类型 - OTA升级文件通常是固件文件,支持常见的固件格式
+        String originalFilename = file.getOriginalFilename();
+        if (originalFilename == null || !isValidOtaFile(originalFilename)) {
+            throw new BizException(ErrorEnum.FILE_ONLY_BIN_HEX_FW.getErrorCode(), "仅支持固件文件格式(.bin, .hex, .fw等)!");
+        }
+
+        // 生成OTA专用的OSS对象名称,使用单独的文件夹
+        String objectName = generateOtaObjectName(originalFilename);
+        
+        // 上传文件到OSS
+        ossClient.upload(file.getInputStream(), OssUtils.BUCKET_NAME, objectName);
+
+        // 保存文件记录到数据库
+        TblOssFile ossFile = new TblOssFile();
+        ossFile.setBusiType("OTA"); // 使用OTA作为业务类型
+        ossFile.setBusiKey("0"); // 业务主键设为默认值
+        ossFile.setFileName(originalFilename);
+        // 存储完整的OSS访问URL
+        String fullOssUrl = downloadUrlPre + objectName;
+        ossFile.setOssUrl(fullOssUrl);
+        ossFileService.save(ossFile);
+        
+        log.info("OTA升级文件上传成功:文件名={}, OSS路径={}", originalFilename, objectName);
+    }
+
+    /**
+     * 校验是否为有效的OTA文件
+     * @param fileName 文件名
+     * @return 是否为有效文件
+     */
+    private boolean isValidOtaFile(String fileName) {
+        if (fileName == null) {
+            return false;
+        }
+        String lowerFileName = fileName.toLowerCase();
+        // 支持常见的固件文件格式
+        return lowerFileName.endsWith(".bin") || 
+               lowerFileName.endsWith(".hex") || 
+               lowerFileName.endsWith(".fw") || 
+               lowerFileName.endsWith(".img") || 
+               lowerFileName.endsWith(".tar.gz") || 
+               lowerFileName.endsWith(".zip");
+    }
+
+    /**
+     * 生成OTA专用的OSS对象名称
+     * @param originalFilename 原始文件名
+     * @return OSS对象名称
+     */
+    private String generateOtaObjectName(String originalFilename) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("ota/") // 使用单独的ota文件夹
+          .append(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")))
+          .append("/")
+          .append(UUID.randomUUID().toString().replace("-", ""))
+          .append("_")
+          .append(originalFilename); // 保留原始文件名以便识别
+
+        return sb.toString();
+    }
 }

+ 0 - 1
portal-service-infrastructure/src/main/java/com/hfln/portal/infrastructure/service/OssFileService.java

@@ -13,5 +13,4 @@ public interface OssFileService extends IService<TblOssFile> {
 
     TblOssFile queryOneFile(String busiType, String busiKey);
 
-//    String uploadFile(MultipartFile file, String objectName) throws IOException;
 }

+ 2 - 1
portal-service-server/src/main/resources/bootstrap-dev.yml

@@ -120,4 +120,5 @@ oss:
   bucket: lnxx.oss-cn-shanghai.aliyuncs.com
   region: cn-shanghai
   expire:
-    seconds: 3600
+    seconds: 3600
+  download-url-pre: https://hflnxx.oss-cn-shanghai.aliyuncs.com/

+ 1 - 0
portal-service-server/src/main/resources/bootstrap-local.yml

@@ -121,3 +121,4 @@ oss:
   region: cn-shanghai
   expire:
     seconds: 3600
+  download-url-pre: https://hflnxx.oss-cn-shanghai.aliyuncs.com/

+ 2 - 1
portal-service-server/src/main/resources/bootstrap-test.yml

@@ -120,4 +120,5 @@ oss:
   bucket: lnxx.oss-cn-shanghai.aliyuncs.com
   region: cn-shanghai
   expire:
-    seconds: 3600
+    seconds: 3600
+  download-url-pre: https://hflnxx.oss-cn-shanghai.aliyuncs.com/