Explorar el Código

Merge branch 'usky-ems' of uskycloud/usky-modules into master

hanzhengyi hace 23 horas
padre
commit
88266319e9
Se han modificado 37 ficheros con 1665 adiciones y 351 borrados
  1. 1 1
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsModelController.java
  2. 33 35
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsOverviewController.java
  3. 25 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/DmpDevice.java
  4. 38 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/DmpDeviceStatus.java
  5. 33 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsDeviceItemCode.java
  6. 30 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsProductEnergyType.java
  7. 48 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsProjectConversionFactor.java
  8. 12 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/DmpDeviceStatusMapper.java
  9. 9 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/DmpProductMapper.java
  10. 19 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsDeviceItemCodeMapper.java
  11. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsProductEnergyTypeMapper.java
  12. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsProjectConversionFactorMapper.java
  13. 1 1
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsModelService.java
  14. 8 9
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsOverviewService.java
  15. 106 34
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsModelServiceImpl.java
  16. 728 267
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsOverviewServiceImpl.java
  17. 18 0
      service-ems/service-ems-biz/src/main/resources/mapper/ems/DmpProductMapper.xml
  18. 16 0
      service-ems/service-ems-biz/src/main/resources/mapper/ems/EmsDeviceItemCodeMapper.xml
  19. 21 0
      service-iot/service-iot-biz/src/main/java/com/usky/iot/controller/web/BaseSpaceGatewayController.java
  20. 51 0
      service-iot/service-iot-biz/src/main/java/com/usky/iot/domain/BaseSpaceGateway.java
  21. 16 0
      service-iot/service-iot-biz/src/main/java/com/usky/iot/mapper/BaseSpaceGatewayMapper.java
  22. 16 0
      service-iot/service-iot-biz/src/main/java/com/usky/iot/service/BaseSpaceGatewayService.java
  23. 20 0
      service-iot/service-iot-biz/src/main/java/com/usky/iot/service/impl/BaseSpaceGatewayServiceImpl.java
  24. 22 4
      service-iot/service-iot-biz/src/main/java/com/usky/iot/service/impl/DmpDeviceInfoServiceImpl.java
  25. 5 0
      service-iot/service-iot-biz/src/main/java/com/usky/iot/service/vo/DmpDeviceInfoRequest.java
  26. 14 0
      service-iot/service-iot-biz/src/main/resources/mapper/iot/BaseSpaceGatewayMapper.xml
  27. 12 0
      service-tsdb/service-tsdb-api/src/main/java/com/usky/demo/RemoteTsdbProxyService.java
  28. 34 0
      service-tsdb/service-tsdb-api/src/main/java/com/usky/demo/domain/EnergyItemSumQueryVO.java
  29. 22 0
      service-tsdb/service-tsdb-api/src/main/java/com/usky/demo/domain/EnergyItemSumResultVO.java
  30. 23 0
      service-tsdb/service-tsdb-api/src/main/java/com/usky/demo/domain/EnergyItemTrendPointVO.java
  31. 33 0
      service-tsdb/service-tsdb-api/src/main/java/com/usky/demo/domain/EnergyItemTrendQueryVO.java
  32. 20 0
      service-tsdb/service-tsdb-api/src/main/java/com/usky/demo/domain/EnergyItemTrendResultVO.java
  33. 12 0
      service-tsdb/service-tsdb-api/src/main/java/com/usky/demo/factory/RemoteTsdbProxyFallbackFactory.java
  34. 19 0
      service-tsdb/service-tsdb-biz/src/main/java/com/usky/demo/controller/api/DataTsdbProxyControllerApi.java
  35. 34 0
      service-tsdb/service-tsdb-biz/src/main/java/com/usky/demo/controller/web/QueryInfluxdbDataController.java
  36. 10 0
      service-tsdb/service-tsdb-biz/src/main/java/com/usky/demo/service/QueryTdengineDataService.java
  37. 136 0
      service-tsdb/service-tsdb-biz/src/main/java/com/usky/demo/service/impl/QueryTdengineDataServiceImpl.java

+ 1 - 1
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsModelController.java

@@ -32,7 +32,7 @@ public class EmsModelController {
 //    }
 
     /**
-     * 能源类型列表(电、水、气
+     * 能源类型列表(与概览 /overview/item 同源:租户产品关联的能源类型
      */
     @GetMapping("/energy-type/list")
     public ApiResult<List<EmsEnergyTypeVO>> getEnergyTypeList() {

+ 33 - 35
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsOverviewController.java

@@ -1,6 +1,7 @@
 package com.usky.ems.controller.web;
 
 import com.usky.common.core.bean.ApiResult;
+import com.usky.common.security.utils.SecurityUtils;
 import com.usky.ems.service.EmsOverviewService;
 import com.usky.ems.service.vo.EmsProjectResponse;
 import com.usky.ems.service.vo.EmsSummaryRequest;
@@ -10,6 +11,9 @@ import com.usky.ems.service.vo.EmsOverviewEnergyItemVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
+import java.util.List;
+import java.util.Map;
+
 /**
  * 能源能耗 - 概览模块 API
  * 建筑排名等涉及空间树的数据来自 base_space(见 {@link com.usky.ems.service.impl.EmsOverviewServiceImpl})。
@@ -26,7 +30,8 @@ public class EmsOverviewController {
      * 获取项目信息
      */
     @GetMapping("/project")
-    public ApiResult<EmsProjectResponse> getProject(@RequestParam(required = false) Integer projectId) {
+    public ApiResult<EmsProjectResponse> getProject(
+            @RequestParam(required = false) Integer projectId) {
         return ApiResult.success(emsOverviewService.getProject(projectId));
     }
 
@@ -43,65 +48,60 @@ public class EmsOverviewController {
      * 获取概览页能源类型条目(如电/水/气)
      */
     @GetMapping("/item")
-    public ApiResult<java.util.List<EmsOverviewEnergyItemVO>> queryOverviewItem(
-            @RequestParam(required = false) Long projectId) {
-        return ApiResult.success(emsOverviewService.queryOverviewItem(projectId));
+    public ApiResult<List<EmsOverviewEnergyItemVO>> queryOverviewItem() {
+        return ApiResult.success(emsOverviewService.queryOverviewItem());
     }
 
     /**
      * 获取概览页设备系统统计(每个系统下设备数量)
      */
     @GetMapping("/device-info")
-    public ApiResult<java.util.List<EmsOverviewDeviceSystemStatVO>> queryOverviewDeviceInfo(
+    public ApiResult<List<EmsOverviewDeviceSystemStatVO>> queryOverviewDeviceInfo(
             @RequestParam(required = false) Long projectId) {
         return ApiResult.success(emsOverviewService.queryOverviewDeviceInfo(projectId));
     }
 
     /**
-     * 分类能耗统计(模拟数据
+     * 分类能耗统计(按能源类型关联产品,调用 TSDB 分项汇总
      * 参数说明:
-     * - dateType:时间类型(1-日,2-月,3-年等,暂仅作为占位,不影响当前模拟结果)
-     * - itemCode:能耗条目编码
-     * - energyType:能耗类型
-     * - projectId:项目 ID(可选,当前实现未做真实查询,仅占位)
+     * - dateType:时间类型(1-日,2-月,3-年)
+     * - energyType:能源类型(1电 2水 3冷 4热;分项 identifier 从 ems_energy_item_code 根节点解析)
+     * - projectId:项目 ID(可选,不传则取当前租户第一个项目;用于查询折算系数)
      */
     @GetMapping("/classification-energy")
-    public ApiResult<java.util.Map<String, Object>> queryClassificationEnergy(
+    public ApiResult<Map<String, Object>> queryClassificationEnergy(
             @RequestParam Integer dateType,
-            @RequestParam String itemCode,
             @RequestParam Integer energyType,
             @RequestParam(required = false) Long projectId) {
-        return ApiResult.success(emsOverviewService.queryClassificationEnergy(dateType, itemCode, energyType, projectId));
+        return ApiResult.success(emsOverviewService.queryClassificationEnergy(dateType, energyType, projectId));
     }
 
     /**
-     * 能耗用能趋势(模拟数据
+     * 能耗用能趋势(按能源类型关联产品,调用 TSDB 分项按时间粒度汇总,并与去年同期对比
      * 参数说明:
-     * - dateType:时间类型(1-日,2-月,3-年)
-     * - itemCode:能耗条目编码
-     * - spaceId:空间ID(可选,当前仍为模拟数据占位
+     * - dateType:时间类型(1-日/小时,2-月/天,3-年/月
+     * - identifier:分项字段编码
+     * - energyType:能源类型(1电 2水 3冷 4热
      */
     @GetMapping("/energy-trend")
-    public ApiResult<java.util.List<java.util.Map<String, Object>>> queryEnergyTrend(
+    public ApiResult<List<Map<String, Object>>> queryEnergyTrend(
             @RequestParam Integer dateType,
-            @RequestParam String itemCode,
-            @RequestParam(required = false) Long spaceId) {
-        return ApiResult.success(emsOverviewService.queryEnergyTrend(dateType, itemCode, spaceId));
+            @RequestParam Integer energyType) {
+        return ApiResult.success(emsOverviewService.queryEnergyTrend(dateType, energyType));
     }
 
     /**
-     * 建筑能耗分析(模拟数据
+     * 建筑能耗分析(按建筑排名
      * 参数说明:
      * - dateType:时间类型(1-日,2-月,3-年)
-     * - itemCode:能耗条目编码
-     * - spaceId:空间ID(可选;下钻时与 base_space 子节点一致
+     * - itemCode:能耗分项字段编码
+     * - energyType:能源类型(1电 2水 3冷 4热
      */
     @GetMapping("/building-ranking")
-    public ApiResult<java.util.List<java.util.Map<String, Object>>> queryBuildingRanking(
+    public ApiResult<List<Map<String, Object>>> queryBuildingRanking(
             @RequestParam Integer dateType,
-            @RequestParam String itemCode,
-            @RequestParam(required = false) Long spaceId) {
-        return ApiResult.success(emsOverviewService.queryBuildingRanking(dateType, itemCode, spaceId));
+            @RequestParam Integer energyType) {
+        return ApiResult.success(emsOverviewService.queryBuildingRanking(dateType, energyType));
     }
 
     /**
@@ -111,7 +111,7 @@ public class EmsOverviewController {
      * - projectId:项目ID(可选,不传则取第一个项目)
      */
     @GetMapping("/top")
-    public ApiResult<java.util.Map<String, Object>> queryOverviewTop(
+    public ApiResult<Map<String, Object>> queryOverviewTop(
             @RequestParam Integer dateType,
             @RequestParam(required = false) Long projectId) {
         return ApiResult.success(emsOverviewService.queryOverviewTop(dateType, projectId));
@@ -121,14 +121,12 @@ public class EmsOverviewController {
      * 分项能耗占比
      * 参数说明:
      * - dateType:时间类型(1-日,2-月,3-年)
-     * - itemCode:分项编码(作为父项,展开下级分项)
-     * - projectId:项目ID(可选,不传则取第一个项目)
+     * - energyType:能源类型(1电 2水 3冷 4热;根分项从 ems_energy_item_code 解析)
      */
     @GetMapping("/item-ratio")
-    public ApiResult<java.util.List<java.util.Map<String, Object>>> queryItemRatio(
+    public ApiResult<List<Map<String, Object>>> queryItemRatio(
             @RequestParam Integer dateType,
-            @RequestParam String itemCode,
-            @RequestParam(required = false) Long projectId) {
-        return ApiResult.success(emsOverviewService.queryItemRatio(dateType, itemCode, projectId));
+            @RequestParam Integer energyType) {
+        return ApiResult.success(emsOverviewService.queryItemRatio(dateType, energyType));
     }
 }

+ 25 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/DmpDevice.java

@@ -1,12 +1,14 @@
 package com.usky.ems.domain;
 
 import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
 import java.io.Serializable;
+import java.time.LocalDateTime;
 
 /**
  * <p>
@@ -83,4 +85,27 @@ public class DmpDevice implements Serializable {
      * 租户号
      */
     private Integer tenantId;
+
+    /**
+     * 安装位置
+     */
+    private String installAddress;
+
+    /**
+     * 设备状态;1:在线,2:离线
+     */
+    @TableField(exist = false)
+    private Integer deviceStatus;
+
+    /**
+     * 最后上线时间
+     */
+    @TableField(exist = false)
+    private LocalDateTime lastOnlineTime;
+
+    /**
+     * 最后离线时间
+     */
+    @TableField(exist = false)
+    private LocalDateTime lastOfflineTime;
 }

+ 38 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/DmpDeviceStatus.java

@@ -0,0 +1,38 @@
+package com.usky.ems.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 设备状态表(dmp_device_status)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("dmp_device_status")
+public class DmpDeviceStatus implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    private String deviceId;
+
+    private Integer productId;
+
+    private Integer deviceStatus;
+
+    private LocalDateTime lastOnlineTime;
+
+    private LocalDateTime lastOfflineTime;
+
+    private String productCode;
+
+    private String deviceUuid;
+}

+ 33 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsDeviceItemCode.java

@@ -0,0 +1,33 @@
+package com.usky.ems.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+
+/**
+ * 设备与能耗分项关联(ems_device_item_code)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("ems_device_item_code")
+public class EmsDeviceItemCode implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @TableField("device_id")
+    private String deviceId;
+
+    @TableField("item_code")
+    private String itemCode;
+
+    @TableField("project_id")
+    private Long projectId;
+}

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

@@ -0,0 +1,30 @@
+package com.usky.ems.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+
+/**
+ * 产品能源类型关联(ems_product_energy_type)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("ems_product_energy_type")
+public class EmsProductEnergyType implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @TableField("product_id")
+    private Long productId;
+
+    @TableField("energy_type")
+    private Integer energyType;
+}

+ 48 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsProjectConversionFactor.java

@@ -0,0 +1,48 @@
+package com.usky.ems.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 项目能源折算系数(ems_project_conversion_factor)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("ems_project_conversion_factor")
+public class EmsProjectConversionFactor implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @TableField("project_id")
+    private Long projectId;
+
+    @TableField("energy_type")
+    private Integer energyType;
+
+    private String name;
+
+    private BigDecimal value;
+
+    @TableField("updated_by")
+    private String updatedBy;
+
+    @TableField("update_time")
+    private LocalDateTime updateTime;
+
+    @TableField("created_by")
+    private String createdBy;
+
+    @TableField("create_time")
+    private LocalDateTime createTime;
+}

+ 12 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/DmpDeviceStatusMapper.java

@@ -0,0 +1,12 @@
+package com.usky.ems.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.usky.ems.domain.DmpDeviceStatus;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 设备状态表 Mapper
+ */
+@Mapper
+public interface DmpDeviceStatusMapper extends BaseMapper<DmpDeviceStatus> {
+}

+ 9 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/DmpProductMapper.java

@@ -2,10 +2,19 @@ package com.usky.ems.mapper;
 
 import com.usky.common.mybatis.core.CrudMapper;
 import com.usky.ems.domain.DmpProduct;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
 
 /**
  * 产品信息表 Mapper(dmp_product)
  */
 public interface DmpProductMapper extends CrudMapper<DmpProduct> {
+
+    /**
+     * 按租户与能源类型查询关联产品编码
+     */
+    List<String> selectProductCodesByEnergyType(@Param("tenantId") Integer tenantId,
+                                                @Param("energyType") Integer energyType);
 }
 

+ 19 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsDeviceItemCodeMapper.java

@@ -0,0 +1,19 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.EmsDeviceItemCode;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 设备能耗分项关联 Mapper(ems_device_item_code)
+ */
+public interface EmsDeviceItemCodeMapper extends CrudMapper<EmsDeviceItemCode> {
+
+    /**
+     * 按分项编码解析 TSDB 设备 UUID(ems_device_item_code → ems_device → dmp_device)
+     */
+    List<String> selectDeviceUuidsByItemCode(@Param("tenantId") Integer tenantId,
+                                             @Param("itemCode") String itemCode);
+}

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

@@ -0,0 +1,10 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.EmsProductEnergyType;
+
+/**
+ * 产品能源类型关联 Mapper(ems_product_energy_type)
+ */
+public interface EmsProductEnergyTypeMapper extends CrudMapper<EmsProductEnergyType> {
+}

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

@@ -0,0 +1,10 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.EmsProjectConversionFactor;
+
+/**
+ * 项目能源折算系数 Mapper(ems_project_conversion_factor)
+ */
+public interface EmsProjectConversionFactorMapper extends CrudMapper<EmsProjectConversionFactor> {
+}

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

@@ -21,7 +21,7 @@ public interface EmsModelService {
 //    EmsStructureTreeNode getStructureTree(Long projectId, Boolean includeGateway);
 
     /**
-     * 能源类型列表(电、水、气
+     * 能源类型列表(按当前租户产品关联的 ems_product_energy_type 动态返回
      */
     List<EmsEnergyTypeVO> getEnergyTypeList();
 

+ 8 - 9
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsOverviewService.java

@@ -13,7 +13,7 @@ import java.util.Map;
 public interface EmsOverviewService {
 
     /**
-     * 获取项目信息(当前项目或指定 projectId
+     * 获取项目信息(当前租户下指定 projectId,或未传 projectId 时取该租户第一个项目
      */
     EmsProjectResponse getProject(Integer projectId);
 
@@ -25,7 +25,7 @@ public interface EmsOverviewService {
     /**
      * 获取概览页能源类型条目(如电/水/气)
      */
-    java.util.List<EmsOverviewEnergyItemVO> queryOverviewItem(Long projectId);
+    java.util.List<EmsOverviewEnergyItemVO> queryOverviewItem();
 
     /**
      * 获取概览页设备系统统计(每个系统下设备数量)
@@ -36,19 +36,19 @@ public interface EmsOverviewService {
      * 分类能耗统计(按时间维度、能耗类型等)
      * 先按 Map 结构返回,后续可以再封装 VO。
      */
-    Map<String, Object> queryClassificationEnergy(Integer dateType, String itemCode, Integer energyType, Long projectId);
+    Map<String, Object> queryClassificationEnergy(Integer dateType, Integer energyType, Long projectId);
 
     /**
      * 能耗用能趋势(按时间维度:日/月/年)
-     * 返回当前期与去年同期的用能趋势数据列表(当前为模拟数据实现)
+     * 按能源类型关联产品调用 TSDB 汇总,返回当前期与去年同期各时间粒度的用能数据。
      */
-    java.util.List<java.util.Map<String, Object>> queryEnergyTrend(Integer dateType, String itemCode, Long spaceId);
+    java.util.List<java.util.Map<String, Object>> queryEnergyTrend(Integer dateType, Integer energyType);
 
     /**
      * 建筑能耗分析(建筑/楼层能耗排名)
      * 返回按能耗从高到低排序的建筑(或楼层)名称及其能耗值列表(当前为模拟数据实现)。
      */
-    java.util.List<java.util.Map<String, Object>> queryBuildingRanking(Integer dateType, String itemCode, Long spaceId);
+    java.util.List<java.util.Map<String, Object>> queryBuildingRanking(Integer dateType, Integer energyType);
 
     /**
      * 单位综合能耗、综合累计能耗、分类能耗占比(总览页顶部)
@@ -57,8 +57,7 @@ public interface EmsOverviewService {
     Map<String, Object> queryOverviewTop(Integer dateType, Long projectId);
 
     /**
-     * 分项能耗占比(按分项编码 itemCode 展开下级条目)
-     * 部分数据(分项定义)来自 leo.ems_energy_item_code,能耗数值按规则模拟。
+     * 分项能耗占比(按能源类型展开下级分项,设备范围来自 ems_device_item_code)
      */
-    java.util.List<java.util.Map<String, Object>> queryItemRatio(Integer dateType, String itemCode, Long projectId);
+    java.util.List<java.util.Map<String, Object>> queryItemRatio(Integer dateType, Integer energyType);
 }

+ 106 - 34
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsModelServiceImpl.java

@@ -2,6 +2,7 @@ package com.usky.ems.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.usky.common.security.utils.SecurityUtils;
 import com.usky.ems.domain.*;
 import com.usky.ems.mapper.*;
@@ -12,7 +13,6 @@ import com.usky.ems.service.vo.EmsAttributePointVO;
 import com.usky.ems.service.vo.EmsEnergyTypeVO;
 import com.usky.ems.service.vo.EmsGatewayDeviceTreeNode;
 import com.usky.ems.service.vo.EmsModelSaveRequest;
-import com.usky.ems.service.vo.EmsStructureTreeNode;
 import com.usky.ems.service.vo.SimpleProductVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -27,6 +27,7 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Collectors;
 
 /**
@@ -36,6 +37,9 @@ import java.util.stream.Collectors;
 @Service
 public class EmsModelServiceImpl implements EmsModelService {
 
+    /** 热循环,建模与概览页均不展示 */
+    private static final int EXCLUDED_ENERGY_TYPE = 5;
+
     @Autowired
     private BaseSpaceMapper baseSpaceMapper;
     @Autowired
@@ -59,7 +63,15 @@ public class EmsModelServiceImpl implements EmsModelService {
     @Autowired
     private DmpProductMapper dmpProductMapper;
     @Autowired
+    private EmsProductEnergyTypeMapper emsProductEnergyTypeMapper;
+    @Autowired
+    private DmpDeviceMapper dmpDeviceMapper;
+    @Autowired
+    private DmpDeviceStatusMapper dmpDeviceStatusMapper;
+    @Autowired
     private BaseSpaceServiceImpl baseSpaceService;
+    @Autowired
+    private EmsEnergyItemCodeMapper emsEnergyItemCodeMapper;
 
     private static final int SPACE_TYPE_PROJECT = 1;
     private static final int SPACE_TYPE_REGION = 2;
@@ -153,20 +165,78 @@ public class EmsModelServiceImpl implements EmsModelService {
 
     @Override
     public List<EmsEnergyTypeVO> getEnergyTypeList() {
-        String[] names = {"", "电", "水", "气"};
-        String[] codes = {"", "electric", "water", "gas"};
-        String[] units = {"", "kWh", "m³", "m³"};
-        List<EmsEnergyTypeVO> result = new ArrayList<>();
-        for (int i = 1; i <= 3; i++) {
-            EmsEnergyTypeVO vo = new EmsEnergyTypeVO();
-            vo.setId((long) i);
-            vo.setName(names[i]);
-            vo.setCode(codes[i]);
-            vo.setUnit(units[i]);
-            vo.setSortOrder(i - 1);
-            result.add(vo);
+        List<DmpProduct> products = dmpProductMapper.selectList(
+                Wrappers.<DmpProduct>lambdaQuery()
+                        .eq(DmpProduct::getTenantId, SecurityUtils.getTenantId())
+                        .eq(DmpProduct::getDeleteFlag, 0)
+        );
+        if (products.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        List<Long> productIds = products.stream()
+                .map(DmpProduct::getId)
+                .filter(Objects::nonNull)
+                .map(Integer::longValue)
+                .collect(Collectors.toList());
+        if (productIds.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        List<EmsProductEnergyType> energyTypeList = emsProductEnergyTypeMapper.selectList(
+                Wrappers.<EmsProductEnergyType>lambdaQuery()
+                        .in(EmsProductEnergyType::getProductId, productIds)
+        );
+        if (energyTypeList.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        Map<Integer, EmsEnergyItemCode> rootItemByEnergyType = emsEnergyItemCodeMapper.selectList(
+                Wrappers.<EmsEnergyItemCode>lambdaQuery()
+                        .eq(EmsEnergyItemCode::getParentCode, "0")
+        ).stream()
+                .filter(item -> item.getEnergyType() != null)
+                .collect(Collectors.toMap(EmsEnergyItemCode::getEnergyType, item -> item, (a, b) -> a));
+
+        AtomicInteger sortOrder = new AtomicInteger(0);
+        return energyTypeList.stream()
+                .map(EmsProductEnergyType::getEnergyType)
+                .filter(Objects::nonNull)
+                .filter(type -> type != EXCLUDED_ENERGY_TYPE)
+                .distinct()
+                .sorted()
+                .map(type -> {
+                    EmsEnergyItemCode itemCode = rootItemByEnergyType.get(type);
+                    if (itemCode == null) {
+                        return null;
+                    }
+                    EmsEnergyTypeVO vo = new EmsEnergyTypeVO();
+                    vo.setId(type.longValue());
+                    vo.setName(itemCode.getName());
+                    vo.setCode(itemCode.getIdentifier());
+                    vo.setUnit(StringUtils.isNotBlank(itemCode.getUnit()) ? itemCode.getUnit() : energyTypeUnit(type));
+                    vo.setSortOrder(sortOrder.getAndIncrement());
+                    return vo;
+                })
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+    }
+
+    private static String energyTypeUnit(Integer energyType) {
+        if (energyType == null) {
+            return "";
+        }
+        switch (energyType) {
+            case 1:
+                return "kWh";
+            case 2:
+                return "m³";
+            case 3:
+            case 4:
+                return "kWh";
+            default:
+                return "";
         }
-        return result;
     }
 
     @Override
@@ -759,10 +829,10 @@ public class EmsModelServiceImpl implements EmsModelService {
             return;
         }
 
-        List<DmpGateway> gateways = dmpGatewayMapper.selectList(
-                new LambdaQueryWrapper<DmpGateway>().in(DmpGateway::getDeviceUuid, normalizedUuids));
+        List<DmpDevice> gateways = dmpDeviceMapper.selectList(
+                new LambdaQueryWrapper<DmpDevice>().in(DmpDevice::getDeviceUuid, normalizedUuids));
         Set<String> existsGatewayUuids = gateways.stream()
-                .map(DmpGateway::getDeviceUuid)
+                .map(DmpDevice::getDeviceUuid)
                 .filter(Objects::nonNull)
                 .collect(Collectors.toSet());
         if (existsGatewayUuids.isEmpty()) {
@@ -826,31 +896,33 @@ public class EmsModelServiceImpl implements EmsModelService {
             return Collections.emptyList();
         }
 
-        List<DmpGateway> gateways = dmpGatewayMapper.selectList(
-                new LambdaQueryWrapper<DmpGateway>().in(DmpGateway::getDeviceUuid, gatewayUuids));
+        List<DmpDevice> gateways = dmpDeviceMapper.selectList(
+                new LambdaQueryWrapper<DmpDevice>()
+                        .in(DmpDevice::getDeviceUuid, gatewayUuids)
+                        .eq(DmpDevice::getDeleteFlag, 0));
         if (gateways == null || gateways.isEmpty()) {
             return Collections.emptyList();
         }
 
+        List<DmpDeviceStatus> statusList = dmpDeviceStatusMapper.selectList(
+                Wrappers.<DmpDeviceStatus>lambdaQuery().in(DmpDeviceStatus::getDeviceUuid, gatewayUuids));
+        Map<String, DmpDeviceStatus> statusByUuid = statusList == null ? Collections.emptyMap()
+                : statusList.stream()
+                .filter(s -> s.getDeviceUuid() != null)
+                .collect(Collectors.toMap(DmpDeviceStatus::getDeviceUuid, s -> s, (a, b) -> a));
+
         return gateways.stream().map(item -> {
             DmpGatewayDetailResponse resp = new DmpGatewayDetailResponse();
             resp.setId(item.getId());
             resp.setDeviceUuid(item.getDeviceUuid());
-            resp.setName(item.getName());
-            resp.setIp(item.getIp());
-            resp.setPort(item.getPort());
-            resp.setCommunicationStatus(item.getCommStatus());
-            resp.setOnlineTime(item.getOnlineTime());
-            resp.setOfflineTime(item.getOfflineTime());
-            resp.setUpdateConfigTime(item.getUpdateConfigTime());
-            resp.setUpdateProtocolTime(item.getUpdateProtocolTime());
-            resp.setUpgradeTime(item.getUpgradeTime());
-            resp.setVirtualDevice(item.getVirtualDevice());
-            resp.setRemark(item.getRemark());
-            resp.setUpdatedBy(item.getUpdatedBy());
-            resp.setUpdateTime(item.getUpdateTime());
-            resp.setCreatedBy(item.getCreatedBy());
-            resp.setCreateTime(item.getCreateTime());
+            resp.setName(item.getDeviceName());
+            resp.setInstallAddress(item.getInstallAddress());
+            DmpDeviceStatus status = statusByUuid.get(item.getDeviceUuid());
+            if (status != null) {
+                resp.setCommunicationStatus(status.getDeviceStatus());
+                resp.setOnlineTime(status.getLastOnlineTime());
+                resp.setOfflineTime(status.getLastOfflineTime());
+            }
             return resp;
         }).collect(Collectors.toList());
     }

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 728 - 267
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsOverviewServiceImpl.java


+ 18 - 0
service-ems/service-ems-biz/src/main/resources/mapper/ems/DmpProductMapper.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.usky.ems.mapper.DmpProductMapper">
+
+    <select id="selectProductCodesByEnergyType" resultType="java.lang.String">
+        SELECT dp.product_code
+        FROM dmp_product dp
+        INNER JOIN ems_product_energy_type ep ON dp.id = ep.product_id
+        WHERE dp.tenant_id = #{tenantId}
+          AND dp.delete_flag = 0
+          AND dp.product_code IS NOT NULL
+          AND dp.product_code != ''
+        <if test="energyType != null">
+            AND ep.energy_type = #{energyType}
+        </if>
+    </select>
+
+</mapper>

+ 16 - 0
service-ems/service-ems-biz/src/main/resources/mapper/ems/EmsDeviceItemCodeMapper.xml

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.usky.ems.mapper.EmsDeviceItemCodeMapper">
+
+    <select id="selectDeviceUuidsByItemCode" resultType="java.lang.String">
+        SELECT DISTINCT ic.device_uuid AS device_uuid
+        FROM ems_device_item_code ic
+        INNER JOIN dmp_device d ON d.device_uuid = ic.device_uuid
+        WHERE ic.item_code = #{itemCode}
+          AND d.delete_flag = 0
+          AND d.tenant_id = #{tenantId}
+          AND d.device_uuid IS NOT NULL
+          AND d.device_uuid != ''
+    </select>
+
+</mapper>

+ 21 - 0
service-iot/service-iot-biz/src/main/java/com/usky/iot/controller/web/BaseSpaceGatewayController.java

@@ -0,0 +1,21 @@
+package com.usky.iot.controller.web;
+
+
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import org.springframework.stereotype.Controller;
+
+/**
+ * <p>
+ * 空间网关关联表 前端控制器
+ * </p>
+ *
+ * @author fu
+ * @since 2026-06-08
+ */
+@Controller
+@RequestMapping("/baseSpaceGateway")
+public class BaseSpaceGatewayController {
+
+}
+

+ 51 - 0
service-iot/service-iot-biz/src/main/java/com/usky/iot/domain/BaseSpaceGateway.java

@@ -0,0 +1,51 @@
+package com.usky.iot.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.time.LocalDateTime;
+import java.io.Serializable;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * <p>
+ * 空间网关关联表
+ * </p>
+ *
+ * @author fu
+ * @since 2026-06-08
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class BaseSpaceGateway implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键id
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 空间id
+     */
+    private Integer spaceId;
+
+    /**
+     * 网关uuid
+     */
+    private String gatewayUuid;
+
+    /**
+     * 创建人
+     */
+    private String createdBy;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createdTime;
+
+
+}

+ 16 - 0
service-iot/service-iot-biz/src/main/java/com/usky/iot/mapper/BaseSpaceGatewayMapper.java

@@ -0,0 +1,16 @@
+package com.usky.iot.mapper;
+
+import com.usky.iot.domain.BaseSpaceGateway;
+import com.usky.common.mybatis.core.CrudMapper;
+
+/**
+ * <p>
+ * 空间网关关联表 Mapper 接口
+ * </p>
+ *
+ * @author fu
+ * @since 2026-06-08
+ */
+public interface BaseSpaceGatewayMapper extends CrudMapper<BaseSpaceGateway> {
+
+}

+ 16 - 0
service-iot/service-iot-biz/src/main/java/com/usky/iot/service/BaseSpaceGatewayService.java

@@ -0,0 +1,16 @@
+package com.usky.iot.service;
+
+import com.usky.iot.domain.BaseSpaceGateway;
+import com.usky.common.mybatis.core.CrudService;
+
+/**
+ * <p>
+ * 空间网关关联表 服务类
+ * </p>
+ *
+ * @author fu
+ * @since 2026-06-08
+ */
+public interface BaseSpaceGatewayService extends CrudService<BaseSpaceGateway> {
+
+}

+ 20 - 0
service-iot/service-iot-biz/src/main/java/com/usky/iot/service/impl/BaseSpaceGatewayServiceImpl.java

@@ -0,0 +1,20 @@
+package com.usky.iot.service.impl;
+
+import com.usky.iot.domain.BaseSpaceGateway;
+import com.usky.iot.mapper.BaseSpaceGatewayMapper;
+import com.usky.iot.service.BaseSpaceGatewayService;
+import com.usky.common.mybatis.core.AbstractCrudService;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 空间网关关联表 服务实现类
+ * </p>
+ *
+ * @author fu
+ * @since 2026-06-08
+ */
+@Service
+public class BaseSpaceGatewayServiceImpl extends AbstractCrudService<BaseSpaceGatewayMapper, BaseSpaceGateway> implements BaseSpaceGatewayService {
+
+}

+ 22 - 4
service-iot/service-iot-biz/src/main/java/com/usky/iot/service/impl/DmpDeviceInfoServiceImpl.java

@@ -38,10 +38,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.service.BaseFacilityDeviceService;
-import com.usky.iot.service.DmpDeviceInfoService;
-import com.usky.iot.service.DmpDeviceStatusService;
-import com.usky.iot.service.DmpProductAttributeService;
+import com.usky.iot.service.*;
 import com.usky.iot.service.config.DeviceOperate;
 import com.usky.iot.service.enums.TopicInfo;
 import com.usky.iot.service.vo.*;
@@ -103,6 +100,9 @@ public class DmpDeviceInfoServiceImpl extends AbstractCrudService<DmpDeviceInfoM
     @Autowired
     private DmpProductAttributeMapper dmpProductAttributeMapper;
 
+    @Autowired
+    private BaseSpaceGatewayService baseSpaceGatewayService;
+
     @Autowired
     private DeviceOperate deviceOperate;
 
@@ -648,6 +648,13 @@ public class DmpDeviceInfoServiceImpl extends AbstractCrudService<DmpDeviceInfoM
         Integer pageSize = diRequest.getSize();
         IPage<DmpDeviceInfo> page = new Page<>(pageCurrent, pageSize);
 
+        List<String> gatewayUuidList = new ArrayList<>();
+        if (diRequest.getSpaceId() != null) {
+            LambdaQueryWrapper<BaseSpaceGateway> spaceGatewayLambdaQueryWrapper = Wrappers.lambdaQuery();
+            spaceGatewayLambdaQueryWrapper.eq(BaseSpaceGateway::getSpaceId, diRequest.getSpaceId());
+            gatewayUuidList = baseSpaceGatewayService.list(spaceGatewayLambdaQueryWrapper).stream().map(BaseSpaceGateway::getGatewayUuid).collect(Collectors.toList());
+        }
+
         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) {
                 LambdaQueryWrapper<DmpDeviceInfo> queryWrapper = Wrappers.lambdaQuery();
@@ -658,6 +665,7 @@ public class DmpDeviceInfoServiceImpl extends AbstractCrudService<DmpDeviceInfoM
                         .like(StringUtils.isNotBlank(diRequest.getProductCode()), DmpDeviceInfo::getProductCode, diRequest.getProductCode())
                         .eq(diRequest.getServiceStatus() != null, DmpDeviceInfo::getServiceStatus, diRequest.getServiceStatus())
                         .eq(diRequest.getCategoryType() != null, DmpDeviceInfo::getCategoryType, diRequest.getCategoryType())
+                        .in(CollectionUtils.isNotEmpty(gatewayUuidList), DmpDeviceInfo::getGatewayUuid, gatewayUuidList)
                         .eq(DmpDeviceInfo::getDeleteFlag, 0)
                         .eq(DmpDeviceInfo::getTenantId, SecurityUtils.getTenantId())
                         .orderByDesc(DmpDeviceInfo::getId);
@@ -769,6 +777,7 @@ public class DmpDeviceInfoServiceImpl extends AbstractCrudService<DmpDeviceInfoM
                     .like(StringUtils.isNotBlank(diRequest.getProductCode()), DmpDeviceInfo::getProductCode, diRequest.getProductCode())
                     .eq(diRequest.getServiceStatus() != null, DmpDeviceInfo::getServiceStatus, diRequest.getServiceStatus())
                     .eq(diRequest.getCategoryType() != null, DmpDeviceInfo::getCategoryType, diRequest.getCategoryType())
+                    .in(CollectionUtils.isNotEmpty(gatewayUuidList), DmpDeviceInfo::getGatewayUuid, gatewayUuidList)
                     .eq(DmpDeviceInfo::getDeleteFlag, 0)
                     .eq(DmpDeviceInfo::getTenantId, SecurityUtils.getTenantId());
 
@@ -845,6 +854,13 @@ public class DmpDeviceInfoServiceImpl extends AbstractCrudService<DmpDeviceInfoM
             tenantId = baseMapper.sysTenantId(domain);
         }
 
+        List<String> gatewayUuidList = new ArrayList<>();
+        if (diRequest.getSpaceId() != null) {
+            LambdaQueryWrapper<BaseSpaceGateway> spaceGatewayLambdaQueryWrapper = Wrappers.lambdaQuery();
+            spaceGatewayLambdaQueryWrapper.eq(BaseSpaceGateway::getSpaceId, diRequest.getSpaceId());
+            gatewayUuidList = baseSpaceGatewayService.list(spaceGatewayLambdaQueryWrapper).stream().map(BaseSpaceGateway::getGatewayUuid).collect(Collectors.toList());
+        }
+
         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) {
                 LambdaQueryWrapper<DmpDeviceInfo> queryWrapper = Wrappers.lambdaQuery();
@@ -854,6 +870,7 @@ public class DmpDeviceInfoServiceImpl extends AbstractCrudService<DmpDeviceInfoM
                         .like(StringUtils.isNotBlank(diRequest.getProductCode()), DmpDeviceInfo::getProductCode, diRequest.getProductCode())
                         .eq(diRequest.getServiceStatus() != null, DmpDeviceInfo::getServiceStatus, diRequest.getServiceStatus())
                         .eq(diRequest.getCategoryType() != null, DmpDeviceInfo::getCategoryType, diRequest.getCategoryType())
+                        .in(CollectionUtils.isNotEmpty(gatewayUuidList), DmpDeviceInfo::getGatewayUuid, gatewayUuidList)
                         .eq(DmpDeviceInfo::getDeleteFlag, 0)
                         .eq(DmpDeviceInfo::getTenantId, tenantId)
                         .orderByDesc(DmpDeviceInfo::getId);
@@ -964,6 +981,7 @@ public class DmpDeviceInfoServiceImpl extends AbstractCrudService<DmpDeviceInfoM
                     .like(StringUtils.isNotBlank(diRequest.getProductCode()), DmpDeviceInfo::getProductCode, diRequest.getProductCode())
                     .eq(diRequest.getServiceStatus() != null, DmpDeviceInfo::getServiceStatus, diRequest.getServiceStatus())
                     .eq(diRequest.getCategoryType() != null, DmpDeviceInfo::getCategoryType, diRequest.getCategoryType())
+                    .in(CollectionUtils.isNotEmpty(gatewayUuidList), DmpDeviceInfo::getGatewayUuid, gatewayUuidList)
                     .eq(DmpDeviceInfo::getDeleteFlag, 0)
                     .eq(DmpDeviceInfo::getTenantId, tenantId)
                     .orderByDesc(DmpDeviceInfo::getId);

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

@@ -61,4 +61,9 @@ public class DmpDeviceInfoRequest {
      */
     private Integer categoryType;
 
+    /**
+     * 空间Id
+     */
+    private Integer spaceId;
+
 }

+ 14 - 0
service-iot/service-iot-biz/src/main/resources/mapper/iot/BaseSpaceGatewayMapper.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.usky.iot.mapper.BaseSpaceGatewayMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.usky.iot.domain.BaseSpaceGateway">
+        <id column="id" property="id" />
+        <result column="space_id" property="spaceId" />
+        <result column="gateway_uuid" property="gatewayUuid" />
+        <result column="created_by" property="createdBy" />
+        <result column="created_time" property="createdTime" />
+    </resultMap>
+
+</mapper>

+ 12 - 0
service-tsdb/service-tsdb-api/src/main/java/com/usky/demo/RemoteTsdbProxyService.java

@@ -93,4 +93,16 @@ public interface RemoteTsdbProxyService {
      */
     @PostMapping("/dropSuperTableColumn")
     Void dropSuperTableColumn(@RequestBody SuperTableDTO superTableDTO);
+
+    /**
+     * 能耗分项汇总(TDengine 超级表 LAST-FIRST 按设备聚合后求和)
+     */
+    @PostMapping("/energyItemSum")
+    EnergyItemSumResultVO sumEnergyItem(@RequestBody EnergyItemSumQueryVO requestVO);
+
+    /**
+     * 能耗分项趋势(按产品+时间范围返回 time/value 序列)
+     */
+    @PostMapping("/energyItemTrend")
+    EnergyItemTrendResultVO sumEnergyItemTrend(@RequestBody EnergyItemTrendQueryVO requestVO);
 }

+ 34 - 0
service-tsdb/service-tsdb-api/src/main/java/com/usky/demo/domain/EnergyItemSumQueryVO.java

@@ -0,0 +1,34 @@
+package com.usky.demo.domain;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 能耗分项汇总查询参数(TDengine 超级表)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class EnergyItemSumQueryVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /** 超级表字段名(差值计算列) */
+    private String identifier;
+
+    /** 超级表名 */
+    @JsonProperty("super_table")
+    private String superTable;
+
+    /** 开始时间(含),如 2024-01-01 00:00:00.000 */
+    private String startTime;
+
+    /** 结束时间(不含),如 2024-01-02 00:00:00.000 */
+    private String endTime;
+
+    /** 设备 UUID 列表(可选;传入时仅汇总对应子表数据) */
+    private List<String> deviceUuid;
+}

+ 22 - 0
service-tsdb/service-tsdb-api/src/main/java/com/usky/demo/domain/EnergyItemSumResultVO.java

@@ -0,0 +1,22 @@
+package com.usky.demo.domain;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 能耗分项汇总查询结果
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class EnergyItemSumResultVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /** 各设备区间用量差值之和 */
+    @JsonProperty("sum_diff")
+    private BigDecimal sumDiff;
+}

+ 23 - 0
service-tsdb/service-tsdb-api/src/main/java/com/usky/demo/domain/EnergyItemTrendPointVO.java

@@ -0,0 +1,23 @@
+package com.usky.demo.domain;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 能耗分项趋势单点数据(time + value)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class EnergyItemTrendPointVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /** 时间点 */
+    private String time;
+
+    /** 用量值 */
+    private BigDecimal value;
+}

+ 33 - 0
service-tsdb/service-tsdb-api/src/main/java/com/usky/demo/domain/EnergyItemTrendQueryVO.java

@@ -0,0 +1,33 @@
+package com.usky.demo.domain;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+
+/**
+ * 能耗分项趋势查询参数(TDengine 超级表按时间粒度聚合)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class EnergyItemTrendQueryVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /** 超级表字段名(差值计算列) */
+    private String identifier;
+
+    /** 超级表名 */
+    @JsonProperty("super_table")
+    private String superTable;
+
+    /** 开始时间(含) */
+    private String startTime;
+
+    /** 结束时间(不含) */
+    private String endTime;
+
+    /** 时间类型:1-小时、2-天、3-月 */
+    private Integer dateType;
+}

+ 20 - 0
service-tsdb/service-tsdb-api/src/main/java/com/usky/demo/domain/EnergyItemTrendResultVO.java

@@ -0,0 +1,20 @@
+package com.usky.demo.domain;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 能耗分项趋势查询结果
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class EnergyItemTrendResultVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private List<EnergyItemTrendPointVO> points = new ArrayList<>();
+}

+ 12 - 0
service-tsdb/service-tsdb-api/src/main/java/com/usky/demo/factory/RemoteTsdbProxyFallbackFactory.java

@@ -92,6 +92,18 @@ public class RemoteTsdbProxyFallbackFactory implements FallbackFactory<RemoteTsd
             {
                 throw new BusinessException("删除超级表列:" + throwable.getMessage());
             }
+
+            @Override
+            public EnergyItemSumResultVO sumEnergyItem(EnergyItemSumQueryVO requestVO)
+            {
+                throw new BusinessException("能耗分项汇总查询:" + throwable.getMessage());
+            }
+
+            @Override
+            public EnergyItemTrendResultVO sumEnergyItemTrend(EnergyItemTrendQueryVO requestVO)
+            {
+                throw new BusinessException("能耗分项趋势查询:" + throwable.getMessage());
+            }
         };
     }
 }

+ 19 - 0
service-tsdb/service-tsdb-biz/src/main/java/com/usky/demo/controller/api/DataTsdbProxyControllerApi.java

@@ -156,4 +156,23 @@ public class DataTsdbProxyControllerApi implements RemoteTsdbProxyService {
         return null;
     }
 
+    /**
+     * 能耗分项汇总(对外 Feign)
+     */
+    @Override
+    public EnergyItemSumResultVO sumEnergyItem(@RequestBody EnergyItemSumQueryVO requestVO) {
+        if (!"taos".equals(sourcetype)) {
+            throw new BusinessException("当前数据源不支持能耗分项汇总查询");
+        }
+        return queryTdengineDataService.sumEnergyItemDiff(requestVO);
+    }
+
+    @Override
+    public EnergyItemTrendResultVO sumEnergyItemTrend(@RequestBody EnergyItemTrendQueryVO requestVO) {
+        if (!"taos".equals(sourcetype)) {
+            throw new BusinessException("当前数据源不支持能耗分项趋势查询");
+        }
+        return queryTdengineDataService.sumEnergyItemTrend(requestVO);
+    }
+
 }

+ 34 - 0
service-tsdb/service-tsdb-biz/src/main/java/com/usky/demo/controller/web/QueryInfluxdbDataController.java

@@ -4,6 +4,7 @@ package com.usky.demo.controller.web;
 import cn.hutool.core.util.StrUtil;
 import com.usky.common.core.bean.ApiResult;
 import com.usky.common.core.domain.R;
+import com.usky.common.core.exception.BusinessException;
 import com.usky.demo.constant.TdsConstants;
 import com.usky.demo.domain.*;
 import com.usky.demo.service.QueryInfluxdbDataService;
@@ -150,5 +151,38 @@ public class QueryInfluxdbDataController {
         return ApiResult.success(superTableDescribeVOS);
     }
 
+    /**
+     * 能耗分项汇总(按超级表、设备维度 LAST-FIRST 后求和)
+     */
+    @ApiOperation("能耗分项汇总查询")
+    @PostMapping("/energy-item/sum")
+    public ApiResult<EnergyItemSumResultVO> sumEnergyItem(@RequestBody EnergyItemSumQueryVO requestVO) {
+        if (!"taos".equals(sourcetype)) {
+            throw new BusinessException("当前数据源不支持能耗分项汇总查询");
+        }
+        return ApiResult.success(queryTdengineDataService.sumEnergyItemDiff(requestVO));
+    }
+
+    /**
+     * 能耗分项汇总(GET,便于调试)
+     */
+    @ApiOperation("能耗分项汇总查询(GET)")
+    @GetMapping("/energy-item/sum")
+    public ApiResult<EnergyItemSumResultVO> sumEnergyItemGet(
+            @RequestParam String identifier,
+            @RequestParam("super_table") String superTable,
+            @RequestParam String startTime,
+            @RequestParam String endTime) {
+        if (!"taos".equals(sourcetype)) {
+            throw new BusinessException("当前数据源不支持能耗分项汇总查询");
+        }
+        EnergyItemSumQueryVO requestVO = new EnergyItemSumQueryVO();
+        requestVO.setIdentifier(identifier);
+        requestVO.setSuperTable(superTable);
+        requestVO.setStartTime(startTime);
+        requestVO.setEndTime(endTime);
+        return ApiResult.success(queryTdengineDataService.sumEnergyItemDiff(requestVO));
+    }
+
 }
 

+ 10 - 0
service-tsdb/service-tsdb-biz/src/main/java/com/usky/demo/service/QueryTdengineDataService.java

@@ -38,4 +38,14 @@ public interface QueryTdengineDataService extends CrudService<QueryTdengineData>
     void dropSuperTableTag(String superTableName, Fields fields);
 
     List<SuperTableDescribeVO> describeSuperOrSubTable(String tableName);
+
+    /**
+     * 能耗分项汇总:按设备计算 LAST(identifier)-FIRST(identifier) 后求和
+     */
+    EnergyItemSumResultVO sumEnergyItemDiff(EnergyItemSumQueryVO requestVO);
+
+    /**
+     * 能耗分项趋势:按时间粒度 INTERVAL 聚合,返回 time/value 列表
+     */
+    EnergyItemTrendResultVO sumEnergyItemTrend(EnergyItemTrendQueryVO requestVO);
 }

+ 136 - 0
service-tsdb/service-tsdb-biz/src/main/java/com/usky/demo/service/impl/QueryTdengineDataServiceImpl.java

@@ -25,10 +25,12 @@ import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Service;
 
 import javax.sql.DataSource;
+import java.math.BigDecimal;
 import java.sql.*;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.*;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
 /**
@@ -42,6 +44,9 @@ import java.util.stream.Collectors;
 @Service
 @DS("taos")
 public class QueryTdengineDataServiceImpl extends AbstractCrudService<QueryTdengineDataMapper, QueryTdengineData> implements QueryTdengineDataService {
+
+    private static final Pattern SQL_IDENTIFIER_PATTERN = Pattern.compile("^[a-zA-Z_][a-zA-Z0-9_]*$");
+
     @Autowired
     private TsdbUtils tsdbUtils;
     @Autowired
@@ -308,4 +313,135 @@ public class QueryTdengineDataServiceImpl extends AbstractCrudService<QueryTdeng
     public void dropSuperTableTag(String superTableName, Fields fields) {
         queryTdengineDataMapper.dropSuperTableTag(superTableName, fields);
     }
+
+    @Override
+    public EnergyItemSumResultVO sumEnergyItemDiff(EnergyItemSumQueryVO requestVO) {
+        if (requestVO == null) {
+            throw new BusinessException("查询参数不能为空");
+        }
+        String superTable = requestVO.getSuperTable();
+        String identifier = requestVO.getIdentifier();
+        String startTime = requestVO.getStartTime();
+        String endTime = requestVO.getEndTime();
+        if (StringUtils.isAnyBlank(superTable, identifier, startTime, endTime)) {
+            throw new BusinessException("super_table、identifier、startTime、endTime 均不能为空");
+        }
+        assertSqlIdentifier(superTable, "super_table");
+        assertSqlIdentifier(identifier, "identifier");
+
+        String deviceFilter = buildDeviceUuidFilter(requestVO.getDeviceUuid());
+        String sql = "SELECT SUM(diff) AS sum_diff FROM ("
+                + " SELECT device_id, LAST(" + identifier + ") - FIRST(" + identifier + ") AS diff"
+                + " FROM " + superTable
+                + " WHERE ts >= '" + startTime + "' AND ts < '" + endTime + "'"
+                + deviceFilter
+                + " GROUP BY device_id"
+                + ") AS sub_table_diff";
+
+        EnergyItemSumResultVO result = new EnergyItemSumResultVO();
+        result.setSumDiff(BigDecimal.ZERO);
+
+        try (Connection connection = dataSource.getConnection();
+             PreparedStatement stmt = connection.prepareStatement(sql);
+             ResultSet rs = stmt.executeQuery()) {
+            if (rs.next()) {
+                Object value = rs.getObject("sum_diff");
+                if (value != null) {
+                    result.setSumDiff(new BigDecimal(value.toString()));
+                }
+            }
+        } catch (SQLException e) {
+            log.error("能耗分项汇总查询失败, sql=" + sql + ", " + e.getMessage());
+            throw new BusinessException("能耗分项汇总查询失败: " + e.getMessage());
+        }
+        return result;
+    }
+
+    @Override
+    public EnergyItemTrendResultVO sumEnergyItemTrend(EnergyItemTrendQueryVO requestVO) {
+        if (requestVO == null) {
+            throw new BusinessException("查询参数不能为空");
+        }
+        String superTable = requestVO.getSuperTable();
+        String identifier = requestVO.getIdentifier();
+        String startTime = requestVO.getStartTime();
+        String endTime = requestVO.getEndTime();
+        Integer dateType = requestVO.getDateType();
+        if (StringUtils.isAnyBlank(superTable, identifier, startTime, endTime) || dateType == null) {
+            throw new BusinessException("super_table、identifier、startTime、endTime、dateType 均不能为空");
+        }
+        assertSqlIdentifier(superTable, "super_table");
+        assertSqlIdentifier(identifier, "identifier");
+        String interval = resolveTrendInterval(dateType);
+
+        String sql = "SELECT _wstart, SUM(diff) AS sum_diff FROM ("
+                + " SELECT _wstart, device_id, LAST(" + identifier + ") - FIRST(" + identifier + ") AS diff"
+                + " FROM " + superTable
+                + " WHERE ts >= '" + startTime + "' AND ts < '" + endTime + "'"
+                + " PARTITION BY device_id"
+                + " INTERVAL(" + interval + ")"
+                + ") AS trend_diff GROUP BY _wstart ORDER BY _wstart";
+
+        EnergyItemTrendResultVO result = new EnergyItemTrendResultVO();
+        List<EnergyItemTrendPointVO> points = new ArrayList<>();
+
+        try (Connection connection = dataSource.getConnection();
+             PreparedStatement stmt = connection.prepareStatement(sql);
+             ResultSet rs = stmt.executeQuery()) {
+            while (rs.next()) {
+                Object wstart = rs.getObject("_wstart");
+                if (wstart == null) {
+                    continue;
+                }
+                Object sumDiff = rs.getObject("sum_diff");
+                EnergyItemTrendPointVO point = new EnergyItemTrendPointVO();
+                point.setTime(wstart.toString());
+                if (sumDiff != null) {
+                    point.setValue(new BigDecimal(sumDiff.toString()));
+                } else {
+                    point.setValue(BigDecimal.ZERO);
+                }
+                points.add(point);
+            }
+        } catch (SQLException e) {
+            log.error("能耗分项趋势查询失败, sql=" + sql + ", " + e.getMessage());
+            throw new BusinessException("能耗分项趋势查询失败: " + e.getMessage());
+        }
+
+        result.setPoints(points);
+        return result;
+    }
+
+    private static String resolveTrendInterval(Integer dateType) {
+        if (dateType == 1) {
+            return "1h";
+        }
+        if (dateType == 2) {
+            return "1d";
+        }
+        if (dateType == 3) {
+            return "1n";
+        }
+        throw new BusinessException("dateType 仅支持 1-小时、2-天、3-月");
+    }
+
+    private static String buildDeviceUuidFilter(List<String> deviceUuids) {
+        if (deviceUuids == null || deviceUuids.isEmpty()) {
+            return "";
+        }
+        String tbnames = deviceUuids.stream()
+                .filter(StringUtils::isNotBlank)
+                .map(uuid -> "'_" + uuid.trim().replace("'", "''") + "'")
+                .collect(Collectors.joining(","));
+        if (tbnames.isEmpty()) {
+            return "";
+        }
+        return " AND tbname IN (" + tbnames + ")";
+    }
+
+    private static void assertSqlIdentifier(String name, String paramName) {
+        if (!SQL_IDENTIFIER_PATTERN.matcher(name).matches()) {
+            throw new BusinessException(paramName + " 格式非法,仅允许字母、数字与下划线,且不能以数字开头");
+        }
+    }
 }

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio