Bladeren bron

解决冲突

fuyuchuan 4 dagen geleden
bovenliggende
commit
9b40fa4299
37 gewijzigde bestanden met toevoegingen van 2137 en 671 verwijderingen
  1. 9 12
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsAnalysisController.java
  2. 60 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsProjectController.java
  3. 18 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsReportController.java
  4. 20 4
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsDeviceItemCode.java
  5. 22 6
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsEnergyItemCode.java
  6. 16 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsProductEnergyType.java
  7. 10 7
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsProjectConversionFactor.java
  8. 2 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsAnalysisService.java
  9. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsEnergyItemCodeService.java
  10. 26 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsProjectService.java
  11. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsReportService.java
  12. 254 471
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsAnalysisServiceImpl.java
  13. 84 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsEnergyItemCodeServiceImpl.java
  14. 170 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsProjectServiceImpl.java
  15. 1105 94
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsReportServiceImpl.java
  16. 20 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsAveragePointVO.java
  17. 48 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsAverageRequest.java
  18. 23 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsAverageResponseVO.java
  19. 17 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsAverageSpaceItemVO.java
  20. 4 1
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsCompareResponse.java
  21. 22 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsDeviceItemCodeSaveRequest.java
  22. 19 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsProductEnergyTypeSaveRequest.java
  23. 2 2
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EnergyReportRequest.java
  24. 4 2
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EnergyReportResponse.java
  25. 1 1
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/ItemReportRequest.java
  26. 1 1
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/SpaceReportRequest.java
  27. 4 2
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/SpaceReportResponse.java
  28. 3 3
      service-iot/service-iot-biz/src/main/java/com/usky/iot/controller/web/DmpDeviceInfoController.java
  29. 5 0
      service-iot/service-iot-biz/src/main/java/com/usky/iot/domain/DmpDeviceInfo.java
  30. 2 0
      service-iot/service-iot-biz/src/main/java/com/usky/iot/mapper/DmpProductInfoMapper.java
  31. 2 2
      service-iot/service-iot-biz/src/main/java/com/usky/iot/service/DmpDeviceInfoService.java
  32. 94 47
      service-iot/service-iot-biz/src/main/java/com/usky/iot/service/impl/DmpDeviceInfoServiceImpl.java
  33. 5 0
      service-iot/service-iot-biz/src/main/java/com/usky/iot/service/vo/DmpDeviceInfoRequest.java
  34. 13 0
      service-iot/service-iot-biz/src/main/java/com/usky/iot/service/vo/GatewayDeviceExportItemVO.java
  35. 0 10
      service-iot/service-iot-biz/src/main/java/com/usky/iot/service/vo/GatewayDeviceInfoRequestVO.java
  36. 10 0
      service-iot/service-iot-biz/src/main/resources/mapper/iot/DmpProductInfoMapper.xml
  37. 22 6
      service-tsdb/service-tsdb-biz/src/main/java/com/usky/demo/service/impl/QueryTdengineDataServiceImpl.java

+ 9 - 12
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsAnalysisController.java

@@ -19,14 +19,14 @@ public class EmsAnalysisController {
 
     @Autowired
     private EmsAnalysisService emsAnalysisService;
-    
+
     /**
      * 获取当前日期的默认值
      */
     private String getCurrentDefaultTimeDimension() {
         return "D"; // 按日统计
     }
-    
+
     /**
      * 获取当前日期的默认值(根据时间维度返回对应格式)
      */
@@ -80,16 +80,13 @@ public class EmsAnalysisController {
         return ApiResult.success(emsAnalysisService.getTrendCategory(projectId, timeDimension, timeValue, energyTypeId));
     }
 
-    @GetMapping("/region")
-    public ApiResult<EmsRegionAnalysisResponse> getRegionAnalysis(
-            @RequestParam(required = false) Long projectId,
-            @RequestParam(required = false) String regionIds,
-            @RequestParam(required = false) String timeDimension,
-            @RequestParam(required = false) String timeValue,
-            @RequestParam(required = false) Long energyTypeId) {
-        if (timeDimension == null) timeDimension = getCurrentDefaultTimeDimension();
-        if (timeValue == null) timeValue = getCurrentDefaultTimeValue(timeDimension);
-        return ApiResult.success(emsAnalysisService.getRegionAnalysis(projectId, regionIds, timeDimension, timeValue, energyTypeId));
+    /**
+     * 区域能耗时序分析
+     * 按分项编码与区域列表,查询各区域在时间轴上的能耗数据
+     */
+    @PostMapping("/region")
+    public ApiResult<EmsAverageResponseVO> getRegionAnalysis(@RequestBody EmsAverageRequest request) {
+        return ApiResult.success(emsAnalysisService.getRegionalAnalysis(request));
     }
 
     @PostMapping("/compare")

+ 60 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsProjectController.java

@@ -1,12 +1,19 @@
 package com.usky.ems.controller.web;
 
 import com.usky.common.core.bean.ApiResult;
+import com.usky.ems.domain.DmpDevice;
+import com.usky.ems.domain.EmsEnergyItemCode;
+import com.usky.ems.service.EmsEnergyItemCodeService;
 import com.usky.ems.service.EmsProjectService;
+import com.usky.ems.service.vo.EmsDeviceItemCodeSaveRequest;
+import com.usky.ems.service.vo.EmsProductEnergyTypeSaveRequest;
 import com.usky.ems.service.vo.EmsProjectResponse;
 import com.usky.ems.service.vo.EmsProjectSaveRequest;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
+import java.util.List;
+
 /**
  * 项目(ems_project)新增、修改、删除
  * 基础路径前缀:/project(网关再加 /prod-api/service-ems)
@@ -17,6 +24,59 @@ public class EmsProjectController {
 
     @Autowired
     private EmsProjectService emsProjectService;
+    @Autowired
+    private EmsEnergyItemCodeService emsEnergyItemCodeService;
+
+    /**
+     * 能耗类型列表(SELECT * FROM ems_energy_item_code WHERE parent_code = 0)
+     */
+    @GetMapping("/energy-type/list")
+    public ApiResult<List<EmsEnergyItemCode>> listEnergyTypes(
+            @RequestParam(required = false) Integer energyType) {
+        return ApiResult.success(emsEnergyItemCodeService.listEnergyTypes(energyType));
+    }
+
+    /**
+     * 能耗分项列表(parent_code 为能耗类型编码)
+     */
+    @GetMapping("/energy-item/list")
+    public ApiResult<List<EmsEnergyItemCode>> listEnergySubItems() {
+        return ApiResult.success(emsEnergyItemCodeService.listEnergySubItems());
+    }
+
+    /**
+     * 区域设备信息:按空间及其子空间关联网关,查询 dmp_device 子设备列表
+     */
+    @GetMapping("/area/device/list")
+    public ApiResult<List<DmpDevice>> listAreaDevices(@RequestParam Integer spaceId) {
+        return ApiResult.success(emsProjectService.listAreaDevices(spaceId));
+    }
+
+    /**
+     * 区域能耗分项类型信息:按区域设备关联的分项编码查询 ems_energy_item_code
+     */
+    @GetMapping("/area/device-item-code/list")
+    public ApiResult<List<EmsEnergyItemCode>> listAreaDeviceItemCodes(@RequestParam Integer spaceId) {
+        return ApiResult.success(emsProjectService.listAreaDeviceItemCodes(spaceId));
+    }
+
+    /**
+     * 产品关联的能源类型(ems_product_energy_type)
+     */
+    @PostMapping("/product-energy-type")
+    public ApiResult<Void> saveProductEnergyTypes(@RequestBody EmsProductEnergyTypeSaveRequest request) {
+        emsProjectService.saveProductEnergyTypes(request);
+        return ApiResult.success();
+    }
+
+    /**
+     * 设备关联的能源分项(ems_device_item_code)
+     */
+    @PostMapping("/device-item-code")
+    public ApiResult<Void> saveDeviceItemCodes(@RequestBody EmsDeviceItemCodeSaveRequest request) {
+        emsProjectService.saveDeviceItemCodes(request);
+        return ApiResult.success();
+    }
 
     @PostMapping
     public ApiResult<EmsProjectResponse> create(@RequestBody EmsProjectSaveRequest request) {

+ 18 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsReportController.java

@@ -124,6 +124,15 @@ public class EmsReportController {
         return ApiResult.success(emsReportService.getEnergyReport(request));
     }
 
+    /**
+     * 能源报表导出 Excel(请求体与 /report/energy 一致)
+     */
+    @PostMapping("/energy/export")
+    @ApiOperation("能源报表导出")
+    public void exportEnergyReport(@RequestBody EnergyReportRequest request, HttpServletResponse response) {
+        emsReportService.exportEnergyReport(request, response);
+    }
+
     /**
      * 2. 分项报表
      * 支持按空间ID、分项编码、日期类型统计能耗
@@ -149,4 +158,13 @@ public class EmsReportController {
     public ApiResult<SpaceReportResponse> getSpaceReport(@RequestBody SpaceReportRequest request) {
         return ApiResult.success(emsReportService.getSpaceReport(request));
     }
+
+    /**
+     * 区域报表导出 Excel(请求体与 /report/space 一致)
+     */
+    @PostMapping("/space/export")
+    @ApiOperation("区域报表导出")
+    public void exportSpaceReport(@RequestBody SpaceReportRequest request, HttpServletResponse response) {
+        emsReportService.exportSpaceReport(request, response);
+    }
 }

+ 20 - 4
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsDeviceItemCode.java

@@ -8,6 +8,7 @@ import lombok.Data;
 import lombok.EqualsAndHashCode;
 
 import java.io.Serializable;
+import java.time.LocalDateTime;
 
 /**
  * 设备与能耗分项关联(ems_device_item_code)
@@ -22,12 +23,27 @@ public class EmsDeviceItemCode implements Serializable {
     @TableId(value = "id", type = IdType.AUTO)
     private Long id;
 
-    @TableField("device_id")
-    private String deviceId;
+    @TableField("device_uuid")
+    private String deviceUuid;
 
     @TableField("item_code")
     private String itemCode;
 
-    @TableField("project_id")
-    private Long projectId;
+    @TableField("energy_type")
+    private Integer energyType;
+
+    @TableField("created_by")
+    private String createdBy;
+
+    @TableField("created_time")
+    private LocalDateTime createdTime;
+
+    @TableField("updated_by")
+    private String updatedBy;
+
+    @TableField("updated_time")
+    private LocalDateTime updatedTime;
+
+    /** 租户号 */
+    private Integer tenantId;
 }

+ 22 - 6
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsEnergyItemCode.java

@@ -9,6 +9,7 @@ import lombok.EqualsAndHashCode;
 
 import java.io.Serializable;
 import java.time.LocalDateTime;
+import java.util.List;
 
 /**
  * 能源分项编码(leo.ems_energy_item_code),用于能源类型列表
@@ -33,12 +34,27 @@ public class EmsEnergyItemCode implements Serializable {
     private String identifier;
     @TableField("energy_type")
     private Integer energyType;
+
+    @TableField("created_by")
+    private String createdBy;
+
+    @TableField("created_time")
+    private LocalDateTime createdTime;
+
     @TableField("updated_by")
     private Long updatedBy;
-    @TableField("update_time")
-    private LocalDateTime updateTime;
-    @TableField("created_by")
-    private Long createdBy;
-    @TableField("create_time")
-    private LocalDateTime createTime;
+
+    @TableField("updated_time")
+    private LocalDateTime updatedTime;
+
+    /** 租户号 */
+    private Integer tenantId;
+
+    /** 能耗绑定产品id集合 **/
+    @TableField(exist = false)
+    private List<Long> productIdList;
+
+    /** 能耗分项绑定设备uuid集合 **/
+    @TableField(exist = false)
+    private List<String> deviceUuidList;
 }

+ 16 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsProductEnergyType.java

@@ -8,6 +8,7 @@ import lombok.Data;
 import lombok.EqualsAndHashCode;
 
 import java.io.Serializable;
+import java.time.LocalDateTime;
 
 /**
  * 产品能源类型关联(ems_product_energy_type)
@@ -27,4 +28,19 @@ public class EmsProductEnergyType implements Serializable {
 
     @TableField("energy_type")
     private Integer energyType;
+
+    @TableField("created_by")
+    private String createdBy;
+
+    @TableField("created_time")
+    private LocalDateTime createdTime;
+
+    @TableField("updated_by")
+    private String updatedBy;
+
+    @TableField("updated_time")
+    private LocalDateTime updatedTime;
+
+    /** 租户号 */
+    private Integer tenantId;
 }

+ 10 - 7
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsProjectConversionFactor.java

@@ -34,15 +34,18 @@ public class EmsProjectConversionFactor implements Serializable {
 
     private BigDecimal value;
 
+    @TableField("created_by")
+    private String createdBy;
+
+    @TableField("created_time")
+    private LocalDateTime createdTime;
+
     @TableField("updated_by")
     private String updatedBy;
 
-    @TableField("update_time")
-    private LocalDateTime updateTime;
-
-    @TableField("created_by")
-    private String createdBy;
+    @TableField("updated_time")
+    private LocalDateTime updatedTime;
 
-    @TableField("create_time")
-    private LocalDateTime createTime;
+    /** 租户号 */
+    private Integer tenantId;
 }

+ 2 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsAnalysisService.java

@@ -18,4 +18,6 @@ public interface EmsAnalysisService {
     EmsCompareResponse getCompare(EmsCompareRequest request);
 
     EmsAverageResponse getAverage(Long projectId, String timeDimension, String timeValue, Long energyTypeId);
+
+    EmsAverageResponseVO getRegionalAnalysis(EmsAverageRequest request);
 }

+ 10 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsEnergyItemCodeService.java

@@ -15,5 +15,15 @@ public interface EmsEnergyItemCodeService extends CrudService<EmsEnergyItemCode>
      * 查询指定空间下的分项能耗配置(基于能耗公式和分项编码组装树形结构)。
      */
     List<EnergyItemCodeVO> querySpaceEnergyItem(Long spaceId);
+
+    /**
+     * 能耗类型列表:parent_code = 0,可按 energyType 筛选
+     */
+    List<EmsEnergyItemCode> listEnergyTypes(Integer energyType);
+
+    /**
+     * 能耗分项列表:parent_code 为能耗类型(parent_code = 0)的编码
+     */
+    List<EmsEnergyItemCode> listEnergySubItems();
 }
 

+ 26 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsProjectService.java

@@ -1,8 +1,14 @@
 package com.usky.ems.service;
 
+import com.usky.ems.domain.DmpDevice;
+import com.usky.ems.domain.EmsEnergyItemCode;
+import com.usky.ems.service.vo.EmsDeviceItemCodeSaveRequest;
+import com.usky.ems.service.vo.EmsProductEnergyTypeSaveRequest;
 import com.usky.ems.service.vo.EmsProjectResponse;
 import com.usky.ems.service.vo.EmsProjectSaveRequest;
 
+import java.util.List;
+
 /**
  * 项目(ems_project)维护:新增、修改、删除(含空间树、省市区校验、设备系统关联)
  */
@@ -13,4 +19,24 @@ public interface EmsProjectService {
     EmsProjectResponse update(EmsProjectSaveRequest request);
 
     void remove(Long spaceId);
+
+    /**
+     * 保存产品关联的能源类型:先删除当前租户下该能耗类型的记录,再按产品列表插入
+     */
+    void saveProductEnergyTypes(EmsProductEnergyTypeSaveRequest request);
+
+    /**
+     * 保存设备关联的能源分项:先删除当前租户下该分项编码的记录,再按设备列表插入
+     */
+    void saveDeviceItemCodes(EmsDeviceItemCodeSaveRequest request);
+
+    /**
+     * 区域设备信息:按空间及其子空间关联网关,查询 dmp_device 子设备列表
+     */
+    List<DmpDevice> listAreaDevices(Integer spaceId);
+
+    /**
+     * 区域能耗分项类型信息:按区域设备关联的分项编码查询 ems_energy_item_code
+     */
+    List<EmsEnergyItemCode> listAreaDeviceItemCodes(Integer spaceId);
 }

+ 10 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsReportService.java

@@ -53,4 +53,14 @@ public interface EmsReportService {
      * @return 区域报表响应
      */
     SpaceReportResponse getSpaceReport(SpaceReportRequest request);
+
+    /**
+     * 导出能源报表 Excel
+     */
+    void exportEnergyReport(EnergyReportRequest request, HttpServletResponse response);
+
+    /**
+     * 导出区域报表 Excel
+     */
+    void exportSpaceReport(SpaceReportRequest request, HttpServletResponse response);
 }

File diff suppressed because it is too large
+ 254 - 471
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsAnalysisServiceImpl.java


+ 84 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsEnergyItemCodeServiceImpl.java

@@ -3,10 +3,15 @@ package com.usky.ems.service.impl;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.usky.common.mybatis.core.AbstractCrudService;
+import com.usky.common.security.utils.SecurityUtils;
+import com.usky.ems.domain.EmsDeviceItemCode;
 import com.usky.ems.domain.EmsEnergyConsumptionFormula;
 import com.usky.ems.domain.EmsEnergyItemCode;
+import com.usky.ems.domain.EmsProductEnergyType;
+import com.usky.ems.mapper.EmsDeviceItemCodeMapper;
 import com.usky.ems.mapper.EmsEnergyConsumptionFormulaMapper;
 import com.usky.ems.mapper.EmsEnergyItemCodeMapper;
+import com.usky.ems.mapper.EmsProductEnergyTypeMapper;
 import com.usky.ems.service.EmsEnergyItemCodeService;
 import com.usky.ems.service.vo.EnergyItemCodeVO;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -25,6 +30,10 @@ public class EmsEnergyItemCodeServiceImpl
 
     @Autowired
     private EmsEnergyConsumptionFormulaMapper energyConsumptionFormulaMapper;
+    @Autowired
+    private EmsProductEnergyTypeMapper emsProductEnergyTypeMapper;
+    @Autowired
+    private EmsDeviceItemCodeMapper emsDeviceItemCodeMapper;
 
     @Override
     public List<EnergyItemCodeVO> querySpaceEnergyItem(Long spaceId) {
@@ -106,5 +115,80 @@ public class EmsEnergyItemCodeServiceImpl
 
         return result;
     }
+
+    @Override
+    public List<EmsEnergyItemCode> listEnergyTypes(Integer energyType) {
+        List<EmsEnergyItemCode> energyTypes = listRootEnergyTypes(energyType);
+        if (energyTypes.isEmpty()) {
+            return energyTypes;
+        }
+        Map<Integer, List<Long>> productIdsByEnergyType = loadProductIdsByEnergyType(SecurityUtils.getTenantId());
+        for (EmsEnergyItemCode energyType1 : energyTypes) {
+            energyType1.setProductIdList(
+                    productIdsByEnergyType.getOrDefault(energyType1.getEnergyType(), Collections.emptyList()));
+        }
+        return energyTypes;
+    }
+
+    @Override
+    public List<EmsEnergyItemCode> listEnergySubItems() {
+        List<String> rootCodes = listRootEnergyTypes(null).stream()
+                .map(EmsEnergyItemCode::getCode)
+                .filter(Objects::nonNull)
+                .distinct()
+                .collect(Collectors.toList());
+        if (rootCodes.isEmpty()) {
+            return Collections.emptyList();
+        }
+        List<EmsEnergyItemCode> energySubItems = this.list(
+                Wrappers.<EmsEnergyItemCode>lambdaQuery()
+                        .in(EmsEnergyItemCode::getParentCode, rootCodes)
+                        .orderByAsc(EmsEnergyItemCode::getParentCode, EmsEnergyItemCode::getCode));
+        if (energySubItems.isEmpty()) {
+            return energySubItems;
+        }
+        Map<String, List<String>> deviceUuidsByItemCode = loadDeviceUuidsByItemCode(SecurityUtils.getTenantId());
+        for (EmsEnergyItemCode energySubItem : energySubItems) {
+            energySubItem.setDeviceUuidList(
+                    deviceUuidsByItemCode.getOrDefault(energySubItem.getCode(), Collections.emptyList()));
+        }
+        return energySubItems;
+    }
+
+    private List<EmsEnergyItemCode> listRootEnergyTypes(Integer energyType) {
+        LambdaQueryWrapper<EmsEnergyItemCode> wrapper = Wrappers.<EmsEnergyItemCode>lambdaQuery()
+                .eq(EmsEnergyItemCode::getParentCode, "0")
+                .eq(energyType != null, EmsEnergyItemCode::getEnergyType, energyType)
+                .orderByAsc(EmsEnergyItemCode::getCode);
+        return this.list(wrapper);
+    }
+
+    private Map<Integer, List<Long>> loadProductIdsByEnergyType(Integer tenantId) {
+        List<EmsProductEnergyType> bindings = emsProductEnergyTypeMapper.selectList(
+                Wrappers.<EmsProductEnergyType>lambdaQuery()
+                        .eq(EmsProductEnergyType::getTenantId, tenantId));
+        if (bindings.isEmpty()) {
+            return Collections.emptyMap();
+        }
+        return bindings.stream()
+                .filter(binding -> binding.getEnergyType() != null && binding.getProductId() != null)
+                .collect(Collectors.groupingBy(
+                        EmsProductEnergyType::getEnergyType,
+                        Collectors.mapping(EmsProductEnergyType::getProductId, Collectors.toList())));
+    }
+
+    private Map<String, List<String>> loadDeviceUuidsByItemCode(Integer tenantId) {
+        List<EmsDeviceItemCode> bindings = emsDeviceItemCodeMapper.selectList(
+                Wrappers.<EmsDeviceItemCode>lambdaQuery()
+                        .eq(EmsDeviceItemCode::getTenantId, tenantId));
+        if (bindings.isEmpty()) {
+            return Collections.emptyMap();
+        }
+        return bindings.stream()
+                .filter(binding -> binding.getItemCode() != null && binding.getDeviceUuid() != null)
+                .collect(Collectors.groupingBy(
+                        EmsDeviceItemCode::getItemCode,
+                        Collectors.mapping(EmsDeviceItemCode::getDeviceUuid, Collectors.toList())));
+    }
 }
 

+ 170 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsProjectServiceImpl.java

@@ -1,16 +1,29 @@
 package com.usky.ems.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.usky.common.core.exception.BusinessException;
 import com.usky.common.security.utils.SecurityUtils;
 import com.usky.ems.domain.BaseSpace;
+import com.usky.ems.domain.BaseSpaceGateway;
+import com.usky.ems.domain.DmpDevice;
+import com.usky.ems.domain.EmsDeviceItemCode;
+import com.usky.ems.domain.EmsEnergyItemCode;
+import com.usky.ems.domain.EmsProductEnergyType;
 import com.usky.ems.domain.EmsProject;
 import com.usky.ems.domain.EmsProjectDeviceSystem;
+import com.usky.ems.mapper.BaseSpaceGatewayMapper;
+import com.usky.ems.mapper.DmpDeviceMapper;
+import com.usky.ems.mapper.EmsDeviceItemCodeMapper;
+import com.usky.ems.mapper.EmsEnergyItemCodeMapper;
+import com.usky.ems.mapper.EmsProductEnergyTypeMapper;
 import com.usky.ems.mapper.EmsProjectDeviceSystemMapper;
 import com.usky.ems.mapper.EmsProjectMapper;
 import com.usky.ems.service.BaseSpaceService;
 import com.usky.ems.service.EmsProjectService;
 import com.usky.ems.service.EmsSystemDictRegionService;
+import com.usky.ems.service.vo.EmsDeviceItemCodeSaveRequest;
+import com.usky.ems.service.vo.EmsProductEnergyTypeSaveRequest;
 import com.usky.ems.service.vo.EmsProjectResponse;
 import com.usky.ems.service.vo.EmsProjectSaveRequest;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -20,9 +33,11 @@ import org.springframework.util.CollectionUtils;
 import org.springframework.util.StringUtils;
 
 import java.time.LocalDateTime;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.stream.Collectors;
 
 /**
  * 项目维护实现(逻辑参考原 ProjectServiceImpl:省市区校验、创建项目空间、设备系统关联、删除前校验叶子节点等)
@@ -40,6 +55,16 @@ public class EmsProjectServiceImpl implements EmsProjectService {
     private BaseSpaceService baseSpaceService;
     @Autowired
     private EmsSystemDictRegionService emsSystemDictRegionService;
+    @Autowired
+    private EmsProductEnergyTypeMapper emsProductEnergyTypeMapper;
+    @Autowired
+    private EmsDeviceItemCodeMapper emsDeviceItemCodeMapper;
+    @Autowired
+    private BaseSpaceGatewayMapper baseSpaceGatewayMapper;
+    @Autowired
+    private DmpDeviceMapper dmpDeviceMapper;
+    @Autowired
+    private EmsEnergyItemCodeMapper emsEnergyItemCodeMapper;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -103,6 +128,151 @@ public class EmsProjectServiceImpl implements EmsProjectService {
         baseSpaceService.removeById(spaceId);
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void saveProductEnergyTypes(EmsProductEnergyTypeSaveRequest request) {
+        if (request == null || request.getEnergyType() == null) {
+            throw new BusinessException("能耗类型不能为空");
+        }
+        Integer tenantId = SecurityUtils.getTenantId();
+        emsProductEnergyTypeMapper.delete(
+                new LambdaQueryWrapper<EmsProductEnergyType>()
+                        .eq(EmsProductEnergyType::getTenantId, tenantId)
+                        .eq(EmsProductEnergyType::getEnergyType, request.getEnergyType()));
+        if (CollectionUtils.isEmpty(request.getProductIdList())) {
+            return;
+        }
+        LocalDateTime now = LocalDateTime.now();
+        String auditUser = SecurityUtils.getUsername();
+        for (Long productId : request.getProductIdList()) {
+            if (productId == null) {
+                continue;
+            }
+            EmsProductEnergyType row = new EmsProductEnergyType();
+            row.setProductId(productId);
+            row.setEnergyType(request.getEnergyType());
+            row.setTenantId(tenantId);
+            row.setCreatedBy(auditUser);
+            row.setUpdatedBy(auditUser);
+            row.setCreatedTime(now);
+            row.setUpdatedTime(now);
+            emsProductEnergyTypeMapper.insert(row);
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void saveDeviceItemCodes(EmsDeviceItemCodeSaveRequest request) {
+        if (request == null || request.getEnergyType() == null) {
+            throw new BusinessException("能耗类型不能为空");
+        }
+        if (!StringUtils.hasText(request.getItemCode())) {
+            throw new BusinessException("能耗分项编码不能为空");
+        }
+        Integer tenantId = SecurityUtils.getTenantId();
+        String itemCode = request.getItemCode().trim();
+        emsDeviceItemCodeMapper.delete(
+                new LambdaQueryWrapper<EmsDeviceItemCode>()
+                        .eq(EmsDeviceItemCode::getTenantId, tenantId)
+                        .eq(EmsDeviceItemCode::getItemCode, itemCode));
+        if (CollectionUtils.isEmpty(request.getDeviceUuidList())) {
+            return;
+        }
+        LocalDateTime now = LocalDateTime.now();
+        String auditUser = SecurityUtils.getUsername();
+        for (String deviceUuid : request.getDeviceUuidList()) {
+            if (!StringUtils.hasText(deviceUuid)) {
+                continue;
+            }
+            EmsDeviceItemCode row = new EmsDeviceItemCode();
+            row.setDeviceUuid(deviceUuid.trim());
+            row.setItemCode(itemCode);
+            row.setEnergyType(request.getEnergyType());
+            row.setTenantId(tenantId);
+            row.setCreatedBy(auditUser);
+            row.setUpdatedBy(auditUser);
+            row.setCreatedTime(now);
+            row.setUpdatedTime(now);
+            emsDeviceItemCodeMapper.insert(row);
+        }
+    }
+
+    @Override
+    public List<DmpDevice> listAreaDevices(Integer spaceId) {
+        List<String> gatewayUuids = resolveGatewayUuidsBySpaceId(spaceId);
+        if (gatewayUuids.isEmpty()) {
+            return Collections.emptyList();
+        }
+        return dmpDeviceMapper.selectList(
+                Wrappers.<DmpDevice>lambdaQuery()
+                        .in(DmpDevice::getGatewayUuid, gatewayUuids)
+                        .eq(DmpDevice::getCategoryType, 3)
+                        .eq(DmpDevice::getDeleteFlag, 0));
+    }
+
+    @Override
+    public List<EmsEnergyItemCode> listAreaDeviceItemCodes(Integer spaceId) {
+        List<DmpDevice> devices = listAreaDevices(spaceId);
+        if (devices.isEmpty()) {
+            return Collections.emptyList();
+        }
+        List<String> deviceUuids = devices.stream()
+                .map(DmpDevice::getDeviceUuid)
+                .filter(StringUtils::hasText)
+                .map(String::trim)
+                .distinct()
+                .collect(Collectors.toList());
+        if (deviceUuids.isEmpty()) {
+            return Collections.emptyList();
+        }
+        List<EmsDeviceItemCode> deviceItemCodes = emsDeviceItemCodeMapper.selectList(
+                Wrappers.<EmsDeviceItemCode>lambdaQuery()
+                        .in(EmsDeviceItemCode::getDeviceUuid, deviceUuids)
+                        .eq(EmsDeviceItemCode::getTenantId, SecurityUtils.getTenantId()));
+        if (deviceItemCodes.isEmpty()) {
+            return Collections.emptyList();
+        }
+        List<String> itemCodes = deviceItemCodes.stream()
+                .map(EmsDeviceItemCode::getItemCode)
+                .filter(StringUtils::hasText)
+                .map(String::trim)
+                .distinct()
+                .collect(Collectors.toList());
+        if (itemCodes.isEmpty()) {
+            return Collections.emptyList();
+        }
+        return emsEnergyItemCodeMapper.selectList(
+                Wrappers.<EmsEnergyItemCode>lambdaQuery()
+                        .in(EmsEnergyItemCode::getCode, itemCodes)
+                        .orderByAsc(EmsEnergyItemCode::getCode));
+    }
+
+    private List<String> resolveGatewayUuidsBySpaceId(Integer spaceId) {
+        if (spaceId == null) {
+            throw new BusinessException("空间id不能为空");
+        }
+        BaseSpace space = baseSpaceService.getById(spaceId.longValue());
+        if (space == null) {
+            throw new BusinessException("空间不存在");
+        }
+        List<Long> spaceIds = baseSpaceService.getAuthorizedSpaceIds(space.getId());
+        if (spaceIds.isEmpty()) {
+            return Collections.emptyList();
+        }
+        List<BaseSpaceGateway> links = baseSpaceGatewayMapper.selectList(
+                Wrappers.<BaseSpaceGateway>lambdaQuery()
+                        .in(BaseSpaceGateway::getSpaceId, spaceIds));
+        if (CollectionUtils.isEmpty(links)) {
+            return Collections.emptyList();
+        }
+        return links.stream()
+                .map(BaseSpaceGateway::getGatewayUuid)
+                .filter(StringUtils::hasText)
+                .map(String::trim)
+                .distinct()
+                .collect(Collectors.toList());
+    }
+
     /**
      * 对应原 UserService.hasLoggedUser(projectId)。接入统一用户/权限模块后可在此查询用户与项目绑定关系。
      */

File diff suppressed because it is too large
+ 1105 - 94
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsReportServiceImpl.java


+ 20 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsAveragePointVO.java

@@ -0,0 +1,20 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+/**
+ * 能耗分析-单时间点数据
+ */
+@Data
+public class EmsAveragePointVO {
+
+    /**
+     * 用量值,无数据时为 "-"
+     */
+    private String value;
+
+    /**
+     * 时间点,与 timeData 一致(如 2026年6月3日0时)
+     */
+    private String time;
+}

+ 48 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsAverageRequest.java

@@ -0,0 +1,48 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 能耗分析-区域时序数据请求
+ */
+@Data
+public class EmsAverageRequest {
+
+    /**
+     * 开始时间,格式:yyyy-MM-dd HH:mm:ss
+     */
+    private String startTime;
+
+    /**
+     * 结束时间,格式:yyyy-MM-dd HH:mm:ss
+     */
+    private String endTime;
+
+    /**
+     * 时间粒度:hour、day、month、year
+     */
+    private String dateType;
+
+    /**
+     * 区域列表
+     */
+    private List<SpaceItem> spaces;
+
+    /**
+     * 查询类型:0-总量/KWh,1-单位面积用量(kWh/㎡)
+     */
+    private Integer type;
+
+    /**
+     * 能耗分项编码
+     */
+    private String code;
+
+    @Data
+    public static class SpaceItem {
+        private Long id;
+        private String name;
+    }
+}

+ 23 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsAverageResponseVO.java

@@ -0,0 +1,23 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 能耗分析-区域时序数据响应
+ */
+@Data
+public class EmsAverageResponseVO {
+
+    /**
+     * 时间轴展示标签
+     */
+    private List<String> timeData = new ArrayList<>();
+
+    /**
+     * 各区域时序数据
+     */
+    private List<EmsAverageSpaceItemVO> dataList = new ArrayList<>();
+}

+ 17 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsAverageSpaceItemVO.java

@@ -0,0 +1,17 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 能耗分析-区域时序数据
+ */
+@Data
+public class EmsAverageSpaceItemVO {
+
+    private Long spaceId;
+    private String name;
+    private List<EmsAveragePointVO> list = new ArrayList<>();
+}

+ 4 - 1
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsCompareResponse.java

@@ -9,6 +9,9 @@ import java.util.List;
 public class EmsCompareResponse {
 
     private String energyTypeName;
-    private String dimension;
+    /** 查询粒度:D/M/Y */
+    private String timeDimension;
+    /** 时间轴标签,如日 2026-06-01…2026-06-25 */
+    private List<String> dimension = new ArrayList<>();
     private List<EmsCompareSeriesItemVO> series = new ArrayList<>();
 }

+ 22 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsDeviceItemCodeSaveRequest.java

@@ -0,0 +1,22 @@
+package com.usky.ems.service.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 设备关联能源分项保存请求(ems_device_item_code)
+ */
+@Data
+public class EmsDeviceItemCodeSaveRequest {
+
+    @ApiModelProperty(value = "选中的设备UUID集合", required = true)
+    private List<String> deviceUuidList;
+
+    @ApiModelProperty(value = "能耗类型", required = true)
+    private Integer energyType;
+
+    @ApiModelProperty(value = "能耗分项编码", required = true)
+    private String itemCode;
+}

+ 19 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsProductEnergyTypeSaveRequest.java

@@ -0,0 +1,19 @@
+package com.usky.ems.service.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 产品关联能源类型保存请求(ems_product_energy_type)
+ */
+@Data
+public class EmsProductEnergyTypeSaveRequest {
+
+    @ApiModelProperty(value = "选中的产品ID集合", required = true)
+    private List<Long> productIdList;
+
+    @ApiModelProperty(value = "能耗类型", required = true)
+    private Integer energyType;
+}

+ 2 - 2
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EnergyReportRequest.java

@@ -58,9 +58,9 @@ public class EnergyReportRequest {
         private String name;
 
         /**
-         * 设备ID
+         * 设备主键ID
          */
-        private String id;
+        private Integer id;
     }
 
     @Data

+ 4 - 2
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EnergyReportResponse.java

@@ -16,9 +16,11 @@ public class EnergyReportResponse {
     private List<ColumnItem> columnList;
 
     /**
-     * 数据值列表
+     * 数据值列表(按设备分组的二维数组)
+     * 外层 List 代表不同的设备
+     * 内层 List 包含该设备下所有请求的功能点(属性)数据
      */
-    private List<ValueItem> valueList;
+    private List<List<ValueItem>> valueList;
 
     @Data
     public static class ColumnItem {

+ 1 - 1
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/ItemReportRequest.java

@@ -33,7 +33,7 @@ public class ItemReportRequest {
     /**
      * 日期类型:day-按日,month-按月,year-按年
      */
-    private String dateType;
+    private String timeType;
 
     @Data
     public static class ItemCodeItem {

+ 1 - 1
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/SpaceReportRequest.java

@@ -33,7 +33,7 @@ public class SpaceReportRequest {
     /**
      * 日期类型:month-按月,year-按年
      */
-    private String dateType;
+    private String timeType;
 
     @Data
     public static class SpaceItem {

+ 4 - 2
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/SpaceReportResponse.java

@@ -17,9 +17,11 @@ public class SpaceReportResponse {
     private List<EnergyReportResponse.ColumnItem> columnList;
 
     /**
-     * 数据值列表
+     * 数据值列表(按区域/空间分组的二维数组)
+     * 外层 List 代表不同的区域
+     * 内层 List 包含该区域下所有设备的详细数据记录
      */
-    private List<Map<String, Object>> valueList;
+    private List<List<Map<String, Object>>> valueList;
 
     /**
      * 合计值

+ 3 - 3
service-iot/service-iot-biz/src/main/java/com/usky/iot/controller/web/DmpDeviceInfoController.java

@@ -138,13 +138,13 @@ public class DmpDeviceInfoController {
     }
 
     /**
-     * 网关配置导出
+     * 网关配置查询
      * @param gatewayDeviceInfoRequestVO
      * @return
      */
     @PostMapping("/gatewayExport")
-    public void gatewayExport(@RequestBody GatewayDeviceInfoRequestVO gatewayDeviceInfoRequestVO) {
-        dmpDeviceInfoService.gatewayExport(gatewayDeviceInfoRequestVO);
+    public ApiResult<Map<String, Map<String, GatewayDeviceExportItemVO>>> gatewayExport(@RequestBody GatewayDeviceInfoRequestVO gatewayDeviceInfoRequestVO) {
+        return ApiResult.success(dmpDeviceInfoService.gatewayExport(gatewayDeviceInfoRequestVO));
     }
 
     /**

+ 5 - 0
service-iot/service-iot-biz/src/main/java/com/usky/iot/domain/DmpDeviceInfo.java

@@ -157,6 +157,11 @@ public class DmpDeviceInfo implements Serializable {
      */
     private Integer buildId;
 
+    /**
+     * 地址码
+     */
+    private String addressCode;
+
     /**
      * 设备状态;1:在线,2:离线
      */

+ 2 - 0
service-iot/service-iot-biz/src/main/java/com/usky/iot/mapper/DmpProductInfoMapper.java

@@ -21,4 +21,6 @@ public interface DmpProductInfoMapper extends CrudMapper<DmpProductInfo> {
 
 
     List<Map<String,Object>> selectCollect(@Param("tenantId") Integer tenantId);
+
+    List<Map<String, Object>> selectDeviceTypeByProductIds(@Param("productIds") List<Integer> productIds);
 }

+ 2 - 2
service-iot/service-iot-biz/src/main/java/com/usky/iot/service/DmpDeviceInfoService.java

@@ -69,10 +69,10 @@ public interface DmpDeviceInfoService extends CrudService<DmpDeviceInfo> {
     void export(DmpDeviceInfoRequest dmpDeviceInfoRequest, HttpServletResponse response);
 
     /**
-     * 网关设备信息导出
+     * 网关设备配置查询
      * @param gatewayDeviceInfoRequestVO
      */
-    void gatewayExport(GatewayDeviceInfoRequestVO gatewayDeviceInfoRequestVO);
+    Map<String, Map<String, GatewayDeviceExportItemVO>> gatewayExport(GatewayDeviceInfoRequestVO gatewayDeviceInfoRequestVO);
 
     CommonPage<Object> deviceCurrentDataList(String deviceName,String installAddress,String productCode,Integer pageNum,Integer pageSize);
 

+ 94 - 47
service-iot/service-iot-biz/src/main/java/com/usky/iot/service/impl/DmpDeviceInfoServiceImpl.java

@@ -8,7 +8,6 @@ import cn.afterturn.easypoi.excel.entity.ImportParams;
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.date.DateTime;
 import cn.hutool.core.date.DateUtil;
-import cn.hutool.core.io.FileUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@@ -38,6 +37,7 @@ import com.usky.iot.domain.*;
 import com.usky.common.security.utils.SecurityUtils;
 import com.usky.iot.mapper.DmpDeviceInfoMapper;
 import com.usky.iot.mapper.DmpProductAttributeMapper;
+import com.usky.iot.mapper.DmpProductInfoMapper;
 import com.usky.iot.service.*;
 import com.usky.iot.service.config.DeviceOperate;
 import com.usky.iot.service.enums.TopicInfo;
@@ -53,10 +53,7 @@ import org.springframework.web.multipart.MultipartFile;
 
 import javax.servlet.ServletOutputStream;
 import javax.servlet.http.HttpServletResponse;
-import java.io.File;
 import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
 import java.text.DecimalFormat;
 import java.time.Instant;
 import java.time.LocalDateTime;
@@ -106,6 +103,12 @@ public class DmpDeviceInfoServiceImpl extends AbstractCrudService<DmpDeviceInfoM
     @Autowired
     private DeviceOperate deviceOperate;
 
+    @Autowired
+    private DmpProductInfoMapper dmpProductInfoMapper;
+
+    @Autowired
+    private DmpDeviceTypeService dmpDeviceTypeService;
+
     private static final String ALARM_HTTP_URL = "/service-alarm/baseAlarm/alarmInfo";
 
 
@@ -654,6 +657,9 @@ public class DmpDeviceInfoServiceImpl extends AbstractCrudService<DmpDeviceInfoM
             spaceGatewayLambdaQueryWrapper.eq(BaseSpaceGateway::getSpaceId, diRequest.getSpaceId());
             gatewayUuidList = baseSpaceGatewayService.list(spaceGatewayLambdaQueryWrapper).stream().map(BaseSpaceGateway::getGatewayUuid).collect(Collectors.toList());
         }
+        if(StringUtils.isNotBlank(diRequest.getGatewayUuid())){
+            gatewayUuidList.add(diRequest.getGatewayUuid());
+        }
 
         if (diRequest.getDeviceStatus() != null) {
             if (StringUtils.isNotBlank(diRequest.getDeviceId()) || StringUtils.isNotBlank(diRequest.getDeviceUuid()) || StringUtils.isNotBlank(diRequest.getDeviceName()) || diRequest.getProductId() != null || StringUtils.isNotBlank(diRequest.getProductCode()) || diRequest.getServiceStatus() != null || diRequest.getCategoryType() != null) {
@@ -860,6 +866,9 @@ public class DmpDeviceInfoServiceImpl extends AbstractCrudService<DmpDeviceInfoM
             spaceGatewayLambdaQueryWrapper.eq(BaseSpaceGateway::getSpaceId, diRequest.getSpaceId());
             gatewayUuidList = baseSpaceGatewayService.list(spaceGatewayLambdaQueryWrapper).stream().map(BaseSpaceGateway::getGatewayUuid).collect(Collectors.toList());
         }
+        if(StringUtils.isNotBlank(diRequest.getGatewayUuid())){
+            gatewayUuidList.add(diRequest.getGatewayUuid());
+        }
 
         if (diRequest.getDeviceStatus() != null) {
             if (StringUtils.isNotBlank(diRequest.getDeviceId()) || StringUtils.isNotBlank(diRequest.getDeviceName()) || diRequest.getProductId() != null || StringUtils.isNotBlank(diRequest.getProductCode()) || diRequest.getServiceStatus() != null || diRequest.getCategoryType() != null) {
@@ -1180,58 +1189,96 @@ public class DmpDeviceInfoServiceImpl extends AbstractCrudService<DmpDeviceInfoM
     }
 
     @Override
-    public void gatewayExport(GatewayDeviceInfoRequestVO gatewayDeviceInfoRequestVO){
-        try {
-            // 1. 查询数据库中的设备信息
-            LambdaQueryWrapper<DmpDeviceInfo> queryWrapper = Wrappers.lambdaQuery();
-            queryWrapper.eq(DmpDeviceInfo::getCategoryType,3)
-                    .eq(DmpDeviceInfo::getDeleteFlag, 0)
-                    .eq(StringUtils.isNotBlank(gatewayDeviceInfoRequestVO.getGatewayUuid()), DmpDeviceInfo::getGatewayUuid, gatewayDeviceInfoRequestVO.getGatewayUuid())
-                    .eq(StringUtils.isNotBlank(gatewayDeviceInfoRequestVO.getProductCode()), DmpDeviceInfo::getProductCode, gatewayDeviceInfoRequestVO.getProductCode())
-                    .eq(DmpDeviceInfo::getTenantId, SecurityUtils.getTenantId())
-                    .orderByDesc(DmpDeviceInfo::getId);
-            List<DmpDeviceInfo> gatewayDeviceList = this.list(queryWrapper);
-
-            // 2. 定义服务器本地保存路径(建议配置在application.yml中,这里简化写死)
-            // 注意:要确保该目录存在,否则会报错;Windows路径用\\,Linux/Mac用/
-            String localPath = gatewayDeviceInfoRequestVO.getFilePath()+ "/" + gatewayDeviceInfoRequestVO.getProductCode() + ".cfg";
-
-            // 3. 调用exportToLocalFile导出到本地
-            File file = FileUtil.file(localPath);
-            exportDeviceToTxt(gatewayDeviceList, FileUtil.getOutputStream(file));
-        } catch (Exception e) {
-            throw new RuntimeException("导出网关设备配置信息失败", e);
+    public Map<String, Map<String, GatewayDeviceExportItemVO>> gatewayExport(GatewayDeviceInfoRequestVO gatewayDeviceInfoRequestVO) {
+        if (StringUtils.isBlank(gatewayDeviceInfoRequestVO.getGatewayUuid())) {
+            throw new BusinessException("网关UUID不能为空");
         }
+        LambdaQueryWrapper<DmpDeviceInfo> queryWrapper = Wrappers.lambdaQuery();
+        queryWrapper.eq(DmpDeviceInfo::getCategoryType, 3)
+                .eq(DmpDeviceInfo::getDeleteFlag, 0)
+                .eq(DmpDeviceInfo::getGatewayUuid, gatewayDeviceInfoRequestVO.getGatewayUuid())
+                .eq(DmpDeviceInfo::getTenantId, SecurityUtils.getTenantId())
+                .orderByDesc(DmpDeviceInfo::getId);
+        List<DmpDeviceInfo> gatewayDeviceList = this.list(queryWrapper);
+        if (CollectionUtil.isEmpty(gatewayDeviceList)) {
+            throw new BusinessException("未查询到网关子设备信息");
+        }
+        return buildGatewayDeviceExportMap(gatewayDeviceList);
     }
 
     /**
-     * 导出设备信息到TXT文件
-     * @param deviceList 设备列表
-     * @param outputStream 输出流(用于下载)
+     * 按设备类型简称 -> 地址码 构建网关子设备配置
      */
-    public static void exportDeviceToTxt(List<DmpDeviceInfo> deviceList, OutputStream outputStream) {
-        // 1. 构建TXT内容(自定义格式,按行写入)
-        StringBuilder content = new StringBuilder();
-        // 写入表头
-        content.append("{");
-        // 写入数据行
+    private Map<String, Map<String, GatewayDeviceExportItemVO>> buildGatewayDeviceExportMap(List<DmpDeviceInfo> deviceList) {
+        List<Integer> productIds = deviceList.stream()
+                .map(DmpDeviceInfo::getProductId)
+                .filter(Objects::nonNull)
+                .distinct()
+                .collect(Collectors.toList());
+        Map<Integer, String> productTypeAbbreviaMap = getProductTypeAbbreviaMap(productIds);
+
+        Map<String, Map<String, GatewayDeviceExportItemVO>> grouped = new LinkedHashMap<>();
         for (DmpDeviceInfo device : deviceList) {
-            content.append("\n\t\"").append(device.getImsiCode()).append("\":")
-                    .append("{").append("\"").append("deviceid").append("\"").append(":").append("\"").append(device.getDeviceId()).append("\"").append(",")
-                    .append("\"").append("deviceUuid").append("\"").append(":").append("\"").append(device.getDeviceUuid()).append("\"").append(",")
-                    .append("\"").append("productCode").append("\"").append(":").append("\"").append(device.getProductCode()).append("\"")
-                    .append("}").append(",");
+            if (StringUtils.isBlank(device.getAddressCode())) {
+                continue;
+            }
+            String typeAbbrevia = productTypeAbbreviaMap.get(device.getProductId());
+            if (StringUtils.isBlank(typeAbbrevia)) {
+                throw new BusinessException("产品ID " + device.getProductId() + " 未找到对应设备类型简称");
+            }
+            GatewayDeviceExportItemVO deviceInfo = new GatewayDeviceExportItemVO();
+            deviceInfo.setDeviceid(device.getDeviceId());
+            deviceInfo.setDeviceUuid(device.getDeviceUuid());
+            deviceInfo.setProductCode(device.getProductCode());
+            grouped.computeIfAbsent(typeAbbrevia, key -> new LinkedHashMap<>())
+                    .put(device.getAddressCode(), deviceInfo);
         }
-        content.delete(content.length()-1, content.length());
-        content.append("\n}");
+        if (grouped.isEmpty()) {
+            throw new BusinessException("网关子设备地址码为空,无法导出配置");
+        }
+        return grouped;
+    }
 
-        // 2. 将内容写入输出流(UTF-8编码避免乱码)
-        try {
-            outputStream.write(content.toString().getBytes(StandardCharsets.UTF_8));
-            outputStream.flush();
-        } catch (Exception e) {
-            throw new RuntimeException("导出TXT文件失败", e);
+    /**
+     * 根据产品ID批量查询设备类型简称
+     */
+    private Map<Integer, String> getProductTypeAbbreviaMap(List<Integer> productIds) {
+        if (CollectionUtil.isEmpty(productIds)) {
+            return Collections.emptyMap();
         }
+        List<Map<String, Object>> products = dmpProductInfoMapper.selectDeviceTypeByProductIds(productIds);
+        if (CollectionUtil.isEmpty(products)) {
+            return Collections.emptyMap();
+        }
+
+        Map<Integer, Integer> productDeviceTypeMap = products.stream()
+                .collect(Collectors.toMap(
+                        map -> Integer.valueOf(map.get("id").toString()),
+                        map -> Integer.valueOf(map.get("deviceType").toString()),
+                        (a, b) -> a));
+
+        Set<String> typeCodes = productDeviceTypeMap.values().stream()
+                .filter(Objects::nonNull)
+                .map(String::valueOf)
+                .collect(Collectors.toSet());
+        if (CollectionUtil.isEmpty(typeCodes)) {
+            return Collections.emptyMap();
+        }
+
+        LambdaQueryWrapper<DmpDeviceType> typeQuery = Wrappers.lambdaQuery();
+        typeQuery.select(DmpDeviceType::getTypeCode, DmpDeviceType::getTypeAbbrevia)
+                .in(DmpDeviceType::getTypeCode, typeCodes);
+        Map<String, String> typeCodeAbbreviaMap = dmpDeviceTypeService.list(typeQuery).stream()
+                .collect(Collectors.toMap(DmpDeviceType::getTypeCode, DmpDeviceType::getTypeAbbrevia, (a, b) -> a));
+
+        Map<Integer, String> result = new HashMap<>();
+        productDeviceTypeMap.forEach((productId, deviceType) -> {
+            String typeAbbrevia = typeCodeAbbreviaMap.get(String.valueOf(deviceType));
+            if (StringUtils.isNotBlank(typeAbbrevia)) {
+                result.put(productId, typeAbbrevia);
+            }
+        });
+        return result;
     }
 
     public void exportExcel(Workbook workbook, HttpServletResponse response) {

+ 5 - 0
service-iot/service-iot-biz/src/main/java/com/usky/iot/service/vo/DmpDeviceInfoRequest.java

@@ -66,4 +66,9 @@ public class DmpDeviceInfoRequest {
      */
     private Integer spaceId;
 
+    /**
+     * 所属网关
+     */
+    private String gatewayUuid;
+
 }

+ 13 - 0
service-iot/service-iot-biz/src/main/java/com/usky/iot/service/vo/GatewayDeviceExportItemVO.java

@@ -0,0 +1,13 @@
+package com.usky.iot.service.vo;
+
+import lombok.Data;
+
+@Data
+public class GatewayDeviceExportItemVO {
+
+    private String deviceid;
+
+    private String deviceUuid;
+
+    private String productCode;
+}

+ 0 - 10
service-iot/service-iot-biz/src/main/java/com/usky/iot/service/vo/GatewayDeviceInfoRequestVO.java

@@ -5,18 +5,8 @@ import lombok.Data;
 @Data
 public class GatewayDeviceInfoRequestVO {
 
-    /**
-     * 产品编码
-     */
-    private String productCode;
-
     /**
      * 网关设备Uuid
      */
     private String gatewayUuid;
-
-    /**
-     * 文件路径
-     */
-    private String filePath;
 }

+ 10 - 0
service-iot/service-iot-biz/src/main/resources/mapper/iot/DmpProductInfoMapper.xml

@@ -43,4 +43,14 @@
         group by product_name
     </select>
 
+    <select id="selectDeviceTypeByProductIds" resultType="java.util.Map">
+        select id, device_type as deviceType
+        from dmp_product
+        where delete_flag = 0
+        and id in
+        <foreach collection="productIds" item="productId" open="(" separator="," close=")">
+            #{productId}
+        </foreach>
+    </select>
+
 </mapper>

+ 22 - 6
service-tsdb/service-tsdb-biz/src/main/java/com/usky/demo/service/impl/QueryTdengineDataServiceImpl.java

@@ -63,10 +63,10 @@ public class QueryTdengineDataServiceImpl extends AbstractCrudService<QueryTdeng
 
         // 遍历设备表集合,自动忽略表中不存在的字段,用有效字段构建查询,避免错误
         for (int i = 0; i < deviceUUIds.size(); i++) {
-            try (Connection connection = dataSource.getConnection()) {
 
-                String tablename = "_"+deviceUUIds.get(i);
+            String tablename = "_"+deviceUUIds.get(i);
 
+            try (Connection connection = dataSource.getConnection()) {
                 String sql = "describe " + tablename;
 
                 try (PreparedStatement itemStmt = connection.prepareStatement(sql)) {
@@ -123,6 +123,15 @@ public class QueryTdengineDataServiceImpl extends AbstractCrudService<QueryTdeng
                     }
                 }
 
+            } catch (SQLException e) {
+                // 判断TDengine专属错误码 0x2603 = 表不存在
+                if (e.getMessage().contains("0x2603") || e.getMessage().contains("Table does not exist")) {
+                    System.out.println("表" + tablename + "不存在,跳过");
+                    log.error("表" + tablename + "不存在,跳过");
+                } else {
+                    // 其他异常正常打印堆栈
+                    e.printStackTrace();
+                }
             } catch (Exception e) {
                 e.printStackTrace();
             }
@@ -141,10 +150,10 @@ public class QueryTdengineDataServiceImpl extends AbstractCrudService<QueryTdeng
 
         // 遍历设备表集合,自动忽略表中不存在的字段,用有效字段构建查询,避免错误
         for (int i = 0; i < deviceUUIds.size(); i++) {
-            try (Connection connection = dataSource.getConnection()) {
 
-                String tablename = "_"+deviceUUIds.get(i);
+            String tablename = "_"+deviceUUIds.get(i);
 
+            try (Connection connection = dataSource.getConnection()) {
                 String sql = "describe " + tablename;
 
                 try (PreparedStatement itemStmt = connection.prepareStatement(sql)) {
@@ -228,6 +237,15 @@ public class QueryTdengineDataServiceImpl extends AbstractCrudService<QueryTdeng
                     }
                 }
 
+            } catch (SQLException e) {
+                // 判断TDengine专属错误码 0x2603 = 表不存在
+                if (e.getMessage().contains("0x2603") || e.getMessage().contains("Table does not exist")) {
+                    System.out.println("表" + tablename + "不存在,跳过");
+                    log.error("表" + tablename + "不存在,跳过");
+                } else {
+                    // 其他异常正常打印堆栈
+                    e.printStackTrace();
+                }
             } catch (Exception e) {
                 e.printStackTrace();
             }
@@ -352,7 +370,6 @@ public class QueryTdengineDataServiceImpl extends AbstractCrudService<QueryTdeng
             }
         } catch (SQLException e) {
             log.error("能耗分项汇总查询失败, sql=" + sql + ", " + e.getMessage());
-            throw new BusinessException("能耗分项汇总查询失败: " + e.getMessage());
         }
         return result;
     }
@@ -405,7 +422,6 @@ public class QueryTdengineDataServiceImpl extends AbstractCrudService<QueryTdeng
             }
         } catch (SQLException e) {
             log.error("能耗分项趋势查询失败, sql=" + sql + ", " + e.getMessage());
-            throw new BusinessException("能耗分项趋势查询失败: " + e.getMessage());
         }
 
         result.setPoints(points);

Some files were not shown because too many files changed in this diff