Prechádzať zdrojové kódy

Merge branch 'usky-ems' into server-165

james 15 hodín pred
rodič
commit
9e38cda2e3
100 zmenil súbory, kde vykonal 5297 pridanie a 23 odobranie
  1. 28 19
      pom.xml
  2. 1 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/MybatisGeneratorUtils.java
  3. 1 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/AlarmDataController.java
  4. 2 2
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/BaseDataTransferService.java
  5. 69 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/alarm/AlarmMessage1VO.java
  6. 1 1
      service-ems/service-ems-biz/src/main/java/com/usky/ems/MybatisGenerator.java
  7. 30 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/BaseSpaceController.java
  8. 76 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/DmpGatewayController.java
  9. 113 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsAnalysisController.java
  10. 36 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsChannelTypeController.java
  11. 21 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsChannelTypeParameterController.java
  12. 21 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsChannelTypeParameterValueController.java
  13. 32 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsDeviceEventController.java
  14. 210 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsModelController.java
  15. 132 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsOverviewController.java
  16. 37 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsProjectController.java
  17. 112 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsReportController.java
  18. 54 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsSystemDictController.java
  19. 46 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/BaseArea.java
  20. 97 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/BaseBuild.java
  21. 45 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/BaseSpace.java
  22. 34 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/BaseSpaceArea.java
  23. 34 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/BaseSpaceBuild.java
  24. 34 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/BaseSpaceGateway.java
  25. 25 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/DmpDevice.java
  26. 38 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/DmpDeviceStatus.java
  27. 72 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/DmpGateway.java
  28. 92 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/DmpProduct.java
  29. 40 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsChannel.java
  30. 61 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsChannelType.java
  31. 71 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsChannelTypeParameter.java
  32. 71 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsChannelTypeParameterValue.java
  33. 47 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsDataUploadShanghaiProduct.java
  34. 69 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsDevice.java
  35. 71 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsDeviceEvent.java
  36. 50 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsDeviceFunction.java
  37. 33 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsDeviceItemCode.java
  38. 61 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsEnergyConsumptionFormula.java
  39. 44 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsEnergyItemCode.java
  40. 30 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsProductEnergyType.java
  41. 74 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsProject.java
  42. 48 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsProjectConversionFactor.java
  43. 39 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsProjectDeviceSystem.java
  44. 37 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsSystemDictCode.java
  45. 37 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsSystemDictRegion.java
  46. 43 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsSystemDictValue.java
  47. 82 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/enums/TemporalTypeEnum.java
  48. 55 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/enums/ValueTypeEnum.java
  49. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/BaseAreaMapper.java
  50. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/BaseBuildMapper.java
  51. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/BaseSpaceAreaMapper.java
  52. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/BaseSpaceBuildMapper.java
  53. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/BaseSpaceGatewayMapper.java
  54. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/BaseSpaceMapper.java
  55. 12 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/DmpDeviceStatusMapper.java
  56. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/DmpGatewayMapper.java
  57. 20 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/DmpProductMapper.java
  58. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsChannelMapper.java
  59. 16 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsChannelTypeMapper.java
  60. 16 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsChannelTypeParameterMapper.java
  61. 16 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsChannelTypeParameterValueMapper.java
  62. 12 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsDataUploadShanghaiProductMapper.java
  63. 11 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsDeviceEventMapper.java
  64. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsDeviceFunctionMapper.java
  65. 19 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsDeviceItemCodeMapper.java
  66. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsDeviceMapper.java
  67. 11 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsEnergyConsumptionFormulaMapper.java
  68. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsEnergyItemCodeMapper.java
  69. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsProductEnergyTypeMapper.java
  70. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsProjectConversionFactorMapper.java
  71. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsProjectDeviceSystemMapper.java
  72. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsProjectMapper.java
  73. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsSystemDictCodeMapper.java
  74. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsSystemDictRegionMapper.java
  75. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsSystemDictValueMapper.java
  76. 41 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/BaseSpaceService.java
  77. 28 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/DmpGatewayService.java
  78. 23 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/DmpProductService.java
  79. 21 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsAnalysisService.java
  80. 16 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsChannelTypeParameterService.java
  81. 16 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsChannelTypeParameterValueService.java
  82. 23 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsChannelTypeService.java
  83. 18 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsDeviceEventService.java
  84. 25 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsDeviceReportService.java
  85. 19 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsEnergyItemCodeService.java
  86. 108 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsModelService.java
  87. 63 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsOverviewService.java
  88. 16 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsProjectService.java
  89. 32 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsReportService.java
  90. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsSystemDictCodeService.java
  91. 22 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsSystemDictRegionService.java
  92. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsSystemDictValueService.java
  93. 162 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/BaseSpaceServiceImpl.java
  94. 191 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/DmpGatewayServiceImpl.java
  95. 75 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/DmpProductServiceImpl.java
  96. 1205 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsAnalysisServiceImpl.java
  97. 20 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsChannelTypeParameterServiceImpl.java
  98. 20 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsChannelTypeParameterValueServiceImpl.java
  99. 71 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsChannelTypeServiceImpl.java
  100. 193 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsDeviceEventServiceImpl.java

+ 28 - 19
pom.xml

@@ -91,25 +91,34 @@
 
         <!--    <module>service-data</module>-->
 
-    </modules>
-
-
-    <dependencies>
-
-
-        <dependency>
-
-
-            <groupId>org.projectlombok</groupId>
-
-
-            <artifactId>lombok</artifactId>
-
-
-        </dependency>
-
-
-    </dependencies>
+        <module>service-sas</module>
+
+  </modules>
+          
+  
+  
+  <dependencies>
+                    
+    
+    
+    <dependency>
+                              
+      
+      
+      <groupId>org.projectlombok</groupId>
+                              
+      
+      
+      <artifactId>lombok</artifactId>
+                          
+    
+    
+    </dependency>
+                
+  
+  
+  </dependencies>
+    
 
 
 </project>

+ 1 - 1
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/MybatisGeneratorUtils.java

@@ -71,7 +71,7 @@ public class MybatisGeneratorUtils {
         // strategy.setTablePrefix("t_"); // 表名前缀
         strategy.setEntityLombokModel(true); //使用lombokbase_build_plane
         //修改自己想要生成的表
-        strategy.setInclude("base_build");  // 逆向工程使用的表   如果要生成多个,这里可以传入String[]
+        strategy.setInclude("dmp_device");  // 逆向工程使用的表   如果要生成多个,这里可以传入String[]
         mpg.setStrategy(strategy);
 
         // 关闭默认 xml 生成,调整生成 至 根目录

+ 1 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/AlarmDataController.java

@@ -2,6 +2,7 @@ package com.usky.cdi.controller;
 
 import com.usky.cdi.service.impl.AlarmDataTransferService;
 import com.usky.cdi.service.vo.alarm.AlarmMessageVO;
+import com.usky.cdi.service.vo.alarm.AlarmMessage1VO;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;

+ 2 - 2
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/BaseDataTransferService.java

@@ -228,8 +228,8 @@ public class BaseDataTransferService {
             userIdToName.put(709, 15);
             userIdToName.put(710, 16);
             userIdToName.put(711, 2);
-            // userIdToName.put(712, 34);
-            // userIdToName.put(713, 36);
+            //userIdToName.put(712, 34);
+            //userIdToName.put(713, 36);
             userIdToName.put(714, 37);
 
             HashMap<String, Object> map = new HashMap<>();

+ 69 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/alarm/AlarmMessage1VO.java

@@ -0,0 +1,69 @@
+package com.usky.cdi.service.vo.alarm;
+
+import lombok.Data;
+
+@Data
+public class AlarmMessage1VO {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 数据包ID
+     */
+    private Long dataPacketID;
+
+    /**
+     * 人防工程ID
+     */
+    private Long engineeringID;
+
+    /**
+     * 事件ID
+     */
+    private Integer alarmID;
+
+    /**
+     * 事件来源
+     */
+    private Integer alarmSource;
+
+    /**
+     * 物联设施ID
+     */
+    private Integer sensorID;
+
+    /**
+     * 事件类型
+     */
+    private String alarmType;
+
+    /**
+     * 事件状态
+     */
+    private Integer alarmStatus;
+
+    /**
+     * 最新水浸状态
+     */
+    private Integer sensorValue;
+
+    /**
+     * 事件发生/更新时间
+     */
+    private String alarmUpdateTime;
+
+    /**
+     * 监测对象编号
+     */
+    private String monitorObjNo;
+
+    /**
+     * 事件描述
+     */
+    private String alarmDesc;
+
+    /**
+     * 上报时间
+     */
+    private String publishTime;
+
+}

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

@@ -70,7 +70,7 @@ public class MybatisGenerator {
         // strategy.setTablePrefix("t_"); // 表名前缀
         strategy.setEntityLombokModel(true); //使用lombok
         //修改自己想要生成的表
-        strategy.setInclude("ems_cons_platform_config");  // 逆向工程使用的表   如果要生成多个,这里可以传入String[]
+        strategy.setInclude(new String[]{"ems_channel_type", "ems_channel_type_parameter", "ems_channel_type_parameter_value"});  // 逆向工程使用的表   如果要生成多个,这里可以传入String[]
         mpg.setStrategy(strategy);
 
         // 关闭默认 xml 生成,调整生成 至 根目录

+ 30 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/BaseSpaceController.java

@@ -0,0 +1,30 @@
+package com.usky.ems.controller.web;
+
+import com.usky.common.core.bean.ApiResult;
+import com.usky.ems.service.BaseSpaceService;
+import com.usky.ems.service.vo.BaseSpaceForestNodeVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 空间树接口(base_space)
+ *
+ * GET /base-space/tree  获取空间树
+ */
+@RestController
+@RequestMapping("/space")
+public class BaseSpaceController {
+
+    @Autowired
+    private BaseSpaceService baseSpaceService;
+
+    /**
+     * 获取空间树(根据 base_space 构建)
+     */
+    @GetMapping("/tree")
+    public ApiResult<BaseSpaceForestNodeVO> tree() {
+        return ApiResult.success(baseSpaceService.tree());
+    }
+}

+ 76 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/DmpGatewayController.java

@@ -0,0 +1,76 @@
+package com.usky.ems.controller.web;
+
+import com.usky.common.core.bean.ApiResult;
+import com.usky.common.core.bean.CommonPage;
+import com.usky.ems.domain.DmpDevice;
+import com.usky.ems.domain.DmpGateway;
+import com.usky.ems.service.DmpGatewayService;
+import com.usky.ems.service.vo.DmpGatewayDevicePageRequest;
+import com.usky.ems.service.vo.DmpGatewayDetailResponse;
+import com.usky.ems.service.vo.DmpGatewayListItem;
+import com.usky.ems.service.vo.DmpGatewayPageRequest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * dmp_gateway 网关维护 API(REST 风格对齐 IoT 模块 DmpDeviceInfoController 的增删改)
+ * 基础路径:/dmp/gateway(网关前缀如 /prod-api/service-ems 依部署而定)
+ */
+@RestController
+@RequestMapping("/gateway")
+public class DmpGatewayController {
+
+    @Autowired
+    private DmpGatewayService dmpGatewayService;
+
+    /**
+     * 分页列表
+     */
+    @PostMapping("/list")
+    public ApiResult<CommonPage<DmpGatewayListItem>> gatewayList(@RequestBody DmpGatewayPageRequest request) {
+        return ApiResult.success(dmpGatewayService.pageGateways(request));
+    }
+
+    /**
+     * 按网关 UUID 分页查询 dmp_device 列表
+     */
+    @PostMapping("/device/list")
+    public ApiResult<CommonPage<DmpDevice>> gatewayDeviceList(@RequestBody DmpGatewayDevicePageRequest request) {
+        return ApiResult.success(dmpGatewayService.pageGatewayDevices(request));
+    }
+
+    /**
+     * 详情
+     */
+    @GetMapping("/{id}")
+    public ApiResult<DmpGatewayDetailResponse> getGateway(@PathVariable Integer id) {
+        return ApiResult.success(dmpGatewayService.getGatewayDetail(id));
+    }
+
+    /**
+     * 新增
+     */
+    @PostMapping
+    public ApiResult<Void> add(@RequestBody DmpGateway entity) {
+        dmpGatewayService.add(entity);
+        return ApiResult.success();
+    }
+
+    /**
+     * 修改
+     */
+    @PutMapping
+    public ApiResult<Void> edit(@RequestBody DmpGateway entity) {
+        dmpGatewayService.update(entity);
+        return ApiResult.success();
+    }
+
+    /**
+     * 删除
+     */
+    @DeleteMapping("/{id}")
+    public ApiResult<Void> remove(@PathVariable("id") Integer id) {
+        dmpGatewayService.remove(id);
+        return ApiResult.success();
+    }
+}

+ 113 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsAnalysisController.java

@@ -0,0 +1,113 @@
+package com.usky.ems.controller.web;
+
+import com.usky.common.core.bean.ApiResult;
+import com.usky.ems.service.EmsAnalysisService;
+import com.usky.ems.service.vo.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 能耗分析接口
+ * 趋势、指标、分类占比、区域分析、对比分析
+ * 区域与面积相关统计在 {@link com.usky.ems.service.impl.EmsAnalysisServiceImpl} 中基于
+ * base_space、base_area、base_space_area、base_space_build 等表查询。
+ * 基础路径前缀:/analysis(网关再加 /prod-api/service-ems)
+ */
+@RestController
+@RequestMapping("/analysis")
+public class EmsAnalysisController {
+
+    @Autowired
+    private EmsAnalysisService emsAnalysisService;
+    
+    /**
+     * 获取当前日期的默认值
+     */
+    private String getCurrentDefaultTimeDimension() {
+        return "D"; // 按日统计
+    }
+    
+    /**
+     * 获取当前日期的默认值(根据时间维度返回对应格式)
+     */
+    private String getCurrentDefaultTimeValue(String timeDimension) {
+        java.time.LocalDate now = java.time.LocalDate.now();
+        int year = now.getYear();
+        int month = now.getMonthValue();
+        int day = now.getDayOfMonth();
+        if ("D".equals(timeDimension)) {
+            // 按日:返回年月日格式,如 2026-03-09
+            return year + "-" + (month < 10 ? "0" : "") + month + "-" + (day < 10 ? "0" : "") + day;
+        } else if ("M".equals(timeDimension)) {
+            // 按月:返回年月格式,如 2026-03
+            return year + "-" + (month < 10 ? "0" : "") + month;
+        } else {
+            // 按年:返回年格式,如 2026
+            return String.valueOf(year);
+        }
+    }
+
+    @GetMapping("/trend")
+    public ApiResult<EmsTrendResponse> getTrend(
+            @RequestParam(required = false) Long projectId,
+            @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.getTrend(projectId, timeDimension, timeValue, energyTypeId));
+    }
+
+    @GetMapping("/trend/indicators")
+    public ApiResult<EmsTrendIndicatorsResponse> getTrendIndicators(
+            @RequestParam(required = false) Long projectId,
+            @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.getTrendIndicators(projectId, timeDimension, timeValue, energyTypeId));
+    }
+
+    @GetMapping("/trend/category")
+    public ApiResult<EmsTrendCategoryResponse> getTrendCategory(
+            @RequestParam(required = false) Long projectId,
+            @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.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("/compare")
+    public ApiResult<EmsCompareResponse> getCompare(@RequestBody EmsCompareRequest request) {
+        return ApiResult.success(emsAnalysisService.getCompare(request));
+    }
+
+    @GetMapping("/average")
+    public ApiResult<EmsAverageResponse> getAverage(
+            @RequestParam(required = false) Long projectId,
+            @RequestParam(required = false) String timeDimension,
+            @RequestParam(required = false) String timeValue,
+            @RequestParam(required = false) Long energyTypeId) {
+        // 设置默认值:如果没有提供参数,使用默认的项目和能源类型
+        if (projectId == null) projectId = 1L;  // 默认项目ID
+        if (energyTypeId == null) energyTypeId = 1L;  // 默认电能源类型
+        if (timeDimension == null) timeDimension = getCurrentDefaultTimeDimension();
+        if (timeValue == null) timeValue = getCurrentDefaultTimeValue(timeDimension);
+        return ApiResult.success(emsAnalysisService.getAverage(projectId, timeDimension, timeValue, energyTypeId));
+    }
+}

+ 36 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsChannelTypeController.java

@@ -0,0 +1,36 @@
+package com.usky.ems.controller.web;
+
+import com.usky.common.core.bean.ApiResult;
+import com.usky.ems.service.EmsChannelTypeService;
+import com.usky.ems.service.vo.EmsChannelTypeShowVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 通道类型 前端控制器
+ * </p>
+ *
+ * @author ya
+ * @since 2026-03-16
+ */
+@RestController
+@RequestMapping("/channelType")
+public class EmsChannelTypeController {
+
+    @Autowired
+    private EmsChannelTypeService emsChannelTypeService;
+
+    /**
+     * 通道配置数据查询:返回所有通道类型及其参数、参数可选值(树形结构)
+     */
+    @GetMapping("/show")
+    public ApiResult<List<EmsChannelTypeShowVO>> show() {
+        return ApiResult.success(emsChannelTypeService.show());
+    }
+}
+

+ 21 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsChannelTypeParameterController.java

@@ -0,0 +1,21 @@
+package com.usky.ems.controller.web;
+
+
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import org.springframework.stereotype.Controller;
+
+/**
+ * <p>
+ * 通道类型参数 前端控制器
+ * </p>
+ *
+ * @author ya
+ * @since 2026-03-16
+ */
+@Controller
+@RequestMapping("/emsChannelTypeParameter")
+public class EmsChannelTypeParameterController {
+
+}
+

+ 21 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsChannelTypeParameterValueController.java

@@ -0,0 +1,21 @@
+package com.usky.ems.controller.web;
+
+
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import org.springframework.stereotype.Controller;
+
+/**
+ * <p>
+ * 通道类型参数可选值 前端控制器
+ * </p>
+ *
+ * @author ya
+ * @since 2026-03-16
+ */
+@Controller
+@RequestMapping("/emsChannelTypeParameterValue")
+public class EmsChannelTypeParameterValueController {
+
+}
+

+ 32 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsDeviceEventController.java

@@ -0,0 +1,32 @@
+package com.usky.ems.controller.web;
+
+import com.usky.common.core.bean.ApiResult;
+import com.usky.ems.service.EmsDeviceEventService;
+import com.usky.ems.service.vo.DeviceEventDTO;
+import com.usky.ems.service.vo.DeviceEventVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * 设备事件接口
+ *
+ * POST /device-event/list  查询设备事件列表
+ */
+@RestController
+@RequestMapping("/device-event")
+public class EmsDeviceEventController {
+
+    @Autowired
+    private EmsDeviceEventService emsDeviceEventService;
+
+    @PostMapping("/list")
+    public ApiResult<List<DeviceEventVO>> list(@RequestBody DeviceEventDTO dto) {
+        return ApiResult.success(emsDeviceEventService.list(dto));
+    }
+}
+

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

@@ -0,0 +1,210 @@
+package com.usky.ems.controller.web;
+
+import com.usky.common.core.bean.ApiResult;
+import com.usky.ems.service.EmsModelService;
+import com.usky.ems.service.vo.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 能源能耗 - 模型/结构模块 API(项目层级树、能源类型、建筑/区域/网关/通道/设备/属性点位 CRUD)
+ * 空间与建筑/区域/网关分别通过 base_space、base_build、base_area 及
+ * base_space_build、base_space_area、base_space_gateway 关联;网关在 dmp_gateway。
+ * 基础路径前缀:/model(网关再加 /prod-api/service-ems)
+ */
+@RestController
+@RequestMapping("/model")
+public class EmsModelController {
+
+    @Autowired
+    private EmsModelService emsModelService;
+
+//    /**
+//     * 获取项目层级树(建筑、区域、网关)
+//     */
+//    @GetMapping("/structure/tree")
+//    public ApiResult<EmsStructureTreeNode> getStructureTree(
+//            @RequestParam(required = false) Long projectId,
+//            @RequestParam(required = false, defaultValue = "true") Boolean includeGateway) {
+//        return ApiResult.success(emsModelService.getStructureTree(projectId, includeGateway));
+//    }
+
+    /**
+     * 能源类型列表(与概览 /overview/item 同源:租户产品关联的能源类型)
+     */
+    @GetMapping("/energy-type/list")
+    public ApiResult<List<EmsEnergyTypeVO>> getEnergyTypeList() {
+        return ApiResult.success(emsModelService.getEnergyTypeList());
+    }
+
+    /**
+     * 展示已关联的能源类型及其下属分项/产品列表
+     * 数据来源:leo.ems_energy_item_code.energy_type 分组
+     */
+    @GetMapping("/energy-type/associated")
+    public ApiResult<List<EnergyTypeWrapperProductVO>> showAssociatedEnergyTypes() {
+        return ApiResult.success(emsModelService.showAssociatedEnergyTypes());
+    }
+
+    @PostMapping("/building")
+    public ApiResult<EmsIdResponse> createBuilding(@RequestBody EmsModelSaveRequest request) {
+        Long id = emsModelService.createBuilding(request);
+        return ApiResult.success(new EmsIdResponse(id));
+    }
+
+    @PutMapping("/building/{id}")
+    public ApiResult<Void> updateBuilding(@PathVariable Long id, @RequestBody EmsModelSaveRequest request) {
+        emsModelService.updateBuilding(id, request);
+        return ApiResult.success();
+    }
+
+    @DeleteMapping("/building/{id}")
+    public ApiResult<Void> deleteBuilding(@PathVariable Long id) {
+        emsModelService.deleteBuilding(id);
+        return ApiResult.success();
+    }
+
+    @PostMapping("/region")
+    public ApiResult<EmsIdResponse> createRegion(@RequestBody EmsModelSaveRequest request) {
+        Long id = emsModelService.createRegion(request);
+        return ApiResult.success(new EmsIdResponse(id));
+    }
+
+    @PutMapping("/region/{id}")
+    public ApiResult<Void> updateRegion(@PathVariable Long id, @RequestBody EmsModelSaveRequest request) {
+        emsModelService.updateRegion(id, request);
+        return ApiResult.success();
+    }
+
+    @DeleteMapping("/region/{id}")
+    public ApiResult<Void> deleteRegion(@PathVariable Long id) {
+        emsModelService.deleteRegion(id);
+        return ApiResult.success();
+    }
+
+    @PostMapping("/gateway")
+    public ApiResult<EmsIdResponse> createGateway(@RequestBody EmsModelSaveRequest request) {
+        String id = emsModelService.createGateway(request);
+        return ApiResult.success(new EmsIdResponse(id));
+    }
+
+    @PutMapping("/gateway/{id}")
+    public ApiResult<Void> updateGateway(@PathVariable String id, @RequestBody EmsModelSaveRequest request) {
+        emsModelService.updateGateway(id, request);
+        return ApiResult.success();
+    }
+
+    @DeleteMapping("/gateway/{id}")
+    public ApiResult<Void> deleteGateway(@PathVariable String id) {
+        emsModelService.deleteGateway(id);
+        return ApiResult.success();
+    }
+
+//    @PostMapping("/channel")
+//    public ApiResult<EmsIdResponse> createChannel(@RequestBody EmsModelSaveRequest request) {
+//        Long id = emsModelService.createChannel(request);
+//        return ApiResult.success(new EmsIdResponse(id));
+//    }
+//
+//    @PutMapping("/channel/{id}")
+//    public ApiResult<Void> updateChannel(@PathVariable Long id, @RequestBody EmsModelSaveRequest request) {
+//        emsModelService.updateChannel(id, request);
+//        return ApiResult.success();
+//    }
+//
+//    @DeleteMapping("/channel/{id}")
+//    public ApiResult<Void> deleteChannel(@PathVariable Long id) {
+//        emsModelService.deleteChannel(id);
+//        return ApiResult.success();
+//    }
+//
+//    @PostMapping("/device")
+//    public ApiResult<EmsIdResponse> createDevice(@RequestBody EmsModelSaveRequest request) {
+//        String id = emsModelService.createDevice(request);
+//        return ApiResult.success(new EmsIdResponse(id));
+//    }
+//
+//    @PutMapping("/device/{id}")
+//    public ApiResult<Void> updateDevice(@PathVariable String id, @RequestBody EmsModelSaveRequest request) {
+//        emsModelService.updateDevice(id, request);
+//        return ApiResult.success();
+//    }
+//
+//    @DeleteMapping("/device/{id}")
+//    public ApiResult<Void> deleteDevice(@PathVariable String id) {
+//        emsModelService.deleteDevice(id);
+//        return ApiResult.success();
+//    }
+//
+//    @PostMapping("/attribute-point")
+//    public ApiResult<EmsIdResponse> createAttributePoint(@RequestBody EmsModelSaveRequest request) {
+//        Long id = emsModelService.createAttributePoint(request);
+//        return ApiResult.success(new EmsIdResponse(id));
+//    }
+//
+//    @PutMapping("/attribute-point/{id}")
+//    public ApiResult<Void> updateAttributePoint(@PathVariable Long id, @RequestBody EmsModelSaveRequest request) {
+//        emsModelService.updateAttributePoint(id, request);
+//        return ApiResult.success();
+//    }
+//
+//    @DeleteMapping("/attribute-point/{id}")
+//    public ApiResult<Void> deleteAttributePoint(@PathVariable Long id) {
+//        emsModelService.deleteAttributePoint(id);
+//        return ApiResult.success();
+//    }
+
+    /**
+     * 网关-通道-设备树(中间面板)
+     * GET /prod-api/service-ems/model/gateway-device/tree(与类上 /model 前缀组合,勿再嵌套 /model)
+     * @param spaceId 空间ID(可选,不传返回全部网关)
+     * @param keyword 网关名称关键字(可选,模糊)
+     */
+    @GetMapping("/gateway-device/tree")
+    public ApiResult<List<EmsGatewayDeviceTreeNode>> getGatewayDeviceTree(
+            @RequestParam(required = false) Long spaceId,
+            @RequestParam(required = false) String keyword) {
+        return ApiResult.success(emsModelService.getGatewayDeviceTree(spaceId, keyword));
+    }
+
+    /**
+     * 属性点位列表(右侧面板)
+     * GET /prod-api/service-ems/model/attribute-point/list
+     * @param deviceId 设备ID(必填)
+     */
+    @GetMapping("/attribute-point/list")
+    public ApiResult<List<EmsAttributePointVO>> getAttributePointList(
+            @RequestParam String deviceId) {
+        return ApiResult.success(emsModelService.getAttributePointList(deviceId));
+    }
+
+    /**
+     * 空间绑定网关(一个空间绑定一个或多个网关)
+     */
+    @PostMapping("/space/gateway/bind")
+    public ApiResult<Void> bindSpaceGateways(@RequestBody SpaceGatewayBindRequest request) {
+        emsModelService.bindSpaceGateways(request.getSpaceId(), request.getGatewayUuids());
+        return ApiResult.success();
+    }
+
+    /**
+     * 空间解绑网关(一个空间解绑一个或多个网关)
+     */
+    @PostMapping("/space/gateway/unbind")
+    public ApiResult<Void> unbindSpaceGateways(@RequestBody SpaceGatewayBindRequest request) {
+        emsModelService.unbindSpaceGateways(request.getSpaceId(), request.getGatewayUuids());
+        return ApiResult.success();
+    }
+
+    /**
+     * 根据空间ID查询网关列表
+     */
+    @GetMapping("/space/{spaceId}/gateway/list")
+    public ApiResult<List<DmpGatewayDetailResponse>> getGatewayListBySpaceId(@PathVariable Long spaceId) {
+        return ApiResult.success(emsModelService.getGatewayListBySpaceId(spaceId));
+    }
+
+
+}

+ 132 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsOverviewController.java

@@ -0,0 +1,132 @@
+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;
+import com.usky.ems.service.vo.EmsSummaryResponse;
+import com.usky.ems.service.vo.EmsOverviewDeviceSystemStatVO;
+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})。
+ * 基础路径前缀:/overview(网关再加 /prod-api/service-ems)
+ */
+@RestController
+@RequestMapping("/overview")
+public class EmsOverviewController {
+
+    @Autowired
+    private EmsOverviewService emsOverviewService;
+
+    /**
+     * 获取项目信息
+     */
+    @GetMapping("/project")
+    public ApiResult<EmsProjectResponse> getProject(
+            @RequestParam(required = false) Integer projectId) {
+        return ApiResult.success(emsOverviewService.getProject(projectId));
+    }
+
+    /**
+     * 获取项目数据概括(时间维度联动)
+     */
+    @GetMapping("/summary")
+    public ApiResult<EmsSummaryResponse> getSummary(EmsSummaryRequest request) {
+        return ApiResult.success(emsOverviewService.getSummary(
+                request.getProjectId(), request.getTimeDimension(), request.getTimeValue()));
+    }
+
+    /**
+     * 获取概览页能源类型条目(如电/水/气)
+     */
+    @GetMapping("/item")
+    public ApiResult<List<EmsOverviewEnergyItemVO>> queryOverviewItem() {
+        return ApiResult.success(emsOverviewService.queryOverviewItem());
+    }
+
+    /**
+     * 获取概览页设备系统统计(每个系统下设备数量)
+     */
+    @GetMapping("/device-info")
+    public ApiResult<List<EmsOverviewDeviceSystemStatVO>> queryOverviewDeviceInfo(
+            @RequestParam(required = false) Long projectId) {
+        return ApiResult.success(emsOverviewService.queryOverviewDeviceInfo(projectId));
+    }
+
+    /**
+     * 分类能耗统计(按能源类型关联产品,调用 TSDB 分项汇总)
+     * 参数说明:
+     * - dateType:时间类型(1-日,2-月,3-年)
+     * - energyType:能源类型(1电 2水 3冷 4热;分项 identifier 从 ems_energy_item_code 根节点解析)
+     * - projectId:项目 ID(可选,不传则取当前租户第一个项目;用于查询折算系数)
+     */
+    @GetMapping("/classification-energy")
+    public ApiResult<Map<String, Object>> queryClassificationEnergy(
+            @RequestParam Integer dateType,
+            @RequestParam Integer energyType,
+            @RequestParam(required = false) Long projectId) {
+        return ApiResult.success(emsOverviewService.queryClassificationEnergy(dateType, energyType, projectId));
+    }
+
+    /**
+     * 能耗用能趋势(按能源类型关联产品,调用 TSDB 分项按时间粒度汇总,并与去年同期对比)
+     * 参数说明:
+     * - dateType:时间类型(1-日/小时,2-月/天,3-年/月)
+     * - identifier:分项字段编码
+     * - energyType:能源类型(1电 2水 3冷 4热)
+     */
+    @GetMapping("/energy-trend")
+    public ApiResult<List<Map<String, Object>>> queryEnergyTrend(
+            @RequestParam Integer dateType,
+            @RequestParam Integer energyType) {
+        return ApiResult.success(emsOverviewService.queryEnergyTrend(dateType, energyType));
+    }
+
+    /**
+     * 建筑能耗分析(按建筑排名)
+     * 参数说明:
+     * - dateType:时间类型(1-日,2-月,3-年)
+     * - itemCode:能耗分项字段编码
+     * - energyType:能源类型(1电 2水 3冷 4热)
+     */
+    @GetMapping("/building-ranking")
+    public ApiResult<List<Map<String, Object>>> queryBuildingRanking(
+            @RequestParam Integer dateType,
+            @RequestParam Integer energyType) {
+        return ApiResult.success(emsOverviewService.queryBuildingRanking(dateType, energyType));
+    }
+
+    /**
+     * 单位综合能耗、综合累计能耗、分类能耗占比(总览顶部)
+     * 参数说明:
+     * - dateType:时间类型(1-日,2-月,3-年)
+     * - projectId:项目ID(可选,不传则取第一个项目)
+     */
+    @GetMapping("/top")
+    public ApiResult<Map<String, Object>> queryOverviewTop(
+            @RequestParam Integer dateType,
+            @RequestParam(required = false) Long projectId) {
+        return ApiResult.success(emsOverviewService.queryOverviewTop(dateType, projectId));
+    }
+
+    /**
+     * 分项能耗占比
+     * 参数说明:
+     * - dateType:时间类型(1-日,2-月,3-年)
+     * - energyType:能源类型(1电 2水 3冷 4热;根分项从 ems_energy_item_code 解析)
+     */
+    @GetMapping("/item-ratio")
+    public ApiResult<List<Map<String, Object>>> queryItemRatio(
+            @RequestParam Integer dateType,
+            @RequestParam Integer energyType) {
+        return ApiResult.success(emsOverviewService.queryItemRatio(dateType, energyType));
+    }
+}

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

@@ -0,0 +1,37 @@
+package com.usky.ems.controller.web;
+
+import com.usky.common.core.bean.ApiResult;
+import com.usky.ems.service.EmsProjectService;
+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.*;
+
+/**
+ * 项目(ems_project)新增、修改、删除
+ * 基础路径前缀:/project(网关再加 /prod-api/service-ems)
+ */
+@RestController
+@RequestMapping("/project")
+public class EmsProjectController {
+
+    @Autowired
+    private EmsProjectService emsProjectService;
+
+    @PostMapping
+    public ApiResult<EmsProjectResponse> create(@RequestBody EmsProjectSaveRequest request) {
+        return ApiResult.success(emsProjectService.save(request));
+    }
+
+    @PutMapping
+    public ApiResult<Void> update(@RequestBody EmsProjectSaveRequest request) {
+        emsProjectService.update(request);
+        return ApiResult.success();
+    }
+
+    @DeleteMapping("/{spaceId}")
+    public ApiResult<Void> delete(@PathVariable Long spaceId) {
+        emsProjectService.remove(spaceId);
+        return ApiResult.success();
+    }
+}

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

@@ -0,0 +1,112 @@
+package com.usky.ems.controller.web;
+
+import com.usky.common.core.bean.ApiResult;
+import com.usky.ems.service.EmsReportService;
+import com.usky.ems.service.EmsModelService;
+import com.usky.ems.service.vo.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 统计报表接口
+ * 能源报表、区域报表、采集报表
+ * 基础路径前缀:/report(网关再加 /prod-api/service-ems)
+ */
+@RestController
+@RequestMapping("/report")
+public class EmsReportController {
+
+    @Autowired
+    private EmsReportService emsReportService;
+
+    @Autowired
+    private EmsModelService emsModelService;
+
+    // ---------- 能源报表 ----------
+    @GetMapping("/energy/devices")
+    public ApiResult<List<EnergyTypeWrapperProductVO>> getEnergyDevices(
+            @RequestParam(required = false) Long energyTypeId,
+            @RequestParam(required = false) String keyword,
+            @RequestParam(required = false) Long projectId) {
+        // TODO: 如需按 energyTypeId/keyword/projectId 过滤,可在 EmsModelService 中扩展方法
+        return ApiResult.success(emsModelService.showAssociatedEnergyTypes());
+    }
+
+    @PostMapping("/energy/statistics")
+    public ApiResult<EmsEnergyStatisticsResponse> getEnergyStatistics(@RequestBody EmsEnergyStatisticsRequest request) {
+        return ApiResult.success(emsReportService.getEnergyStatistics(request));
+    }
+
+    @GetMapping("/energy/export")
+    public void exportEnergy(
+            @RequestParam String deviceIds,
+            @RequestParam(required = false) String attributePointIds,
+            @RequestParam String timeDimension,
+            @RequestParam String timeValue,
+            @RequestParam(required = false, defaultValue = "excel") String format,
+            HttpServletResponse response) {
+        emsReportService.exportEnergy(deviceIds, attributePointIds, timeDimension, timeValue, format, response);
+    }
+
+    // ---------- 区域报表 ----------
+    @GetMapping("/region/devices")
+    public ApiResult<EmsReportDevicesResponse> getRegionDevices(
+            @RequestParam Long energyTypeId,
+            @RequestParam(required = false) Long regionId,
+            @RequestParam(required = false) String keyword,
+            @RequestParam(required = false) Long projectId) {
+        return ApiResult.success(emsReportService.getRegionDevices(energyTypeId, regionId, keyword, projectId));
+    }
+
+    @PostMapping("/region/statistics")
+    public ApiResult<EmsEnergyStatisticsResponse> getRegionStatistics(
+            @RequestBody EmsEnergyStatisticsRequest request,
+            @RequestParam(required = false) List<Long> regionIds) {
+        return ApiResult.success(emsReportService.getRegionStatistics(request, regionIds));
+    }
+
+    @GetMapping("/region/export")
+    public void exportRegion(
+            @RequestParam String deviceIds,
+            @RequestParam(required = false) String attributePointIds,
+            @RequestParam String timeDimension,
+            @RequestParam String timeValue,
+            @RequestParam(required = false) String regionIds,
+            @RequestParam(required = false, defaultValue = "excel") String format,
+            HttpServletResponse response) {
+        emsReportService.exportRegion(deviceIds, attributePointIds, timeDimension, timeValue, regionIds, format, response);
+    }
+
+    // ---------- 采集报表 ----------
+    @GetMapping("/collection/devices")
+    public ApiResult<EmsReportDevicesResponse> getCollectionDevices(
+            @RequestParam Long energyTypeId,
+            @RequestParam(required = false) String keyword,
+            @RequestParam(required = false) Long projectId) {
+        return ApiResult.success(emsReportService.getCollectionDevices(energyTypeId, keyword, projectId));
+    }
+
+    @PostMapping("/collection/realtime")
+    public ApiResult<EmsCollectionRealtimeResponse> getCollectionRealtime(@RequestBody EmsCollectionRealtimeRequest request) {
+        return ApiResult.success(emsReportService.getCollectionRealtime(request));
+    }
+
+    @PostMapping("/collection/statistics")
+    public ApiResult<EmsEnergyStatisticsResponse> getCollectionStatistics(@RequestBody EmsEnergyStatisticsRequest request) {
+        return ApiResult.success(emsReportService.getCollectionStatistics(request));
+    }
+
+    @GetMapping("/collection/export")
+    public void exportCollection(
+            @RequestParam String deviceIds,
+            @RequestParam(required = false) String attributePointIds,
+            @RequestParam String timeDimension,
+            @RequestParam String timeValue,
+            @RequestParam(required = false, defaultValue = "excel") String format,
+            HttpServletResponse response) {
+        emsReportService.exportCollection(deviceIds, attributePointIds, timeDimension, timeValue, format, response);
+    }
+}

+ 54 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsSystemDictController.java

@@ -0,0 +1,54 @@
+package com.usky.ems.controller.web;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.usky.common.core.bean.ApiResult;
+import com.usky.ems.domain.EmsSystemDictCode;
+import com.usky.ems.domain.EmsSystemDictRegion;
+import com.usky.ems.domain.EmsSystemDictValue;
+import com.usky.ems.service.EmsSystemDictCodeService;
+import com.usky.ems.service.EmsSystemDictRegionService;
+import com.usky.ems.service.EmsSystemDictValueService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * 系统字典(ems_system_dict_code / ems_system_dict_region / ems_system_dict_value)查询接口
+ * 基础路径前缀:/system-dict
+ */
+@RestController
+@RequestMapping("/system-dict")
+public class EmsSystemDictController {
+
+    @Autowired
+    private EmsSystemDictCodeService emsSystemDictCodeService;
+    @Autowired
+    private EmsSystemDictRegionService emsSystemDictRegionService;
+    @Autowired
+    private EmsSystemDictValueService emsSystemDictValueService;
+
+    @GetMapping("/code/list")
+    public ApiResult<List<EmsSystemDictCode>> listCodes() {
+        return ApiResult.success(emsSystemDictCodeService.list());
+    }
+
+    /**
+     * @param parent 父级区域编码(如国标省市区上一级 code;省级可传约定根编码,需与字典数据一致)
+     */
+    @GetMapping("/region/list")
+    public ApiResult<List<EmsSystemDictRegion>> listRegions(@RequestParam String parent) {
+        return ApiResult.success(emsSystemDictRegionService.listByParentCode(parent));
+    }
+
+    @GetMapping("/value/list")
+    public ApiResult<List<EmsSystemDictValue>> listValues(@RequestParam String dictCode) {
+        return ApiResult.success(emsSystemDictValueService.list(
+                new LambdaQueryWrapper<EmsSystemDictValue>()
+                        .eq(EmsSystemDictValue::getDictCode, dictCode)
+                        .orderByAsc(EmsSystemDictValue::getSort)));
+    }
+}

+ 46 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/BaseArea.java

@@ -0,0 +1,46 @@
+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;
+
+/**
+ * 区域(base_area)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("base_area")
+public class BaseArea implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    private String name;
+    private Integer type;
+    private BigDecimal area;
+    @TableField("common_area")
+    private BigDecimal commonArea;
+    @TableField("air_conditioned_area")
+    private BigDecimal airConditionedArea;
+    @TableField("resident_population")
+    private Integer residentPopulation;
+    @TableField("tenant_id")
+    private Integer tenantId;
+    @TableField("updated_by")
+    private String updatedBy;
+    @TableField("created_by")
+    private String createdBy;
+    @TableField("update_time")
+    private LocalDateTime updateTime;
+    @TableField("create_time")
+    private LocalDateTime createTime;
+}

+ 97 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/BaseBuild.java

@@ -0,0 +1,97 @@
+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.LocalDate;
+import java.time.LocalDateTime;
+
+/**
+ * 建筑信息(base_build),字段与表结构一致
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("base_build")
+public class BaseBuild implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @TableField("build_num")
+    private String buildNum;
+    @TableField("build_name")
+    private String buildName;
+    private String address;
+    @TableField("model_address")
+    private String modelAddress;
+    @TableField("above_floor")
+    private Integer aboveFloor;
+    @TableField("under_floor")
+    private Integer underFloor;
+    @TableField("build_area")
+    private Double buildArea;
+    @TableField("cover_area")
+    private Double coverArea;
+    @TableField("fire_rating")
+    private Integer fireRating;
+    @TableField("use_character")
+    private Integer useCharacter;
+    @TableField("build_structure")
+    private Integer buildStructure;
+    @TableField("build_high")
+    private Double buildHigh;
+    @TableField("high_type")
+    private Integer highType;
+    @TableField("complete_year")
+    private LocalDate completeYear;
+    @TableField("safe_person")
+    private String safePerson;
+    @TableField("manage_person")
+    private String managePerson;
+    @TableField("fire_risk")
+    private Integer fireRisk;
+    @TableField("fire_control_room")
+    private String fireControlRoom;
+    @TableField("build_inside")
+    private String buildInside;
+    @TableField("build_plan")
+    private String buildPlan;
+    @TableField("facility_id")
+    private Integer facilityId;
+    @TableField("bim_url")
+    private String bimUrl;
+    @TableField("contact_phone")
+    private String contactPhone;
+    @TableField("build_desc")
+    private String buildDesc;
+    @TableField("create_time")
+    private LocalDateTime createTime;
+    @TableField("update_time")
+    private LocalDateTime updateTime;
+    @TableField("update_by")
+    private String updateBy;
+    @TableField("create_by")
+    private String createBy;
+    @TableField("delete_flag")
+    private Integer deleteFlag;
+    @TableField("under_space")
+    private Double underSpace;
+    /** 防火涂层:0 无、1 有 */
+    @TableField("fireproof_coat")
+    private Integer fireproofCoat;
+    @TableField("dept_id")
+    private Integer deptId;
+    @TableField("tenant_id")
+    private Integer tenantId;
+    private String longitude;
+    private String latitude;
+    @TableField("unit_count")
+    private Integer unitCount;
+}

+ 45 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/BaseSpace.java

@@ -0,0 +1,45 @@
+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;
+
+/**
+ * 空间(base_space)
+ * 空间类型:1项目 2区域 3建筑
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("base_space")
+public class BaseSpace implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    private String name;
+    @TableField("parent_id")
+    private Long parentId;
+    private Integer type;
+    @TableField("root_id")
+    private Long rootId;
+    private String path;
+    private Integer deep;
+    @TableField("updated_by")
+    private String updatedBy;
+    @TableField("update_time")
+    private LocalDateTime updateTime;
+    @TableField("created_by")
+    private String createdBy;
+    @TableField("create_time")
+    private LocalDateTime createTime;
+    @TableField("tenant_id")
+    private Integer tenantId;
+}

+ 34 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/BaseSpaceArea.java

@@ -0,0 +1,34 @@
+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;
+
+/**
+ * 空间-区域关联(base_space_area)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("base_space_area")
+public class BaseSpaceArea implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @TableField("space_id")
+    private Long spaceId;
+    @TableField("area_id")
+    private Long areaId;
+    @TableField("created_by")
+    private String createdBy;
+    @TableField("created_time")
+    private LocalDateTime createdTime;
+}

+ 34 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/BaseSpaceBuild.java

@@ -0,0 +1,34 @@
+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;
+
+/**
+ * 空间-建筑关联(base_space_build)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("base_space_build")
+public class BaseSpaceBuild implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @TableField("space_id")
+    private Long spaceId;
+    @TableField("build_id")
+    private Integer buildId;
+    @TableField("created_by")
+    private String createdBy;
+    @TableField("created_time")
+    private LocalDateTime createdTime;
+}

+ 34 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/BaseSpaceGateway.java

@@ -0,0 +1,34 @@
+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;
+
+/**
+ * 空间-网关关联(base_space_gateway)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("base_space_gateway")
+public class BaseSpaceGateway implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @TableField("space_id")
+    private Long spaceId;
+    @TableField("gateway_uuid")
+    private String gatewayUuid;
+    @TableField("created_by")
+    private String createdBy;
+    @TableField("created_time")
+    private LocalDateTime createdTime;
+}

+ 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;
+}

+ 72 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/DmpGateway.java

@@ -0,0 +1,72 @@
+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;
+
+/**
+ * 网关表(dmp_gateway)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("dmp_gateway")
+public class DmpGateway implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @TableField("device_uuid")
+    private String deviceUuid;
+
+    private String name;
+
+    private String ip;
+
+    private Integer port;
+
+    private String installAddress;
+
+    @TableField("comm_status")
+    private Integer commStatus;
+
+    @TableField("online_time")
+    private LocalDateTime onlineTime;
+
+    @TableField("offline_time")
+    private LocalDateTime offlineTime;
+
+    @TableField("update_config_time")
+    private LocalDateTime updateConfigTime;
+
+    @TableField("update_protocol_time")
+    private LocalDateTime updateProtocolTime;
+
+    @TableField("upgrade_time")
+    private LocalDateTime upgradeTime;
+
+    @TableField("virtual_device")
+    private Integer virtualDevice;
+
+    private String remark;
+
+    @TableField("updated_by")
+    private String updatedBy;
+
+    @TableField("update_time")
+    private LocalDateTime updateTime;
+
+    @TableField("created_by")
+    private String createdBy;
+
+    @TableField("create_time")
+    private LocalDateTime createTime;
+    private Integer tenantId;
+}

+ 92 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/DmpProduct.java

@@ -0,0 +1,92 @@
+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_product)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("dmp_product")
+public class DmpProduct implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /** 主键id */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /** 产品名称 */
+    private String productName;
+
+    /** 接入方式(1 设备直连;2 网关接入) */
+    private Integer accessMode;
+
+    /** 网络类型(1 WIFI;2 移动蜂窝数据;3 NB-IoT;4 以太网) */
+    private Integer networkType;
+
+    /** 设备类型(501 监控系统;502 门禁系统;503 梯控系统;504 机房系统;509 环境系统;510 照明系统) */
+    private Integer deviceType;
+
+    /** 通信协议(1 MQTT;2 TCP 设备直连;3 HTTP) */
+    private Integer comProtocol;
+
+    /** 认证方式 */
+    private String authMode;
+
+    /** 设备型号 */
+    private String deviceModel;
+
+    /** 产品描述 */
+    private String productDescribe;
+
+    /** 厂家名称 */
+    private String factoryName;
+
+    /** 厂家联系人 */
+    private String factoryPerson;
+
+    /** 厂家联系电话 */
+    private String factoryPhone;
+
+    /** 资质证书1 */
+    private String certificateUrl1;
+
+    /** 资质证书2 */
+    private String certificateUrl2;
+
+    /** 资质证书3 */
+    private String certificateUrl3;
+
+    /** 协议文档 */
+    private String agreementUrl;
+
+    /** 删除标识 */
+    private Integer deleteFlag;
+
+    /** 创建人 */
+    private String createdBy;
+
+    /** 创建时间 */
+    private LocalDateTime createdTime;
+
+    /** 更新人 */
+    private String updatedBy;
+
+    /** 更新时间 */
+    private LocalDateTime updatedTime;
+
+    /** 租户号 */
+    private Integer tenantId;
+
+    /** 产品编码 */
+    private String productCode;
+}
+

+ 40 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsChannel.java

@@ -0,0 +1,40 @@
+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;
+
+/**
+ * 网关通道(leo.ems_channel)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("ems_channel")
+public class EmsChannel implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @TableField("gateway_id")
+    private String gatewayId;
+    private String name;
+    @TableField("channel_type_id")
+    // 通道类型ID(1:Serial Port,2:TCP)
+    private Integer channelTypeId;
+    @TableField("updated_by")
+    private Long updatedBy;
+    @TableField("update_time")
+    private LocalDateTime updateTime;
+    @TableField("created_by")
+    private Long createdBy;
+    @TableField("create_time")
+    private LocalDateTime createTime;
+}

+ 61 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsChannelType.java

@@ -0,0 +1,61 @@
+package com.usky.ems.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 ya
+ * @since 2026-03-16
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class EmsChannelType implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 自增ID
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 类型名称
+     */
+    private String name;
+
+    /**
+     * 类型值
+     */
+    private String value;
+
+    /**
+     * 更新人
+     */
+    private Long updatedBy;
+
+    /**
+     * 记录更新时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 创建人
+     */
+    private Long createdBy;
+
+    /**
+     * 记录创建时间
+     */
+    private LocalDateTime createTime;
+
+
+}

+ 71 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsChannelTypeParameter.java

@@ -0,0 +1,71 @@
+package com.usky.ems.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 ya
+ * @since 2026-03-16
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class EmsChannelTypeParameter implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 自增ID
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 通道类型ID
+     */
+    private Integer channelTypeId;
+
+    /**
+     * 参数名称
+     */
+    private String name;
+
+    /**
+     * 参数值
+     */
+    private String value;
+
+    /**
+     * 排序
+     */
+    private Integer sort;
+
+    /**
+     * 更新人
+     */
+    private Long updatedBy;
+
+    /**
+     * 记录更新时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 创建人
+     */
+    private Long createdBy;
+
+    /**
+     * 记录创建时间
+     */
+    private LocalDateTime createTime;
+
+
+}

+ 71 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsChannelTypeParameterValue.java

@@ -0,0 +1,71 @@
+package com.usky.ems.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 ya
+ * @since 2026-03-16
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class EmsChannelTypeParameterValue implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 自增ID
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 通道类型参数ID
+     */
+    private Integer channelTypeParameterId;
+
+    /**
+     * 可选值名称
+     */
+    private String name;
+
+    /**
+     * 可选值
+     */
+    private String value;
+
+    /**
+     * 排序
+     */
+    private Integer sort;
+
+    /**
+     * 更新人
+     */
+    private Long updatedBy;
+
+    /**
+     * 记录更新时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 创建人
+     */
+    private Long createdBy;
+
+    /**
+     * 记录创建时间
+     */
+    private LocalDateTime createTime;
+
+
+}

+ 47 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsDataUploadShanghaiProduct.java

@@ -0,0 +1,47 @@
+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;
+
+/**
+ * 上海市区平台数据上传(产品功能标识对应的区平台功能标识)
+ * 表:ems_data_upload_shanghai_product
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("ems_data_upload_shanghai_product")
+public class EmsDataUploadShanghaiProduct implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /** 产品ID */
+    private Long productId;
+
+    /** 功能标识 */
+    private String identifier;
+
+    /** 自定义标识符 */
+    private String customIdentifier;
+
+    /** 更新人 */
+    private Long updatedBy;
+
+    /** 记录更新时间 */
+    private LocalDateTime updateTime;
+
+    /** 创建人 */
+    private Long createdBy;
+
+    /** 记录创建时间 */
+    private LocalDateTime createTime;
+}
+

+ 69 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsDevice.java

@@ -0,0 +1,69 @@
+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;
+
+/**
+ * 设备(leo.ems_device)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("ems_device")
+public class EmsDevice implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.INPUT)
+    private String id;
+
+    @TableField("project_id")
+    private Long projectId;
+    private String number;
+    private String name;
+    @TableField("product_id")
+    private Long productId;
+    @TableField("product_template_id")
+    private Long productTemplateId;
+    @TableField("installation_location")
+    private Long installationLocation;
+    @TableField("monitoring_location")
+    private Long monitoringLocation;
+    private String location;
+    @TableField("comm_address")
+    private String commAddress;
+    @TableField("channel_id")
+    private Long channelId;
+    @TableField("gateway_id")
+    private String gatewayId;
+    @TableField("virtual_device")
+    private Integer virtualDevice;
+    private Integer focus;
+    @TableField("device_system")
+    private Integer deviceSystem;
+    private Integer status;
+    @TableField("comm_status")
+    private Integer commStatus;
+    @TableField("comm_status_code")
+    private String commStatusCode;
+    @TableField("online_time")
+    private LocalDateTime onlineTime;
+    @TableField("offline_time")
+    private LocalDateTime offlineTime;
+    @TableField("external_id")
+    private String externalId;
+    @TableField("updated_by")
+    private Long updatedBy;
+    @TableField("update_time")
+    private LocalDateTime updateTime;
+    @TableField("created_by")
+    private Long createdBy;
+    @TableField("create_time")
+    private LocalDateTime createTime;
+}

+ 71 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsDeviceEvent.java

@@ -0,0 +1,71 @@
+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;
+
+/**
+ * 设备事件记录(leo.ems_device_event)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("ems_device_event")
+public class EmsDeviceEvent implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @TableField("project_id")
+    private Long projectId;
+
+    private String sn;
+
+    @TableField("device_id")
+    private String deviceId;
+
+    private String identifier;
+
+    /** 设备类型 1:设备 2:网关 */
+    @TableField("device_type")
+    private Integer deviceType;
+
+    /** 事件类型 1:通讯告警 2:功能告警 3:数据异常 */
+    @TableField("event_type")
+    private Integer eventType;
+
+    @TableField("suppressed_to")
+    private LocalDateTime suppressedTo;
+
+    /** 确认类型 0:未确认 1:自动确认 2:人工确认 */
+    @TableField("confirmation_type")
+    private Integer confirmationType;
+
+    @TableField("task_sn")
+    private String taskSn;
+
+    private String content;
+
+    @TableField("start_time")
+    private LocalDateTime startTime;
+
+    @TableField("updated_by")
+    private Long updatedBy;
+
+    @TableField("update_time")
+    private LocalDateTime updateTime;
+
+    @TableField("created_by")
+    private Long createdBy;
+
+    @TableField("create_time")
+    private LocalDateTime createTime;
+}
+

+ 50 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsDeviceFunction.java

@@ -0,0 +1,50 @@
+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;
+
+/**
+ * 设备功能数据/属性点位(leo.ems_device_function)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("ems_device_function")
+public class EmsDeviceFunction implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @TableField("product_id")
+    private Long productId;
+    @TableField("product_template_id")
+    private Long productTemplateId;
+    @TableField("device_id")
+    private String deviceId;
+    private String identifier;
+    private String name;
+    private String value;
+    @TableField("acq_time")
+    private LocalDateTime acqTime;
+    private BigDecimal ratio;
+    private Integer preservable;
+    @TableField("binding_acq")
+    private Integer bindingAcq;
+    @TableField("updated_by")
+    private Long updatedBy;
+    @TableField("update_time")
+    private LocalDateTime updateTime;
+    @TableField("created_by")
+    private Long createdBy;
+    @TableField("create_time")
+    private LocalDateTime createTime;
+}

+ 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;
+}

+ 61 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsEnergyConsumptionFormula.java

@@ -0,0 +1,61 @@
+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;
+
+/**
+ * 空间能耗计算公式(ems_energy_consumption_formula)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("ems_energy_consumption_formula")
+public class EmsEnergyConsumptionFormula implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @TableField("space_id")
+    private Long spaceId;
+
+    @TableField("item_code")
+    private String itemCode;
+
+    private String name;
+
+    private String formula;
+
+    @TableField("compute_start_time")
+    private LocalDateTime computeStartTime;
+
+    @TableField("compute_end_time")
+    private LocalDateTime computeEndTime;
+
+    @TableField("data_time")
+    private LocalDateTime dataTime;
+
+    private Integer status;
+
+    private String cause;
+
+    @TableField("updated_by")
+    private Long updatedBy;
+
+    @TableField("update_time")
+    private LocalDateTime updateTime;
+
+    @TableField("created_by")
+    private Long createdBy;
+
+    @TableField("create_time")
+    private LocalDateTime createTime;
+}
+

+ 44 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsEnergyItemCode.java

@@ -0,0 +1,44 @@
+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;
+
+/**
+ * 能源分项编码(leo.ems_energy_item_code),用于能源类型列表
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("ems_energy_item_code")
+public class EmsEnergyItemCode implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    private String code;
+    @TableField("parent_code")
+    private String parentCode;
+    private String unit;
+    @TableField("unit_name")
+    private String unitName;
+    private String name;
+    private String identifier;
+    @TableField("energy_type")
+    private Integer energyType;
+    @TableField("updated_by")
+    private Long updatedBy;
+    @TableField("update_time")
+    private LocalDateTime updateTime;
+    @TableField("created_by")
+    private Long createdBy;
+    @TableField("create_time")
+    private LocalDateTime createTime;
+}

+ 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;
+}

+ 74 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsProject.java

@@ -0,0 +1,74 @@
+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)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("ems_project")
+public class EmsProject implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @TableField("space_id")
+    private Long spaceId;
+
+    private String name;
+    @TableField("platform_name")
+    private String platformName;
+    private String abbreviation;
+    private BigDecimal area;
+    @TableField("common_area")
+    private BigDecimal commonArea;
+    @TableField("air_conditioned_area")
+    private BigDecimal airConditionedArea;
+    @TableField("resident_population")
+    private Integer residentPopulation;
+    @TableField("province_code")
+    private String provinceCode;
+    @TableField("province_name")
+    private String provinceName;
+    @TableField("city_code")
+    private String cityCode;
+    @TableField("city_name")
+    private String cityName;
+    @TableField("district_code")
+    private String districtCode;
+    @TableField("district_name")
+    private String districtName;
+    private String location;
+    private String address;
+    @TableField("type_id")
+    private Integer typeId;
+    @TableField("type_name")
+    private String typeName;
+    private String image;
+    private String introduction;
+    private String logo;
+    @TableField("logo_min")
+    private String logoMin;
+    @TableField("tenant_id")
+    private Integer tenantId;
+    @TableField("updated_by")
+    private String updatedBy;
+    @TableField("update_time")
+    private LocalDateTime updateTime;
+    @TableField("created_by")
+    private String createdBy;
+    @TableField("create_time")
+    private LocalDateTime createTime;
+}

+ 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;
+}

+ 39 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsProjectDeviceSystem.java

@@ -0,0 +1,39 @@
+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;
+
+/**
+ * 项目与设备系统关联(ems_project_device_system)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("ems_project_device_system")
+public class EmsProjectDeviceSystem implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @TableField("project_id")
+    private Long projectId;
+
+    @TableField("device_system")
+    private Integer deviceSystem;
+    @TableField("updated_by")
+    private String updatedBy;
+    @TableField("update_time")
+    private LocalDateTime updateTime;
+    @TableField("created_by")
+    private String createdBy;
+    @TableField("create_time")
+    private LocalDateTime createTime;
+}

+ 37 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsSystemDictCode.java

@@ -0,0 +1,37 @@
+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;
+
+/**
+ * 系统字典编码(ems_system_dict_code)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("ems_system_dict_code")
+public class EmsSystemDictCode implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    private String name;
+    private String code;
+    private String remark;
+    @TableField("updated_by")
+    private Long updatedBy;
+    @TableField("update_time")
+    private LocalDateTime updateTime;
+    @TableField("created_by")
+    private Long createdBy;
+    @TableField("create_time")
+    private LocalDateTime createTime;
+}

+ 37 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsSystemDictRegion.java

@@ -0,0 +1,37 @@
+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;
+
+/**
+ * 省市区字典(ems_system_dict_region)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("ems_system_dict_region")
+public class EmsSystemDictRegion implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    private String code;
+    private String name;
+    private String parent;
+    @TableField("updated_by")
+    private Long updatedBy;
+    @TableField("update_time")
+    private LocalDateTime updateTime;
+    @TableField("created_by")
+    private Long createdBy;
+    @TableField("create_time")
+    private LocalDateTime createTime;
+}

+ 43 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsSystemDictValue.java

@@ -0,0 +1,43 @@
+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;
+
+/**
+ * 系统字典值(ems_system_dict_value)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("ems_system_dict_value")
+public class EmsSystemDictValue implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @TableField("dict_code")
+    private String dictCode;
+    @TableField("parent_id")
+    private Integer parentId;
+    private String name;
+    private String value;
+    private String icon;
+    private Integer sort;
+    private String remark;
+    @TableField("updated_by")
+    private Long updatedBy;
+    @TableField("update_time")
+    private LocalDateTime updateTime;
+    @TableField("created_by")
+    private Long createdBy;
+    @TableField("create_time")
+    private LocalDateTime createTime;
+}

+ 82 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/enums/TemporalTypeEnum.java

@@ -0,0 +1,82 @@
+package com.usky.ems.enums;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+
+/**
+ * 时间维度枚举:D-日 / M-月 / Y-年
+ */
+public enum TemporalTypeEnum {
+
+    DAY("D"),
+    MONTH("M"),
+    YEAR("Y");
+
+    private final String code;
+
+    TemporalTypeEnum(String code) {
+        this.code = code;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public static TemporalTypeEnum get(String code) {
+        if (code == null) {
+            return null;
+        }
+        for (TemporalTypeEnum e : values()) {
+            if (e.code.equalsIgnoreCase(code)) {
+                return e;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 将开始时间归一化到当前时间维度的起点
+     */
+    public LocalDateTime normalizeStart(LocalDateTime time) {
+        if (time == null) {
+            return null;
+        }
+        LocalDate date = time.toLocalDate();
+        switch (this) {
+            case DAY:
+                return LocalDateTime.of(date, LocalTime.MIN);
+            case MONTH:
+                LocalDate firstDayOfMonth = date.withDayOfMonth(1);
+                return LocalDateTime.of(firstDayOfMonth, LocalTime.MIN);
+            case YEAR:
+                LocalDate firstDayOfYear = date.withDayOfYear(1);
+                return LocalDateTime.of(firstDayOfYear, LocalTime.MIN);
+            default:
+                return time;
+        }
+    }
+
+    /**
+     * 将结束时间归一化到当前时间维度的结束
+     */
+    public LocalDateTime normalizeEnd(LocalDateTime time) {
+        if (time == null) {
+            return null;
+        }
+        LocalDate date = time.toLocalDate();
+        switch (this) {
+            case DAY:
+                return LocalDateTime.of(date, LocalTime.MAX);
+            case MONTH:
+                LocalDate lastDayOfMonth = date.withDayOfMonth(date.lengthOfMonth());
+                return LocalDateTime.of(lastDayOfMonth, LocalTime.MAX);
+            case YEAR:
+                LocalDate lastDayOfYear = date.withDayOfYear(date.lengthOfYear());
+                return LocalDateTime.of(lastDayOfYear, LocalTime.MAX);
+            default:
+                return time;
+        }
+    }
+}
+

+ 55 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/enums/ValueTypeEnum.java

@@ -0,0 +1,55 @@
+package com.usky.ems.enums;
+
+import java.util.Objects;
+
+/**
+ * 数值类型枚举,对应 leo.sql 中 ems_device_function_formula.value_type 字段:
+ * 0: 其他  1: 累计值  2: 波动值
+ */
+public enum ValueTypeEnum {
+
+    OTHER("其他", 0),
+    CUMULATIVE("累计值", 1),
+    FLUCTUATION("波动值", 2);
+
+    private final String name;
+    private final Short value;
+
+    ValueTypeEnum(String name, Integer value) {
+        this.name = name;
+        this.value = value.shortValue();
+    }
+
+    /**
+     * 根据数值获取名称
+     */
+    public static String getName(Short type) {
+        for (ValueTypeEnum e : values()) {
+            if (Objects.equals(e.getValue(), type)) {
+                return e.getName();
+            }
+        }
+        return "";
+    }
+
+    /**
+     * 根据数值获取枚举实例;如果不存在则抛出异常
+     */
+    public static ValueTypeEnum getInstance(Short type) {
+        for (ValueTypeEnum e : values()) {
+            if (Objects.equals(e.getValue(), type)) {
+                return e;
+            }
+        }
+        throw new IllegalArgumentException("错误的值类型");
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public Short getValue() {
+        return value;
+    }
+}
+

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

@@ -0,0 +1,10 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.BaseArea;
+
+/**
+ * 区域 Mapper(base_area)
+ */
+public interface BaseAreaMapper extends CrudMapper<BaseArea> {
+}

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

@@ -0,0 +1,10 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.BaseBuild;
+
+/**
+ * 建筑 Mapper(base_build)
+ */
+public interface BaseBuildMapper extends CrudMapper<BaseBuild> {
+}

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

@@ -0,0 +1,10 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.BaseSpaceArea;
+
+/**
+ * 空间-区域关联 Mapper(base_space_area)
+ */
+public interface BaseSpaceAreaMapper extends CrudMapper<BaseSpaceArea> {
+}

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

@@ -0,0 +1,10 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.BaseSpaceBuild;
+
+/**
+ * 空间-建筑关联 Mapper(base_space_build)
+ */
+public interface BaseSpaceBuildMapper extends CrudMapper<BaseSpaceBuild> {
+}

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

@@ -0,0 +1,10 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.BaseSpaceGateway;
+
+/**
+ * 空间-网关关联 Mapper(base_space_gateway)
+ */
+public interface BaseSpaceGatewayMapper extends CrudMapper<BaseSpaceGateway> {
+}

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

@@ -0,0 +1,10 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.BaseSpace;
+
+/**
+ * 空间 Mapper(base_space)
+ */
+public interface BaseSpaceMapper extends CrudMapper<BaseSpace> {
+}

+ 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> {
+}

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

@@ -0,0 +1,10 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.DmpGateway;
+
+/**
+ * 网关表 dmp_gateway Mapper
+ */
+public interface DmpGatewayMapper extends CrudMapper<DmpGateway> {
+}

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

@@ -0,0 +1,20 @@
+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);
+}
+

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

@@ -0,0 +1,10 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.EmsChannel;
+
+/**
+ * 通道 Mapper(leo.ems_channel)
+ */
+public interface EmsChannelMapper extends CrudMapper<EmsChannel> {
+}

+ 16 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsChannelTypeMapper.java

@@ -0,0 +1,16 @@
+package com.usky.ems.mapper;
+
+import com.usky.ems.domain.EmsChannelType;
+import com.usky.common.mybatis.core.CrudMapper;
+
+/**
+ * <p>
+ * 通道类型 Mapper 接口
+ * </p>
+ *
+ * @author ya
+ * @since 2026-03-16
+ */
+public interface EmsChannelTypeMapper extends CrudMapper<EmsChannelType> {
+
+}

+ 16 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsChannelTypeParameterMapper.java

@@ -0,0 +1,16 @@
+package com.usky.ems.mapper;
+
+import com.usky.ems.domain.EmsChannelTypeParameter;
+import com.usky.common.mybatis.core.CrudMapper;
+
+/**
+ * <p>
+ * 通道类型参数 Mapper 接口
+ * </p>
+ *
+ * @author ya
+ * @since 2026-03-16
+ */
+public interface EmsChannelTypeParameterMapper extends CrudMapper<EmsChannelTypeParameter> {
+
+}

+ 16 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsChannelTypeParameterValueMapper.java

@@ -0,0 +1,16 @@
+package com.usky.ems.mapper;
+
+import com.usky.ems.domain.EmsChannelTypeParameterValue;
+import com.usky.common.mybatis.core.CrudMapper;
+
+/**
+ * <p>
+ * 通道类型参数可选值 Mapper 接口
+ * </p>
+ *
+ * @author ya
+ * @since 2026-03-16
+ */
+public interface EmsChannelTypeParameterValueMapper extends CrudMapper<EmsChannelTypeParameterValue> {
+
+}

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

@@ -0,0 +1,12 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.EmsDataUploadShanghaiProduct;
+
+/**
+ * 上海市区平台数据上传(产品功能标识对应的区平台功能标识)Mapper
+ * 表:ems_data_upload_shanghai_product
+ */
+public interface EmsDataUploadShanghaiProductMapper extends CrudMapper<EmsDataUploadShanghaiProduct> {
+}
+

+ 11 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsDeviceEventMapper.java

@@ -0,0 +1,11 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.EmsDeviceEvent;
+
+/**
+ * 设备事件记录 Mapper(leo.ems_device_event)
+ */
+public interface EmsDeviceEventMapper extends CrudMapper<EmsDeviceEvent> {
+}
+

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

@@ -0,0 +1,10 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.EmsDeviceFunction;
+
+/**
+ * 设备功能/属性点位 Mapper(leo.ems_device_function)
+ */
+public interface EmsDeviceFunctionMapper extends CrudMapper<EmsDeviceFunction> {
+}

+ 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/EmsDeviceMapper.java

@@ -0,0 +1,10 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.EmsDevice;
+
+/**
+ * 设备 Mapper(leo.ems_device)
+ */
+public interface EmsDeviceMapper extends CrudMapper<EmsDevice> {
+}

+ 11 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsEnergyConsumptionFormulaMapper.java

@@ -0,0 +1,11 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.EmsEnergyConsumptionFormula;
+
+/**
+ * 空间能耗计算公式 Mapper(ems_energy_consumption_formula)
+ */
+public interface EmsEnergyConsumptionFormulaMapper extends CrudMapper<EmsEnergyConsumptionFormula> {
+}
+

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

@@ -0,0 +1,10 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.EmsEnergyItemCode;
+
+/**
+ * 能源分项编码 Mapper(leo.ems_energy_item_code)
+ */
+public interface EmsEnergyItemCodeMapper extends CrudMapper<EmsEnergyItemCode> {
+}

+ 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> {
+}

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

@@ -0,0 +1,10 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.EmsProjectDeviceSystem;
+
+/**
+ * 项目设备系统关联 Mapper(ems_project_device_system)
+ */
+public interface EmsProjectDeviceSystemMapper extends CrudMapper<EmsProjectDeviceSystem> {
+}

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

@@ -0,0 +1,10 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.EmsProject;
+
+/**
+ * 项目 Mapper(ems_project)
+ */
+public interface EmsProjectMapper extends CrudMapper<EmsProject> {
+}

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

@@ -0,0 +1,10 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.EmsSystemDictCode;
+
+/**
+ * 系统字典编码 Mapper(ems_system_dict_code)
+ */
+public interface EmsSystemDictCodeMapper extends CrudMapper<EmsSystemDictCode> {
+}

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

@@ -0,0 +1,10 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.EmsSystemDictRegion;
+
+/**
+ * 省市区字典 Mapper(ems_system_dict_region)
+ */
+public interface EmsSystemDictRegionMapper extends CrudMapper<EmsSystemDictRegion> {
+}

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

@@ -0,0 +1,10 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.EmsSystemDictValue;
+
+/**
+ * 系统字典值 Mapper(ems_system_dict_value)
+ */
+public interface EmsSystemDictValueMapper extends CrudMapper<EmsSystemDictValue> {
+}

+ 41 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/BaseSpaceService.java

@@ -0,0 +1,41 @@
+package com.usky.ems.service;
+
+import com.usky.common.mybatis.core.CrudService;
+import com.usky.ems.domain.BaseSpace;
+import com.usky.ems.service.vo.BaseSpaceForestNodeVO;
+
+import java.util.List;
+
+/**
+ * 空间服务(base_space)
+ */
+public interface BaseSpaceService extends CrudService<BaseSpace> {
+
+    /**
+     * 构建空间树
+     *
+     * @return 根节点(如存在多个根,则返回一个虚拟根节点)
+     */
+    BaseSpaceForestNodeVO tree();
+
+    /**
+     * 获取当前用户在指定空间下有权限访问的空间ID列表
+     * 目前简单返回传入的 spaceId 本身,后续可根据实际权限模型扩展
+     */
+    List<Long> getAuthorizedSpaceIds(Long spaceId);
+
+    /**
+     * 递归获取某空间节点下的所有子节点(不包含自身)
+     */
+    List<BaseSpace> recursiveAllChildrenNode(Long spaceId);
+
+    /**
+     * 更新项目根空间名称(与 ems_project 名称同步)
+     */
+    void updateProjectSpaceName(Long spaceId, String newName);
+
+    /**
+     * 是否叶子节点:无子空间
+     */
+    boolean isLeafSpace(Long spaceId);
+}

+ 28 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/DmpGatewayService.java

@@ -0,0 +1,28 @@
+package com.usky.ems.service;
+
+import com.usky.common.core.bean.CommonPage;
+import com.usky.common.mybatis.core.CrudService;
+import com.usky.ems.domain.DmpDevice;
+import com.usky.ems.domain.DmpGateway;
+import com.usky.ems.service.vo.DmpGatewayDevicePageRequest;
+import com.usky.ems.service.vo.DmpGatewayDetailResponse;
+import com.usky.ems.service.vo.DmpGatewayListItem;
+import com.usky.ems.service.vo.DmpGatewayPageRequest;
+
+/**
+ * dmp_gateway 网关维护与查询
+ */
+public interface DmpGatewayService extends CrudService<DmpGateway> {
+
+    void add(DmpGateway entity);
+
+    void update(DmpGateway entity);
+
+    boolean remove(Integer id);
+
+    CommonPage<DmpGatewayListItem> pageGateways(DmpGatewayPageRequest request);
+
+    CommonPage<DmpDevice> pageGatewayDevices(DmpGatewayDevicePageRequest request);
+
+    DmpGatewayDetailResponse getGatewayDetail(Integer id);
+}

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

@@ -0,0 +1,23 @@
+package com.usky.ems.service;
+
+import com.usky.common.mybatis.core.CrudService;
+import com.usky.ems.domain.DmpProduct;
+import com.usky.ems.enums.ValueTypeEnum;
+import com.usky.ems.service.vo.ProductFunctionVO;
+
+import java.util.List;
+
+/**
+ * 产品信息表 服务接口(service-ems)
+ */
+public interface DmpProductService extends CrudService<DmpProduct> {
+
+    /**
+     * 根据产品 ID 与数值类型获取功能列表
+     * 说明:当前实现基于 leo.sql 中 ems_data_upload_shanghai_product 表,
+     * 仅按 productId 过滤,不区分 valueTypeEnum。
+     */
+    List<ProductFunctionVO> getFunctions(Long id, ValueTypeEnum valueTypeEnum);
+
+}
+

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

@@ -0,0 +1,21 @@
+package com.usky.ems.service;
+
+import com.usky.ems.service.vo.*;
+
+/**
+ * 能耗分析服务:趋势、指标、分类占比、区域分析、对比分析
+ */
+public interface EmsAnalysisService {
+
+    EmsTrendResponse getTrend(Long projectId, String timeDimension, String timeValue, Long energyTypeId);
+
+    EmsTrendIndicatorsResponse getTrendIndicators(Long projectId, String timeDimension, String timeValue, Long energyTypeId);
+
+    EmsTrendCategoryResponse getTrendCategory(Long projectId, String timeDimension, String timeValue, Long energyTypeId);
+
+    EmsRegionAnalysisResponse getRegionAnalysis(Long projectId, String regionIds, String timeDimension, String timeValue, Long energyTypeId);
+
+    EmsCompareResponse getCompare(EmsCompareRequest request);
+
+    EmsAverageResponse getAverage(Long projectId, String timeDimension, String timeValue, Long energyTypeId);
+}

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

@@ -0,0 +1,16 @@
+package com.usky.ems.service;
+
+import com.usky.ems.domain.EmsChannelTypeParameter;
+import com.usky.common.mybatis.core.CrudService;
+
+/**
+ * <p>
+ * 通道类型参数 服务类
+ * </p>
+ *
+ * @author ya
+ * @since 2026-03-16
+ */
+public interface EmsChannelTypeParameterService extends CrudService<EmsChannelTypeParameter> {
+
+}

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

@@ -0,0 +1,16 @@
+package com.usky.ems.service;
+
+import com.usky.ems.domain.EmsChannelTypeParameterValue;
+import com.usky.common.mybatis.core.CrudService;
+
+/**
+ * <p>
+ * 通道类型参数可选值 服务类
+ * </p>
+ *
+ * @author ya
+ * @since 2026-03-16
+ */
+public interface EmsChannelTypeParameterValueService extends CrudService<EmsChannelTypeParameterValue> {
+
+}

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

@@ -0,0 +1,23 @@
+package com.usky.ems.service;
+
+import com.usky.ems.domain.EmsChannelType;
+import com.usky.ems.service.vo.EmsChannelTypeShowVO;
+import com.usky.common.mybatis.core.CrudService;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 通道类型 服务类
+ * </p>
+ *
+ * @author ya
+ * @since 2026-03-16
+ */
+public interface EmsChannelTypeService extends CrudService<EmsChannelType> {
+
+    /**
+     * 查询通道配置数据(类型 + 参数 + 参数可选值树形结构)
+     */
+    List<EmsChannelTypeShowVO> show();
+}

+ 18 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsDeviceEventService.java

@@ -0,0 +1,18 @@
+package com.usky.ems.service;
+
+import com.usky.ems.service.vo.DeviceEventDTO;
+import com.usky.ems.service.vo.DeviceEventVO;
+
+import java.util.List;
+
+/**
+ * 设备事件服务(EmsDeviceEvent 子模块)
+ */
+public interface EmsDeviceEventService {
+
+    /**
+     * 按条件查询设备事件列表
+     */
+    List<DeviceEventVO> list(DeviceEventDTO dto);
+}
+

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

@@ -0,0 +1,25 @@
+package com.usky.ems.service;
+
+import com.usky.ems.service.vo.DeviceEnergyReportRequest;
+import com.usky.ems.service.vo.DeviceEnergyReportItemVO;
+import com.usky.ems.service.vo.DeviceFunctionValueReportDTO;
+
+import java.util.List;
+
+/**
+ * 设备能耗报表相关服务(EmsDevice 子模块)
+ */
+public interface EmsDeviceReportService {
+
+    /**
+     * 查询能耗报表用的设备列表,并按名称进行排序。
+     */
+    List<DeviceEnergyReportItemVO> showEnergyReportDevices(DeviceEnergyReportRequest request);
+
+    /**
+     * 展示设备能耗报表数据(按时间维度与功能点进行统计)。
+     * 返回值结构后续可根据实际前端展示需求进行扩展,这里使用 Object 以保持灵活性。
+     */
+    Object showEnergyReportData(DeviceFunctionValueReportDTO dto);
+}
+

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

@@ -0,0 +1,19 @@
+package com.usky.ems.service;
+
+import com.usky.common.mybatis.core.CrudService;
+import com.usky.ems.domain.EmsEnergyItemCode;
+import com.usky.ems.service.vo.EnergyItemCodeVO;
+
+import java.util.List;
+
+/**
+ * 能源分项编码 服务接口
+ */
+public interface EmsEnergyItemCodeService extends CrudService<EmsEnergyItemCode> {
+
+    /**
+     * 查询指定空间下的分项能耗配置(基于能耗公式和分项编码组装树形结构)。
+     */
+    List<EnergyItemCodeVO> querySpaceEnergyItem(Long spaceId);
+}
+

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

@@ -0,0 +1,108 @@
+package com.usky.ems.service;
+
+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.EnergyTypeWrapperProductVO;
+import com.usky.ems.service.vo.DmpGatewayDetailResponse;
+
+import java.util.List;
+
+/**
+ * 基础建模服务(model:结构树、能源类型、建筑/区域/网关/通道/设备/属性点位)
+ */
+public interface EmsModelService {
+
+    /**
+     * 获取项目层级树(建筑、区域、网关)
+     */
+//    EmsStructureTreeNode getStructureTree(Long projectId, Boolean includeGateway);
+
+    /**
+     * 能源类型列表(按当前租户产品关联的 ems_product_energy_type 动态返回)
+     */
+    List<EmsEnergyTypeVO> getEnergyTypeList();
+
+    /**
+     * 展示已关联的能源类型及其下属分项/产品列表
+     * 数据来源:leo.ems_energy_item_code.energy_type 分组
+     */
+    List<EnergyTypeWrapperProductVO> showAssociatedEnergyTypes();
+
+    /** 建筑:新增 */
+    Long createBuilding(EmsModelSaveRequest request);
+    /** 建筑:编辑 */
+    void updateBuilding(Long id, EmsModelSaveRequest request);
+    /** 建筑:删除 */
+    void deleteBuilding(Long id);
+
+    /** 区域:新增 */
+    Long createRegion(EmsModelSaveRequest request);
+    /** 区域:编辑 */
+    void updateRegion(Long id, EmsModelSaveRequest request);
+    /** 区域:删除 */
+    void deleteRegion(Long id);
+
+    /** 网关:新增 */
+    String createGateway(EmsModelSaveRequest request);
+    /** 网关:编辑 */
+    void updateGateway(String id, EmsModelSaveRequest request);
+    /** 网关:删除 */
+    void deleteGateway(String id);
+//
+//    /** 通道:新增 */
+//    Long createChannel(EmsModelSaveRequest request);
+//    /** 通道:编辑 */
+//    void updateChannel(Long id, EmsModelSaveRequest request);
+//    /** 通道:删除 */
+//    void deleteChannel(Long id);
+//
+//    /** 设备:新增 */
+//    String createDevice(EmsModelSaveRequest request);
+//    /** 设备:编辑 */
+//    void updateDevice(String id, EmsModelSaveRequest request);
+//    /** 设备:删除 */
+//    void deleteDevice(String id);
+//
+//    /** 属性点位:新增 */
+//    Long createAttributePoint(EmsModelSaveRequest request);
+//    /** 属性点位:编辑 */
+//    void updateAttributePoint(Long id, EmsModelSaveRequest request);
+//    /** 属性点位:删除 */
+//    void deleteAttributePoint(Long id);
+
+    /**
+     * 网关-通道-设备树(中间面板)
+     * @param spaceId 空间ID(可选,不传返回全部网关)
+     * @param keyword 网关名称关键字模糊搜索(可选)
+     */
+    List<EmsGatewayDeviceTreeNode> getGatewayDeviceTree(Long spaceId, String keyword);
+
+    /**
+     * 属性点位列表(右侧面板)
+     * @param deviceId 设备ID
+     */
+    List<EmsAttributePointVO> getAttributePointList(String deviceId);
+
+    /**
+     * 空间绑定网关(一个空间可绑定多个网关)
+     * @param spaceId 空间ID
+     * @param gatewayUuids 网关uuid列表
+     */
+    void bindSpaceGateways(Long spaceId, List<String> gatewayUuids);
+
+    /**
+     * 空间解绑网关(一个空间可解绑多个网关)
+     * @param spaceId 空间ID
+     * @param gatewayUuids 网关uuid列表
+     */
+    void unbindSpaceGateways(Long spaceId, List<String> gatewayUuids);
+
+    /**
+     * 根据空间ID查询绑定的网关列表
+     * @param spaceId 空间ID
+     */
+    List<DmpGatewayDetailResponse> getGatewayListBySpaceId(Long spaceId);
+}

+ 63 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsOverviewService.java

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

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

@@ -0,0 +1,16 @@
+package com.usky.ems.service;
+
+import com.usky.ems.service.vo.EmsProjectResponse;
+import com.usky.ems.service.vo.EmsProjectSaveRequest;
+
+/**
+ * 项目(ems_project)维护:新增、修改、删除(含空间树、省市区校验、设备系统关联)
+ */
+public interface EmsProjectService {
+
+    EmsProjectResponse save(EmsProjectSaveRequest request);
+
+    EmsProjectResponse update(EmsProjectSaveRequest request);
+
+    void remove(Long spaceId);
+}

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

@@ -0,0 +1,32 @@
+package com.usky.ems.service;
+
+import com.usky.ems.service.vo.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 统计报表服务:能源报表、区域报表、采集报表
+ */
+public interface EmsReportService {
+
+    EmsReportDevicesResponse getEnergyDevices(Long energyTypeId, String keyword, Long projectId);
+
+    EmsEnergyStatisticsResponse getEnergyStatistics(EmsEnergyStatisticsRequest request);
+
+    void exportEnergy(String deviceIds, String attributePointIds, String timeDimension, String timeValue, String format, HttpServletResponse response);
+
+    EmsReportDevicesResponse getRegionDevices(Long energyTypeId, Long regionId, String keyword, Long projectId);
+
+    EmsEnergyStatisticsResponse getRegionStatistics(EmsEnergyStatisticsRequest request, List<Long> regionIds);
+
+    void exportRegion(String deviceIds, String attributePointIds, String timeDimension, String timeValue, String regionIds, String format, HttpServletResponse response);
+
+    EmsReportDevicesResponse getCollectionDevices(Long energyTypeId, String keyword, Long projectId);
+
+    EmsCollectionRealtimeResponse getCollectionRealtime(EmsCollectionRealtimeRequest request);
+
+    EmsEnergyStatisticsResponse getCollectionStatistics(EmsEnergyStatisticsRequest request);
+
+    void exportCollection(String deviceIds, String attributePointIds, String timeDimension, String timeValue, String format, HttpServletResponse response);
+}

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

@@ -0,0 +1,10 @@
+package com.usky.ems.service;
+
+import com.usky.common.mybatis.core.CrudService;
+import com.usky.ems.domain.EmsSystemDictCode;
+
+/**
+ * 系统字典编码(ems_system_dict_code)
+ */
+public interface EmsSystemDictCodeService extends CrudService<EmsSystemDictCode> {
+}

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

@@ -0,0 +1,22 @@
+package com.usky.ems.service;
+
+import com.usky.ems.domain.EmsSystemDictRegion;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 省市区字典(ems_system_dict_region)
+ */
+public interface EmsSystemDictRegionService {
+
+    /**
+     * 校验省市区编码并返回 code -&gt; name;非法时抛出业务异常
+     */
+    Map<String, String> checkAndBuildMap(String provinceCode, String cityCode, String districtCode);
+
+    /**
+     * 按父级编码查询子级区域列表
+     */
+    List<EmsSystemDictRegion> listByParentCode(String parentCode);
+}

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

@@ -0,0 +1,10 @@
+package com.usky.ems.service;
+
+import com.usky.common.mybatis.core.CrudService;
+import com.usky.ems.domain.EmsSystemDictValue;
+
+/**
+ * 系统字典值(ems_system_dict_value)
+ */
+public interface EmsSystemDictValueService extends CrudService<EmsSystemDictValue> {
+}

+ 162 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/BaseSpaceServiceImpl.java

@@ -0,0 +1,162 @@
+package com.usky.ems.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.usky.common.mybatis.core.AbstractCrudService;
+import com.usky.common.security.utils.SecurityUtils;
+import com.usky.ems.domain.BaseSpace;
+import com.usky.ems.mapper.BaseSpaceMapper;
+import com.usky.ems.service.BaseSpaceService;
+import com.usky.ems.service.vo.BaseSpaceForestNodeVO;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * 空间服务实现(base_space)
+ */
+@Service
+public class BaseSpaceServiceImpl extends AbstractCrudService<BaseSpaceMapper, BaseSpace> implements BaseSpaceService {
+
+    @Override
+    public BaseSpaceForestNodeVO tree() {
+        List<BaseSpace> allSpaces = this.list();
+        if (allSpaces == null || allSpaces.isEmpty()) {
+            return null;
+        }
+
+        List<BaseSpaceForestNodeVO> allNodes = allSpaces.stream()
+                .map(this::toNode)
+                .collect(Collectors.toList());
+
+        allNodes.sort(Comparator.comparing(
+                BaseSpaceForestNodeVO::getName,
+                Comparator.nullsFirst(String::compareToIgnoreCase)
+        ));
+
+        Map<Long, BaseSpaceForestNodeVO> idNodeMap = new HashMap<>();
+        for (BaseSpaceForestNodeVO node : allNodes) {
+            if (node.getId() != null) {
+                idNodeMap.put(node.getId(), node);
+            }
+        }
+
+        List<BaseSpaceForestNodeVO> roots = new ArrayList<>();
+        for (BaseSpaceForestNodeVO node : allNodes) {
+            Long parentId = node.getParentId();
+            if (parentId != null && parentId != 0L) {
+                BaseSpaceForestNodeVO parent = idNodeMap.get(parentId);
+                if (parent != null) {
+                    node.setParentSpaceName(parent.getName());
+                    parent.getChildren().add(node);
+                } else {
+                    roots.add(node);
+                }
+            } else {
+                roots.add(node);
+            }
+        }
+
+        if (roots.isEmpty()) {
+            return null;
+        }
+
+        List<BaseSpaceForestNodeVO> distinctRoots = roots.stream()
+                .filter(Objects::nonNull)
+                .distinct()
+                .collect(Collectors.toList());
+
+        if (distinctRoots.size() == 1) {
+            return distinctRoots.get(0);
+        }
+
+        BaseSpaceForestNodeVO virtualRoot = new BaseSpaceForestNodeVO();
+        virtualRoot.setId(0L);
+        virtualRoot.setName("ROOT");
+        virtualRoot.setChildren(distinctRoots);
+        return virtualRoot;
+    }
+
+    @Override
+    public List<Long> getAuthorizedSpaceIds(Long spaceId) {
+        if (spaceId == null) {
+            return Collections.emptyList();
+        }
+        Set<Long> ids = new LinkedHashSet<>();
+        ids.add(spaceId);
+        for (BaseSpace child : recursiveAllChildrenNode(spaceId)) {
+            if (child != null && child.getId() != null) {
+                ids.add(child.getId());
+            }
+        }
+        return new ArrayList<>(ids);
+    }
+
+    @Override
+    public List<BaseSpace> recursiveAllChildrenNode(Long spaceId) {
+        if (spaceId == null) {
+            return Collections.emptyList();
+        }
+        List<BaseSpace> result = new ArrayList<>();
+        collectChildren(spaceId, result);
+        return result;
+    }
+
+    private void collectChildren(Long parentId, List<BaseSpace> out) {
+        List<BaseSpace> children = this.list(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<BaseSpace>()
+                .eq(BaseSpace::getParentId, parentId));
+        if (children == null || children.isEmpty()) {
+            return;
+        }
+        out.addAll(children);
+        for (BaseSpace child : children) {
+            if (child != null && child.getId() != null) {
+                collectChildren(child.getId(), out);
+            }
+        }
+    }
+
+    private BaseSpaceForestNodeVO toNode(BaseSpace space) {
+        BaseSpaceForestNodeVO vo = new BaseSpaceForestNodeVO();
+        vo.setId(space.getId());
+        vo.setName(space.getName());
+        vo.setParentId(space.getParentId());
+        vo.setType(space.getType());
+        vo.setRootId(space.getRootId());
+        vo.setPath(space.getPath());
+        vo.setDeep(space.getDeep());
+        return vo;
+    }
+
+    @Override
+    public void updateProjectSpaceName(Long spaceId, String newName) {
+        if (spaceId == null || newName == null) {
+            return;
+        }
+        BaseSpace space = this.getById(spaceId);
+        if (space == null) {
+            return;
+        }
+        space.setName(newName);
+        space.setUpdatedBy(SecurityUtils.getUsername());
+        space.setUpdateTime(LocalDateTime.now());
+        this.updateById(space);
+    }
+
+    @Override
+    public boolean isLeafSpace(Long spaceId) {
+        if (spaceId == null) {
+            return true;
+        }
+        return this.count(new LambdaQueryWrapper<BaseSpace>().eq(BaseSpace::getParentId, spaceId)) == 0;
+    }
+}

+ 191 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/DmpGatewayServiceImpl.java

@@ -0,0 +1,191 @@
+package com.usky.ems.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.usky.common.core.bean.CommonPage;
+import com.usky.common.core.exception.BusinessException;
+import com.usky.common.core.util.UUIDUtils;
+import com.usky.common.mybatis.core.AbstractCrudService;
+import com.usky.common.security.utils.SecurityUtils;
+import com.usky.ems.domain.DmpDevice;
+import com.usky.ems.domain.DmpGateway;
+import com.usky.ems.mapper.DmpDeviceMapper;
+import com.usky.ems.mapper.DmpGatewayMapper;
+import com.usky.ems.service.DmpGatewayService;
+import com.usky.ems.service.vo.DmpGatewayDevicePageRequest;
+import com.usky.ems.service.vo.DmpGatewayDetailResponse;
+import com.usky.ems.service.vo.DmpGatewayListItem;
+import com.usky.ems.service.vo.DmpGatewayPageRequest;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * dmp_gateway 服务实现
+ */
+@Service
+public class DmpGatewayServiceImpl extends AbstractCrudService<DmpGatewayMapper, DmpGateway> implements DmpGatewayService {
+
+    private final DmpDeviceMapper dmpDeviceMapper;
+
+    public DmpGatewayServiceImpl(DmpDeviceMapper dmpDeviceMapper) {
+        this.dmpDeviceMapper = dmpDeviceMapper;
+    }
+
+    @Override
+    public void add(DmpGateway entity) {
+        if (!StringUtils.hasText(entity.getDeviceUuid())) {
+            entity.setDeviceUuid(UUIDUtils.uuid().substring(0, 16));
+        }
+
+        if (!StringUtils.hasText(entity.getName())) {
+            throw new BusinessException("新增失败,名称不能为空");
+        }
+        if (deviceUuidExists(entity.getDeviceUuid(), null)) {
+            throw new BusinessException("新增失败,设备 UUID 已存在");
+        }
+        LocalDateTime now = LocalDateTime.now();
+        if (entity.getCommStatus() == null) {
+            entity.setCommStatus(0);
+        }
+        if (entity.getVirtualDevice() == null) {
+            entity.setVirtualDevice(0);
+        }
+        entity.setCreatedBy(SecurityUtils.getUsername());
+        entity.setCreateTime(now);
+        entity.setUpdatedBy(SecurityUtils.getUsername());
+        entity.setUpdateTime(now);
+        entity.setTenantId(SecurityUtils.getTenantId());
+        this.save(entity);
+    }
+
+    @Override
+    public void update(DmpGateway entity) {
+        if (entity.getId() == null) {
+            throw new BusinessException("修改失败,缺少主键");
+        }
+        DmpGateway existing = this.getById(entity.getId());
+        Optional.ofNullable(existing).orElseThrow(() -> new BusinessException("网关不存在"));
+
+        entity.setUpdatedBy(SecurityUtils.getUsername());
+        entity.setUpdateTime(LocalDateTime.now());
+        this.updateById(entity);
+    }
+
+    @Override
+    public boolean remove(Integer id) {
+        DmpGateway g = this.getById(id);
+        Optional.ofNullable(g).orElseThrow(() -> new BusinessException("网关不存在"));
+        return this.removeById(id);
+    }
+
+    @Override
+    public CommonPage<DmpGatewayListItem> pageGateways(DmpGatewayPageRequest request) {
+        int current = request.getCurrent() == null ? 1 : request.getCurrent();
+        int size = request.getSize() == null ? 10 : request.getSize();
+        Page<DmpGateway> p = new Page<>(current, size);
+        LambdaQueryWrapper<DmpGateway> q = Wrappers.lambdaQuery();
+        if (request.getCommunicationStatus() != null) {
+            q.eq(DmpGateway::getCommStatus, request.getCommunicationStatus());
+        }
+        if (StringUtils.hasText(request.getName())) {
+            q.like(DmpGateway::getName, request.getName());
+        }
+        if (StringUtils.hasText(request.getInstallAddress())) {
+            q.like(DmpGateway::getInstallAddress, request.getInstallAddress());
+        }
+        if (StringUtils.hasText(request.getDeviceUuid())) {
+            q.eq(DmpGateway::getDeviceUuid, request.getDeviceUuid());
+        }
+        q.orderByDesc(DmpGateway::getId);
+        Page<DmpGateway> page = baseMapper.selectPage(p, q);
+        List<DmpGatewayListItem> list = page.getRecords().stream().map(this::toListItem).collect(Collectors.toList());
+        return new CommonPage<>(list, page.getTotal(), size, current);
+    }
+
+    @Override
+    public CommonPage<DmpDevice> pageGatewayDevices(DmpGatewayDevicePageRequest request) {
+        if (!StringUtils.hasText(request.getGatewayUuid())) {
+            throw new BusinessException("查询失败,gatewayUuid 不能为空");
+        }
+        int current = request.getCurrent() == null ? 1 : request.getCurrent();
+        int size = request.getSize() == null ? 10 : request.getSize();
+        Page<DmpDevice> p = new Page<>(current, size);
+        LambdaQueryWrapper<DmpDevice> q = Wrappers.lambdaQuery();
+        q.eq(DmpDevice::getGatewayUuid, request.getGatewayUuid());
+        q.eq(DmpDevice::getDeleteFlag, 0);
+        q.eq(DmpDevice::getCategoryType, 3);
+        Integer tenantId = SecurityUtils.getTenantId();
+        if (tenantId != null) {
+            q.eq(DmpDevice::getTenantId, tenantId);
+        }
+        q.orderByDesc(DmpDevice::getId);
+        Page<DmpDevice> page = dmpDeviceMapper.selectPage(p, q);
+        return new CommonPage<>(page.getRecords(), page.getTotal(), size, current);
+    }
+
+    @Override
+    public DmpGatewayDetailResponse getGatewayDetail(Integer id) {
+        DmpGateway g = this.getById(id);
+        if (g == null) {
+            return null;
+        }
+        return toDetail(g);
+    }
+
+    private boolean deviceUuidExists(String deviceUuid, Integer excludeId) {
+        LambdaQueryWrapper<DmpGateway> w = Wrappers.lambdaQuery();
+        w.eq(DmpGateway::getDeviceUuid, deviceUuid);
+        DmpGateway one = this.getOne(w);
+        if (one == null) {
+            return false;
+        }
+        return excludeId == null || !Objects.equals(one.getId(), excludeId);
+    }
+
+    private DmpGatewayListItem toListItem(DmpGateway g) {
+        DmpGatewayListItem item = new DmpGatewayListItem();
+        item.setId(g.getId());
+        item.setDeviceUuid(g.getDeviceUuid());
+        item.setName(g.getName());
+        item.setIp(g.getIp());
+        item.setPort(g.getPort());
+        item.setCommunicationStatus(g.getCommStatus());
+        item.setInstallAddress(g.getInstallAddress());
+        item.setOnlineTime(g.getOnlineTime());
+        item.setOfflineTime(g.getOfflineTime());
+        item.setVirtualDevice(g.getVirtualDevice());
+        item.setCreateTime(g.getCreateTime());
+        item.setUpdateTime(g.getUpdateTime());
+        return item;
+    }
+
+    private DmpGatewayDetailResponse toDetail(DmpGateway g) {
+        DmpGatewayDetailResponse r = new DmpGatewayDetailResponse();
+        r.setId(g.getId());
+        r.setDeviceUuid(g.getDeviceUuid());
+        r.setName(g.getName());
+        r.setIp(g.getIp());
+        r.setPort(g.getPort());
+        r.setCommunicationStatus(g.getCommStatus());
+        r.setInstallAddress(g.getInstallAddress());
+        r.setOnlineTime(g.getOnlineTime());
+        r.setOfflineTime(g.getOfflineTime());
+        r.setUpdateConfigTime(g.getUpdateConfigTime());
+        r.setUpdateProtocolTime(g.getUpdateProtocolTime());
+        r.setUpgradeTime(g.getUpgradeTime());
+        r.setVirtualDevice(g.getVirtualDevice());
+        r.setRemark(g.getRemark());
+        r.setUpdatedBy(g.getUpdatedBy());
+        r.setUpdateTime(g.getUpdateTime());
+        r.setCreatedBy(g.getCreatedBy());
+        r.setCreateTime(g.getCreateTime());
+        return r;
+    }
+}

+ 75 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/DmpProductServiceImpl.java

@@ -0,0 +1,75 @@
+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.ems.domain.DmpProduct;
+import com.usky.ems.domain.EmsDataUploadShanghaiProduct;
+import com.usky.ems.enums.ValueTypeEnum;
+import com.usky.ems.mapper.DmpProductMapper;
+import com.usky.ems.mapper.EmsDataUploadShanghaiProductMapper;
+import com.usky.ems.service.DmpProductService;
+import com.usky.ems.service.vo.ProductFunctionVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 产品信息表 服务实现类(service-ems)
+ */
+@Service
+public class DmpProductServiceImpl extends AbstractCrudService<DmpProductMapper, DmpProduct> implements DmpProductService {
+
+    @Autowired
+    private EmsDataUploadShanghaiProductMapper shanghaiProductMapper;
+
+    @Override
+    public List<ProductFunctionVO> getFunctions(Long id, ValueTypeEnum valueTypeEnum) {
+        // 参照原有实现:valueTypeEnum 为空返回全部,否则按值类型过滤
+        if (valueTypeEnum == null) {
+            LambdaQueryWrapper<EmsDataUploadShanghaiProduct> wrapper =
+                    Wrappers.<EmsDataUploadShanghaiProduct>lambdaQuery()
+                            .eq(EmsDataUploadShanghaiProduct::getProductId, id)
+                            .orderByAsc(EmsDataUploadShanghaiProduct::getIdentifier)
+                            .orderByDesc(EmsDataUploadShanghaiProduct::getUpdateTime);
+
+            List<EmsDataUploadShanghaiProduct> list = shanghaiProductMapper.selectList(wrapper);
+            return list.stream()
+                    .map(item -> new ProductFunctionVO(
+                            item.getId(),
+                            item.getProductId(),
+                            item.getIdentifier(),
+                            item.getCustomIdentifier()
+                    ))
+                    .collect(Collectors.toList());
+        }
+
+        // 预留:按值类型过滤,目前表结构中暂无 value_type 字段,仅根据 productId 查询
+        return getByProductIdAndValueType(id, valueTypeEnum.getValue());
+    }
+
+    /**
+     * 根据产品 ID 与值类型获取功能列表。
+     * 当前实现仅按 productId 过滤,后续可在 ems_data_upload_shanghai_product 表中增加 value_type 字段后扩展。
+     */
+    private List<ProductFunctionVO> getByProductIdAndValueType(Long id, Short valueType) {
+        LambdaQueryWrapper<EmsDataUploadShanghaiProduct> wrapper =
+                Wrappers.<EmsDataUploadShanghaiProduct>lambdaQuery()
+                        .eq(EmsDataUploadShanghaiProduct::getProductId, id)
+                        .orderByAsc(EmsDataUploadShanghaiProduct::getIdentifier)
+                        .orderByDesc(EmsDataUploadShanghaiProduct::getUpdateTime);
+
+        List<EmsDataUploadShanghaiProduct> list = shanghaiProductMapper.selectList(wrapper);
+        return list.stream()
+                .map(item -> new ProductFunctionVO(
+                        item.getId(),
+                        item.getProductId(),
+                        item.getIdentifier(),
+                        item.getCustomIdentifier()
+                ))
+                .collect(Collectors.toList());
+    }
+}
+

+ 1205 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsAnalysisServiceImpl.java

@@ -0,0 +1,1205 @@
+package com.usky.ems.service.impl;
+
+import com.baomidou.dynamic.datasource.annotation.DS;
+import com.usky.ems.mapper.EmsDeviceMapper;
+import com.usky.ems.mapper.EmsEnergyItemCodeMapper;
+import com.usky.ems.service.EmsAnalysisService;
+import com.usky.ems.service.TdengineService;
+import com.usky.ems.service.vo.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.sql.DataSource;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 能耗分析服务实现
+ */
+@Service
+@DS("mysql")
+public class EmsAnalysisServiceImpl implements EmsAnalysisService {
+
+    private static final String[] ENERGY_TYPE_NAMES = {"", "电", "水", "气"};
+    
+    // 标准煤转换系数
+    private static final Map<Long, BigDecimal> STANDARD_COAL_FACTORS = new HashMap<Long, BigDecimal>();
+    static {
+        STANDARD_COAL_FACTORS.put(Long.valueOf(1), new BigDecimal("0.1229")); // 电
+        STANDARD_COAL_FACTORS.put(Long.valueOf(2), new BigDecimal("0.0857")); // 水  
+        STANDARD_COAL_FACTORS.put(Long.valueOf(3), new BigDecimal("0.2143")); // 气
+    }
+    
+    // 碳排放转换系数
+    private static final Map<Long, BigDecimal> CARBON_EMISSION_FACTORS = new HashMap<Long, BigDecimal>();
+    static {
+        CARBON_EMISSION_FACTORS.put(Long.valueOf(1), new BigDecimal("0.7850")); // 电
+        CARBON_EMISSION_FACTORS.put(Long.valueOf(2), new BigDecimal("0.2600")); // 水
+        CARBON_EMISSION_FACTORS.put(Long.valueOf(3), new BigDecimal("0.4920")); // 气
+    }
+    
+    // 能源单位
+    private static final Map<Long, String> ENERGY_UNITS = new HashMap<Long, String>();
+    static {
+        ENERGY_UNITS.put(Long.valueOf(1), "kWh"); // 电
+        ENERGY_UNITS.put(Long.valueOf(2), "m³");   // 水
+        ENERGY_UNITS.put(Long.valueOf(3), "m³");   // 气
+    }
+
+    @Autowired
+    private TdengineService tdengineService;
+    
+    
+    @Autowired
+    private EmsDeviceMapper emsDeviceMapper;
+    
+    @Autowired
+    private EmsEnergyItemCodeMapper emsEnergyItemCodeMapper;
+    
+    @Autowired
+    private DataSource mysqlDataSource;
+
+    @Override
+    public EmsTrendResponse getTrend(Long projectId, String timeDimension, String timeValue, Long energyTypeId) {
+        EmsTrendResponse resp = new EmsTrendResponse();
+        resp.setTimeDimension(timeDimension);
+        resp.setTimeValue(timeValue);
+
+        try {
+            // 获取项目下的所有设备
+            List<String> deviceIds = getDeviceIdsByProject(projectId, energyTypeId);
+            if (deviceIds.isEmpty()) {
+                return resp;
+            }
+
+            // 根据时间维度生成时间点
+            List<String> timePoints = generateTimePoints(timeDimension, timeValue);
+            
+            // 查询每个时间点的能耗数据
+            List<EmsTrendItemVO> trendItems = new ArrayList<>();
+            for (String timePoint : timePoints) {
+                EmsTrendItemVO item;
+                if (energyTypeId == null) {
+                    // 当不指定能源类型时,查询项目中所有能源类型设备的总能耗
+                    item = calculateTrendItemForAllEnergyTypes(projectId, timeDimension, timePoint);
+                } else {
+                    item = calculateTrendItem(deviceIds, timeDimension, timePoint, energyTypeId);
+                }
+                trendItems.add(item);
+            }
+            
+            resp.setTrend(trendItems);
+        } catch (Exception e) {
+            // 日志记录错误,返回空数据
+            e.printStackTrace();
+        }
+        
+        return resp;
+    }
+
+    /**
+     * 生成时间点列表
+     */
+    private List<String> generateTimePoints(String timeDimension, String timeValue) {
+        List<String> timePoints = new ArrayList<>();
+        
+        try {
+            switch (timeDimension.toUpperCase()) {
+                case "D": // 日维度,生成该日每小时
+                    timePoints = generateHourlyPoints(timeValue);
+                    break;
+                case "M": // 月维度,生成该月每天
+                    timePoints = generateDailyPoints(timeValue);
+                    break;
+                case "Y": // 年维度,生成该年每月
+                    timePoints = generateMonthlyPoints(timeValue);
+                    break;
+                default:
+                    timePoints.add(timeValue);
+            }
+        } catch (Exception e) {
+            // 如果解析出错,返回原始时间值
+            timePoints.add(timeValue);
+        }
+        
+        return timePoints;
+    }
+    
+    /**
+     * 生成日维度时间点
+     */
+    private List<String> generateDailyPoints(String yearMonth) {
+        List<String> days = new ArrayList<>();
+        try {
+            String[] parts = yearMonth.split("-");
+            int year = Integer.parseInt(parts[0]);
+            int month = Integer.parseInt(parts[1]);
+            
+            java.time.YearMonth ym = java.time.YearMonth.of(year, month);
+            int daysInMonth = ym.lengthOfMonth();
+            
+            for (int day = 1; day <= daysInMonth; day++) {
+                days.add(year + "-" + (month < 10 ? "0" : "") + month + "-" + (day < 10 ? "0" : "") + day);
+            }
+        } catch (Exception e) {
+            days.add(yearMonth);
+        }
+        return days;
+    }
+    
+    /**
+     * 生成月维度时间点
+     */
+    private List<String> generateMonthlyPoints(String year) {
+        List<String> months = new ArrayList<>();
+        try {
+            int yearInt = Integer.parseInt(year);
+            for (int month = 1; month <= 12; month++) {
+                months.add(yearInt + "-" + (month < 10 ? "0" : "") + month);
+            }
+        } catch (Exception e) {
+            months.add(year);
+        }
+        return months;
+    }
+    
+    /**
+     * 生成小时维度时间点
+     */
+    private List<String> generateHourlyPoints(String date) {
+        List<String> hours = new ArrayList<>();
+        try {
+            String[] parts = date.split("-");
+            int year = Integer.parseInt(parts[0]);
+            int month = Integer.parseInt(parts[1]);
+            int day = Integer.parseInt(parts[2]);
+            
+            for (int hour = 0; hour < 24; hour++) {
+                hours.add(year + "-" + (month < 10 ? "0" : "") + month + "-" + (day < 10 ? "0" : "") + day + " " + (hour < 10 ? "0" : "") + hour + ":00:00");
+            }
+        } catch (Exception e) {
+            hours.add(date);
+        }
+        return hours;
+    }
+    
+    /**
+     * 计算单个时间点的趋势数据
+     */
+    private EmsTrendItemVO calculateTrendItem(List<String> deviceIds, String timeDimension, String timePoint, Long energyTypeId) {
+        EmsTrendItemVO item = new EmsTrendItemVO();
+        item.setTimeLabel(timePoint);
+        
+        try {
+        // 查询设备能耗数据
+        BigDecimal totalUsage = queryDeviceEnergyConsumption(deviceIds, timeDimension, timePoint, energyTypeId);
+            
+            // 计算标准煤和碳排放
+            BigDecimal standardCoal = BigDecimal.ZERO;
+            BigDecimal carbonEmission = BigDecimal.ZERO;
+            
+            if (energyTypeId != null && totalUsage != null && totalUsage.compareTo(BigDecimal.ZERO) > 0) {
+                BigDecimal coalFactor = STANDARD_COAL_FACTORS.getOrDefault(energyTypeId, BigDecimal.ZERO);
+                BigDecimal carbonFactor = CARBON_EMISSION_FACTORS.getOrDefault(energyTypeId, BigDecimal.ZERO);
+                
+                standardCoal = totalUsage.multiply(coalFactor).setScale(2, RoundingMode.HALF_UP);
+                carbonEmission = totalUsage.multiply(carbonFactor).setScale(2, RoundingMode.HALF_UP);
+            }
+            
+            item.setUsage(totalUsage != null ? totalUsage.setScale(2, RoundingMode.HALF_UP) : BigDecimal.ZERO);
+            item.setStandardCoal(standardCoal);
+            item.setCarbonEmission(carbonEmission);
+            
+        } catch (Exception e) {
+            // 出错时设置默认值
+            item.setUsage(BigDecimal.ZERO);
+            item.setStandardCoal(BigDecimal.ZERO);
+            item.setCarbonEmission(BigDecimal.ZERO);
+        }
+        
+        return item;
+    }
+    
+    /**
+     * 查询设备能耗消耗量
+     */
+    private BigDecimal queryDeviceEnergyConsumption(List<String> deviceIds, String timeDimension, String timePoint, Long energyTypeId) {
+        BigDecimal totalUsage = BigDecimal.ZERO;
+        
+        System.out.println("Querying energy for timePoint: " + timePoint + ", deviceIds: " + deviceIds + ", energyTypeId: " + energyTypeId);
+        
+        try (Connection conn = mysqlDataSource.getConnection()) {
+            String sql = buildEnergyQuerySQL(deviceIds, timeDimension, energyTypeId != null);
+    
+            System.out.println("Time range for " + timePoint + ": " + String.join(" | ", getTimeRange(timeDimension, timePoint)));
+            
+            try (PreparedStatement stmt = conn.prepareStatement(sql)) {
+                int paramIndex = 1;
+                
+                // 设置设备ID参数
+                System.out.println("Setting device IDs:");
+                for (String deviceId : deviceIds) {
+                    System.out.println("  - " + deviceId);
+                    stmt.setString(paramIndex++, deviceId);
+                }
+                
+                // 设置时间参数
+                String[] timeRange = getTimeRange(timeDimension, timePoint);
+                System.out.println("Setting time range: start=" + timeRange[0] + ", end=" + timeRange[1]);
+                stmt.setString(paramIndex++, timeRange[0]);
+                stmt.setString(paramIndex++, timeRange[1]);
+                
+                if (energyTypeId != null) {
+                    System.out.println("Setting energyTypeId: " + energyTypeId);
+                    stmt.setLong(paramIndex++, energyTypeId);
+                }
+                
+                try (ResultSet rs = stmt.executeQuery()) {
+                    if (rs.next()) {
+                        double usage = rs.getDouble("total_usage");
+                        if (!rs.wasNull()) {
+                            totalUsage = new BigDecimal(usage);
+                            System.out.println("Found usage: " + totalUsage + " for timePoint: " + timePoint);
+                        } else {
+                            System.out.println("No usage data (wasNull=true) for timePoint: " + timePoint);
+                        }
+                    } else {
+                        System.out.println("No result set for timePoint: " + timePoint);
+                    }
+                }
+            }
+        } catch (Exception e) {
+            System.err.println("Error querying energy consumption for timePoint: " + timePoint);
+            e.printStackTrace();
+        }
+        
+        System.out.println("Final totalUsage for " + timePoint + ": " + totalUsage);
+        return totalUsage;
+    }
+    
+    /**
+     * 构建能耗查询SQL
+     */
+    private String buildEnergyQuerySQL(List<String> deviceIds, String timeDimension, boolean hasEnergyTypeFilter) {
+        StringBuilder sql = new StringBuilder();
+        sql.append("SELECT SUM(eed.end_value - eed.start_value) as total_usage ");
+        sql.append("FROM ems_energy_consumption_device_data eed ");
+        
+        // 如果需要能源类型过滤,关联设备表和产品能源类型表
+        if (hasEnergyTypeFilter) {
+            sql.append("INNER JOIN ems_device ed ON eed.device_id = ed.id ");
+            sql.append("INNER JOIN ems_product_energy_type epe ON ed.product_id = epe.product_id ");
+        }
+        
+        sql.append("WHERE eed.device_id IN (");
+        
+        // 为每个设备ID添加占位符
+        for (int i = 0; i < deviceIds.size(); i++) {
+            if (i > 0) {
+                sql.append(", ");
+            }
+            sql.append("?");
+        }
+        sql.append(") ");
+        
+        // 添加时间条件
+        sql.append("AND eed.start_time >= ? AND eed.end_time <= ? ");
+        
+        if (hasEnergyTypeFilter) {
+            sql.append("AND epe.energy_type = ? ");
+        }
+        
+        System.out.println("Generated SQL: " + sql.toString());
+        
+        return sql.toString();
+    }
+    
+    /**
+     * 获取时间范围
+     */
+    private String[] getTimeRange(String timeDimension, String timePoint) {
+        String[] range = new String[2];
+        try {
+            switch (timeDimension.toUpperCase()) {
+                case "D":
+                    // 对于小时级数据,timePoint已经包含具体时间,格式为 "YYYY-MM-DD HH:00:00"
+                    if (timePoint.contains(" ")) {
+                        // 提取日期和时间部分,格式:YYYY-MM-DD HH
+                        String datePart = timePoint.split(" ")[0];
+                        String hourPart = timePoint.split(" ")[1].split(":")[0];
+                        
+                        // 设置整小时范围:HH:00:00 到 HH:59:59
+                        range[0] = datePart + " " + hourPart + ":00:00";
+                        range[1] = datePart + " " + hourPart + ":59:59";
+                    } else {
+                        // 如果没有时间,默认为全天
+                        range[0] = timePoint + " 00:00:00";
+                        range[1] = timePoint + " 23:59:59";
+                    }
+                    break;
+                case "M":
+                    range[0] = timePoint + " 00:00:00";
+                    range[1] = timePoint + " 23:59:59";
+                    break;
+                case "Y":
+                    // For yearly dimension, timePoint is in format "YYYY-MM", need full month
+                    String[] yearMonth = timePoint.split("-");
+                    if (yearMonth.length == 2) {
+                        String yearPart = yearMonth[0];
+                        String monthPart = yearMonth[1];
+                        range[0] = yearPart + "-" + monthPart + "-01 00:00:00";
+                        
+                        // Calculate last day of the month properly
+                        java.time.YearMonth ym = java.time.YearMonth.of(Integer.parseInt(yearPart), Integer.parseInt(monthPart));
+                        int lastDay = ym.lengthOfMonth();
+                        range[1] = yearPart + "-" + monthPart + "-" + lastDay + " 23:59:59";
+                    } else {
+                        range[0] = timePoint + "-01-01 00:00:00";
+                        range[1] = timePoint + "-12-31 23:59:59";
+                    }
+                    break;
+                default:
+                    range[0] = timePoint;
+                    range[1] = timePoint;
+            }
+        } catch (Exception e) {
+            range[0] = timePoint;
+            range[1] = timePoint;
+        }
+        
+        return range;
+    }
+    
+    /**
+     * 根据项目ID获取设备列表
+     */
+    private List<String> getDeviceIdsByProject(Long projectId, Long energyTypeId) {
+        List<String> deviceIds = new ArrayList<>();
+        try (Connection conn = mysqlDataSource.getConnection()) {
+            StringBuilder sql = new StringBuilder();
+            sql.append("SELECT DISTINCT ed.id ");
+            sql.append("FROM ems_device ed ");
+            
+            if (energyTypeId != null) {
+                sql.append("INNER JOIN ems_product_energy_type epe ON ed.product_id = epe.product_id ");
+            }
+            
+            sql.append("WHERE 1=1 ");
+            
+            if (projectId != null) {
+                sql.append("AND ed.project_id = ? ");
+            }
+            
+            if (energyTypeId != null) {
+                sql.append("AND epe.energy_type = ? ");
+            }
+            
+            try (PreparedStatement stmt = conn.prepareStatement(sql.toString())) {
+                int paramIndex = 1;
+                
+                if (projectId != null) {
+                    System.out.println("Setting projectId parameter: " + projectId);
+                    stmt.setLong(paramIndex++, projectId);
+                }
+                
+                if (energyTypeId != null) {
+                    System.out.println("Setting energyTypeId parameter: " + energyTypeId);
+                    stmt.setLong(paramIndex++, energyTypeId);
+                }
+                
+                System.out.println("Device query SQL: " + sql.toString());
+                
+                try (ResultSet rs = stmt.executeQuery()) {
+                    while (rs.next()) {
+                        String deviceId = rs.getString("id");
+                        deviceIds.add(deviceId);
+                        System.out.println("Found device ID: " + deviceId);
+                    }
+                }
+            }
+        } catch (Exception e) {
+            System.err.println("Error getting device IDs: " + e.getMessage());
+            e.printStackTrace();
+        }
+        
+        System.out.println("Total device IDs found: " + deviceIds.size());
+        return deviceIds;
+    }
+
+    @Override
+    public EmsTrendIndicatorsResponse getTrendIndicators(Long projectId, String timeDimension, String timeValue, Long energyTypeId) {
+        EmsTrendIndicatorsResponse resp = new EmsTrendIndicatorsResponse();
+        resp.setTimeDimension(timeDimension);
+        resp.setTimeValue(timeValue);
+
+        try {
+            // 获取项目下的所有设备
+            List<String> deviceIds = getDeviceIdsByProject(projectId, energyTypeId);
+            if (deviceIds.isEmpty()) {
+                return resp;
+            }
+
+            // 计算总用量
+            String[] timeRange = getTimeRange(timeDimension, timeValue);
+            BigDecimal totalUsage = queryTotalEnergyConsumption(deviceIds, timeRange[0], timeRange[1], energyTypeId);
+            
+            // 计算标准煤和碳排放
+            BigDecimal standardCoal = BigDecimal.ZERO;
+            BigDecimal carbonEmission = BigDecimal.ZERO;
+            
+            if (energyTypeId != null && totalUsage.compareTo(BigDecimal.ZERO) > 0) {
+                BigDecimal coalFactor = STANDARD_COAL_FACTORS.getOrDefault(energyTypeId, BigDecimal.ZERO);
+                BigDecimal carbonFactor = CARBON_EMISSION_FACTORS.getOrDefault(energyTypeId, BigDecimal.ZERO);
+                
+                standardCoal = totalUsage.multiply(coalFactor).setScale(2, RoundingMode.HALF_UP);
+                carbonEmission = totalUsage.multiply(carbonFactor).setScale(2, RoundingMode.HALF_UP);
+            }
+            
+            // 计算单位面积能耗和人均能耗
+            Map<String, BigDecimal> areaAndPopulation = getProjectAreaAndPopulation(projectId);
+            BigDecimal totalArea = areaAndPopulation.get("area");
+            BigDecimal totalPopulation = areaAndPopulation.get("population");
+            
+            BigDecimal areaUsage = BigDecimal.ZERO;
+            BigDecimal perCapitaUsage = BigDecimal.ZERO;
+            
+            if (totalArea.compareTo(BigDecimal.ZERO) > 0) {
+                areaUsage = totalUsage.divide(totalArea, 2, RoundingMode.HALF_UP);
+            }
+            
+            if (totalPopulation.compareTo(BigDecimal.ZERO) > 0) {
+                perCapitaUsage = totalUsage.divide(totalPopulation, 2, RoundingMode.HALF_UP);
+            }
+            
+            // 评定等级
+            Map<String, String> rating = calculateRating(areaUsage, energyTypeId);
+            
+            resp.setTotalUsage(totalUsage.setScale(2, RoundingMode.HALF_UP));
+            resp.setStandardCoal(standardCoal);
+            resp.setCarbonEmission(carbonEmission);
+            resp.setAreaUsage(areaUsage);
+            resp.setPerCapitaUsage(perCapitaUsage);
+            resp.setRating(rating);
+            
+        } catch (Exception e) {
+            System.err.println("Error getting trend indicators: " + e.getMessage());
+        }
+        
+        return resp;
+    }
+    
+    /**
+     * 查询总能耗
+     */
+    private BigDecimal queryTotalEnergyConsumption(List<String> deviceIds, String startTime, String endTime, Long energyTypeId) {
+        BigDecimal totalUsage = BigDecimal.ZERO;
+        
+        try (Connection conn = mysqlDataSource.getConnection()) {
+            StringBuilder sql = new StringBuilder();
+            sql.append("SELECT SUM(eed.end_value - eed.start_value) as total_usage ");
+            sql.append("FROM ems_energy_consumption_device_data eed ");
+            if (energyTypeId != null) {
+                sql.append("INNER JOIN ems_device ed ON eed.device_id = ed.id ");
+                sql.append("INNER JOIN ems_product_energy_type epe ON ed.product_id = epe.product_id ");
+            }
+            sql.append("WHERE eed.device_id IN (")
+                .append(String.join(",", Collections.nCopies(deviceIds.size(), "?")))
+                .append(") ");
+            sql.append("AND eed.start_time >= ? AND eed.end_time <= ? ");
+
+            if (energyTypeId != null) {
+                sql.append("AND epe.energy_type = ? ");
+            }
+            
+            try (PreparedStatement stmt = conn.prepareStatement(sql.toString())) {
+                int paramIndex = 1;
+                
+                // 设置设备ID参数
+                for (String deviceId : deviceIds) {
+                    stmt.setString(paramIndex++, deviceId);
+                }
+                
+                // 设置时间参数
+                stmt.setString(paramIndex++, startTime);
+                stmt.setString(paramIndex++, endTime);
+                
+                if (energyTypeId != null) {
+                    stmt.setLong(paramIndex++, energyTypeId);
+                }
+                
+                try (ResultSet rs = stmt.executeQuery()) {
+                    if (rs.next()) {
+                        double usage = rs.getDouble("total_usage");
+                        if (!rs.wasNull()) {
+                            totalUsage = new BigDecimal(usage);
+                        }
+                    } else {
+                    }
+                }
+            }
+        } catch (Exception e) {
+            System.err.println("Error querying total energy consumption: " + e.getMessage());
+        }
+        
+        return totalUsage;
+    }
+    
+    /**
+     * 获取项目总面积和总人数
+     */
+    private Map<String, BigDecimal> getProjectAreaAndPopulation(Long projectId) {
+        Map<String, BigDecimal> result = new HashMap<>();
+        result.put("area", BigDecimal.ZERO);
+        result.put("population", BigDecimal.ZERO);
+        
+        if (projectId == null) {
+            return result;
+        }
+        
+        System.out.println("Getting project area and population for projectId: " + projectId);
+        
+        try (Connection conn = mysqlDataSource.getConnection()) {
+            String sql = "SELECT COALESCE(SUM(ba.area),0) as total_area, COALESCE(SUM(ba.resident_population),0) as total_population " +
+                        "FROM base_area ba " +
+                        "INNER JOIN base_space_area bsa ON bsa.area_id = ba.id " +
+                        "INNER JOIN base_space bs ON bs.id = bsa.space_id " +
+                        "WHERE bs.root_id = (SELECT space_id FROM ems_project WHERE id = ?)";
+            
+            System.out.println("Project area SQL: " + sql);
+            System.out.println("Setting projectId parameter: " + projectId);
+            
+            try (PreparedStatement stmt = conn.prepareStatement(sql)) {
+                stmt.setLong(1, projectId);
+                
+                try (ResultSet rs = stmt.executeQuery()) {
+                    if (rs.next()) {
+                        double area = rs.getDouble("total_area");
+                        double population = rs.getDouble("total_population");
+                        
+                        result.put("area", rs.wasNull() || Double.isNaN(area) ? BigDecimal.ZERO : new BigDecimal(area));
+                        result.put("population", rs.wasNull() || Double.isNaN(population) ? BigDecimal.ZERO : new BigDecimal(population));
+                    }
+                }
+            }
+        } catch (Exception e) {
+            System.err.println("Error getting project area and population: " + e.getMessage());
+        }
+        
+        return result;
+    }
+    
+    /**
+     * 计算能耗评定等级
+     */
+    private Map<String, String> calculateRating(BigDecimal areaUsage, Long energyTypeId) {
+        Map<String, String> rating = new HashMap<>();
+        
+        // 简单的评定逻辑,可以根据实际需求调整
+        String level;
+        String description = "单位面积能耗评定";
+        
+        if (energyTypeId == null) {
+            level = "良好";
+        } else {
+            switch (energyTypeId.intValue()) {
+                case 1: // 电
+                    if (areaUsage.compareTo(new BigDecimal("10")) < 0) {
+                        level = "优秀";
+                    } else if (areaUsage.compareTo(new BigDecimal("20")) < 0) {
+                        level = "良好";
+                    } else if (areaUsage.compareTo(new BigDecimal("30")) < 0) {
+                        level = "一般";
+                    } else {
+                        level = "较差";
+                    }
+                    break;
+                case 2: // 水
+                    if (areaUsage.compareTo(new BigDecimal("2")) < 0) {
+                        level = "优秀";
+                    } else if (areaUsage.compareTo(new BigDecimal("5")) < 0) {
+                        level = "良好";
+                    } else if (areaUsage.compareTo(new BigDecimal("8")) < 0) {
+                        level = "一般";
+                    } else {
+                        level = "较差";
+                    }
+                    break;
+                case 3: // 气
+                    if (areaUsage.compareTo(new BigDecimal("5")) < 0) {
+                        level = "优秀";
+                    } else if (areaUsage.compareTo(new BigDecimal("10")) < 0) {
+                        level = "良好";
+                    } else if (areaUsage.compareTo(new BigDecimal("15")) < 0) {
+                        level = "一般";
+                    } else {
+                        level = "较差";
+                    }
+                    break;
+                default:
+                    level = "良好";
+            }
+        }
+        
+        rating.put("level", level);
+        rating.put("description", description);
+        
+         return rating;
+     }
+     
+     /**
+      * 获取所有能源类型
+      */
+     private List<Long> getAllEnergyTypes(Long specificEnergyType) {
+         List<Long> energyTypes = new ArrayList<>();
+         
+         if (specificEnergyType != null) {
+             energyTypes.add(specificEnergyType);
+         } else {
+             // 返回所有支持的能源类型(1:电, 2:水, 3:气)
+             energyTypes.addAll(Arrays.asList(1L, 2L, 3L));
+         }
+         
+         return energyTypes;
+     }
+     
+     /**
+      * 解析区域ID
+      */
+     private List<Long> parseRegionIds(String regionIds, Long projectId) {
+         List<Long> regionIdList = new ArrayList<>();
+         
+         if (regionIds != null && !regionIds.trim().isEmpty()) {
+             // 解析逗号分隔的区域ID
+             try {
+                 regionIdList = Arrays.stream(regionIds.split(","))
+                     .map(String::trim)
+                     .map(new java.util.function.Function<String, Long>() {
+                         @Override
+                         public Long apply(String s) {
+                             return Long.valueOf(s);
+                         }
+                     })
+                     .collect(Collectors.toList());
+             } catch (Exception e) {
+                 System.err.println("Error parsing region IDs: " + e.getMessage());
+                 return regionIdList;
+             }
+         } else if (projectId != null) {
+             // 如果没有指定区域ID,则获取项目下的所有区域
+             try (Connection conn = mysqlDataSource.getConnection()) {
+                 // 查找该项目下的所有区域(type=2表示区域类型)
+                 String sql = "SELECT id FROM base_space WHERE type = 2 AND root_id = " +
+                         "(SELECT space_id FROM ems_project WHERE id = ?)";
+                 
+                 try (PreparedStatement stmt = conn.prepareStatement(sql)) {
+                     stmt.setLong(1, projectId);
+                     
+                     try (ResultSet rs = stmt.executeQuery()) {
+                         while (rs.next()) {
+                             regionIdList.add(Long.valueOf(rs.getLong("id")));
+                         }
+                     }
+                 }
+             } catch (Exception e) {
+                 System.err.println("Error getting region IDs by project: " + e.getMessage());
+             }
+         }
+         
+         return regionIdList;
+     }
+     
+     /**
+      * 分析区域能耗情况
+      */
+     private EmsRegionAnalysisItemVO analyzeRegionEnergyConsumption(Long regionId, String startTime, String endTime, 
+                                                                   Long energyTypeId, String timeDimension, String timeValue) {
+         try (Connection conn = mysqlDataSource.getConnection()) {
+             // 获取区域基本信息
+             String regionName = getRegionName(regionId, conn);
+             if (regionName == null) {
+                 return null;
+             }
+             
+             // 获取区域面积
+             BigDecimal area = getRegionArea(regionId, conn);
+             
+             // 获取该区域下的设备能耗
+             List<String> deviceIds = getDeviceIdsByRegion(regionId, energyTypeId, conn);
+             BigDecimal totalUsage = BigDecimal.ZERO;
+             String unit = "kWh";
+             
+             if (!deviceIds.isEmpty()) {
+                 totalUsage = queryTotalEnergyConsumption(deviceIds, startTime, endTime, energyTypeId);
+                 if (energyTypeId != null) {
+                     unit = ENERGY_UNITS.getOrDefault(energyTypeId, "kWh");
+                 }
+             }
+             
+             // 计算单位面积能耗
+             BigDecimal areaUsage = BigDecimal.ZERO;
+             if (area.compareTo(BigDecimal.ZERO) > 0) {
+                 areaUsage = totalUsage.divide(area, 2, RoundingMode.HALF_UP);
+             }
+             
+             EmsRegionAnalysisItemVO item = new EmsRegionAnalysisItemVO();
+             item.setRegionId(regionId);
+             item.setRegionName(regionName);
+             item.setArea(area.setScale(2, RoundingMode.HALF_UP));
+             item.setTotalUsage(totalUsage.setScale(2, RoundingMode.HALF_UP));
+             item.setAreaUsage(areaUsage);
+             item.setUnit(unit);
+             item.setTimeDimension(timeDimension);
+             item.setTimeValue(timeValue);
+             
+             return item;
+         } catch (Exception e) {
+             System.err.println("Error analyzing region energy consumption: " + e.getMessage());
+             return null;
+         }
+     }
+     
+     /**
+      * 获取区域名称
+      */
+     private String getRegionName(Long regionId, Connection conn) {
+         try {
+             String sql = "SELECT name FROM base_space WHERE id = ?";
+             try (PreparedStatement stmt = conn.prepareStatement(sql)) {
+                 stmt.setLong(1, regionId);
+                 try (ResultSet rs = stmt.executeQuery()) {
+                     if (rs.next()) {
+                         return rs.getString("name");
+                     }
+                 }
+             }
+         } catch (Exception e) {
+             System.err.println("Error getting region name: " + e.getMessage());
+         }
+         return "未知区域";
+     }
+     
+     /**
+      * 获取区域面积
+      */
+     private BigDecimal getRegionArea(Long regionId, Connection conn) {
+         try {
+             String sql = "SELECT COALESCE(SUM(ba.area),0) as total_area FROM base_area ba " +
+                     "INNER JOIN base_space_area bsa ON bsa.area_id = ba.id WHERE bsa.space_id = ?";
+             try (PreparedStatement stmt = conn.prepareStatement(sql)) {
+                 stmt.setLong(1, regionId);
+                 try (ResultSet rs = stmt.executeQuery()) {
+                     if (rs.next()) {
+                         double area = rs.getDouble("total_area");
+                         if (!rs.wasNull()) {
+                             return new BigDecimal(area);
+                         }
+                     }
+                 }
+             }
+         } catch (Exception e) {
+             System.err.println("Error getting region area: " + e.getMessage());
+         }
+         return BigDecimal.ZERO;
+     }
+     
+     /**
+      * 获取区域下的设备ID列表
+      */
+     private List<String> getDeviceIdsByRegion(Long regionId, Long energyTypeId, Connection conn) {
+         List<String> deviceIds = new ArrayList<>();
+         
+         try {
+             StringBuilder sql = new StringBuilder();
+             sql.append("SELECT DISTINCT ed.id ");
+             sql.append("FROM ems_device ed ");
+             
+             if (energyTypeId != null) {
+                 sql.append("INNER JOIN ems_product_energy_type epe ON ed.product_id = epe.product_id ");
+             }
+             
+             // 查找该区域下的建筑,然后在这些建筑中查找设备
+             sql.append("WHERE ed.monitoring_location IN ");
+             sql.append("(SELECT build_id FROM base_space_build WHERE space_id = ?) ");
+             
+             if (energyTypeId != null) {
+                 sql.append("AND epe.energy_type = ? ");
+             }
+             
+             try (PreparedStatement stmt = conn.prepareStatement(sql.toString())) {
+                 int paramIndex = 1;
+                 stmt.setLong(paramIndex++, regionId);
+                 
+                 if (energyTypeId != null) {
+                     stmt.setLong(paramIndex++, energyTypeId);
+                 }
+                 
+                 try (ResultSet rs = stmt.executeQuery()) {
+                     while (rs.next()) {
+                         deviceIds.add(rs.getString("id"));
+                     }
+                 }
+             }
+         } catch (Exception e) {
+             System.err.println("Error getting device IDs by region: " + e.getMessage());
+         }
+         
+         return deviceIds;
+     }
+
+     @Override
+     public EmsTrendCategoryResponse getTrendCategory(Long projectId, String timeDimension, String timeValue, Long energyTypeId) {
+        EmsTrendCategoryResponse resp = new EmsTrendCategoryResponse();
+        resp.setTimeDimension(timeDimension);
+        resp.setTimeValue(timeValue);
+
+        try {
+            // 获取所有能源类型
+            List<Long> energyTypes = getAllEnergyTypes(energyTypeId);
+            
+            // 查询每个能源类型的用量
+            List<EmsCategoryRatioItemVO> categoryItems = new ArrayList<>();
+            BigDecimal totalAllUsage = BigDecimal.ZERO;
+            Map<Long, BigDecimal> energyTypeUsage = new HashMap<>();
+            
+            for (Long typeId : energyTypes) {
+                List<String> deviceIds = getDeviceIdsByProject(projectId, typeId);
+                if (!deviceIds.isEmpty()) {
+                    String[] timeRange = getTimeRange(timeDimension, timeValue);
+                    BigDecimal usage = queryTotalEnergyConsumption(deviceIds, timeRange[0], timeRange[1], typeId);
+                    energyTypeUsage.put(typeId, usage);
+                    totalAllUsage = totalAllUsage.add(usage);
+                } else {
+                    energyTypeUsage.put(typeId, BigDecimal.ZERO);
+                }
+            }
+            
+            // 计算占比和构建响应
+            for (Long typeId : energyTypes) {
+                BigDecimal usage = energyTypeUsage.get(typeId);
+                BigDecimal ratio = BigDecimal.ZERO;
+                
+                if (totalAllUsage.compareTo(BigDecimal.ZERO) > 0) {
+                    ratio = usage.divide(totalAllUsage, 4, RoundingMode.HALF_UP)
+                                 .multiply(new BigDecimal("100"))
+                                 .setScale(1, RoundingMode.HALF_UP);
+                }
+                
+                 EmsCategoryRatioItemVO item = new EmsCategoryRatioItemVO();
+                 item.setEnergyTypeName(typeId >= 1 && typeId <= 3 ? ENERGY_TYPE_NAMES[typeId.intValue()] : "未知");
+                 item.setRatio(ratio);
+                 item.setUsage(usage.setScale(2, RoundingMode.HALF_UP));
+                 item.setUnit(ENERGY_UNITS.get(typeId));
+                
+                categoryItems.add(item);
+            }
+            
+            resp.setCategoryRatio(categoryItems);
+            
+        } catch (Exception e) {
+            System.err.println("Error getting trend category: " + e.getMessage());
+        }
+        
+        return resp;
+    }
+
+     @Override
+     public EmsRegionAnalysisResponse getRegionAnalysis(Long projectId, String regionIds, String timeDimension, String timeValue, Long energyTypeId) {
+         EmsRegionAnalysisResponse resp = new EmsRegionAnalysisResponse();
+ 
+         try {
+             // 简化实现:如果没有指定区域ID,返回项目整体数据作为一个区域
+             List<EmsRegionAnalysisItemVO> items = new ArrayList<>();
+             String[] timeRange = getTimeRange(timeDimension, timeValue);
+             
+             // 获取项目下的所有设备
+             List<String> deviceIds = getDeviceIdsByProject(projectId, energyTypeId);
+             if (!deviceIds.isEmpty()) {
+                 // 计算项目总体能耗
+                 BigDecimal totalUsage = queryTotalEnergyConsumption(deviceIds, timeRange[0], timeRange[1], energyTypeId);
+                 
+                 // 获取项目面积
+                 Map<String, BigDecimal> areaAndPopulation = getProjectAreaAndPopulation(projectId);
+                 BigDecimal totalArea = areaAndPopulation.get("area");
+                 
+                 // 计算单位面积能耗
+                 BigDecimal areaUsage = BigDecimal.ZERO;
+                 if (totalArea.compareTo(BigDecimal.ZERO) > 0) {
+                     areaUsage = totalUsage.divide(totalArea, 2, RoundingMode.HALF_UP);
+                 }
+                 
+                 // 设置单位
+                 String unit = "kWh";
+                 if (energyTypeId != null) {
+                     unit = ENERGY_UNITS.getOrDefault(energyTypeId, "kWh");
+                 }
+                 
+                 EmsRegionAnalysisItemVO item = new EmsRegionAnalysisItemVO();
+                 item.setRegionId(projectId != null ? projectId : 1L);
+                 item.setRegionName(projectId != null ? "项目" + projectId : "默认项目");
+                 item.setArea(totalArea.setScale(2, RoundingMode.HALF_UP));
+                 item.setTotalUsage(totalUsage.setScale(2, RoundingMode.HALF_UP));
+                 item.setAreaUsage(areaUsage);
+                 item.setUnit(unit);
+                 item.setTimeDimension(timeDimension);
+                 item.setTimeValue(timeValue);
+                 
+                 items.add(item);
+             }
+             
+             resp.setItems(items);
+             
+         } catch (Exception e) {
+             System.err.println("Error getting region analysis: " + e.getMessage());
+         }
+         
+         return resp;
+     }
+
+    @Override
+    public EmsCompareResponse getCompare(EmsCompareRequest request) {
+        EmsCompareResponse resp = new EmsCompareResponse();
+        
+        try {
+            if (request == null || request.getDeviceIds() == null || request.getDeviceIds().isEmpty() || 
+                request.getTimeValues() == null || request.getTimeValues().isEmpty()) {
+                return resp;
+            }
+            
+            // 设置能源类型名称
+            if (request.getEnergyTypeId() != null) {
+                long id = request.getEnergyTypeId();
+                resp.setEnergyTypeName(id >= 1 && id <= 3 ? ENERGY_TYPE_NAMES[(int) id] : "");
+            }
+            
+            // 设置时间维度
+            resp.setDimension(request.getTimeDimension());
+            
+            // 为每个设备查询对比数据
+            List<EmsCompareSeriesItemVO> series = new ArrayList<>();
+            
+            for (String deviceId : request.getDeviceIds()) {
+                EmsCompareSeriesItemVO seriesItem = getDeviceCompareData(deviceId, request.getEnergyTypeId(), 
+                                                                       request.getTimeDimension(), request.getTimeValues());
+                if (seriesItem != null) {
+                    series.add(seriesItem);
+                }
+            }
+            
+            resp.setSeries(series);
+            
+        } catch (Exception e) {
+            System.err.println("Error getting compare data: " + e.getMessage());
+        }
+        
+        return resp;
+    }
+    
+    /**
+     * 获取设备对比数据
+     */
+    private EmsCompareSeriesItemVO getDeviceCompareData(String deviceId, Long energyTypeId, String timeDimension, List<String> timeValues) {
+        try (Connection conn = mysqlDataSource.getConnection()) {
+            // 获取设备名称
+            String deviceName = getDeviceName(deviceId, conn);
+            
+            List<EmsCompareValueVO> values = new ArrayList<>();
+            
+            // 为每个时间点查询数据
+            for (String timeValue : timeValues) {
+                String[] timeRange = getTimeRange(timeDimension, timeValue);
+                BigDecimal usage = queryDeviceEnergyForCompare(deviceId, energyTypeId, timeRange[0], timeRange[1], conn);
+                
+                EmsCompareValueVO valueItem = new EmsCompareValueVO();
+                valueItem.setTimeValue(timeValue);
+                valueItem.setUsage(usage.setScale(2, RoundingMode.HALF_UP));
+                values.add(valueItem);
+            }
+            
+            EmsCompareSeriesItemVO seriesItem = new EmsCompareSeriesItemVO();
+            seriesItem.setDeviceId(deviceId);
+            seriesItem.setDeviceName(deviceName);
+            seriesItem.setValues(values);
+            
+            return seriesItem;
+            
+        } catch (Exception e) {
+            System.err.println("Error getting device compare data: " + e.getMessage());
+            return null;
+        }
+    }
+    
+    /**
+     * 获取设备名称
+     */
+    private String getDeviceName(String deviceId, Connection conn) {
+        try {
+            String sql = "SELECT name FROM ems_device WHERE id = ?";
+            try (PreparedStatement stmt = conn.prepareStatement(sql)) {
+                stmt.setString(1, deviceId);
+                try (ResultSet rs = stmt.executeQuery()) {
+                    if (rs.next()) {
+                        return rs.getString("name");
+                    }
+                }
+            }
+        } catch (Exception e) {
+            System.err.println("Error getting device name: " + e.getMessage());
+        }
+        return "未知设备";
+    }
+    
+    /**
+     * 查询设备能耗(用于对比)
+     */
+    private BigDecimal queryDeviceEnergyForCompare(String deviceId, Long energyTypeId, String startTime, String endTime, Connection conn) {
+        try {
+            StringBuilder sql = new StringBuilder();
+            sql.append("SELECT SUM(eed.end_value - eed.start_value) as total_usage ");
+sql.append("FROM ems_energy_consumption_device_data eed ");
+        if (energyTypeId != null) {
+            sql.append("INNER JOIN ems_device ed ON eed.device_id = ed.id ");
+            sql.append("INNER JOIN ems_product_energy_type epe ON ed.product_id = epe.product_id ");
+        }
+        sql.append("WHERE eed.device_id = ? ");
+            sql.append("AND eed.start_time >= ? AND eed.end_time <= ? ");
+            
+            if (energyTypeId != null) {
+                sql.append("AND epe.energy_type = ? ");
+            }
+            
+            try (PreparedStatement stmt = conn.prepareStatement(sql.toString())) {
+                int paramIndex = 1;
+                stmt.setString(paramIndex++, deviceId);
+                stmt.setString(paramIndex++, startTime);
+                stmt.setString(paramIndex++, endTime);
+                
+                if (energyTypeId != null) {
+                    stmt.setLong(paramIndex++, energyTypeId);
+                }
+                
+                try (ResultSet rs = stmt.executeQuery()) {
+                    if (rs.next()) {
+                        double usage = rs.getDouble("total_usage");
+                        if (!rs.wasNull()) {
+                            return new BigDecimal(usage);
+                        }
+                    }
+                }
+            }
+        } catch (Exception e) {
+            System.err.println("Error querying device energy for compare: " + e.getMessage());
+        }
+        
+        return BigDecimal.ZERO;
+    }
+
+    @Override
+    public EmsAverageResponse getAverage(Long projectId, String timeDimension, String timeValue, Long energyTypeId) {
+        EmsAverageResponse resp = new EmsAverageResponse();
+        resp.setDimension(timeDimension);
+        resp.setTimeValue(timeValue);
+
+        try {
+            // 获取项目下的所有设备
+            List<String> deviceIds = getDeviceIdsByProject(projectId, energyTypeId);
+            if (deviceIds.isEmpty()) {
+                return resp;
+            }
+
+            // 查询总能耗
+            String[] timeRange = getTimeRange(timeDimension, timeValue);
+            BigDecimal totalUsage = queryTotalEnergyConsumption(deviceIds, timeRange[0], timeRange[1], energyTypeId);
+
+            // 获取项目面积和人口信息
+            Map<String, BigDecimal> areaAndPopulation = getProjectAreaAndPopulation(projectId);
+            BigDecimal totalArea = areaAndPopulation.get("area");
+            BigDecimal totalPopulation = areaAndPopulation.get("population");
+
+            // 计算单位面积能耗和人均能耗
+            BigDecimal areaUsage = BigDecimal.ZERO;
+            BigDecimal perCapitaUsage = BigDecimal.ZERO;
+
+            if (totalArea.compareTo(BigDecimal.ZERO) > 0) {
+                areaUsage = totalUsage.divide(totalArea, 2, RoundingMode.HALF_UP);
+            }
+
+            if (totalPopulation.compareTo(BigDecimal.ZERO) > 0) {
+                perCapitaUsage = totalUsage.divide(totalPopulation, 2, RoundingMode.HALF_UP);
+            }
+
+            // 设置单位
+            String unit = "kWh";
+            if (energyTypeId != null) {
+                unit = ENERGY_UNITS.getOrDefault(energyTypeId, "kWh");
+            }
+
+            resp.setAreaUsage(areaUsage);
+            resp.setPerCapitaUsage(perCapitaUsage);
+            resp.setUnit(unit);
+
+        } catch (Exception e) {
+            System.err.println("Error getting average data: " + e.getMessage());
+        }
+
+        return resp;
+    }
+
+    /**
+     * 计算所有能源类型的趋势项
+     * 当energyTypeId为null时使用,查询项目中所有能源类型设备的总能耗
+     */
+    private EmsTrendItemVO calculateTrendItemForAllEnergyTypes(Long projectId, String timeDimension, String timePoint) {
+        EmsTrendItemVO item = new EmsTrendItemVO();
+        item.setTimeLabel(timePoint);
+        
+        try {
+            String[] timeRange = getTimeRange(timeDimension, timePoint);
+            
+            // 对每种能源类型分别统计
+            BigDecimal totalAllUsage = BigDecimal.ZERO;
+            BigDecimal totalStandardCoal = BigDecimal.ZERO;
+            BigDecimal totalCarbonEmission = BigDecimal.ZERO;
+            
+            for (Long energyType : Arrays.asList(1L, 2L, 3L)) {
+                // 获取该能源类型的设备
+                List<String> deviceIds = getDeviceIdsByProject(projectId, energyType);
+                if (!deviceIds.isEmpty()) {
+                    // 查询该能源类型的用量
+                    BigDecimal usage = queryTotalEnergyConsumption(deviceIds, timeRange[0], timeRange[1], energyType);
+                    if (usage != null && usage.compareTo(BigDecimal.ZERO) > 0) {
+                        totalAllUsage = totalAllUsage.add(usage);
+                        
+                        // 计算该能源类型的标准煤和碳排放
+                        BigDecimal coalFactor = STANDARD_COAL_FACTORS.get(energyType);
+                        BigDecimal carbonFactor = CARBON_EMISSION_FACTORS.get(energyType);
+                        
+                        if (coalFactor != null) {
+                            totalStandardCoal = totalStandardCoal.add(usage.multiply(coalFactor));
+                        }
+                        if (carbonFactor != null) {
+                            totalCarbonEmission = totalCarbonEmission.add(usage.multiply(carbonFactor));
+                        }
+                    }
+                }
+            }
+            
+            // 设置结果
+            item.setUsage(totalAllUsage.setScale(2, RoundingMode.HALF_UP));
+            item.setStandardCoal(totalStandardCoal.setScale(2, RoundingMode.HALF_UP));
+            item.setCarbonEmission(totalCarbonEmission.setScale(2, RoundingMode.HALF_UP));
+            
+        } catch (Exception e) {
+            System.err.println("Error calculating trend item for all energy types: " + e.getMessage());
+            item.setUsage(BigDecimal.ZERO);
+            item.setStandardCoal(BigDecimal.ZERO);
+            item.setCarbonEmission(BigDecimal.ZERO);
+        }
+        
+        return item;
+    }
+}

+ 20 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsChannelTypeParameterServiceImpl.java

@@ -0,0 +1,20 @@
+package com.usky.ems.service.impl;
+
+import com.usky.ems.domain.EmsChannelTypeParameter;
+import com.usky.ems.mapper.EmsChannelTypeParameterMapper;
+import com.usky.ems.service.EmsChannelTypeParameterService;
+import com.usky.common.mybatis.core.AbstractCrudService;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 通道类型参数 服务实现类
+ * </p>
+ *
+ * @author ya
+ * @since 2026-03-16
+ */
+@Service
+public class EmsChannelTypeParameterServiceImpl extends AbstractCrudService<EmsChannelTypeParameterMapper, EmsChannelTypeParameter> implements EmsChannelTypeParameterService {
+
+}

+ 20 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsChannelTypeParameterValueServiceImpl.java

@@ -0,0 +1,20 @@
+package com.usky.ems.service.impl;
+
+import com.usky.ems.domain.EmsChannelTypeParameterValue;
+import com.usky.ems.mapper.EmsChannelTypeParameterValueMapper;
+import com.usky.ems.service.EmsChannelTypeParameterValueService;
+import com.usky.common.mybatis.core.AbstractCrudService;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 通道类型参数可选值 服务实现类
+ * </p>
+ *
+ * @author ya
+ * @since 2026-03-16
+ */
+@Service
+public class EmsChannelTypeParameterValueServiceImpl extends AbstractCrudService<EmsChannelTypeParameterValueMapper, EmsChannelTypeParameterValue> implements EmsChannelTypeParameterValueService {
+
+}

+ 71 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsChannelTypeServiceImpl.java

@@ -0,0 +1,71 @@
+package com.usky.ems.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.usky.ems.domain.EmsChannelType;
+import com.usky.ems.domain.EmsChannelTypeParameter;
+import com.usky.ems.domain.EmsChannelTypeParameterValue;
+import com.usky.ems.mapper.EmsChannelTypeMapper;
+import com.usky.ems.mapper.EmsChannelTypeParameterMapper;
+import com.usky.ems.mapper.EmsChannelTypeParameterValueMapper;
+import com.usky.ems.service.EmsChannelTypeService;
+import com.usky.ems.service.vo.EmsChannelTypeParameterShowVO;
+import com.usky.ems.service.vo.EmsChannelTypeParameterValueShowVO;
+import com.usky.ems.service.vo.EmsChannelTypeShowVO;
+import com.usky.common.mybatis.core.AbstractCrudService;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * <p>
+ * 通道类型 服务实现类
+ * </p>
+ *
+ * @author ya
+ * @since 2026-03-16
+ */
+@Service
+public class EmsChannelTypeServiceImpl extends AbstractCrudService<EmsChannelTypeMapper, EmsChannelType> implements EmsChannelTypeService {
+
+    @Autowired
+    private EmsChannelTypeMapper emsChannelTypeMapper;
+    @Autowired
+    private EmsChannelTypeParameterMapper channelTypeParameterMapper;
+    @Autowired
+    private EmsChannelTypeParameterValueMapper channelTypeParameterValueMapper;
+
+    @Override
+    public List<EmsChannelTypeShowVO> show() {
+        List<EmsChannelType> types = emsChannelTypeMapper.selectList(new LambdaQueryWrapper<>());
+        List<EmsChannelTypeShowVO> result = new ArrayList<>(types.size());
+        for (EmsChannelType type : types) {
+            EmsChannelTypeShowVO vo = new EmsChannelTypeShowVO();
+            BeanUtils.copyProperties(type, vo);
+            List<EmsChannelTypeParameter> params = channelTypeParameterMapper.selectList(
+                    new LambdaQueryWrapper<EmsChannelTypeParameter>()
+                            .eq(EmsChannelTypeParameter::getChannelTypeId, type.getId())
+                            .orderByAsc(EmsChannelTypeParameter::getSort));
+            List<EmsChannelTypeParameterShowVO> paramVos = params.stream().map(p -> {
+                EmsChannelTypeParameterShowVO pvo = new EmsChannelTypeParameterShowVO();
+                BeanUtils.copyProperties(p, pvo);
+                List<EmsChannelTypeParameterValue> values = channelTypeParameterValueMapper.selectList(
+                        new LambdaQueryWrapper<EmsChannelTypeParameterValue>()
+                                .eq(EmsChannelTypeParameterValue::getChannelTypeParameterId, p.getId())
+                                .orderByAsc(EmsChannelTypeParameterValue::getSort));
+                pvo.setChannelTypeValueList(values.stream().map(v -> {
+                    EmsChannelTypeParameterValueShowVO vvo = new EmsChannelTypeParameterValueShowVO();
+                    BeanUtils.copyProperties(v, vvo);
+                    return vvo;
+                }).collect(Collectors.toList()));
+                return pvo;
+            }).collect(Collectors.toList());
+            vo.setChannelTypeParameterList(paramVos);
+            result.add(vo);
+        }
+        return result;
+    }
+}

+ 193 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsDeviceEventServiceImpl.java

@@ -0,0 +1,193 @@
+package com.usky.ems.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.usky.common.core.exception.BusinessException;
+import com.usky.ems.domain.BaseSpace;
+import com.usky.ems.domain.BaseSpaceGateway;
+import com.usky.ems.domain.DmpGateway;
+import com.usky.ems.domain.EmsDevice;
+import com.usky.ems.domain.EmsDeviceEvent;
+import com.usky.ems.mapper.BaseSpaceGatewayMapper;
+import com.usky.ems.mapper.DmpGatewayMapper;
+import com.usky.ems.mapper.EmsDeviceEventMapper;
+import com.usky.ems.mapper.EmsDeviceMapper;
+import com.usky.ems.service.BaseSpaceService;
+import com.usky.ems.service.EmsDeviceEventService;
+import com.usky.ems.service.vo.DeviceEventDTO;
+import com.usky.ems.service.vo.DeviceEventVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * 设备事件服务实现(全部逻辑在 Service 层)
+ */
+@Service
+public class EmsDeviceEventServiceImpl implements EmsDeviceEventService {
+
+    @Autowired
+    private BaseSpaceService baseSpaceService;
+
+    @Autowired
+    private EmsDeviceMapper emsDeviceMapper;
+
+    @Autowired
+    private BaseSpaceGatewayMapper baseSpaceGatewayMapper;
+
+    @Autowired
+    private DmpGatewayMapper dmpGatewayMapper;
+
+    @Autowired
+    private EmsDeviceEventMapper emsDeviceEventMapper;
+
+    @Override
+    public List<DeviceEventVO> list(DeviceEventDTO dto) {
+        if (dto == null || dto.getInstallationLocation() == null) {
+            throw new BusinessException("安装位置不存在");
+        }
+
+        BaseSpace space = baseSpaceService.getById(dto.getInstallationLocation());
+        if (space == null) {
+            throw new BusinessException("安装位置不存在");
+        }
+
+        // 1) 递归获取所有子空间 + 本空间
+        List<BaseSpace> spaceList = new ArrayList<>(baseSpaceService.recursiveAllChildrenNode(dto.getInstallationLocation()));
+        spaceList.add(space);
+
+        List<Long> spaceIds = spaceList.stream()
+                .map(BaseSpace::getId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+
+        Map<Long, String> spaceMap = spaceList.stream()
+                .filter(s -> s.getId() != null)
+                .collect(Collectors.toMap(BaseSpace::getId, BaseSpace::getName, (a, b) -> a));
+
+        // 2) 根据 deviceType 取设备/网关列表及其名称、空间映射
+        Integer deviceType = dto.getDeviceType();
+        if (deviceType == null || (deviceType != 1 && deviceType != 2)) {
+            throw new BusinessException("设备类型错误");
+        }
+
+        List<String> ids;
+        Map<String, String> nameMap;
+        Map<String, Long> spaceIdMap;
+
+        if (deviceType == 1) {
+            List<EmsDevice> devices = emsDeviceMapper.selectList(new LambdaQueryWrapper<EmsDevice>()
+                    .in(EmsDevice::getInstallationLocation, spaceIds));
+            if (devices == null || devices.isEmpty()) {
+                return Collections.emptyList();
+            }
+            ids = devices.stream().map(EmsDevice::getId).filter(Objects::nonNull).collect(Collectors.toList());
+            nameMap = devices.stream().filter(d -> d.getId() != null)
+                    .collect(Collectors.toMap(EmsDevice::getId, EmsDevice::getName, (a, b) -> a));
+            spaceIdMap = devices.stream().filter(d -> d.getId() != null)
+                    .collect(Collectors.toMap(EmsDevice::getId, EmsDevice::getInstallationLocation, (a, b) -> a));
+        } else {
+            List<BaseSpaceGateway> links = baseSpaceGatewayMapper.selectList(
+                    new LambdaQueryWrapper<BaseSpaceGateway>().in(BaseSpaceGateway::getSpaceId, spaceIds));
+            if (links == null || links.isEmpty()) {
+                return Collections.emptyList();
+            }
+            java.util.Map<String, Long> uuidToSpace = new java.util.HashMap<>();
+            for (BaseSpaceGateway link : links) {
+                if (link.getGatewayUuid() != null && link.getSpaceId() != null) {
+                    uuidToSpace.putIfAbsent(link.getGatewayUuid(), link.getSpaceId());
+                }
+            }
+            if (uuidToSpace.isEmpty()) {
+                return Collections.emptyList();
+            }
+            List<DmpGateway> gateways = dmpGatewayMapper.selectList(
+                    new LambdaQueryWrapper<DmpGateway>().in(DmpGateway::getDeviceUuid, uuidToSpace.keySet()));
+            if (gateways == null || gateways.isEmpty()) {
+                return Collections.emptyList();
+            }
+            ids = gateways.stream().map(DmpGateway::getDeviceUuid).filter(Objects::nonNull).collect(Collectors.toList());
+            nameMap = gateways.stream().filter(g -> g.getDeviceUuid() != null)
+                    .collect(Collectors.toMap(DmpGateway::getDeviceUuid, DmpGateway::getName, (a, b) -> a));
+            spaceIdMap = gateways.stream().filter(g -> g.getDeviceUuid() != null)
+                    .collect(Collectors.toMap(DmpGateway::getDeviceUuid, g -> uuidToSpace.get(g.getDeviceUuid()), (a, b) -> a));
+        }
+
+        if (ids.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        // 3) 查事件表 ems_device_event
+        LambdaQueryWrapper<EmsDeviceEvent> eventWrapper = new LambdaQueryWrapper<>();
+        eventWrapper.eq(EmsDeviceEvent::getDeviceType, deviceType)
+                .in(EmsDeviceEvent::getDeviceId, ids);
+
+        if (dto.getStartTime() != null && dto.getEndTime() != null) {
+            eventWrapper.between(EmsDeviceEvent::getStartTime, dto.getStartTime(), dto.getEndTime());
+        } else if (dto.getStartTime() != null) {
+            eventWrapper.ge(EmsDeviceEvent::getStartTime, dto.getStartTime());
+        } else if (dto.getEndTime() != null) {
+            eventWrapper.le(EmsDeviceEvent::getStartTime, dto.getEndTime());
+        }
+
+        List<EmsDeviceEvent> eventList = emsDeviceEventMapper.selectList(eventWrapper);
+        if (eventList == null || eventList.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        LocalDateTime now = LocalDateTime.now();
+        List<DeviceEventVO> result = new ArrayList<>(eventList.size());
+        for (EmsDeviceEvent deviceEvent : eventList) {
+            DeviceEventVO vo = new DeviceEventVO();
+            vo.setDeviceId(deviceEvent.getDeviceId());
+            vo.setDeviceName(nameMap.get(deviceEvent.getDeviceId()));
+            vo.setContent(deviceEvent.getContent());
+            Long sid = spaceIdMap.get(deviceEvent.getDeviceId());
+            vo.setInstallationLocation(sid == null ? null : spaceMap.get(sid));
+            vo.setEventType(deviceEvent.getEventType());
+            vo.setStartTime(deviceEvent.getStartTime());
+            vo.setIdentifier(deviceEvent.getIdentifier());
+            vo.setDuration(formatDuration(deviceEvent.getStartTime(), now));
+            result.add(vo);
+        }
+
+        result.sort(Comparator.comparing(DeviceEventVO::getDeviceName,
+                Comparator.nullsLast(String.CASE_INSENSITIVE_ORDER)));
+
+        return result;
+    }
+
+    private String formatDuration(LocalDateTime start, LocalDateTime end) {
+        if (start == null || end == null) {
+            return null;
+        }
+        Duration duration = Duration.between(start, end);
+        long minutes = duration.toMinutes();
+        if (minutes < 0) {
+            minutes = 0;
+        }
+        if (minutes >= 1440L) {
+            long days = minutes / 1440L;
+            long hours = (minutes % 1440L) / 60L;
+            long mins = minutes % 60L;
+            return days + "天" + hours + "小时" + mins + "分钟";
+        }
+        if (minutes >= 60L) {
+            long hours = minutes / 60L;
+            long mins = minutes % 60L;
+            return hours + "小时" + mins + "分钟";
+        }
+        return minutes + "分钟";
+    }
+}
+

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov