Просмотр исходного кода

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

hanzhengyi 1 неделя назад
Родитель
Сommit
91b70b7bcf
100 измененных файлов с 6312 добавлено и 959 удалено
  1. 1 1
      service-ems/service-ems-biz/src/main/java/com/usky/ems/MybatisGenerator.java
  2. 30 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/BaseSpaceController.java
  3. 76 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/DmpGatewayController.java
  4. 67 15
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsAnalysisController.java
  5. 0 233
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsApiV1Controller.java
  6. 0 37
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsAuthController.java
  7. 36 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsChannelTypeController.java
  8. 21 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsChannelTypeParameterController.java
  9. 21 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsChannelTypeParameterValueController.java
  10. 32 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsDeviceEventController.java
  11. 210 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsModelController.java
  12. 134 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsOverviewController.java
  13. 37 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsProjectController.java
  14. 20 14
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsReportController.java
  15. 54 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsSystemDictController.java
  16. 8 8
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/BaseArea.java
  17. 97 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/BaseBuild.java
  18. 8 8
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/BaseSpace.java
  19. 34 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/BaseSpaceArea.java
  20. 34 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/BaseSpaceBuild.java
  21. 34 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/BaseSpaceGateway.java
  22. 27 19
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/DmpGateway.java
  23. 92 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/DmpProduct.java
  24. 1 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsChannel.java
  25. 61 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsChannelType.java
  26. 71 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsChannelTypeParameter.java
  27. 71 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsChannelTypeParameterValue.java
  28. 47 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsDataUploadShanghaiProduct.java
  29. 71 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsDeviceEvent.java
  30. 61 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsEnergyConsumptionFormula.java
  31. 6 3
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsProject.java
  32. 39 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsProjectDeviceSystem.java
  33. 0 67
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsSpaceBuilding.java
  34. 6 14
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsSystemDictCode.java
  35. 37 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsSystemDictRegion.java
  36. 43 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsSystemDictValue.java
  37. 82 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/enums/TemporalTypeEnum.java
  38. 55 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/enums/ValueTypeEnum.java
  39. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/BaseAreaMapper.java
  40. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/BaseBuildMapper.java
  41. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/BaseSpaceAreaMapper.java
  42. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/BaseSpaceBuildMapper.java
  43. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/BaseSpaceGatewayMapper.java
  44. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/BaseSpaceMapper.java
  45. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/DmpGatewayMapper.java
  46. 11 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/DmpProductMapper.java
  47. 16 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsChannelTypeMapper.java
  48. 16 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsChannelTypeParameterMapper.java
  49. 16 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsChannelTypeParameterValueMapper.java
  50. 12 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsDataUploadShanghaiProductMapper.java
  51. 11 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsDeviceEventMapper.java
  52. 11 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsEnergyConsumptionFormulaMapper.java
  53. 0 10
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsGatewayMapper.java
  54. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsProjectDeviceSystemMapper.java
  55. 1 1
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsProjectMapper.java
  56. 0 10
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsSpaceAreaMapper.java
  57. 0 10
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsSpaceBuildingMapper.java
  58. 0 10
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsSpaceFloorMapper.java
  59. 0 10
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsSpaceMapper.java
  60. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsSystemDictCodeMapper.java
  61. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsSystemDictRegionMapper.java
  62. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsSystemDictValueMapper.java
  63. 41 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/BaseSpaceService.java
  64. 28 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/DmpGatewayService.java
  65. 23 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/DmpProductService.java
  66. 2 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsAnalysisService.java
  67. 0 14
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsAuthService.java
  68. 16 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsChannelTypeParameterService.java
  69. 16 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsChannelTypeParameterValueService.java
  70. 23 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsChannelTypeService.java
  71. 18 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsDeviceEventService.java
  72. 25 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsDeviceReportService.java
  73. 19 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsEnergyItemCodeService.java
  74. 0 22
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsGatewayQueryService.java
  75. 64 28
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsModelService.java
  76. 45 1
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsOverviewService.java
  77. 16 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsProjectService.java
  78. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsSystemDictCodeService.java
  79. 22 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsSystemDictRegionService.java
  80. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsSystemDictValueService.java
  81. 162 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/BaseSpaceServiceImpl.java
  82. 191 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/DmpGatewayServiceImpl.java
  83. 75 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/DmpProductServiceImpl.java
  84. 1160 16
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsAnalysisServiceImpl.java
  85. 0 30
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsAuthServiceImpl.java
  86. 20 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsChannelTypeParameterServiceImpl.java
  87. 20 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsChannelTypeParameterValueServiceImpl.java
  88. 71 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsChannelTypeServiceImpl.java
  89. 193 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsDeviceEventServiceImpl.java
  90. 272 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsDeviceReportServiceImpl.java
  91. 110 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsEnergyItemCodeServiceImpl.java
  92. 0 87
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsGatewayQueryServiceImpl.java
  93. 711 255
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsModelServiceImpl.java
  94. 625 6
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsOverviewServiceImpl.java
  95. 243 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsProjectServiceImpl.java
  96. 19 30
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsReportServiceImpl.java
  97. 15 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsSystemDictCodeServiceImpl.java
  98. 61 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsSystemDictRegionServiceImpl.java
  99. 15 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsSystemDictValueServiceImpl.java
  100. 43 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/BaseSpaceForestNodeVO.java

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

+ 67 - 15
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsAnalysisController.java

@@ -9,53 +9,105 @@ 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("/prod-api/service-ems")
+@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("/analysis/trend")
+    @GetMapping("/trend")
     public ApiResult<EmsTrendResponse> getTrend(
             @RequestParam(required = false) Long projectId,
-            @RequestParam String timeDimension,
-            @RequestParam String timeValue,
+            @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("/analysis/trend/indicators")
+    @GetMapping("/trend/indicators")
     public ApiResult<EmsTrendIndicatorsResponse> getTrendIndicators(
             @RequestParam(required = false) Long projectId,
-            @RequestParam String timeDimension,
-            @RequestParam String timeValue,
+            @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("/analysis/trend/category")
+    @GetMapping("/trend/category")
     public ApiResult<EmsTrendCategoryResponse> getTrendCategory(
             @RequestParam(required = false) Long projectId,
-            @RequestParam String timeDimension,
-            @RequestParam String timeValue,
+            @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("/analysis/region")
+    @GetMapping("/region")
     public ApiResult<EmsRegionAnalysisResponse> getRegionAnalysis(
             @RequestParam(required = false) Long projectId,
             @RequestParam(required = false) String regionIds,
-            @RequestParam String timeDimension,
-            @RequestParam String timeValue,
+            @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("/analysis/compare")
+    @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));
+    }
+}

+ 0 - 233
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsApiV1Controller.java

@@ -1,233 +0,0 @@
-package com.usky.ems.controller.web;
-
-import com.usky.common.core.bean.ApiResult;
-import com.usky.common.core.bean.CommonPage;
-import com.usky.ems.service.EmsGatewayQueryService;
-import com.usky.ems.service.EmsModelService;
-import com.usky.ems.service.EmsOverviewService;
-import com.usky.ems.service.vo.*;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.*;
-
-import java.util.List;
-
-/**
- * 能源能耗系统 API
- *
- * 对应 API 文档基础路径:/prod-api/service-ems
- * GET  /prod-api/service-ems/overview/project           获取项目信息
- * GET  /prod-api/service-ems/overview/summary            获取项目数据概括
- * GET  /prod-api/service-ems/model/structure/tree        获取项目层级树
- * GET  /prod-api/service-ems/model/energy-type/list      能源类型列表
- * POST /prod-api/service-ems/model/building              新增建筑
- * PUT  /prod-api/service-ems/model/building/{id}        编辑建筑
- * DELETE /prod-api/service-ems/model/building/{id}      删除建筑
- * POST /prod-api/service-ems/model/region                新增区域
- * PUT  /prod-api/service-ems/model/region/{id}           编辑区域
- * DELETE /prod-api/service-ems/model/region/{id}         删除区域
- * POST /prod-api/service-ems/model/floor                 新增楼层
- * PUT  /prod-api/service-ems/model/floor/{id}            编辑楼层
- * DELETE /prod-api/service-ems/model/floor/{id}          删除楼层
- * POST /prod-api/service-ems/model/gateway               新增网关
- * PUT  /prod-api/service-ems/model/gateway/{id}          编辑网关
- * DELETE /prod-api/service-ems/model/gateway/{id}        删除网关
- * POST /prod-api/service-ems/model/channel               新增通道
- * PUT  /prod-api/service-ems/model/channel/{id}          编辑通道
- * DELETE /prod-api/service-ems/model/channel/{id}        删除通道
- * POST /prod-api/service-ems/model/device                新增设备
- * PUT  /prod-api/service-ems/model/device/{id}           编辑设备
- * DELETE /prod-api/service-ems/model/device/{id}         删除设备
- * POST /prod-api/service-ems/model/attribute-point      新增属性点位
- * PUT  /prod-api/service-ems/model/attribute-point/{id}  编辑属性点位
- * DELETE /prod-api/service-ems/model/attribute-point/{id} 删除属性点位
- * GET  /prod-api/service-ems/device/gateway/list         网关列表(分页)
- * GET  /prod-api/service-ems/device/gateway/{id}         网关详情
- */
-@RestController
-@RequestMapping("/prod-api/service-ems")
-public class EmsApiV1Controller {
-
-    @Autowired
-    private EmsOverviewService emsOverviewService;
-    @Autowired
-    private EmsModelService emsModelService;
-    @Autowired
-    private EmsGatewayQueryService emsGatewayQueryService;
-
-    /**
-     * 获取项目信息
-     */
-    @GetMapping("/overview/project")
-    public ApiResult<EmsProjectResponse> getProject(@RequestParam(required = false) Long projectId) {
-        return ApiResult.success(emsOverviewService.getProject(projectId));
-    }
-
-    /**
-     * 获取项目数据概括(时间维度联动)
-     */
-    @GetMapping("/overview/summary")
-    public ApiResult<EmsSummaryResponse> getSummary(EmsSummaryRequest request) {
-        return ApiResult.success(emsOverviewService.getSummary(
-                request.getProjectId(), request.getTimeDimension(), request.getTimeValue()));
-    }
-
-    /**
-     * 获取项目层级树(建筑、区域、楼层、网关)
-     */
-    @GetMapping("/model/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));
-    }
-
-    /**
-     * 能源类型列表(电、水、气)
-     */
-    @GetMapping("/model/energy-type/list")
-    public ApiResult<List<EmsEnergyTypeVO>> getEnergyTypeList() {
-        return ApiResult.success(emsModelService.getEnergyTypeList());
-    }
-
-    @PostMapping("/model/building")
-    public ApiResult<EmsIdResponse> createBuilding(@RequestBody EmsModelSaveRequest request) {
-        Long id = emsModelService.createBuilding(request);
-        return ApiResult.success(new EmsIdResponse(id));
-    }
-
-    @PutMapping("/model/building/{id}")
-    public ApiResult<Void> updateBuilding(@PathVariable Long id, @RequestBody EmsModelSaveRequest request) {
-        emsModelService.updateBuilding(id, request);
-        return ApiResult.success();
-    }
-
-    @DeleteMapping("/model/building/{id}")
-    public ApiResult<Void> deleteBuilding(@PathVariable Long id) {
-        emsModelService.deleteBuilding(id);
-        return ApiResult.success();
-    }
-
-    @PostMapping("/model/region")
-    public ApiResult<EmsIdResponse> createRegion(@RequestBody EmsModelSaveRequest request) {
-        Long id = emsModelService.createRegion(request);
-        return ApiResult.success(new EmsIdResponse(id));
-    }
-
-    @PutMapping("/model/region/{id}")
-    public ApiResult<Void> updateRegion(@PathVariable Long id, @RequestBody EmsModelSaveRequest request) {
-        emsModelService.updateRegion(id, request);
-        return ApiResult.success();
-    }
-
-    @DeleteMapping("/model/region/{id}")
-    public ApiResult<Void> deleteRegion(@PathVariable Long id) {
-        emsModelService.deleteRegion(id);
-        return ApiResult.success();
-    }
-
-    @PostMapping("/model/floor")
-    public ApiResult<EmsIdResponse> createFloor(@RequestBody EmsModelSaveRequest request) {
-        Long id = emsModelService.createFloor(request);
-        return ApiResult.success(new EmsIdResponse(id));
-    }
-
-    @PutMapping("/model/floor/{id}")
-    public ApiResult<Void> updateFloor(@PathVariable Long id, @RequestBody EmsModelSaveRequest request) {
-        emsModelService.updateFloor(id, request);
-        return ApiResult.success();
-    }
-
-    @DeleteMapping("/model/floor/{id}")
-    public ApiResult<Void> deleteFloor(@PathVariable Long id) {
-        emsModelService.deleteFloor(id);
-        return ApiResult.success();
-    }
-
-    @PostMapping("/model/gateway")
-    public ApiResult<EmsIdResponse> createGateway(@RequestBody EmsModelSaveRequest request) {
-        String id = emsModelService.createGateway(request);
-        return ApiResult.success(new EmsIdResponse(id));
-    }
-
-    @PutMapping("/model/gateway/{id}")
-    public ApiResult<Void> updateGateway(@PathVariable String id, @RequestBody EmsModelSaveRequest request) {
-        emsModelService.updateGateway(id, request);
-        return ApiResult.success();
-    }
-
-    @DeleteMapping("/model/gateway/{id}")
-    public ApiResult<Void> deleteGateway(@PathVariable String id) {
-        emsModelService.deleteGateway(id);
-        return ApiResult.success();
-    }
-
-    @PostMapping("/model/channel")
-    public ApiResult<EmsIdResponse> createChannel(@RequestBody EmsModelSaveRequest request) {
-        Long id = emsModelService.createChannel(request);
-        return ApiResult.success(new EmsIdResponse(id));
-    }
-
-    @PutMapping("/model/channel/{id}")
-    public ApiResult<Void> updateChannel(@PathVariable Long id, @RequestBody EmsModelSaveRequest request) {
-        emsModelService.updateChannel(id, request);
-        return ApiResult.success();
-    }
-
-    @DeleteMapping("/model/channel/{id}")
-    public ApiResult<Void> deleteChannel(@PathVariable Long id) {
-        emsModelService.deleteChannel(id);
-        return ApiResult.success();
-    }
-
-    @PostMapping("/model/device")
-    public ApiResult<EmsIdResponse> createDevice(@RequestBody EmsModelSaveRequest request) {
-        String id = emsModelService.createDevice(request);
-        return ApiResult.success(new EmsIdResponse(id));
-    }
-
-    @PutMapping("/model/device/{id}")
-    public ApiResult<Void> updateDevice(@PathVariable String id, @RequestBody EmsModelSaveRequest request) {
-        emsModelService.updateDevice(id, request);
-        return ApiResult.success();
-    }
-
-    @DeleteMapping("/model/device/{id}")
-    public ApiResult<Void> deleteDevice(@PathVariable String id) {
-        emsModelService.deleteDevice(id);
-        return ApiResult.success();
-    }
-
-    @PostMapping("/model/attribute-point")
-    public ApiResult<EmsIdResponse> createAttributePoint(@RequestBody EmsModelSaveRequest request) {
-        Long id = emsModelService.createAttributePoint(request);
-        return ApiResult.success(new EmsIdResponse(id));
-    }
-
-    @PutMapping("/model/attribute-point/{id}")
-    public ApiResult<Void> updateAttributePoint(@PathVariable Long id, @RequestBody EmsModelSaveRequest request) {
-        emsModelService.updateAttributePoint(id, request);
-        return ApiResult.success();
-    }
-
-    @DeleteMapping("/model/attribute-point/{id}")
-    public ApiResult<Void> deleteAttributePoint(@PathVariable Long id) {
-        emsModelService.deleteAttributePoint(id);
-        return ApiResult.success();
-    }
-
-    /**
-     * 网关列表(分页)
-     */
-    @GetMapping("/device/gateway/list")
-    public ApiResult<CommonPage<EmsGatewayListItem>> gatewayList(EmsGatewayPageRequest request) {
-        return ApiResult.success(emsGatewayQueryService.listGateways(request));
-    }
-
-    /**
-     * 网关详情
-     */
-    @GetMapping("/device/gateway/{id}")
-    public ApiResult<EmsGatewayDetailResponse> getGateway(@PathVariable String id) {
-        return ApiResult.success(emsGatewayQueryService.getGatewayById(id));
-    }
-}

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

@@ -1,37 +0,0 @@
-package com.usky.ems.controller.web;
-
-import com.usky.common.core.bean.ApiResult;
-import com.usky.ems.service.EmsAuthService;
-import com.usky.ems.service.vo.EmsLoginRequest;
-import com.usky.ems.service.vo.EmsLoginResponse;
-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;
-
-/**
- * 能源系统认证接口
- *
- * POST /prod-api/service-ems/auth/login  用户登录
- * POST /prod-api/service-ems/auth/logout 退出登录
- */
-@RestController
-@RequestMapping("/prod-api/service-ems")
-public class EmsAuthController {
-
-    @Autowired
-    private EmsAuthService emsAuthService;
-
-    @PostMapping("/auth/login")
-    public ApiResult<EmsLoginResponse> login(@RequestBody EmsLoginRequest request) {
-        EmsLoginResponse data = emsAuthService.login(request);
-        return ApiResult.success("登录成功", data);
-    }
-
-    @PostMapping("/auth/logout")
-    public ApiResult<Void> logout() {
-        emsAuthService.logout();
-        return ApiResult.success("退出成功");
-    }
-}

+ 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));
+//    }
+
+    /**
+     * 能源类型列表(电、水、气)
+     */
+    @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));
+    }
+
+
+}

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

@@ -0,0 +1,134 @@
+package com.usky.ems.controller.web;
+
+import com.usky.common.core.bean.ApiResult;
+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.*;
+
+/**
+ * 能源能耗 - 概览模块 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<java.util.List<EmsOverviewEnergyItemVO>> queryOverviewItem(
+            @RequestParam(required = false) Long projectId) {
+        return ApiResult.success(emsOverviewService.queryOverviewItem(projectId));
+    }
+
+    /**
+     * 获取概览页设备系统统计(每个系统下设备数量)
+     */
+    @GetMapping("/device-info")
+    public ApiResult<java.util.List<EmsOverviewDeviceSystemStatVO>> queryOverviewDeviceInfo(
+            @RequestParam(required = false) Long projectId) {
+        return ApiResult.success(emsOverviewService.queryOverviewDeviceInfo(projectId));
+    }
+
+    /**
+     * 分类能耗统计(模拟数据)
+     * 参数说明:
+     * - dateType:时间类型(1-日,2-月,3-年等,暂仅作为占位,不影响当前模拟结果)
+     * - itemCode:能耗条目编码
+     * - energyType:能耗类型
+     * - projectId:项目 ID(可选,当前实现未做真实查询,仅占位)
+     */
+    @GetMapping("/classification-energy")
+    public ApiResult<java.util.Map<String, Object>> queryClassificationEnergy(
+            @RequestParam Integer dateType,
+            @RequestParam String itemCode,
+            @RequestParam Integer energyType,
+            @RequestParam(required = false) Long projectId) {
+        return ApiResult.success(emsOverviewService.queryClassificationEnergy(dateType, itemCode, energyType, projectId));
+    }
+
+    /**
+     * 能耗用能趋势(模拟数据)
+     * 参数说明:
+     * - dateType:时间类型(1-日,2-月,3-年)
+     * - itemCode:能耗条目编码
+     * - spaceId:空间ID(可选,当前仍为模拟数据占位)
+     */
+    @GetMapping("/energy-trend")
+    public ApiResult<java.util.List<java.util.Map<String, Object>>> queryEnergyTrend(
+            @RequestParam Integer dateType,
+            @RequestParam String itemCode,
+            @RequestParam(required = false) Long spaceId) {
+        return ApiResult.success(emsOverviewService.queryEnergyTrend(dateType, itemCode, spaceId));
+    }
+
+    /**
+     * 建筑能耗分析(模拟数据)
+     * 参数说明:
+     * - dateType:时间类型(1-日,2-月,3-年)
+     * - itemCode:能耗条目编码
+     * - spaceId:空间ID(可选;下钻时与 base_space 子节点一致)
+     */
+    @GetMapping("/building-ranking")
+    public ApiResult<java.util.List<java.util.Map<String, Object>>> queryBuildingRanking(
+            @RequestParam Integer dateType,
+            @RequestParam String itemCode,
+            @RequestParam(required = false) Long spaceId) {
+        return ApiResult.success(emsOverviewService.queryBuildingRanking(dateType, itemCode, spaceId));
+    }
+
+    /**
+     * 单位综合能耗、综合累计能耗、分类能耗占比(总览顶部)
+     * 参数说明:
+     * - dateType:时间类型(1-日,2-月,3-年)
+     * - projectId:项目ID(可选,不传则取第一个项目)
+     */
+    @GetMapping("/top")
+    public ApiResult<java.util.Map<String, Object>> queryOverviewTop(
+            @RequestParam Integer dateType,
+            @RequestParam(required = false) Long projectId) {
+        return ApiResult.success(emsOverviewService.queryOverviewTop(dateType, projectId));
+    }
+
+    /**
+     * 分项能耗占比
+     * 参数说明:
+     * - dateType:时间类型(1-日,2-月,3-年)
+     * - itemCode:分项编码(作为父项,展开下级分项)
+     * - projectId:项目ID(可选,不传则取第一个项目)
+     */
+    @GetMapping("/item-ratio")
+    public ApiResult<java.util.List<java.util.Map<String, Object>>> queryItemRatio(
+            @RequestParam Integer dateType,
+            @RequestParam String itemCode,
+            @RequestParam(required = false) Long projectId) {
+        return ApiResult.success(emsOverviewService.queryItemRatio(dateType, itemCode, projectId));
+    }
+}

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

+ 20 - 14
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsReportController.java

@@ -2,6 +2,7 @@ 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.*;
@@ -12,29 +13,34 @@ import java.util.List;
 /**
  * 统计报表接口
  * 能源报表、区域报表、采集报表
+ * 基础路径前缀:/report(网关再加 /prod-api/service-ems)
  */
 @RestController
-@RequestMapping("/prod-api/service-ems")
+@RequestMapping("/report")
 public class EmsReportController {
 
     @Autowired
     private EmsReportService emsReportService;
 
+    @Autowired
+    private EmsModelService emsModelService;
+
     // ---------- 能源报表 ----------
-    @GetMapping("/report/energy/devices")
-    public ApiResult<EmsReportDevicesResponse> getEnergyDevices(
-            @RequestParam Long energyTypeId,
+    @GetMapping("/energy/devices")
+    public ApiResult<List<EnergyTypeWrapperProductVO>> getEnergyDevices(
+            @RequestParam(required = false) Long energyTypeId,
             @RequestParam(required = false) String keyword,
             @RequestParam(required = false) Long projectId) {
-        return ApiResult.success(emsReportService.getEnergyDevices(energyTypeId, keyword, projectId));
+        // TODO: 如需按 energyTypeId/keyword/projectId 过滤,可在 EmsModelService 中扩展方法
+        return ApiResult.success(emsModelService.showAssociatedEnergyTypes());
     }
 
-    @PostMapping("/report/energy/statistics")
+    @PostMapping("/energy/statistics")
     public ApiResult<EmsEnergyStatisticsResponse> getEnergyStatistics(@RequestBody EmsEnergyStatisticsRequest request) {
         return ApiResult.success(emsReportService.getEnergyStatistics(request));
     }
 
-    @GetMapping("/report/energy/export")
+    @GetMapping("/energy/export")
     public void exportEnergy(
             @RequestParam String deviceIds,
             @RequestParam(required = false) String attributePointIds,
@@ -46,7 +52,7 @@ public class EmsReportController {
     }
 
     // ---------- 区域报表 ----------
-    @GetMapping("/report/region/devices")
+    @GetMapping("/region/devices")
     public ApiResult<EmsReportDevicesResponse> getRegionDevices(
             @RequestParam Long energyTypeId,
             @RequestParam(required = false) Long regionId,
@@ -55,14 +61,14 @@ public class EmsReportController {
         return ApiResult.success(emsReportService.getRegionDevices(energyTypeId, regionId, keyword, projectId));
     }
 
-    @PostMapping("/report/region/statistics")
+    @PostMapping("/region/statistics")
     public ApiResult<EmsEnergyStatisticsResponse> getRegionStatistics(
             @RequestBody EmsEnergyStatisticsRequest request,
             @RequestParam(required = false) List<Long> regionIds) {
         return ApiResult.success(emsReportService.getRegionStatistics(request, regionIds));
     }
 
-    @GetMapping("/report/region/export")
+    @GetMapping("/region/export")
     public void exportRegion(
             @RequestParam String deviceIds,
             @RequestParam(required = false) String attributePointIds,
@@ -75,7 +81,7 @@ public class EmsReportController {
     }
 
     // ---------- 采集报表 ----------
-    @GetMapping("/report/collection/devices")
+    @GetMapping("/collection/devices")
     public ApiResult<EmsReportDevicesResponse> getCollectionDevices(
             @RequestParam Long energyTypeId,
             @RequestParam(required = false) String keyword,
@@ -83,17 +89,17 @@ public class EmsReportController {
         return ApiResult.success(emsReportService.getCollectionDevices(energyTypeId, keyword, projectId));
     }
 
-    @PostMapping("/report/collection/realtime")
+    @PostMapping("/collection/realtime")
     public ApiResult<EmsCollectionRealtimeResponse> getCollectionRealtime(@RequestBody EmsCollectionRealtimeRequest request) {
         return ApiResult.success(emsReportService.getCollectionRealtime(request));
     }
 
-    @PostMapping("/report/collection/statistics")
+    @PostMapping("/collection/statistics")
     public ApiResult<EmsEnergyStatisticsResponse> getCollectionStatistics(@RequestBody EmsEnergyStatisticsRequest request) {
         return ApiResult.success(emsReportService.getCollectionStatistics(request));
     }
 
-    @GetMapping("/report/collection/export")
+    @GetMapping("/collection/export")
     public void exportCollection(
             @RequestParam String deviceIds,
             @RequestParam(required = false) String attributePointIds,

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

+ 8 - 8
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsSpaceArea.java → service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/BaseArea.java

@@ -12,20 +12,18 @@ import java.math.BigDecimal;
 import java.time.LocalDateTime;
 
 /**
- * 区域(leo.ems_space_area)
+ * 区域(base_area)
  */
 @Data
 @EqualsAndHashCode(callSuper = false)
-@TableName("ems_space_area")
-public class EmsSpaceArea implements Serializable {
+@TableName("base_area")
+public class BaseArea 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;
     private Integer type;
     private BigDecimal area;
@@ -35,12 +33,14 @@ public class EmsSpaceArea implements Serializable {
     private BigDecimal airConditionedArea;
     @TableField("resident_population")
     private Integer residentPopulation;
+    @TableField("tenant_id")
+    private Integer tenantId;
     @TableField("updated_by")
-    private Long updatedBy;
+    private String updatedBy;
+    @TableField("created_by")
+    private String createdBy;
     @TableField("update_time")
     private LocalDateTime updateTime;
-    @TableField("created_by")
-    private Long createdBy;
     @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;
+}

+ 8 - 8
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsSpace.java → service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/BaseSpace.java

@@ -11,13 +11,13 @@ import java.io.Serializable;
 import java.time.LocalDateTime;
 
 /**
- * 空间(leo.ems_space)
- * 空间类型:1项目 2区域 3建筑 4楼层 5房间
+ * 空间(base_space)
+ * 空间类型:1项目 2区域 3建筑
  */
 @Data
 @EqualsAndHashCode(callSuper = false)
-@TableName("ems_space")
-public class EmsSpace implements Serializable {
+@TableName("base_space")
+public class BaseSpace implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
@@ -31,15 +31,15 @@ public class EmsSpace implements Serializable {
     @TableField("root_id")
     private Long rootId;
     private String path;
-    @TableField("path_name")
-    private String pathName;
     private Integer deep;
     @TableField("updated_by")
-    private Long updatedBy;
+    private String updatedBy;
     @TableField("update_time")
     private LocalDateTime updateTime;
     @TableField("created_by")
-    private Long createdBy;
+    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;
+}

+ 27 - 19
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsGateway.java → service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/DmpGateway.java

@@ -11,54 +11,62 @@ import java.io.Serializable;
 import java.time.LocalDateTime;
 
 /**
- * 网关(leo.ems_gateway)
+ * 网关表(dmp_gateway)
  */
 @Data
 @EqualsAndHashCode(callSuper = false)
-@TableName("ems_gateway")
-public class EmsGateway implements Serializable {
+@TableName("dmp_gateway")
+public class DmpGateway implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
-    @TableId(value = "id", type = IdType.INPUT)
-    private String id;
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @TableField("device_uuid")
+    private String deviceUuid;
 
-    @TableField("project_id")
-    private Long projectId;
     private String name;
-    @TableField("space_id")
-    private Long spaceId;
-    private String version;
-    private String type;
+
     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("data_center_id")
-    private Long dataCenterId;
-    private String iccid;
-    private Integer rssi;
-    @TableField("secret_key")
-    private String secretKey;
+
     @TableField("virtual_device")
     private Integer virtualDevice;
+
     private String remark;
+
     @TableField("updated_by")
-    private Long updatedBy;
+    private String updatedBy;
+
     @TableField("update_time")
     private LocalDateTime updateTime;
+
     @TableField("created_by")
-    private Long createdBy;
+    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;
+}
+

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

@@ -27,6 +27,7 @@ public class EmsChannel implements Serializable {
     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;

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

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

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

+ 6 - 3
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsProject.java

@@ -12,7 +12,7 @@ import java.math.BigDecimal;
 import java.time.LocalDateTime;
 
 /**
- * 项目(leo.ems_project)
+ * 项目(ems_project)
  */
 @Data
 @EqualsAndHashCode(callSuper = false)
@@ -26,6 +26,7 @@ public class EmsProject implements Serializable {
 
     @TableField("space_id")
     private Long spaceId;
+
     private String name;
     @TableField("platform_name")
     private String platformName;
@@ -60,12 +61,14 @@ public class EmsProject implements Serializable {
     private String logo;
     @TableField("logo_min")
     private String logoMin;
+    @TableField("tenant_id")
+    private Integer tenantId;
     @TableField("updated_by")
-    private Long updatedBy;
+    private String updatedBy;
     @TableField("update_time")
     private LocalDateTime updateTime;
     @TableField("created_by")
-    private Long createdBy;
+    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;
+}

+ 0 - 67
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsSpaceBuilding.java

@@ -1,67 +0,0 @@
-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_space_building)
- */
-@Data
-@EqualsAndHashCode(callSuper = false)
-@TableName("ems_space_building")
-public class EmsSpaceBuilding 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;
-    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;
-    private Integer floor;
-    private BigDecimal height;
-    @TableField("type_id")
-    private Integer typeId;
-    @TableField("type_name")
-    private String typeName;
-    private String image;
-    private String introduction;
-    @TableField("updated_by")
-    private Long updatedBy;
-    @TableField("update_time")
-    private LocalDateTime updateTime;
-    @TableField("created_by")
-    private Long createdBy;
-    @TableField("create_time")
-    private LocalDateTime createTime;
-}

+ 6 - 14
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsSpaceFloor.java → service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsSystemDictCode.java

@@ -8,32 +8,24 @@ import lombok.Data;
 import lombok.EqualsAndHashCode;
 
 import java.io.Serializable;
-import java.math.BigDecimal;
 import java.time.LocalDateTime;
 
 /**
- * 楼层(leo.ems_space_floor
+ * 系统字典编码(ems_system_dict_code
  */
 @Data
 @EqualsAndHashCode(callSuper = false)
-@TableName("ems_space_floor")
-public class EmsSpaceFloor implements Serializable {
+@TableName("ems_system_dict_code")
+public class EmsSystemDictCode implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
     @TableId(value = "id", type = IdType.AUTO)
-    private Long id;
+    private Integer id;
 
-    @TableField("space_id")
-    private Long spaceId;
     private String name;
-    private BigDecimal area;
-    @TableField("common_area")
-    private BigDecimal commonArea;
-    @TableField("air_conditioned_area")
-    private BigDecimal airConditionedArea;
-    @TableField("resident_population")
-    private Integer residentPopulation;
+    private String code;
+    private String remark;
     @TableField("updated_by")
     private Long updatedBy;
     @TableField("update_time")

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

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

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

@@ -0,0 +1,11 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.DmpProduct;
+
+/**
+ * 产品信息表 Mapper(dmp_product)
+ */
+public interface DmpProductMapper extends CrudMapper<DmpProduct> {
+}
+

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

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

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

@@ -1,10 +0,0 @@
-package com.usky.ems.mapper;
-
-import com.usky.common.mybatis.core.CrudMapper;
-import com.usky.ems.domain.EmsGateway;
-
-/**
- * 网关 Mapper(leo.ems_gateway)
- */
-public interface EmsGatewayMapper extends CrudMapper<EmsGateway> {
-}

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

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

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

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

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

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

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

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

@@ -1,10 +0,0 @@
-package com.usky.ems.mapper;
-
-import com.usky.common.mybatis.core.CrudMapper;
-import com.usky.ems.domain.EmsSpaceFloor;
-
-/**
- * 楼层 Mapper(leo.ems_space_floor)
- */
-public interface EmsSpaceFloorMapper extends CrudMapper<EmsSpaceFloor> {
-}

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

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

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

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

@@ -16,4 +16,6 @@ public interface EmsAnalysisService {
     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);
 }

+ 0 - 14
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsAuthService.java

@@ -1,14 +0,0 @@
-package com.usky.ems.service;
-
-import com.usky.ems.service.vo.EmsLoginRequest;
-import com.usky.ems.service.vo.EmsLoginResponse;
-
-/**
- * 能源系统认证服务(登录/登出)
- */
-public interface EmsAuthService {
-
-    EmsLoginResponse login(EmsLoginRequest request);
-
-    void logout();
-}

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

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

@@ -1,22 +0,0 @@
-package com.usky.ems.service;
-
-import com.usky.common.core.bean.CommonPage;
-import com.usky.ems.service.vo.EmsGatewayDetailResponse;
-import com.usky.ems.service.vo.EmsGatewayListItem;
-import com.usky.ems.service.vo.EmsGatewayPageRequest;
-
-/**
- * 网关列表与详情查询服务
- */
-public interface EmsGatewayQueryService {
-
-    /**
-     * 分页查询网关列表
-     */
-    CommonPage<EmsGatewayListItem> listGateways(EmsGatewayPageRequest request);
-
-    /**
-     * 网关详情
-     */
-    EmsGatewayDetailResponse getGatewayById(String id);
-}

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

@@ -1,26 +1,36 @@
 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:结构树、能源类型、建筑/区域/楼层/网关/通道/设备/属性点位)
+ * 基础建模服务(model:结构树、能源类型、建筑/区域/网关/通道/设备/属性点位)
  */
 public interface EmsModelService {
 
     /**
-     * 获取项目层级树(建筑、区域、楼层、网关)
+     * 获取项目层级树(建筑、区域、网关)
      */
-    EmsStructureTreeNode getStructureTree(Long projectId, Boolean includeGateway);
+//    EmsStructureTreeNode getStructureTree(Long projectId, Boolean includeGateway);
 
     /**
      * 能源类型列表(电、水、气)
      */
     List<EmsEnergyTypeVO> getEnergyTypeList();
 
+    /**
+     * 展示已关联的能源类型及其下属分项/产品列表
+     * 数据来源:leo.ems_energy_item_code.energy_type 分组
+     */
+    List<EnergyTypeWrapperProductVO> showAssociatedEnergyTypes();
+
     /** 建筑:新增 */
     Long createBuilding(EmsModelSaveRequest request);
     /** 建筑:编辑 */
@@ -35,38 +45,64 @@ public interface EmsModelService {
     /** 区域:删除 */
     void deleteRegion(Long id);
 
-    /** 楼层:新增 */
-    Long createFloor(EmsModelSaveRequest request);
-    /** 楼层:编辑 */
-    void updateFloor(Long id, EmsModelSaveRequest request);
-    /** 楼层:删除 */
-    void deleteFloor(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);
 
-    /** 通道:新增 */
-    Long createChannel(EmsModelSaveRequest request);
-    /** 通道:编辑 */
-    void updateChannel(Long id, EmsModelSaveRequest request);
-    /** 通道:删除 */
-    void deleteChannel(Long id);
+    /**
+     * 空间绑定网关(一个空间可绑定多个网关)
+     * @param spaceId 空间ID
+     * @param gatewayUuids 网关uuid列表
+     */
+    void bindSpaceGateways(Long spaceId, List<String> gatewayUuids);
 
-    /** 设备:新增 */
-    String createDevice(EmsModelSaveRequest request);
-    /** 设备:编辑 */
-    void updateDevice(String id, EmsModelSaveRequest request);
-    /** 设备:删除 */
-    void deleteDevice(String id);
+    /**
+     * 空间解绑网关(一个空间可解绑多个网关)
+     * @param spaceId 空间ID
+     * @param gatewayUuids 网关uuid列表
+     */
+    void unbindSpaceGateways(Long spaceId, List<String> gatewayUuids);
 
-    /** 属性点位:新增 */
-    Long createAttributePoint(EmsModelSaveRequest request);
-    /** 属性点位:编辑 */
-    void updateAttributePoint(Long id, EmsModelSaveRequest request);
-    /** 属性点位:删除 */
-    void deleteAttributePoint(Long id);
+    /**
+     * 根据空间ID查询绑定的网关列表
+     * @param spaceId 空间ID
+     */
+    List<DmpGatewayDetailResponse> getGatewayListBySpaceId(Long spaceId);
 }

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

@@ -2,6 +2,10 @@ 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)
@@ -11,10 +15,50 @@ public interface EmsOverviewService {
     /**
      * 获取项目信息(当前项目或指定 projectId)
      */
-    EmsProjectResponse getProject(Long projectId);
+    EmsProjectResponse getProject(Integer projectId);
 
     /**
      * 获取项目数据概括(时间维度联动)
      */
     EmsSummaryResponse getSummary(Long projectId, String timeDimension, String timeValue);
+
+    /**
+     * 获取概览页能源类型条目(如电/水/气)
+     */
+    java.util.List<EmsOverviewEnergyItemVO> queryOverviewItem(Long projectId);
+
+    /**
+     * 获取概览页设备系统统计(每个系统下设备数量)
+     */
+    java.util.List<EmsOverviewDeviceSystemStatVO> queryOverviewDeviceInfo(Long projectId);
+
+    /**
+     * 分类能耗统计(按时间维度、能耗类型等)
+     * 先按 Map 结构返回,后续可以再封装 VO。
+     */
+    Map<String, Object> queryClassificationEnergy(Integer dateType, String itemCode, Integer energyType, Long projectId);
+
+    /**
+     * 能耗用能趋势(按时间维度:日/月/年)
+     * 返回当前期与去年同期的用能趋势数据列表(当前为模拟数据实现)。
+     */
+    java.util.List<java.util.Map<String, Object>> queryEnergyTrend(Integer dateType, String itemCode, Long spaceId);
+
+    /**
+     * 建筑能耗分析(建筑/楼层能耗排名)
+     * 返回按能耗从高到低排序的建筑(或楼层)名称及其能耗值列表(当前为模拟数据实现)。
+     */
+    java.util.List<java.util.Map<String, Object>> queryBuildingRanking(Integer dateType, String itemCode, Long spaceId);
+
+    /**
+     * 单位综合能耗、综合累计能耗、分类能耗占比(总览页顶部)
+     * 部分数据来自真实表(项目、折标系数),能耗数值按规则模拟。
+     */
+    Map<String, Object> queryOverviewTop(Integer dateType, Long projectId);
+
+    /**
+     * 分项能耗占比(按分项编码 itemCode 展开下级条目)
+     * 部分数据(分项定义)来自 leo.ems_energy_item_code,能耗数值按规则模拟。
+     */
+    java.util.List<java.util.Map<String, Object>> queryItemRatio(Integer dateType, String itemCode, Long projectId);
 }

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

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

+ 1160 - 16
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsAnalysisServiceImpl.java

@@ -1,61 +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);
-        resp.setTotalUsage(BigDecimal.ZERO);
-        resp.setStandardCoal(BigDecimal.ZERO);
-        resp.setCarbonEmission(BigDecimal.ZERO);
-        resp.setAreaUsage(BigDecimal.ZERO);
-        resp.setPerCapitaUsage(BigDecimal.ZERO);
+
+        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 <= ? ");
 
-    @Override
-    public EmsTrendCategoryResponse getTrendCategory(Long projectId, String timeDimension, String timeValue, Long energyTypeId) {
+            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) {
-        return new EmsRegionAnalysisResponse();
-    }
+     @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();
-        if (request != null && request.getEnergyTypeId() != null) {
-            long id = request.getEnergyTypeId();
-            resp.setEnergyTypeName(id >= 1 && id <= 3 ? ENERGY_TYPE_NAMES[(int) id] : "");
+        
+        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());
         }
-        if (request != null) resp.setDimension(request.getTimeDimension());
+
         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;
+    }
 }

+ 0 - 30
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsAuthServiceImpl.java

@@ -1,30 +0,0 @@
-package com.usky.ems.service.impl;
-
-import com.usky.ems.service.EmsAuthService;
-import com.usky.ems.service.vo.EmsLoginRequest;
-import com.usky.ems.service.vo.EmsLoginResponse;
-import org.springframework.stereotype.Service;
-
-import java.util.UUID;
-
-/**
- * 能源系统认证服务实现(预留与统一认证对接,当前返回占位 token)
- */
-@Service
-public class EmsAuthServiceImpl implements EmsAuthService {
-
-    private static final long EXPIRES_IN_SECONDS = 7200L;
-
-    @Override
-    public EmsLoginResponse login(EmsLoginRequest request) {
-        EmsLoginResponse resp = new EmsLoginResponse();
-        resp.setAccessToken("Bearer " + UUID.randomUUID().toString().replace("-", ""));
-        resp.setExpiresIn(EXPIRES_IN_SECONDS);
-        return resp;
-    }
-
-    @Override
-    public void logout() {
-        // 预留:使当前 token 失效
-    }
-}

+ 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 + "分钟";
+    }
+}
+

+ 272 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsDeviceReportServiceImpl.java

@@ -0,0 +1,272 @@
+package com.usky.ems.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.usky.ems.domain.EmsDevice;
+import com.usky.ems.domain.EmsDeviceFunction;
+import com.usky.ems.enums.TemporalTypeEnum;
+import com.usky.ems.mapper.EmsDeviceFunctionMapper;
+import com.usky.ems.mapper.EmsDeviceMapper;
+import com.usky.ems.service.BaseSpaceService;
+import com.usky.ems.service.EmsDeviceReportService;
+import com.usky.ems.service.vo.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 设备能耗报表相关服务实现(全部逻辑在 Service 层,Mapper 仅用标准 CRUD)
+ */
+@Service
+public class EmsDeviceReportServiceImpl implements EmsDeviceReportService {
+
+    @Autowired
+    private BaseSpaceService baseSpaceService;
+
+    @Autowired
+    private EmsDeviceMapper emsDeviceMapper;
+
+    @Autowired
+    private EmsDeviceFunctionMapper emsDeviceFunctionMapper;
+
+    @Override
+    public List<DeviceEnergyReportItemVO> showEnergyReportDevices(DeviceEnergyReportRequest dto) {
+        if (dto == null || dto.getProjectId() == null) {
+            return Collections.emptyList();
+        }
+
+        // 1. 授权空间
+        List<Long> spaceIds = baseSpaceService.getAuthorizedSpaceIds(dto.getSpaceId());
+
+        // 2. 在 Service 层用 LambdaQueryWrapper 查 ems_device
+        LambdaQueryWrapper<EmsDevice> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(EmsDevice::getProjectId, dto.getProjectId());
+
+        if (dto.getProductIdList() != null && !dto.getProductIdList().isEmpty()) {
+            wrapper.in(EmsDevice::getProductId, dto.getProductIdList());
+        }
+        if (dto.getDeviceSystem() != null) {
+            wrapper.eq(EmsDevice::getDeviceSystem, dto.getDeviceSystem());
+        }
+        if (spaceIds != null && !spaceIds.isEmpty()) {
+            wrapper.and(w -> w.in(EmsDevice::getInstallationLocation, spaceIds)
+                    .or().in(EmsDevice::getMonitoringLocation, spaceIds));
+        }
+        if (dto.getGatewayId() != null && !dto.getGatewayId().isEmpty()) {
+            wrapper.eq(EmsDevice::getGatewayId, dto.getGatewayId());
+        }
+        if (dto.getChannelId() != null) {
+            wrapper.eq(EmsDevice::getChannelId, dto.getChannelId());
+        }
+
+        List<EmsDevice> devices = emsDeviceMapper.selectList(wrapper);
+        if (devices == null || devices.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        // 3. 转为 List<DeviceEnergyReportItemVO>
+        List<DeviceEnergyReportItemVO> deviceList = devices.stream()
+                .map(this::deviceToVo)
+                .collect(Collectors.toList());
+
+        // 4. 按 identifier 过滤:只保留在 ems_device_function 中存在该 identifier 的设备
+        if (dto.getIdentifier() != null && !dto.getIdentifier().isEmpty()) {
+            List<String> deviceIds = deviceList.stream()
+                    .map(DeviceEnergyReportItemVO::getId)
+                    .filter(Objects::nonNull)
+                    .filter(id -> !id.isEmpty())
+                    .collect(Collectors.toList());
+
+            if (!deviceIds.isEmpty()) {
+                LambdaQueryWrapper<EmsDeviceFunction> funcWrapper = new LambdaQueryWrapper<>();
+                funcWrapper.select(EmsDeviceFunction::getDeviceId)
+                        .in(EmsDeviceFunction::getDeviceId, deviceIds)
+                        .eq(EmsDeviceFunction::getIdentifier, dto.getIdentifier());
+                List<EmsDeviceFunction> deviceFunctions = emsDeviceFunctionMapper.selectList(funcWrapper);
+                if (deviceFunctions != null && !deviceFunctions.isEmpty()) {
+                    Set<String> meetDeviceIds = deviceFunctions.stream()
+                            .map(EmsDeviceFunction::getDeviceId)
+                            .collect(Collectors.toSet());
+                    deviceList = deviceList.stream()
+                            .filter(vo -> meetDeviceIds.contains(vo.getId()))
+                            .collect(Collectors.toList());
+                }
+            }
+        }
+
+        // 5. 按 name 排序(不区分大小写)
+        if (!deviceList.isEmpty()) {
+            deviceList.sort(Comparator.comparing(
+                    DeviceEnergyReportItemVO::getName,
+                    Comparator.nullsLast(String.CASE_INSENSITIVE_ORDER)
+            ));
+        }
+
+        return deviceList;
+    }
+
+    @Override
+    public Object showEnergyReportData(DeviceFunctionValueReportDTO dto) {
+        checkParams(dto);
+        TemporalTypeEnum temporalTypeEnum = TemporalTypeEnum.get(dto.getTimeType());
+        LocalDateTime startLocalTime = dto.getStartTime();
+        LocalDateTime endLocalTime = dto.getEndTime();
+        String identifier = getDeviceFunctionIdentifierByEnergyType(dto.getEnergyType());
+        if (temporalTypeEnum != null) {
+            checkDateTime(startLocalTime, endLocalTime, temporalTypeEnum);
+            startLocalTime = temporalTypeEnum.normalizeStart(startLocalTime);
+            endLocalTime = temporalTypeEnum.normalizeEnd(endLocalTime);
+            DeviceFunctionLastValueDTO paramDTO = convertParamDTO(dto, startLocalTime, endLocalTime, temporalTypeEnum, identifier);
+            return asyncProcessData(dto, paramDTO, temporalTypeEnum);
+        } else {
+            DeviceFunctionOneValueDTO paramDTO = convertParamOneLastValueDTO(dto, startLocalTime, endLocalTime, identifier);
+            return getCustomizeEnergyReportVOList(dto, paramDTO, startLocalTime);
+        }
+    }
+
+    private void checkParams(DeviceFunctionValueReportDTO dto) {
+        if (dto == null) {
+            throw new IllegalArgumentException("请求参数不能为空");
+        }
+        if (dto.getProjectId() == null) {
+            throw new IllegalArgumentException("projectId 不能为空");
+        }
+        if (dto.getStartTime() == null || dto.getEndTime() == null) {
+            throw new IllegalArgumentException("开始时间和结束时间不能为空");
+        }
+    }
+
+    private void checkDateTime(LocalDateTime start, LocalDateTime end, TemporalTypeEnum type) {
+        if (start.isAfter(end)) {
+            throw new IllegalArgumentException("开始时间不能晚于结束时间");
+        }
+        // 如需限制时间跨度,可在此扩展逻辑(例如:按日最多查询31天)。
+    }
+
+    private String getDeviceFunctionIdentifierByEnergyType(Integer energyType) {
+        if (energyType == null) {
+            return "total_kwh";
+        }
+        switch (energyType) {
+            case 1:
+                return "total_kwh";
+            case 2:
+                return "total_water";
+            case 3:
+                return "total_gas";
+            default:
+                return "total_kwh";
+        }
+    }
+
+    private DeviceFunctionLastValueDTO convertParamDTO(DeviceFunctionValueReportDTO src,
+                                                       LocalDateTime start,
+                                                       LocalDateTime end,
+                                                       TemporalTypeEnum temporalTypeEnum,
+                                                       String identifier) {
+        DeviceFunctionLastValueDTO dto = new DeviceFunctionLastValueDTO();
+        dto.setProjectId(src.getProjectId());
+        dto.setDeviceIds(src.getDeviceIds());
+        dto.setAttributePointIds(src.getAttributePointIds());
+        dto.setTimeType(temporalTypeEnum != null ? temporalTypeEnum.getCode() : null);
+        dto.setStartTime(start);
+        dto.setEndTime(end);
+        dto.setIdentifier(identifier);
+        return dto;
+    }
+
+    private DeviceFunctionOneValueDTO convertParamOneLastValueDTO(DeviceFunctionValueReportDTO src,
+                                                                  LocalDateTime start,
+                                                                  LocalDateTime end,
+                                                                  String identifier) {
+        DeviceFunctionOneValueDTO dto = new DeviceFunctionOneValueDTO();
+        dto.setProjectId(src.getProjectId());
+        dto.setDeviceIds(src.getDeviceIds());
+        dto.setAttributePointIds(src.getAttributePointIds());
+        dto.setStartTime(start);
+        dto.setEndTime(end);
+        dto.setIdentifier(identifier);
+        return dto;
+    }
+
+    private Object asyncProcessData(DeviceFunctionValueReportDTO origin,
+                                    DeviceFunctionLastValueDTO paramDTO,
+                                    TemporalTypeEnum temporalTypeEnum) {
+        Map<String, Object> result = new HashMap<>();
+        result.put("timeType", temporalTypeEnum != null ? temporalTypeEnum.getCode() : null);
+        result.put("startTime", paramDTO.getStartTime());
+        result.put("endTime", paramDTO.getEndTime());
+
+        // 模拟数据:根据 deviceIds 与 attributePointIds 生成简单的时间序列数据
+        List<Map<String, Object>> items = new ArrayList<>();
+        List<String> deviceIds = paramDTO.getDeviceIds() != null ? paramDTO.getDeviceIds() : Collections.emptyList();
+        List<Long> attrIds = paramDTO.getAttributePointIds() != null ? paramDTO.getAttributePointIds() : Collections.emptyList();
+
+        int index = 0;
+        for (String deviceId : deviceIds) {
+            for (Long attrId : attrIds) {
+                Map<String, Object> item = new HashMap<>();
+                item.put("deviceId", deviceId);
+                item.put("attributePointId", attrId);
+                item.put("identifier", paramDTO.getIdentifier());
+                // 简单模拟一个数值:100 + 10 * index
+                item.put("value", 100 + 10 * index);
+                item.put("unit", "kWh");
+                items.add(item);
+                index++;
+            }
+        }
+
+        result.put("items", items);
+        return result;
+    }
+
+    private Object getCustomizeEnergyReportVOList(DeviceFunctionValueReportDTO origin,
+                                                  DeviceFunctionOneValueDTO paramDTO,
+                                                  LocalDateTime startLocalTime) {
+        Map<String, Object> result = new HashMap<>();
+        result.put("time", startLocalTime);
+
+        List<Map<String, Object>> items = new ArrayList<>();
+        List<String> deviceIds = paramDTO.getDeviceIds() != null ? paramDTO.getDeviceIds() : Collections.emptyList();
+        List<Long> attrIds = paramDTO.getAttributePointIds() != null ? paramDTO.getAttributePointIds() : Collections.emptyList();
+
+        int index = 0;
+        for (String deviceId : deviceIds) {
+            for (Long attrId : attrIds) {
+                Map<String, Object> item = new HashMap<>();
+                item.put("deviceId", deviceId);
+                item.put("attributePointId", attrId);
+                item.put("identifier", paramDTO.getIdentifier());
+                // 单点场景模拟一个数值:200 + 5 * index
+                item.put("value", 200 + 5 * index);
+                item.put("unit", "kWh");
+                items.add(item);
+                index++;
+            }
+        }
+
+        result.put("items", items);
+        return result;
+    }
+
+    private DeviceEnergyReportItemVO deviceToVo(EmsDevice d) {
+        DeviceEnergyReportItemVO vo = new DeviceEnergyReportItemVO();
+        vo.setId(d.getId());
+        vo.setName(d.getName());
+        vo.setNumber(d.getNumber());
+        vo.setProjectId(d.getProjectId());
+        vo.setProductId(d.getProductId());
+        vo.setInstallationLocation(d.getInstallationLocation());
+        vo.setMonitoringLocation(d.getMonitoringLocation());
+        vo.setChannelId(d.getChannelId());
+        vo.setGatewayId(d.getGatewayId());
+        vo.setDeviceSystem(d.getDeviceSystem());
+        vo.setStatus(d.getStatus());
+        vo.setLocation(d.getLocation());
+        return vo;
+    }
+}
+

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

@@ -0,0 +1,110 @@
+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.EmsEnergyConsumptionFormula;
+import com.usky.ems.domain.EmsEnergyItemCode;
+import com.usky.ems.mapper.EmsEnergyConsumptionFormulaMapper;
+import com.usky.ems.mapper.EmsEnergyItemCodeMapper;
+import com.usky.ems.service.EmsEnergyItemCodeService;
+import com.usky.ems.service.vo.EnergyItemCodeVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 能源分项编码 服务实现类
+ */
+@Service
+public class EmsEnergyItemCodeServiceImpl
+        extends AbstractCrudService<EmsEnergyItemCodeMapper, EmsEnergyItemCode>
+        implements EmsEnergyItemCodeService {
+
+    @Autowired
+    private EmsEnergyConsumptionFormulaMapper energyConsumptionFormulaMapper;
+
+    @Override
+    public List<EnergyItemCodeVO> querySpaceEnergyItem(Long spaceId) {
+        if (spaceId == null) {
+            return Collections.emptyList();
+        }
+
+        LambdaQueryWrapper<EmsEnergyConsumptionFormula> lambdaQuery =
+                Wrappers.lambdaQuery(EmsEnergyConsumptionFormula.class)
+                        .eq(EmsEnergyConsumptionFormula::getSpaceId, spaceId);
+        List<EmsEnergyConsumptionFormula> formulaList = energyConsumptionFormulaMapper.selectList(lambdaQuery);
+        if (formulaList == null || formulaList.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        List<String> codes = formulaList.stream()
+                .map(EmsEnergyConsumptionFormula::getItemCode)
+                .filter(Objects::nonNull)
+                .distinct()
+                .collect(Collectors.toList());
+
+        if (codes.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        // 直接从 ems_energy_item_code 表根据编码列表查询
+        List<EmsEnergyItemCode> itemCodeList = this.list(
+                Wrappers.<EmsEnergyItemCode>lambdaQuery()
+                        .in(EmsEnergyItemCode::getCode, codes)
+        );
+        if (itemCodeList == null || itemCodeList.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        // PO -> VO
+        List<EnergyItemCodeVO> itemCodes = itemCodeList.stream().map(po -> {
+            EnergyItemCodeVO vo = new EnergyItemCodeVO();
+            vo.setCode(po.getCode());
+            vo.setParentCode(po.getParentCode());
+            vo.setName(po.getName());
+            vo.setUnit(po.getUnit());
+            vo.setUnitName(po.getUnitName());
+            return vo;
+        }).collect(Collectors.toList());
+
+        // 仅保留配置了公式的分项
+        itemCodes = itemCodes.stream()
+                .filter(vo -> codes.contains(vo.getCode()))
+                .collect(Collectors.toList());
+
+        if (itemCodes.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        Map<String, List<EnergyItemCodeVO>> listMap = itemCodes.stream()
+                .collect(Collectors.groupingBy(EnergyItemCodeVO::getParentCode));
+
+        Map<String, EmsEnergyConsumptionFormula> formulaMap = formulaList.stream()
+                .collect(Collectors.toMap(EmsEnergyConsumptionFormula::getItemCode, f -> f, (a, b) -> a));
+
+        List<EnergyItemCodeVO> result = new ArrayList<>(itemCodes);
+
+        for (EnergyItemCodeVO codeVO : itemCodes) {
+            List<EnergyItemCodeVO> children = listMap.get(codeVO.getCode());
+            if (children != null && !children.isEmpty()) {
+                codeVO.setChildren(children);
+                result.removeAll(children);
+            }
+
+            EmsEnergyConsumptionFormula formula = formulaMap.get(codeVO.getCode());
+            if (formula != null) {
+                codeVO.setId(formula.getId());
+                codeVO.setFormula(formula.getFormula());
+                if (formula.getName() != null) {
+                    codeVO.setName(formula.getName());
+                }
+            }
+        }
+
+        return result;
+    }
+}
+

+ 0 - 87
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsGatewayQueryServiceImpl.java

@@ -1,87 +0,0 @@
-package com.usky.ems.service.impl;
-
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.usky.common.core.bean.CommonPage;
-import com.usky.ems.domain.EmsGateway;
-import com.usky.ems.mapper.EmsGatewayMapper;
-import com.usky.ems.mapper.EmsSpaceMapper;
-import com.usky.ems.service.EmsGatewayQueryService;
-import com.usky.ems.service.vo.EmsGatewayDetailResponse;
-import com.usky.ems.service.vo.EmsGatewayListItem;
-import com.usky.ems.service.vo.EmsGatewayPageRequest;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-import org.springframework.util.StringUtils;
-
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * 网关列表与详情查询服务实现
- */
-@Service
-public class EmsGatewayQueryServiceImpl implements EmsGatewayQueryService {
-
-    @Autowired
-    private EmsGatewayMapper emsGatewayMapper;
-    @Autowired
-    private EmsSpaceMapper emsSpaceMapper;
-
-    @Override
-    public CommonPage<EmsGatewayListItem> listGateways(EmsGatewayPageRequest request) {
-        int current = request.getCurrent() == null ? 1 : request.getCurrent();
-        int size = request.getSize() == null ? 10 : request.getSize();
-        Page<EmsGateway> p = new Page<>(current, size);
-        LambdaQueryWrapper<EmsGateway> q = new LambdaQueryWrapper<>();
-        if (request.getCommunicationStatus() != null) {
-            q.eq(EmsGateway::getCommStatus, request.getCommunicationStatus());
-        }
-        if (StringUtils.hasText(request.getName())) {
-            q.like(EmsGateway::getName, request.getName());
-        }
-        if (StringUtils.hasText(request.getCode())) {
-            q.eq(EmsGateway::getId, request.getCode());
-        }
-        Page<EmsGateway> page = emsGatewayMapper.selectPage(p, q);
-        List<EmsGatewayListItem> list = page.getRecords().stream().map(this::toListItem).collect(Collectors.toList());
-        return new CommonPage<>(list, page.getTotal(), size, current);
-    }
-
-    @Override
-    public EmsGatewayDetailResponse getGatewayById(String id) {
-        EmsGateway g = emsGatewayMapper.selectById(id);
-        if (g == null) return null;
-        EmsGatewayDetailResponse resp = new EmsGatewayDetailResponse();
-        resp.setId(g.getId());
-        resp.setName(g.getName());
-        resp.setSpaceId(g.getSpaceId());
-        resp.setCommunicationStatus(g.getCommStatus());
-        resp.setOnlineTime(g.getOnlineTime());
-        resp.setCreateTime(g.getCreateTime());
-        resp.setUpdateTime(g.getUpdateTime());
-        if (g.getSpaceId() != null) {
-            com.usky.ems.domain.EmsSpace space = emsSpaceMapper.selectById(g.getSpaceId());
-            if (space != null) {
-                resp.setFloorName(space.getName());
-            }
-        }
-        return resp;
-    }
-
-    private EmsGatewayListItem toListItem(EmsGateway g) {
-        EmsGatewayListItem item = new EmsGatewayListItem();
-        item.setId(g.getId());
-        item.setName(g.getName());
-        item.setSpaceId(g.getSpaceId());
-        item.setCommunicationStatus(g.getCommStatus());
-        item.setOnlineTime(g.getOnlineTime());
-        item.setCreateTime(g.getCreateTime());
-        item.setUpdateTime(g.getUpdateTime());
-        if (g.getSpaceId() != null) {
-            com.usky.ems.domain.EmsSpace space = emsSpaceMapper.selectById(g.getSpaceId());
-            if (space != null) item.setFloorName(space.getName());
-        }
-        return item;
-    }
-}

+ 711 - 255
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsModelServiceImpl.java

@@ -1,36 +1,55 @@
 package com.usky.ems.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.usky.common.security.utils.SecurityUtils;
 import com.usky.ems.domain.*;
 import com.usky.ems.mapper.*;
 import com.usky.ems.service.EmsModelService;
+import com.usky.ems.service.vo.DmpGatewayDetailResponse;
+import com.usky.ems.service.vo.EnergyTypeWrapperProductVO;
+import com.usky.ems.service.vo.EmsAttributePointVO;
 import com.usky.ems.service.vo.EmsEnergyTypeVO;
+import com.usky.ems.service.vo.EmsGatewayDeviceTreeNode;
 import com.usky.ems.service.vo.EmsModelSaveRequest;
 import com.usky.ems.service.vo.EmsStructureTreeNode;
+import com.usky.ems.service.vo.SimpleProductVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
-import java.util.*;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
 
 /**
- * 基础建模服务实现(结构树、能源类型、建筑/区域/楼层/网关/通道/设备/属性点位 CRUD)
+ * 基础建模服务实现(结构树、能源类型、建筑/区域/网关/通道/设备/属性点位 CRUD)
+ * 空间:base_space;建筑/区域实体:base_build、base_area;关联:base_space_build、base_space_area、base_space_gateway;网关:dmp_gateway
  */
 @Service
 public class EmsModelServiceImpl implements EmsModelService {
 
     @Autowired
-    private EmsProjectMapper emsProjectMapper;
+    private BaseSpaceMapper baseSpaceMapper;
     @Autowired
-    private EmsSpaceMapper emsSpaceMapper;
+    private BaseBuildMapper baseBuildMapper;
     @Autowired
-    private EmsSpaceBuildingMapper emsSpaceBuildingMapper;
+    private BaseAreaMapper baseAreaMapper;
     @Autowired
-    private EmsSpaceAreaMapper emsSpaceAreaMapper;
+    private BaseSpaceBuildMapper baseSpaceBuildMapper;
     @Autowired
-    private EmsSpaceFloorMapper emsSpaceFloorMapper;
+    private BaseSpaceAreaMapper baseSpaceAreaMapper;
     @Autowired
-    private EmsGatewayMapper emsGatewayMapper;
+    private BaseSpaceGatewayMapper baseSpaceGatewayMapper;
+    @Autowired
+    private DmpGatewayMapper dmpGatewayMapper;
     @Autowired
     private EmsChannelMapper emsChannelMapper;
     @Autowired
@@ -38,72 +57,99 @@ public class EmsModelServiceImpl implements EmsModelService {
     @Autowired
     private EmsDeviceFunctionMapper emsDeviceFunctionMapper;
     @Autowired
-    private EmsEnergyItemCodeMapper emsEnergyItemCodeMapper;
+    private DmpProductMapper dmpProductMapper;
+    @Autowired
+    private BaseSpaceServiceImpl baseSpaceService;
 
     private static final int SPACE_TYPE_PROJECT = 1;
     private static final int SPACE_TYPE_REGION = 2;
     private static final int SPACE_TYPE_BUILDING = 3;
-    private static final int SPACE_TYPE_FLOOR = 4;
-    private static final int SPACE_TYPE_ROOM = 5;
 
-    private EmsProject firstProject() {
-        List<EmsProject> list = emsProjectMapper.selectList(null);
-        return list.isEmpty() ? null : list.get(0);
-    }
-
-    @Override
-    public EmsStructureTreeNode getStructureTree(Long projectId, Boolean includeGateway) {
-        EmsProject project = projectId != null ? emsProjectMapper.selectById(projectId) : firstProject();
-        if (project == null) {
+    /** base_build.create_by / update_by 为 varchar(25) */
+    private static String trimAuditUser(String username) {
+        if (username == null) {
             return null;
         }
-        EmsStructureTreeNode root = new EmsStructureTreeNode();
-        root.setId(project.getId());
-        root.setName(project.getName());
-        root.setType("project");
-        root.setChildren(buildSpaceChildren(project.getSpaceId(), includeGateway == null || includeGateway));
-        return root;
+        return username.length() > 25 ? username.substring(0, 25) : username;
     }
 
-    private List<EmsStructureTreeNode> buildSpaceChildren(Long parentId, boolean includeGateway) {
-        if (parentId == null) return Collections.emptyList();
-        List<EmsSpace> list = emsSpaceMapper.selectList(new LambdaQueryWrapper<EmsSpace>().eq(EmsSpace::getParentId, parentId));
-        List<EmsStructureTreeNode> children = new ArrayList<>();
-        for (EmsSpace s : list) {
-            EmsStructureTreeNode node = new EmsStructureTreeNode();
-            node.setId(s.getId());
-            node.setName(s.getName());
-            if (s.getType() == SPACE_TYPE_BUILDING) {
-                node.setType("building");
-                EmsSpaceBuilding b = emsSpaceBuildingMapper.selectOne(new LambdaQueryWrapper<EmsSpaceBuilding>().eq(EmsSpaceBuilding::getSpaceId, s.getId()));
-                if (b != null) node.setName(b.getName());
-            } else if (s.getType() == SPACE_TYPE_REGION) {
-                node.setType("region");
-                EmsSpaceArea a = emsSpaceAreaMapper.selectOne(new LambdaQueryWrapper<EmsSpaceArea>().eq(EmsSpaceArea::getSpaceId, s.getId()));
-                if (a != null) node.setName(a.getName());
-            } else if (s.getType() == SPACE_TYPE_FLOOR) {
-                node.setType("floor");
-                EmsSpaceFloor f = emsSpaceFloorMapper.selectOne(new LambdaQueryWrapper<EmsSpaceFloor>().eq(EmsSpaceFloor::getSpaceId, s.getId()));
-                if (f != null) node.setName(f.getName());
-            } else {
-                node.setType("space");
-            }
-            List<EmsStructureTreeNode> sub = buildSpaceChildren(s.getId(), includeGateway);
-            if (includeGateway && (s.getType() == SPACE_TYPE_FLOOR || s.getType() == SPACE_TYPE_ROOM)) {
-                List<EmsGateway> gateways = emsGatewayMapper.selectList(new LambdaQueryWrapper<EmsGateway>().eq(EmsGateway::getSpaceId, s.getId()));
-                for (EmsGateway g : gateways) {
-                    EmsStructureTreeNode gw = new EmsStructureTreeNode();
-                    gw.setId(g.getId());
-                    gw.setName(g.getName());
-                    gw.setType("gateway");
-                    sub.add(gw);
-                }
-            }
-            node.setChildren(sub);
-            children.add(node);
-        }
-        return children;
-    }
+//    private EmsProject firstProject() {
+//        List<EmsProject> list = emsProjectMapper.selectList(null);
+//        return list.isEmpty() ? null : list.get(0);
+//    }
+//
+//    @Override
+//    public EmsStructureTreeNode getStructureTree(Long projectId, Boolean includeGateway) {
+//        EmsProject project = projectId != null ? emsProjectMapper.selectById(projectId) : firstProject();
+//        if (project == null) {
+//            return null;
+//        }
+//        EmsStructureTreeNode root = new EmsStructureTreeNode();
+//        root.setId(project.getId());
+//        root.setName(project.getName());
+//        root.setType("project");
+//        root.setChildren(buildSpaceChildren(project.getSpaceId(), includeGateway == null || includeGateway));
+//        return root;
+//    }
+//
+//    private List<EmsStructureTreeNode> buildSpaceChildren(Long parentId, boolean includeGateway) {
+//        if (parentId == null) {
+//            return Collections.emptyList();
+//        }
+//        List<BaseSpace> list = baseSpaceMapper.selectList(new LambdaQueryWrapper<BaseSpace>().eq(BaseSpace::getParentId, parentId));
+//        List<EmsStructureTreeNode> children = new ArrayList<>();
+//        for (BaseSpace s : list) {
+//            EmsStructureTreeNode node = new EmsStructureTreeNode();
+//            node.setId(s.getId());
+//            node.setName(s.getName());
+//            if (s.getType() != null && s.getType() == SPACE_TYPE_BUILDING) {
+//                node.setType("building");
+//                BaseSpaceBuild link = baseSpaceBuildMapper.selectOne(
+//                        new LambdaQueryWrapper<BaseSpaceBuild>().eq(BaseSpaceBuild::getSpaceId, s.getId()).last("LIMIT 1"));
+//                if (link != null && link.getBuildId() != null) {
+//                    BaseBuild b = baseBuildMapper.selectById(link.getBuildId());
+//                    if (b != null && b.getBuildName() != null) {
+//                        node.setName(b.getBuildName());
+//                    }
+//                }
+//            } else if (s.getType() != null && s.getType() == SPACE_TYPE_REGION) {
+//                node.setType("region");
+//                BaseSpaceArea sa = baseSpaceAreaMapper.selectOne(
+//                        new LambdaQueryWrapper<BaseSpaceArea>().eq(BaseSpaceArea::getSpaceId, s.getId()).last("LIMIT 1"));
+//                if (sa != null && sa.getAreaId() != null) {
+//                    BaseArea a = baseAreaMapper.selectById(sa.getAreaId());
+//                    if (a != null && a.getName() != null) {
+//                        node.setName(a.getName());
+//                    }
+//                }
+//            } else {
+//                node.setType("space");
+//            }
+//            List<EmsStructureTreeNode> sub = buildSpaceChildren(s.getId(), includeGateway);
+//            if (includeGateway && (s.getType() == null || s.getType() != SPACE_TYPE_BUILDING)) {
+//                List<BaseSpaceGateway> links = baseSpaceGatewayMapper.selectList(
+//                        new LambdaQueryWrapper<BaseSpaceGateway>().eq(BaseSpaceGateway::getSpaceId, s.getId()));
+//                for (BaseSpaceGateway link : links) {
+//                    if (link.getGatewayUuid() == null) {
+//                        continue;
+//                    }
+//                    DmpGateway g = dmpGatewayMapper.selectOne(
+//                            new LambdaQueryWrapper<DmpGateway>().eq(DmpGateway::getDeviceUuid, link.getGatewayUuid()).last("LIMIT 1"));
+//                    if (g == null) {
+//                        continue;
+//                    }
+//                    EmsStructureTreeNode gw = new EmsStructureTreeNode();
+//                    gw.setId(g.getDeviceUuid());
+//                    gw.setName(g.getName());
+//                    gw.setType("gateway");
+//                    sub.add(gw);
+//                }
+//            }
+//            node.setChildren(sub);
+//            children.add(node);
+//        }
+//        return children;
+//    }
 
     @Override
     public List<EmsEnergyTypeVO> getEnergyTypeList() {
@@ -123,279 +169,689 @@ public class EmsModelServiceImpl implements EmsModelService {
         return result;
     }
 
+    @Override
+    public List<EnergyTypeWrapperProductVO> showAssociatedEnergyTypes() {
+        List<DmpProduct> products = dmpProductMapper.selectList(
+                new LambdaQueryWrapper<DmpProduct>()
+                        .eq(DmpProduct::getDeleteFlag, 0)
+                        .eq(DmpProduct::getTenantId, SecurityUtils.getTenantId())
+        );
+        if (products == null || products.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        Map<Integer, List<SimpleProductVO>> energyMaps = products.stream()
+                .collect(Collectors.groupingBy(
+                        DmpProduct::getDeviceType,
+                        Collectors.mapping(item -> new SimpleProductVO(
+                                item.getProductCode(),
+                                item.getProductName()
+                        ), Collectors.toList())
+                ));
+
+        List<EnergyTypeWrapperProductVO> result = new ArrayList<>();
+        energyMaps.forEach((type, list) -> {
+            String typeName = deviceTypeName(type);
+            EnergyTypeWrapperProductVO vo = new EnergyTypeWrapperProductVO(type, typeName);
+            vo.setProductList(list);
+            result.add(vo);
+        });
+
+        result.sort((o1, o2) -> {
+            if (o1.getEnergyType() == null) {
+                return -1;
+            }
+            if (o2.getEnergyType() == null) {
+                return 1;
+            }
+            return o1.getEnergyType().compareTo(o2.getEnergyType());
+        });
+        return result;
+    }
+
+    private String deviceTypeName(Integer deviceType) {
+        if (deviceType == null) {
+            return "";
+        }
+        switch (deviceType) {
+            case 501:
+                return "监控系统";
+            case 502:
+                return "门禁系统";
+            case 503:
+                return "梯控系统";
+            case 504:
+                return "机房系统";
+            case 509:
+                return "环境系统";
+            case 510:
+                return "照明系统";
+            default:
+                return "";
+        }
+    }
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Long createBuilding(EmsModelSaveRequest request) {
-        Long projectId = request.getProjectId();
-        String name = request.getName();
-        EmsProject project = projectId != null ? emsProjectMapper.selectById(projectId) : firstProject();
-        if (project == null) return null;
-        EmsSpace space = new EmsSpace();
-        space.setName(name);
-        space.setParentId(project.getSpaceId());
+        Long parentSpaceId = request.getParentSpaceId();
+        LocalDateTime now = LocalDateTime.now();
+        String auditUser = trimAuditUser(SecurityUtils.getUsername());
+        Integer tenantId = SecurityUtils.getTenantId();
+        BaseBuild build = new BaseBuild();
+        build.setBuildName(request.getBuildName());
+        build.setAddress(request.getAddress());
+        build.setAboveFloor(request.getAboveFloor());
+        build.setUnderFloor(request.getUnderFloor());
+        build.setBuildArea(request.getBuildArea());
+        build.setCoverArea(request.getCoverArea());
+        build.setBuildHigh(request.getBuildHigh());
+        build.setCompleteYear(request.getCompleteYear());
+        build.setBuildDesc(request.getBuildDesc());
+        build.setLongitude(request.getLongitude());
+        build.setLatitude(request.getLatitude());
+        build.setTenantId(tenantId);
+        build.setCreateBy(auditUser);
+        build.setUpdateBy(auditUser);
+        build.setCreateTime(now);
+        build.setUpdateTime(now);
+        baseBuildMapper.insert(build);
+
+        BaseSpace space = new BaseSpace();
+        space.setName(request.getBuildName());
+        space.setParentId(request.getParentSpaceId());
         space.setType(SPACE_TYPE_BUILDING);
-        space.setRootId(project.getSpaceId());
-        emsSpaceMapper.insert(space);
-        EmsSpaceBuilding building = new EmsSpaceBuilding();
-        building.setSpaceId(space.getId());
-        building.setName(name);
-        emsSpaceBuildingMapper.insert(building);
+        BaseSpace parent = parentSpaceId != null ? baseSpaceMapper.selectById(parentSpaceId) : null;
+        space.setRootId(parent != null ? parent.getRootId() : parentSpaceId);
+        space.setTenantId(tenantId);
+        space.setCreatedBy(auditUser);
+        space.setUpdatedBy(auditUser);
+        space.setCreateTime(now);
+        space.setUpdateTime(now);
+        if(parent.getParentId() == 0) {
+            space.setPath(String.valueOf(parentSpaceId));
+        }else{
+            space.setPath(parent.getPath()+"/"+parentSpaceId);
+        }
+
+        space.setDeep(parent.getDeep()+1);
+        baseSpaceMapper.insert(space);
+
+        BaseSpaceBuild rel = new BaseSpaceBuild();
+        rel.setSpaceId(space.getId());
+        rel.setBuildId(build.getId());
+        rel.setCreatedBy(auditUser);
+        rel.setCreatedTime(now);
+        baseSpaceBuildMapper.insert(rel);
         return space.getId();
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void updateBuilding(Long id, EmsModelSaveRequest request) {
-        String name = request.getName();
-        EmsSpace space = emsSpaceMapper.selectById(id);
+        String name = request.getBuildName();
+        BaseSpace space = baseSpaceMapper.selectById(id);
         if (space != null && name != null) {
             space.setName(name);
-            emsSpaceMapper.updateById(space);
+            space.setUpdatedBy(SecurityUtils.getUsername());
+            space.setUpdateTime(LocalDateTime.now());
+            baseSpaceMapper.updateById(space);
         }
-        EmsSpaceBuilding b = emsSpaceBuildingMapper.selectOne(new LambdaQueryWrapper<EmsSpaceBuilding>().eq(EmsSpaceBuilding::getSpaceId, id));
-        if (b != null && name != null) {
-            b.setName(name);
-            emsSpaceBuildingMapper.updateById(b);
+        BaseSpaceBuild b = baseSpaceBuildMapper.selectOne(new LambdaQueryWrapper<BaseSpaceBuild>().eq(BaseSpaceBuild::getSpaceId, id).last("LIMIT 1"));
+        if (b != null && b.getBuildId() != null && name != null) {
+            BaseBuild build = baseBuildMapper.selectById(b.getBuildId());
+            if (build != null) {
+                if(StringUtils.isNotBlank(request.getBuildName())){
+                    build.setBuildName(request.getBuildName());
+                }
+                if(StringUtils.isNotBlank(request.getAddress())){
+                    build.setAddress(request.getAddress());
+                }
+                if(request.getAboveFloor() != null){
+                    build.setAboveFloor(request.getAboveFloor());
+                }
+                if(request.getUnderFloor() != null){
+                    build.setUnderFloor(request.getUnderFloor());
+                }
+                if(request.getBuildArea() != null){
+                    build.setBuildArea(request.getBuildArea());
+                }
+                if(request.getCoverArea() != null){
+                    build.setCoverArea(request.getCoverArea());
+                }
+                if(request.getBuildHigh() != null){
+                    build.setBuildHigh(request.getBuildHigh());
+                }
+                if(request.getCompleteYear() != null){
+                    build.setCompleteYear(request.getCompleteYear());
+                }
+                build.setBuildDesc(request.getBuildDesc());
+                build.setLongitude(request.getLongitude());
+                build.setLatitude(request.getLatitude());
+                build.setUpdateBy(trimAuditUser(SecurityUtils.getUsername()));
+                build.setUpdateTime(LocalDateTime.now());
+                baseBuildMapper.updateById(build);
+            }
         }
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void deleteBuilding(Long id) {
-        emsSpaceBuildingMapper.delete(new LambdaQueryWrapper<EmsSpaceBuilding>().eq(EmsSpaceBuilding::getSpaceId, id));
-        emsSpaceMapper.deleteById(id);
+        List<BaseSpaceBuild> links = baseSpaceBuildMapper.selectList(
+                new LambdaQueryWrapper<BaseSpaceBuild>().eq(BaseSpaceBuild::getSpaceId, id));
+        for (BaseSpaceBuild link : links) {
+            if (link.getBuildId() != null) {
+                baseBuildMapper.deleteById(link.getBuildId());
+            }
+        }
+        baseSpaceBuildMapper.delete(new LambdaQueryWrapper<BaseSpaceBuild>().eq(BaseSpaceBuild::getSpaceId, id));
+        baseSpaceMapper.deleteById(id);
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Long createRegion(EmsModelSaveRequest request) {
-        Long buildingId = request.getBuildingId();
+        Long parentSpaceId = request.getParentSpaceId();
         String name = request.getName();
-        EmsSpace space = new EmsSpace();
-        space.setName(name);
-        space.setParentId(buildingId);
-        EmsSpace parent = emsSpaceMapper.selectById(buildingId);
-        space.setRootId(parent != null ? parent.getRootId() : buildingId);
-        space.setType(SPACE_TYPE_REGION);
-        emsSpaceMapper.insert(space);
-        EmsSpaceArea area = new EmsSpaceArea();
-        area.setSpaceId(space.getId());
-        area.setName(name);
-        if (request.getArea() != null) area.setArea(request.getArea());
-        return space.getId();
-    }
+        LocalDateTime now = LocalDateTime.now();
+        Integer tenantId = SecurityUtils.getTenantId();
+        String auditUser = trimAuditUser(SecurityUtils.getUsername());
 
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public void updateRegion(Long id, EmsModelSaveRequest request) {
-        String name = request.getName();
-        EmsSpace space = emsSpaceMapper.selectById(id);
-        if (space != null && name != null) {
-            space.setName(name);
-            emsSpaceMapper.updateById(space);
+        BaseArea area = new BaseArea();
+        area.setName(name);
+        area.setType(request.getType());
+        if (request.getArea() != null) {
+            area.setArea(request.getArea());
         }
-        EmsSpaceArea a = emsSpaceAreaMapper.selectOne(new LambdaQueryWrapper<EmsSpaceArea>().eq(EmsSpaceArea::getSpaceId, id));
-        if (a != null) {
-            if (name != null) a.setName(name);
-            if (request.getArea() != null) a.setArea(request.getArea());
-            emsSpaceAreaMapper.updateById(a);
+        if(request.getCommonArea() != null) {
+            area.setCommonArea(request.getCommonArea());
         }
-    }
-
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public void deleteRegion(Long id) {
-        emsSpaceAreaMapper.delete(new LambdaQueryWrapper<EmsSpaceArea>().eq(EmsSpaceArea::getSpaceId, id));
-        emsSpaceMapper.deleteById(id);
-    }
+        if(request.getAirConditionedArea() != null) {
+            area.setAirConditionedArea(request.getAirConditionedArea());
+        }
+        if(request.getResidentPopulation() != null) {
+            area.setResidentPopulation(request.getResidentPopulation());
+        }
+        area.setCreatedBy(auditUser);
+        area.setUpdatedBy(auditUser);
+        area.setCreateTime(now);
+        area.setUpdateTime(now);
+        area.setTenantId(tenantId);
+        baseAreaMapper.insert(area);
 
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public Long createFloor(EmsModelSaveRequest request) {
-        Long regionId = request.getRegionId();
-        String name = request.getName();
-        EmsSpace space = new EmsSpace();
+        BaseSpace space = new BaseSpace();
         space.setName(name);
-        space.setParentId(regionId);
-        EmsSpace parent = emsSpaceMapper.selectById(regionId);
-        space.setRootId(parent != null ? parent.getRootId() : regionId);
-        space.setType(SPACE_TYPE_FLOOR);
-        emsSpaceMapper.insert(space);
-        EmsSpaceFloor floor = new EmsSpaceFloor();
-        floor.setSpaceId(space.getId());
-        floor.setName(name);
-        emsSpaceFloorMapper.insert(floor);
+        space.setParentId(parentSpaceId);
+        BaseSpace parent = parentSpaceId != null ? baseSpaceMapper.selectById(parentSpaceId) : null;
+        space.setRootId(parent != null ? parent.getRootId() : parentSpaceId);
+        space.setType(SPACE_TYPE_REGION);
+        if(parent.getParentId() == 0) {
+            space.setPath(String.valueOf(parentSpaceId));
+        }else{
+            space.setPath(parent.getPath()+"/"+parentSpaceId);
+        }
+        space.setDeep(parent.getDeep()+1);
+        space.setCreatedBy(auditUser);
+        space.setUpdatedBy(auditUser);
+        space.setCreateTime(now);
+        space.setUpdateTime(now);
+        space.setTenantId(tenantId);
+        baseSpaceMapper.insert(space);
+
+        BaseSpaceArea rel = new BaseSpaceArea();
+        rel.setSpaceId(space.getId());
+        rel.setAreaId(area.getId());
+        rel.setCreatedBy(auditUser);
+        rel.setCreatedTime(now);
+        baseSpaceAreaMapper.insert(rel);
         return space.getId();
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public void updateFloor(Long id, EmsModelSaveRequest request) {
+    public void updateRegion(Long id, EmsModelSaveRequest request) {
         String name = request.getName();
-        EmsSpace space = emsSpaceMapper.selectById(id);
+        BaseSpace space = baseSpaceMapper.selectById(id);
         if (space != null && name != null) {
             space.setName(name);
-            emsSpaceMapper.updateById(space);
+            space.setUpdateTime(LocalDateTime.now());
+            baseSpaceMapper.updateById(space);
         }
-        EmsSpaceFloor f = emsSpaceFloorMapper.selectOne(new LambdaQueryWrapper<EmsSpaceFloor>().eq(EmsSpaceFloor::getSpaceId, id));
-        if (f != null && name != null) {
-            f.setName(name);
-            emsSpaceFloorMapper.updateById(f);
+        BaseSpaceArea sa = baseSpaceAreaMapper.selectOne(new LambdaQueryWrapper<BaseSpaceArea>().eq(BaseSpaceArea::getSpaceId, id).last("LIMIT 1"));
+        if (sa != null && sa.getAreaId() != null) {
+            BaseArea area = baseAreaMapper.selectById(sa.getAreaId());
+            if (area != null) {
+                if (name != null) {
+                    area.setName(name);
+                }
+
+                if(request.getType() != null) {
+                    area.setType(request.getType());
+                }
+
+                if (request.getArea() != null) {
+                    area.setArea(request.getArea());
+                }
+                if(request.getCommonArea() != null) {
+                    area.setCommonArea(request.getCommonArea());
+                }
+                if(request.getAirConditionedArea() != null) {
+                    area.setAirConditionedArea(request.getAirConditionedArea());
+                }
+                if(request.getResidentPopulation() != null) {
+                    area.setResidentPopulation(request.getResidentPopulation());
+                }
+                area.setUpdateTime(LocalDateTime.now());
+                baseAreaMapper.updateById(area);
+            }
         }
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public void deleteFloor(Long id) {
-        emsSpaceFloorMapper.delete(new LambdaQueryWrapper<EmsSpaceFloor>().eq(EmsSpaceFloor::getSpaceId, id));
-        emsSpaceMapper.deleteById(id);
+    public void deleteRegion(Long id) {
+        BaseSpaceArea sa = baseSpaceAreaMapper.selectOne(new LambdaQueryWrapper<BaseSpaceArea>().eq(BaseSpaceArea::getSpaceId, id).last("LIMIT 1"));
+        baseSpaceAreaMapper.delete(new LambdaQueryWrapper<BaseSpaceArea>().eq(BaseSpaceArea::getSpaceId, id));
+        if (sa != null && sa.getAreaId() != null) {
+            baseAreaMapper.deleteById(sa.getAreaId());
+        }
+        baseSpaceMapper.deleteById(id);
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
     public String createGateway(EmsModelSaveRequest request) {
-        Long floorId = request.getFloorId();
+        Long spaceId = request.getSpaceId();
         String name = request.getName();
-        String gwId = "GW" + System.currentTimeMillis();
-        if (gwId.length() > 13) gwId = gwId.substring(0, 13);
-        EmsGateway g = new EmsGateway();
-        g.setId(gwId);
-        g.setName(name);
-        g.setSpaceId(floorId);
-        g.setProjectId(0L);
-        EmsSpace space = floorId != null ? emsSpaceMapper.selectById(floorId) : null;
-        if (space != null) {
-            EmsProject p = firstProject();
-            if (p != null) g.setProjectId(p.getId());
+        String uuid = UUID.randomUUID().toString().replace("-", "");
+        if (uuid.length() > 50) {
+            uuid = uuid.substring(0, 50);
         }
+        LocalDateTime now = LocalDateTime.now();
+        DmpGateway g = new DmpGateway();
+        g.setDeviceUuid(uuid);
+        g.setName(name != null ? name : "gateway");
         g.setCommStatus(0);
-        g.setDataCenterId(0L);
-        g.setSecretKey("");
         g.setVirtualDevice(0);
-        emsGatewayMapper.insert(g);
-        return g.getId();
+        g.setCreateTime(now);
+        g.setUpdateTime(now);
+        dmpGatewayMapper.insert(g);
+
+        if (spaceId != null) {
+            BaseSpaceGateway rel = new BaseSpaceGateway();
+            rel.setSpaceId(spaceId);
+            rel.setGatewayUuid(uuid);
+            rel.setCreatedTime(now);
+            baseSpaceGatewayMapper.insert(rel);
+        }
+        return uuid;
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void updateGateway(String id, EmsModelSaveRequest request) {
-        EmsGateway g = emsGatewayMapper.selectById(id);
-        if (g == null) return;
-        if (request.getName() != null) g.setName(request.getName());
-        if (request.getFloorId() != null) g.setSpaceId(request.getFloorId());
-        emsGatewayMapper.updateById(g);
+        DmpGateway g = dmpGatewayMapper.selectOne(
+                new LambdaQueryWrapper<DmpGateway>().eq(DmpGateway::getDeviceUuid, id).last("LIMIT 1"));
+        if (g == null) {
+            return;
+        }
+        if (request.getName() != null) {
+            g.setName(request.getName());
+        }
+        g.setUpdateTime(LocalDateTime.now());
+        dmpGatewayMapper.updateById(g);
+
+        if (request.getSpaceId() != null) {
+            baseSpaceGatewayMapper.delete(new LambdaQueryWrapper<BaseSpaceGateway>().eq(BaseSpaceGateway::getGatewayUuid, id));
+            BaseSpaceGateway rel = new BaseSpaceGateway();
+            rel.setSpaceId(request.getSpaceId());
+            rel.setGatewayUuid(id);
+            rel.setCreatedTime(LocalDateTime.now());
+            baseSpaceGatewayMapper.insert(rel);
+        }
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void deleteGateway(String id) {
-        emsGatewayMapper.deleteById(id);
+        baseSpaceGatewayMapper.delete(new LambdaQueryWrapper<BaseSpaceGateway>().eq(BaseSpaceGateway::getGatewayUuid, id));
+        dmpGatewayMapper.delete(new LambdaQueryWrapper<DmpGateway>().eq(DmpGateway::getDeviceUuid, id));
     }
 
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public Long createChannel(EmsModelSaveRequest request) {
-        String gatewayId = request.getGatewayId();
-        String name = request.getName();
-        EmsChannel c = new EmsChannel();
-        c.setGatewayId(gatewayId);
-        c.setName(name);
-        c.setChannelTypeId(1);
-        emsChannelMapper.insert(c);
-        return c.getId();
-    }
+//    @Override
+//    @Transactional(rollbackFor = Exception.class)
+//    public Long createChannel(EmsModelSaveRequest request) {
+//        String gatewayId = request.getGatewayId();
+//        String name = request.getName();
+//        EmsChannel c = new EmsChannel();
+//        c.setGatewayId(gatewayId);
+//        c.setName(name);
+//        c.setChannelTypeId(request.getChannelTypeId());
+//        emsChannelMapper.insert(c);
+//        return c.getId();
+//    }
+//
+//    @Override
+//    @Transactional(rollbackFor = Exception.class)
+//    public void updateChannel(Long id, EmsModelSaveRequest request) {
+//        EmsChannel c = emsChannelMapper.selectById(id);
+//        if (c == null) {
+//            return;
+//        }
+//        if (request.getName() != null) {
+//            c.setName(request.getName());
+//        }
+//        if (request.getGatewayId() != null) {
+//            c.setGatewayId(request.getGatewayId());
+//        }
+//        emsChannelMapper.updateById(c);
+//    }
+//
+//    @Override
+//    @Transactional(rollbackFor = Exception.class)
+//    public void deleteChannel(Long id) {
+//        emsChannelMapper.deleteById(id);
+//    }
+//
+//    @Override
+//    @Transactional(rollbackFor = Exception.class)
+//    public String createDevice(EmsModelSaveRequest request) {
+//        Long channelId = request.getChannelId();
+//        String name = request.getName();
+//        String devId = "DV" + System.currentTimeMillis();
+//        if (devId.length() > 13) {
+//            devId = devId.substring(0, 13);
+//        }
+//        EmsDevice d = new EmsDevice();
+//        d.setId(devId);
+//        d.setName(name);
+//        d.setProjectId(0L);
+//        d.setChannelId(channelId != null ? channelId : 0L);
+//        d.setGatewayId("");
+//        EmsChannel ch = channelId != null ? emsChannelMapper.selectById(channelId) : null;
+//        if (ch != null) {
+//            d.setGatewayId(ch.getGatewayId());
+//        }
+//        d.setProductId(0L);
+//        d.setInstallationLocation(0L);
+//        d.setMonitoringLocation(0L);
+//        d.setCommAddress("");
+//        d.setVirtualDevice(0);
+//        d.setFocus(0);
+//        d.setDeviceSystem(0);
+//        d.setStatus(1);
+//        d.setCommStatus(0);
+//        emsDeviceMapper.insert(d);
+//        return d.getId();
+//    }
+//
+//    @Override
+//    @Transactional(rollbackFor = Exception.class)
+//    public void updateDevice(String id, EmsModelSaveRequest request) {
+//        EmsDevice d = emsDeviceMapper.selectById(id);
+//        if (d == null) {
+//            return;
+//        }
+//        if (request.getName() != null) {
+//            d.setName(request.getName());
+//        }
+//        if (request.getChannelId() != null) {
+//            d.setChannelId(request.getChannelId());
+//        }
+//        emsDeviceMapper.updateById(d);
+//    }
+//
+//    @Override
+//    @Transactional(rollbackFor = Exception.class)
+//    public void deleteDevice(String id) {
+//        emsDeviceMapper.deleteById(id);
+//    }
+//
+//    @Override
+//    @Transactional(rollbackFor = Exception.class)
+//    public Long createAttributePoint(EmsModelSaveRequest request) {
+//        String deviceId = request.getDeviceId();
+//        String name = request.getName();
+//        String code = request.getCode();
+//        EmsDeviceFunction f = new EmsDeviceFunction();
+//        f.setDeviceId(deviceId);
+//        f.setName(name);
+//        f.setIdentifier(code != null ? code : name);
+//        f.setProductId(0L);
+//        f.setProductTemplateId(0L);
+//        f.setPreservable(1);
+//        f.setBindingAcq(0);
+//        emsDeviceFunctionMapper.insert(f);
+//        return f.getId();
+//    }
+//
+//    @Override
+//    @Transactional(rollbackFor = Exception.class)
+//    public void updateAttributePoint(Long id, EmsModelSaveRequest request) {
+//        EmsDeviceFunction f = emsDeviceFunctionMapper.selectById(id);
+//        if (f == null) {
+//            return;
+//        }
+//        if (request.getName() != null) {
+//            f.setName(request.getName());
+//        }
+//        if (request.getCode() != null) {
+//            f.setIdentifier(request.getCode());
+//        }
+//        emsDeviceFunctionMapper.updateById(f);
+//    }
+//
+//    @Override
+//    @Transactional(rollbackFor = Exception.class)
+//    public void deleteAttributePoint(Long id) {
+//        emsDeviceFunctionMapper.deleteById(id);
+//    }
 
     @Override
-    @Transactional(rollbackFor = Exception.class)
-    public void updateChannel(Long id, EmsModelSaveRequest request) {
-        EmsChannel c = emsChannelMapper.selectById(id);
-        if (c == null) return;
-        if (request.getName() != null) c.setName(request.getName());
-        if (request.getGatewayId() != null) c.setGatewayId(request.getGatewayId());
-        emsChannelMapper.updateById(c);
-    }
+    public List<EmsGatewayDeviceTreeNode> getGatewayDeviceTree(Long spaceId, String keyword) {
+        LambdaQueryWrapper<DmpGateway> gwWrapper = new LambdaQueryWrapper<>();
+        if (spaceId != null) {
+            List<BaseSpaceGateway> links = baseSpaceGatewayMapper.selectList(
+                    new LambdaQueryWrapper<BaseSpaceGateway>().eq(BaseSpaceGateway::getSpaceId, spaceId));
+            Set<String> uuids = links.stream()
+                    .map(BaseSpaceGateway::getGatewayUuid)
+                    .filter(Objects::nonNull)
+                    .collect(Collectors.toSet());
+            if (uuids.isEmpty()) {
+                return Collections.emptyList();
+            }
+            gwWrapper.in(DmpGateway::getDeviceUuid, uuids);
+        }
+        if (keyword != null && !keyword.trim().isEmpty()) {
+            gwWrapper.like(DmpGateway::getName, keyword.trim());
+        }
+        List<DmpGateway> gateways = dmpGatewayMapper.selectList(gwWrapper);
 
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public void deleteChannel(Long id) {
-        emsChannelMapper.deleteById(id);
-    }
+        List<EmsGatewayDeviceTreeNode> result = new ArrayList<>();
+        for (DmpGateway gw : gateways) {
+            EmsGatewayDeviceTreeNode gwNode = new EmsGatewayDeviceTreeNode();
+            gwNode.setId(gw.getDeviceUuid());
+            gwNode.setName(gw.getName());
+            gwNode.setType("gateway");
 
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public String createDevice(EmsModelSaveRequest request) {
-        Long channelId = request.getChannelId();
-        String name = request.getName();
-        String devId = "DV" + System.currentTimeMillis();
-        if (devId.length() > 13) devId = devId.substring(0, 13);
-        EmsDevice d = new EmsDevice();
-        d.setId(devId);
-        d.setName(name);
-        d.setProjectId(0L);
-        d.setChannelId(channelId != null ? channelId : 0L);
-        d.setGatewayId("");
-        EmsChannel ch = channelId != null ? emsChannelMapper.selectById(channelId) : null;
-        if (ch != null) d.setGatewayId(ch.getGatewayId());
-        d.setProductId(0L);
-        d.setInstallationLocation(0L);
-        d.setMonitoringLocation(0L);
-        d.setCommAddress("");
-        d.setVirtualDevice(0);
-        d.setFocus(0);
-        d.setDeviceSystem(0);
-        d.setStatus(1);
-        d.setCommStatus(0);
-        emsDeviceMapper.insert(d);
-        return d.getId();
-    }
+            List<EmsChannel> channels = emsChannelMapper.selectList(
+                    new LambdaQueryWrapper<EmsChannel>().eq(EmsChannel::getGatewayId, gw.getDeviceUuid()));
+            List<EmsGatewayDeviceTreeNode> channelNodes = new ArrayList<>();
+            for (EmsChannel ch : channels) {
+                EmsGatewayDeviceTreeNode chNode = new EmsGatewayDeviceTreeNode();
+                chNode.setId(ch.getId());
+                chNode.setName(ch.getName());
+                chNode.setType("channel");
 
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public void updateDevice(String id, EmsModelSaveRequest request) {
-        EmsDevice d = emsDeviceMapper.selectById(id);
-        if (d == null) return;
-        if (request.getName() != null) d.setName(request.getName());
-        if (request.getChannelId() != null) d.setChannelId(request.getChannelId());
-        emsDeviceMapper.updateById(d);
+                List<EmsDevice> devices = emsDeviceMapper.selectList(
+                        new LambdaQueryWrapper<EmsDevice>().eq(EmsDevice::getChannelId, ch.getId()));
+                List<EmsGatewayDeviceTreeNode> deviceNodes = new ArrayList<>();
+                for (EmsDevice dev : devices) {
+                    EmsGatewayDeviceTreeNode devNode = new EmsGatewayDeviceTreeNode();
+                    devNode.setId(dev.getId());
+                    devNode.setName(dev.getName());
+                    devNode.setType("device");
+                    deviceNodes.add(devNode);
+                }
+
+                if (!deviceNodes.isEmpty()) {
+                    chNode.setChildren(deviceNodes);
+                    channelNodes.add(chNode);
+                }
+            }
+
+            if (!channelNodes.isEmpty()) {
+                gwNode.setChildren(channelNodes);
+                result.add(gwNode);
+            }
+        }
+        return result;
     }
 
     @Override
-    @Transactional(rollbackFor = Exception.class)
-    public void deleteDevice(String id) {
-        emsDeviceMapper.deleteById(id);
+    public List<EmsAttributePointVO> getAttributePointList(String deviceId) {
+        if (deviceId == null || deviceId.trim().isEmpty()) {
+            return Collections.emptyList();
+        }
+        List<EmsDeviceFunction> functions = emsDeviceFunctionMapper.selectList(
+                new LambdaQueryWrapper<EmsDeviceFunction>().eq(EmsDeviceFunction::getDeviceId, deviceId));
+        List<EmsAttributePointVO> result = new ArrayList<>();
+        for (EmsDeviceFunction f : functions) {
+            EmsAttributePointVO vo = new EmsAttributePointVO();
+            vo.setId(f.getId());
+            vo.setProductId(f.getProductId());
+            vo.setProductTemplateId(f.getProductTemplateId());
+            vo.setDeviceId(f.getDeviceId());
+            vo.setName(f.getName());
+            vo.setIdentifier(f.getIdentifier());
+            vo.setValue(f.getValue());
+            vo.setAcqTime(f.getAcqTime());
+            vo.setRatio(f.getRatio());
+            vo.setPreservable(f.getPreservable());
+            vo.setBindingAcq(f.getBindingAcq());
+            vo.setUpdatedBy(f.getUpdatedBy());
+            vo.setUpdateTime(f.getUpdateTime());
+            vo.setCreatedBy(f.getCreatedBy());
+            vo.setCreateTime(f.getCreateTime());
+            result.add(vo);
+        }
+        return result;
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public Long createAttributePoint(EmsModelSaveRequest request) {
-        String deviceId = request.getDeviceId();
-        String name = request.getName();
-        String code = request.getCode();
-        EmsDeviceFunction f = new EmsDeviceFunction();
-        f.setDeviceId(deviceId);
-        f.setName(name);
-        f.setIdentifier(code != null ? code : name);
-        f.setProductId(0L);
-        f.setProductTemplateId(0L);
-        f.setPreservable(1);
-        f.setBindingAcq(0);
-        emsDeviceFunctionMapper.insert(f);
-        return f.getId();
+    public void bindSpaceGateways(Long spaceId, List<String> gatewayUuids) {
+        if (spaceId == null) {
+            return;
+        }
+        baseSpaceGatewayMapper.delete(new LambdaQueryWrapper<BaseSpaceGateway>().eq(BaseSpaceGateway::getSpaceId, spaceId));
+        if (gatewayUuids == null || gatewayUuids.isEmpty()) {
+            return;
+        }
+
+        Set<String> normalizedUuids = gatewayUuids.stream()
+                .filter(Objects::nonNull)
+                .map(String::trim)
+                .filter(s -> !s.isEmpty())
+                .collect(Collectors.toCollection(LinkedHashSet::new));
+        if (normalizedUuids.isEmpty()) {
+            return;
+        }
+
+        List<DmpGateway> gateways = dmpGatewayMapper.selectList(
+                new LambdaQueryWrapper<DmpGateway>().in(DmpGateway::getDeviceUuid, normalizedUuids));
+        Set<String> existsGatewayUuids = gateways.stream()
+                .map(DmpGateway::getDeviceUuid)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+        if (existsGatewayUuids.isEmpty()) {
+            return;
+        }
+
+        LocalDateTime now = LocalDateTime.now();
+        String auditUser = trimAuditUser(SecurityUtils.getUsername());
+        for (String gatewayUuid : normalizedUuids) {
+            if (!existsGatewayUuids.contains(gatewayUuid)) {
+                continue;
+            }
+            BaseSpaceGateway relation = new BaseSpaceGateway();
+            relation.setSpaceId(spaceId);
+            relation.setGatewayUuid(gatewayUuid);
+            relation.setCreatedBy(auditUser);
+            relation.setCreatedTime(now);
+            baseSpaceGatewayMapper.insert(relation);
+        }
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public void updateAttributePoint(Long id, EmsModelSaveRequest request) {
-        EmsDeviceFunction f = emsDeviceFunctionMapper.selectById(id);
-        if (f == null) return;
-        if (request.getName() != null) f.setName(request.getName());
-        if (request.getCode() != null) f.setIdentifier(request.getCode());
-        emsDeviceFunctionMapper.updateById(f);
+    public void unbindSpaceGateways(Long spaceId, List<String> gatewayUuids) {
+        if (spaceId == null || gatewayUuids == null || gatewayUuids.isEmpty()) {
+            return;
+        }
+        Set<String> normalizedUuids = gatewayUuids.stream()
+                .filter(Objects::nonNull)
+                .map(String::trim)
+                .filter(s -> !s.isEmpty())
+                .collect(Collectors.toCollection(LinkedHashSet::new));
+        if (normalizedUuids.isEmpty()) {
+            return;
+        }
+
+        baseSpaceGatewayMapper.delete(new LambdaQueryWrapper<BaseSpaceGateway>()
+                .eq(BaseSpaceGateway::getSpaceId, spaceId)
+                .in(BaseSpaceGateway::getGatewayUuid, normalizedUuids));
     }
 
     @Override
-    @Transactional(rollbackFor = Exception.class)
-    public void deleteAttributePoint(Long id) {
-        emsDeviceFunctionMapper.deleteById(id);
+    public List<DmpGatewayDetailResponse> getGatewayListBySpaceId(Long spaceId) {
+        if (spaceId == null) {
+            return Collections.emptyList();
+        }
+
+        // 1. 授权空间
+        List<Long> spaceIds = baseSpaceService.getAuthorizedSpaceIds(spaceId);
+
+        List<BaseSpaceGateway> links = baseSpaceGatewayMapper.selectList(
+                new LambdaQueryWrapper<BaseSpaceGateway>().in(BaseSpaceGateway::getSpaceId, spaceIds));
+        if (links == null || links.isEmpty()) {
+            return Collections.emptyList();
+        }
+        Set<String> gatewayUuids = links.stream()
+                .map(BaseSpaceGateway::getGatewayUuid)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toCollection(LinkedHashSet::new));
+        if (gatewayUuids.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        List<DmpGateway> gateways = dmpGatewayMapper.selectList(
+                new LambdaQueryWrapper<DmpGateway>().in(DmpGateway::getDeviceUuid, gatewayUuids));
+        if (gateways == null || gateways.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        return gateways.stream().map(item -> {
+            DmpGatewayDetailResponse resp = new DmpGatewayDetailResponse();
+            resp.setId(item.getId());
+            resp.setDeviceUuid(item.getDeviceUuid());
+            resp.setName(item.getName());
+            resp.setIp(item.getIp());
+            resp.setPort(item.getPort());
+            resp.setCommunicationStatus(item.getCommStatus());
+            resp.setOnlineTime(item.getOnlineTime());
+            resp.setOfflineTime(item.getOfflineTime());
+            resp.setUpdateConfigTime(item.getUpdateConfigTime());
+            resp.setUpdateProtocolTime(item.getUpdateProtocolTime());
+            resp.setUpgradeTime(item.getUpgradeTime());
+            resp.setVirtualDevice(item.getVirtualDevice());
+            resp.setRemark(item.getRemark());
+            resp.setUpdatedBy(item.getUpdatedBy());
+            resp.setUpdateTime(item.getUpdateTime());
+            resp.setCreatedBy(item.getCreatedBy());
+            resp.setCreateTime(item.getCreateTime());
+            return resp;
+        }).collect(Collectors.toList());
     }
 }

+ 625 - 6
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsOverviewServiceImpl.java

@@ -1,14 +1,38 @@
 package com.usky.ems.service.impl;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.usky.common.security.utils.SecurityUtils;
+import com.usky.ems.domain.EmsDevice;
+import com.usky.ems.domain.BaseSpace;
+import com.usky.ems.domain.EmsEnergyItemCode;
 import com.usky.ems.domain.EmsProject;
+import com.usky.ems.domain.EmsProjectDeviceSystem;
+import com.usky.ems.mapper.EmsDeviceMapper;
+import com.usky.ems.mapper.EmsEnergyItemCodeMapper;
+import com.usky.ems.mapper.EmsProjectDeviceSystemMapper;
 import com.usky.ems.mapper.EmsProjectMapper;
+import com.usky.ems.service.EmsModelService;
 import com.usky.ems.service.EmsOverviewService;
+import com.usky.ems.service.BaseSpaceService;
+import com.usky.ems.service.vo.EmsEnergyTypeVO;
+import com.usky.ems.service.vo.EmsOverviewDeviceSystemStatVO;
+import com.usky.ems.service.vo.EmsOverviewEnergyItemVO;
 import com.usky.ems.service.vo.EmsProjectResponse;
 import com.usky.ems.service.vo.EmsSummaryResponse;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.temporal.TemporalAdjusters;
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 /**
  * 能源总览服务实现
@@ -16,11 +40,26 @@ import java.util.List;
 @Service
 public class EmsOverviewServiceImpl implements EmsOverviewService {
 
+    @Autowired
+    private EmsModelService emsModelService;
+
+    @Autowired
+    private EmsDeviceMapper emsDeviceMapper;
+
+    @Autowired
+    private BaseSpaceService baseSpaceService;
+
+    @Autowired
+    private EmsEnergyItemCodeMapper emsEnergyItemCodeMapper;
+
     @Autowired
     private EmsProjectMapper emsProjectMapper;
 
+    @Autowired
+    private EmsProjectDeviceSystemMapper emsProjectDeviceSystemMapper;
+
     @Override
-    public EmsProjectResponse getProject(Long projectId) {
+    public EmsProjectResponse getProject(Integer projectId) {
         EmsProject project;
         if (projectId != null) {
             project = emsProjectMapper.selectById(projectId);
@@ -33,13 +72,57 @@ public class EmsOverviewServiceImpl implements EmsOverviewService {
         }
         EmsProjectResponse resp = new EmsProjectResponse();
         resp.setId(project.getId());
+        resp.setSpaceId(project.getSpaceId());
         resp.setName(project.getName());
-        resp.setCode(project.getAbbreviation());
-        resp.setDescription(project.getIntroduction());
+        resp.setAbbreviation(project.getAbbreviation());
         resp.setArea(project.getArea());
-        resp.setResidentPopulation(project.getResidentPopulation());
-        resp.setCreateTime(project.getCreateTime());
-        resp.setUpdateTime(project.getUpdateTime());
+        resp.setCommonArea(project.getCommonArea());
+        resp.setAirConditionedArea(project.getAirConditionedArea());
+        // 源字段为 Integer,这里转为 BigDecimal 以匹配 VO 定义
+        if (project.getResidentPopulation() != null) {
+            resp.setResidentPopulation(java.math.BigDecimal.valueOf(project.getResidentPopulation()));
+        }
+
+        // 设备系统集合
+        List<EmsProjectDeviceSystem> sysList = emsProjectDeviceSystemMapper.selectList(
+                new LambdaQueryWrapper<EmsProjectDeviceSystem>()
+                        .eq(EmsProjectDeviceSystem::getProjectId, project.getId())
+        );
+        if (sysList != null && !sysList.isEmpty()) {
+            resp.setDeviceSystemList(sysList.stream()
+                    .map(EmsProjectDeviceSystem::getDeviceSystem)
+                    .collect(Collectors.toList()));
+        }
+
+        resp.setProvinceCode(project.getProvinceCode());
+        resp.setCityCode(project.getCityCode());
+        resp.setDistrictCode(project.getDistrictCode());
+        resp.setProvinceName(project.getProvinceName());
+        resp.setCityName(project.getCityName());
+        resp.setDistrictName(project.getDistrictName());
+        resp.setLocation(project.getLocation());
+        resp.setAddress(project.getAddress());
+        resp.setTypeId(project.getTypeId());
+        resp.setTypeName(project.getTypeName());
+        resp.setImage(project.getImage());
+        // imageUrl 视实际文件服务而定,这里暂不拼接,直接沿用 image 字段或留空
+        resp.setImageUrl(project.getImage());
+        resp.setIntroduction(project.getIntroduction());
+        resp.setPlatformName(project.getPlatformName());
+        resp.setLogo(project.getLogo());
+        resp.setLogoUrl(project.getLogo());
+        resp.setLogoMin(project.getLogoMin());
+        resp.setLogoMinUrl(project.getLogoMin());
+        resp.setUpdatedBy(project.getUpdatedBy());
+        resp.setCreatedBy(project.getCreatedBy());
+        if (project.getUpdateTime() != null) {
+            resp.setUpdateTime(project.getUpdateTime().toString());
+        }
+        if (project.getCreateTime() != null) {
+            resp.setCreateTime(project.getCreateTime().toString());
+        }
+        // 单位能耗暂无法从现有表直接计算,先置为空,后续可按业务补充
+        resp.setAreaCoal(null);
         return resp;
     }
 
@@ -50,4 +133,540 @@ public class EmsOverviewServiceImpl implements EmsOverviewService {
         resp.setTimeValue(timeValue);
         return resp;
     }
+
+    @Override
+    public List<EmsOverviewEnergyItemVO> queryOverviewItem(Long projectId) {
+        // 当前实现:直接基于能源类型列表(电/水/气)构造概览条目
+        List<EmsEnergyTypeVO> types = emsModelService.getEnergyTypeList();
+        return types.stream().map(t -> {
+            EmsOverviewEnergyItemVO vo = new EmsOverviewEnergyItemVO();
+            vo.setName(t.getName());
+            vo.setItemCode(t.getCode());
+            vo.setEnergyType(t.getId() != null ? t.getId().intValue() : null);
+            return vo;
+        }).collect(Collectors.toList());
+    }
+
+    @Override
+    public List<EmsOverviewDeviceSystemStatVO> queryOverviewDeviceInfo(Long projectId) {
+//        Long pid = resolveProjectId(projectId);
+//        if (pid == null) {
+//            return java.util.Collections.emptyList();
+//        }
+//
+//        // 1. 查询项目已配置的设备系统
+//        List<EmsProjectDeviceSystem> systemList = emsProjectDeviceSystemMapper.selectList(
+//                new LambdaQueryWrapper<EmsProjectDeviceSystem>()
+//                        .eq(EmsProjectDeviceSystem::getProjectId, pid)
+//        );
+//        if (systemList == null || systemList.isEmpty()) {
+//            return java.util.Collections.emptyList();
+//        }
+//        List<Integer> systems = systemList.stream()
+//                .map(EmsProjectDeviceSystem::getDeviceSystem)
+//                .collect(Collectors.toList());
+//
+//        // 2. 查询设备:同项目、启用状态、设备系统在上述集合中
+//        List<EmsDevice> devices = emsDeviceMapper.selectList(
+//                new LambdaQueryWrapper<EmsDevice>()
+//                        .eq(EmsDevice::getProjectId, pid)
+//                        .eq(EmsDevice::getStatus, 1)
+//                        .in(EmsDevice::getDeviceSystem, systems)
+//        );
+//        java.util.Map<Integer, Long> deviceCountMap = devices.stream()
+//                .filter(d -> d.getDeviceSystem() != null)
+//                .collect(Collectors.groupingBy(EmsDevice::getDeviceSystem, Collectors.counting()));
+
+        // 3. 组装统计 VO
+        List<EmsOverviewDeviceSystemStatVO> result = new java.util.ArrayList<>();
+//        for (EmsProjectDeviceSystem s : systemList) {
+//            Integer code = s.getDeviceSystem();
+//            EmsOverviewDeviceSystemStatVO vo = new EmsOverviewDeviceSystemStatVO();
+//            vo.setDeviceSystem(code);
+//            vo.setName(deviceSystemName(code));
+//            vo.setValue(deviceCountMap.getOrDefault(code, 0L).intValue());
+//            result.add(vo);
+//        }
+        return result;
+    }
+
+    /**
+     * 分类能耗统计(模拟数据版本)
+     * 说明:
+     * - 目前不接真实能耗库,仅按固定示例数据做一套完整计算逻辑,保证前端联调正常。
+     * - 返回结构与原系统保持一致:Map<String, Object>。
+     */
+    @Override
+    public Map<String, Object> queryClassificationEnergy(Integer dateType, String itemCode, Integer energyType, Long projectId) {
+        Map<String, Object> result = new HashMap<>();
+
+        // 基础模拟数据:本期/环比/同比能耗(单位:kWh 或折算后的统一单位)
+        BigDecimal consume = new BigDecimal("100.00");
+        BigDecimal sequentialCon = new BigDecimal("80.00");   // 上一期
+        BigDecimal pariPassCon = new BigDecimal("90.00");     // 同期
+
+        // 模拟项目能耗折标系数、单价
+        BigDecimal coal = new BigDecimal("0.70");              // 折标系数(每单位能耗折算标煤)
+        BigDecimal unitPrice = new BigDecimal("1.00");         // 单价(每单位能耗金额)
+
+        // 本期费用 = 本期能耗 * 单价
+        BigDecimal cost = consume.multiply(unitPrice).setScale(2, RoundingMode.UP);
+        BigDecimal sequentialCost = sequentialCon.multiply(unitPrice).setScale(2, RoundingMode.UP);
+        BigDecimal pariPassCost = pariPassCon.multiply(unitPrice).setScale(2, RoundingMode.UP);
+
+        // 标煤量 = 能耗 * 折标系数
+        BigDecimal coalAmount = consume.multiply(coal).setScale(2, RoundingMode.UP);
+        BigDecimal sequentialCoal = sequentialCon.multiply(coal).setScale(2, RoundingMode.UP);
+        BigDecimal pariPassCoal = pariPassCon.multiply(coal).setScale(2, RoundingMode.UP);
+
+        // CO2 排放量 = 标煤量 * 系数 2.4589(固定常量,模拟用)
+        BigDecimal co2Factor = new BigDecimal("2.4589");
+        BigDecimal co2Amount = coalAmount.multiply(co2Factor).setScale(2, RoundingMode.UP);
+        BigDecimal sequentialCo2 = sequentialCoal.multiply(co2Factor).setScale(2, RoundingMode.UP);
+        BigDecimal pariPassCo2 = pariPassCoal.multiply(co2Factor).setScale(2, RoundingMode.UP);
+
+        result.put("consume", consume);
+        result.put("sequentialCon", sequentialCon);
+        result.put("pariPassCon", pariPassCon);
+        result.put("cost", cost);
+        result.put("sequentialCost", sequentialCost);
+        result.put("pariPassCost", pariPassCost);
+        result.put("coalAmount", coalAmount);
+        result.put("sequentialCoal", sequentialCoal);
+        result.put("pariPassCoal", pariPassCoal);
+        result.put("co2Amount", co2Amount);
+        result.put("sequentialCo2", sequentialCo2);
+        result.put("pariPassCo2", pariPassCo2);
+
+        return result;
+    }
+
+    /**
+     * 能耗用能趋势(模拟数据版本)
+     * 参考原系统 queryEnergyTrend 逻辑,仅保留时间维度与同比的结构,数据采用固定规则模拟。
+     */
+    @Override
+    public List<Map<String, Object>> queryEnergyTrend(Integer dateType, String itemCode, Long spaceId) {
+        List<Map<String, Object>> list = new ArrayList<>();
+
+        if (dateType == null || itemCode == null || itemCode.trim().isEmpty()) {
+            return list;
+        }
+
+        // 当前期与去年同期的时间范围
+        LocalDateTime endThisTime;
+        LocalDateTime startThisTime;
+        LocalDateTime endOldTime;
+        LocalDateTime startOldTime;
+
+        if (dateType == 1) {
+            // 日:按小时
+            endThisTime = LocalDateTime.of(LocalDate.now(), LocalTime.MAX);
+        } else if (dateType == 2) {
+            // 月:按天
+            endThisTime = LocalDateTime.of(LocalDate.now().with(TemporalAdjusters.lastDayOfMonth()), LocalTime.MAX);
+        } else if (dateType == 3) {
+            // 年:按月
+            endThisTime = LocalDateTime.of(LocalDate.now().with(TemporalAdjusters.lastDayOfYear()), LocalTime.MAX);
+        } else {
+            return list;
+        }
+
+        startThisTime = getStartTime(dateType, endThisTime);
+        endOldTime = endThisTime.minusYears(1);
+        startOldTime = getStartTime(dateType, endOldTime);
+
+        // 按时间步长循环生成当前期与去年同期的模拟趋势数据
+        LocalDateTime curTime = startThisTime;
+        LocalDateTime oldTime = startOldTime;
+        int index = 0;
+
+        while (!curTime.isAfter(endThisTime)) {
+            Map<String, Object> row = new HashMap<>();
+            // 时间标签:当前期时间
+            row.put("time", curTime.toString());
+            // 当前期用能:示例 100 + index * 5
+            BigDecimal currentValue = BigDecimal.valueOf(100 + index * 5L);
+            // 去年同期用能:示例 80 + index * 5
+            BigDecimal lastYearValue = BigDecimal.valueOf(80 + index * 5L);
+            row.put("currentValue", currentValue);
+            row.put("lastYearValue", lastYearValue);
+            // 去年同期时间标签
+            row.put("lastYearTime", oldTime.toString());
+
+            list.add(row);
+
+            // 根据时间类型推进时间
+            if (dateType == 1) {
+                curTime = curTime.plusHours(1);
+                oldTime = oldTime.plusHours(1);
+            } else if (dateType == 2) {
+                curTime = curTime.plusDays(1);
+                oldTime = oldTime.plusDays(1);
+            } else {
+                curTime = curTime.plusMonths(1);
+                oldTime = oldTime.plusMonths(1);
+            }
+
+            index++;
+        }
+
+        return list;
+    }
+
+    /**
+     * 计算指定时间类型的开始时间
+     * dateType: 1-日(当天 00:00:00)、2-月(当月第一天 00:00:00)、3-年(当年第一天 00:00:00)
+     */
+    private LocalDateTime getStartTime(Integer dateType, LocalDateTime endTime) {
+        if (dateType == null || endTime == null) {
+            return null;
+        }
+        if (dateType == 1) {
+            return LocalDateTime.of(endTime.toLocalDate(), LocalTime.MIN);
+        } else if (dateType == 2) {
+            LocalDate firstDayOfMonth = endTime.toLocalDate().withDayOfMonth(1);
+            return LocalDateTime.of(firstDayOfMonth, LocalTime.MIN);
+        } else if (dateType == 3) {
+            LocalDate firstDayOfYear = endTime.toLocalDate().with(TemporalAdjusters.firstDayOfYear());
+            return LocalDateTime.of(firstDayOfYear, LocalTime.MIN);
+        }
+        return LocalDateTime.of(endTime.toLocalDate(), LocalTime.MIN);
+    }
+
+    /**
+     * 建筑能耗分析(模拟数据版本)
+     * 参考原系统 queryBuildingRanking:按指定时间维度与能耗条目,对建筑/楼层进行能耗排名。
+     * 当前实现:空间信息从 base_space 真实查询(建筑 type=3;指定 spaceId 时取子节点),仅能耗值使用规则模拟。
+     */
+    @Override
+    public List<Map<String, Object>> queryBuildingRanking(Integer dateType, String itemCode, Long spaceId) {
+        List<Map<String, Object>> resultList = new ArrayList<>();
+
+        if (dateType == null || itemCode == null || itemCode.trim().isEmpty()) {
+            return resultList;
+        }
+
+        BaseSpace project = baseSpaceService.getOne(
+                new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<BaseSpace>()
+                        .eq(BaseSpace::getRootId, 0)
+                        .eq(BaseSpace::getTenantId, SecurityUtils.getTenantId()));
+        if (project == null || project.getId() == null) {
+            return resultList;
+        }
+        Long rootSpaceId = project.getId();
+
+        // 2. 查询空间列表:
+        // - 未指定 spaceId:取根空间下 type=3 的建筑(base_space)
+        // - 指定 spaceId:取该空间下直接子节点(新表无独立楼层类型时,用子空间展示排名)
+        List<BaseSpace> spaceList;
+        if (spaceId == null) {
+            spaceList = baseSpaceService.list(
+                    new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<BaseSpace>()
+                            .eq(BaseSpace::getRootId, rootSpaceId)
+                            .eq(BaseSpace::getType, 3)
+            );
+        } else {
+            spaceList = baseSpaceService.list(
+                    new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<BaseSpace>()
+                            .eq(BaseSpace::getParentId, spaceId)
+            );
+        }
+        if (spaceList == null || spaceList.isEmpty()) {
+            return resultList;
+        }
+
+        // 3. 为每个空间模拟能耗值(真实系统中此处应从 TSDB 或能耗统计表获取)
+        BigDecimal base = new BigDecimal("1000");
+        BigDecimal step = new BigDecimal("150");
+        BigDecimal factor;
+        if (dateType == 1) {
+            factor = BigDecimal.ONE;
+        } else if (dateType == 2) {
+            factor = new BigDecimal("1.5");
+        } else if (dateType == 3) {
+            factor = new BigDecimal("2.0");
+        } else {
+            factor = BigDecimal.ONE;
+        }
+
+        for (int i = 0; i < spaceList.size(); i++) {
+            BaseSpace space = spaceList.get(i);
+            if (space == null) {
+                continue;
+            }
+            Map<String, Object> map = new HashMap<>();
+            map.put("name", space.getName());
+            BigDecimal value = base.subtract(step.multiply(BigDecimal.valueOf(i))).multiply(factor);
+            map.put("value", value);
+            resultList.add(map);
+        }
+
+        // 4. 按 value 从大到小排序,形成能耗排名
+        resultList.sort((m1, m2) -> {
+            BigDecimal v1 = (BigDecimal) m1.get("value");
+            BigDecimal v2 = (BigDecimal) m2.get("value");
+            if (v1 == null && v2 == null) return 0;
+            if (v1 == null) return 1;
+            if (v2 == null) return -1;
+            return v2.compareTo(v1);
+        });
+
+        return resultList;
+    }
+
+    /**
+     * 单位综合能耗、综合累计能耗、分类能耗占比(总览顶部)
+     * - 能源类型来自 EmsModelService.getEnergyTypeList(电/水/气)
+     * - 折算系数来自 leo.ems_project_conversion_factor(优先取项目专属,其次 project_id=0 的公共配置)
+     * - 真实 TSDB 能耗数据暂不可用,当前按规则模拟各能源类型的能耗值。
+     */
+    @Override
+    public Map<String, Object> queryOverviewTop(Integer dateType, Long projectId) {
+        Map<String, Object> resultMap = new HashMap<>();
+        if (dateType == null) {
+            return resultMap;
+        }
+
+        // 时间范围,目前仅用于保持与原接口结构一致
+        LocalDateTime endTime = LocalDateTime.now();
+        LocalDateTime startTime = getStartTime(dateType, endTime);
+        if (startTime == null) {
+            return resultMap;
+        }
+
+        // 项目信息(含面积、根空间 ID)
+        EmsProjectResponse project = getProject(SecurityUtils.getTenantId());
+        if (project == null || project.getSpaceId() == null) {
+            return resultMap;
+        }
+
+        // 1. 能源类型列表(电/水/气)
+        List<EmsEnergyTypeVO> energyTypes = emsModelService.getEnergyTypeList();
+        if (energyTypes == null || energyTypes.isEmpty()) {
+            return resultMap;
+        }
+
+        List<Integer> typeIds = energyTypes.stream()
+                .filter(t -> t.getId() != null)
+                .map(t -> t.getId().intValue())
+                .collect(Collectors.toList());
+        if (typeIds.isEmpty()) {
+            return resultMap;
+        }
+
+        // 2. 查询折算系数(项目专属 + 公共)
+//        List<EmsProjectConversionFactor> factorList = emsProjectConversionFactorMapper.selectList(
+//                new LambdaQueryWrapper<EmsProjectConversionFactor>()
+//                        .in(EmsProjectConversionFactor::getEnergyType, typeIds)
+//                        .in(EmsProjectConversionFactor::getProjectId, pid, 0L)
+//        );
+//        if (factorList == null || factorList.isEmpty()) {
+//            return resultMap;
+//        }
+//
+//        // 优先使用项目专属配置;若没有则退回公共配置
+//        Map<Integer, List<EmsProjectConversionFactor>> groupedByType = factorList.stream()
+//                .collect(Collectors.groupingBy(EmsProjectConversionFactor::getEnergyType));
+
+        // 3. 模拟各能源类型能耗值(真实实现应从 TSDB 或历史统计表获取)
+        Map<Integer, BigDecimal> consumeMap = new HashMap<>();
+        BigDecimal base = new BigDecimal("1000");
+        BigDecimal step = new BigDecimal("200");
+        BigDecimal factor;
+        if (dateType == 1) {
+            factor = BigDecimal.ONE;
+        } else if (dateType == 2) {
+            factor = new BigDecimal("1.5");
+        } else if (dateType == 3) {
+            factor = new BigDecimal("2.0");
+        } else {
+            factor = BigDecimal.ONE;
+        }
+
+        int index = 0;
+        for (Integer typeId : typeIds) {
+            BigDecimal value = base.subtract(step.multiply(BigDecimal.valueOf(index))).multiply(factor);
+            if (value.compareTo(BigDecimal.ZERO) < 0) {
+                value = BigDecimal.ZERO;
+            }
+            consumeMap.put(typeId, value);
+            index++;
+        }
+
+        BigDecimal totalCon = consumeMap.values().stream()
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+
+        // 4. 计算各能源类型折算标煤与碳排放、占比
+        BigDecimal coalTotal = BigDecimal.ZERO;
+        BigDecimal co2Total = BigDecimal.ZERO;
+        List<Map<String, Object>> ratioList = new ArrayList<>();
+
+//        for (EmsEnergyTypeVO typeVO : energyTypes) {
+//            if (typeVO.getId() == null) {
+//                continue;
+//            }
+//            Integer typeId = typeVO.getId().intValue();
+//            BigDecimal con = consumeMap.get(typeId);
+//            if (con == null) {
+//                continue;
+//            }
+//
+//            List<EmsProjectConversionFactor> conversionFactorList = groupedByType.get(typeId);
+//            if (conversionFactorList == null || conversionFactorList.isEmpty()) {
+//                continue;
+//            }
+//
+//            // 先筛出项目专属配置
+//            List<EmsProjectConversionFactor> projectFactors = conversionFactorList.stream()
+//                    .filter(f -> pid.equals(f.getProjectId()))
+//                    .collect(Collectors.toList());
+//            List<EmsProjectConversionFactor> effectiveFactors = projectFactors.isEmpty()
+//                    ? conversionFactorList
+//                    : projectFactors;
+//
+//            Map<String, BigDecimal> factorMap = effectiveFactors.stream()
+//                    .filter(f -> f.getName() != null && f.getValue() != null)
+//                    .collect(Collectors.toMap(EmsProjectConversionFactor::getName, EmsProjectConversionFactor::getValue,
+//                            (oldVal, newVal) -> oldVal));
+//
+//            BigDecimal coal = factorMap.get("coal");
+//            if (coal == null) {
+//                continue;
+//            }
+//
+//            BigDecimal coalAmount = coal.multiply(con).setScale(2, RoundingMode.UP);
+//            BigDecimal co2Amount = coalAmount.multiply(new BigDecimal("2.4589")).setScale(2, RoundingMode.UP);
+//            coalTotal = coalTotal.add(coalAmount);
+//            co2Total = co2Total.add(co2Amount);
+//
+//            Map<String, Object> ratioMap = new HashMap<>();
+//            ratioMap.put("name", typeVO.getName());
+//            ratioMap.put("consume", coalAmount);
+//            ratioMap.put("totalConsume", totalCon.multiply(coal).setScale(2, RoundingMode.UP));
+//            ratioList.add(ratioMap);
+//        }
+
+        // 5. 单位综合能耗(每平米标煤量)
+        BigDecimal unitCoal = BigDecimal.ZERO;
+        if (project.getArea() != null
+                && project.getArea().compareTo(BigDecimal.ZERO) != 0
+                && coalTotal.compareTo(BigDecimal.ZERO) != 0) {
+            unitCoal = coalTotal.divide(project.getArea(), 2, RoundingMode.UP);
+        }
+
+        resultMap.put("unitCoal", unitCoal);
+        resultMap.put("ratioList", ratioList);
+        resultMap.put("coalTotal", coalTotal);
+        resultMap.put("co2Total", co2Total);
+        return resultMap;
+    }
+
+    /**
+     * 分项能耗占比
+     * - 分项定义来自 leo.ems_energy_item_code:给定 itemCode 作为父项,按 parent_code 查询下级分项;
+     * - 若无下级分项,则仅返回父项自身;
+     * - 各分项及汇总能耗数值暂按规则模拟。
+     */
+    @Override
+    public List<Map<String, Object>> queryItemRatio(Integer dateType, String itemCode, Long projectId) {
+        List<Map<String, Object>> resultList = new ArrayList<>();
+        if (dateType == null || itemCode == null || itemCode.trim().isEmpty()) {
+            return resultList;
+        }
+
+        // 当前实现中 startTime/spaceId 仅为预留,不参与模拟计算
+        LocalDateTime endTime = LocalDateTime.now();
+        LocalDateTime startTime = getStartTime(dateType, endTime);
+        if (startTime == null) {
+            return resultList;
+        }
+
+        // 查询父分项编码
+        EmsEnergyItemCode parent = emsEnergyItemCodeMapper.selectOne(
+                new LambdaQueryWrapper<EmsEnergyItemCode>()
+                        .eq(EmsEnergyItemCode::getCode, itemCode)
+        );
+        if (parent == null) {
+            return resultList;
+        }
+
+        // 模拟父分项总能耗
+        BigDecimal total = simulateItemConsume(itemCode, dateType, 0);
+
+        // 查询子分项列表(按 parent_code)
+        List<EmsEnergyItemCode> children = emsEnergyItemCodeMapper.selectList(
+                new LambdaQueryWrapper<EmsEnergyItemCode>()
+                        .eq(EmsEnergyItemCode::getParentCode, parent.getCode())
+        );
+
+        if (children == null || children.isEmpty()) {
+            // 无下级分项:仅返回父项自身
+            Map<String, Object> map = new HashMap<>();
+            map.put("name", parent.getName());
+            map.put("consume", total);
+            map.put("total", total);
+            resultList.add(map);
+            return resultList;
+        }
+
+        int index = 0;
+        for (EmsEnergyItemCode child : children) {
+            if (child == null) continue;
+            BigDecimal consume = simulateItemConsume(child.getCode(), dateType, index);
+            Map<String, Object> map = new HashMap<>();
+            map.put("name", child.getName());
+            map.put("consume", consume);
+            map.put("total", total);
+            resultList.add(map);
+            index++;
+        }
+
+        return resultList;
+    }
+
+    /**
+     * 分项能耗模拟函数
+     * 根据 dateType 与 index 返回不同量级的示例值。
+     */
+    private BigDecimal simulateItemConsume(String code, Integer dateType, int index) {
+        BigDecimal base = new BigDecimal("500");
+        BigDecimal step = new BigDecimal("80");
+        BigDecimal factor;
+        if (dateType != null && dateType == 1) {
+            factor = BigDecimal.ONE;
+        } else if (dateType != null && dateType == 2) {
+            factor = new BigDecimal("1.5");
+        } else if (dateType != null && dateType == 3) {
+            factor = new BigDecimal("2.0");
+        } else {
+            factor = BigDecimal.ONE;
+        }
+        BigDecimal value = base.subtract(step.multiply(BigDecimal.valueOf(index))).multiply(factor);
+        if (value.compareTo(BigDecimal.ZERO) < 0) {
+            value = BigDecimal.ZERO;
+        }
+        return value.setScale(2, RoundingMode.UP);
+    }
+
+    /** 根据设备系统编码返回名称(与 dmp_product.deviceType 含义保持一致) */
+    private String deviceSystemName(Integer deviceSystem) {
+        if (deviceSystem == null) return "";
+        switch (deviceSystem) {
+            case 501:
+                return "监控系统";
+            case 502:
+                return "门禁系统";
+            case 503:
+                return "梯控系统";
+            case 504:
+                return "机房系统";
+            case 509:
+                return "环境系统";
+            case 510:
+                return "照明系统";
+            default:
+                return "";
+        }
+    }
 }

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

@@ -0,0 +1,243 @@
+package com.usky.ems.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.usky.common.core.exception.BusinessException;
+import com.usky.common.security.utils.SecurityUtils;
+import com.usky.ems.domain.BaseSpace;
+import com.usky.ems.domain.EmsProject;
+import com.usky.ems.domain.EmsProjectDeviceSystem;
+import com.usky.ems.mapper.EmsProjectDeviceSystemMapper;
+import com.usky.ems.mapper.EmsProjectMapper;
+import com.usky.ems.service.BaseSpaceService;
+import com.usky.ems.service.EmsProjectService;
+import com.usky.ems.service.EmsSystemDictRegionService;
+import com.usky.ems.service.vo.EmsProjectResponse;
+import com.usky.ems.service.vo.EmsProjectSaveRequest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * 项目维护实现(逻辑参考原 ProjectServiceImpl:省市区校验、创建项目空间、设备系统关联、删除前校验叶子节点等)
+ */
+@Service
+public class EmsProjectServiceImpl implements EmsProjectService {
+
+    private static final int SPACE_TYPE_PROJECT = 1;
+
+    @Autowired
+    private EmsProjectMapper emsProjectMapper;
+    @Autowired
+    private EmsProjectDeviceSystemMapper emsProjectDeviceSystemMapper;
+    @Autowired
+    private BaseSpaceService baseSpaceService;
+    @Autowired
+    private EmsSystemDictRegionService emsSystemDictRegionService;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public EmsProjectResponse save(EmsProjectSaveRequest request) {
+        validateSaveRequest(request, false);
+        Integer currentTenantId = SecurityUtils.getTenantId();
+        Map<String, String> regionMap = emsSystemDictRegionService.checkAndBuildMap(
+                request.getProvinceCode(), request.getCityCode(), request.getDistrictCode());
+        Long spaceId = createProjectSpace(request.getName());
+        EmsProject po = new EmsProject();
+        copyRequestToEntity(request, po, regionMap);
+        po.setSpaceId(spaceId);
+        po.setTenantId(currentTenantId);
+        fillAuditOnCreate(po);
+        emsProjectMapper.insert(po);
+        replaceDeviceSystems(po.getId(), request.getDeviceSystemList());
+        return toResponse(po, request.getDeviceSystemList());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public EmsProjectResponse update(EmsProjectSaveRequest request) {
+        if (request.getSpaceId() == null) {
+            throw new BusinessException("修改项目时 空间id 不能为空");
+        }
+        EmsProject old = emsProjectMapper.selectOne(
+                new LambdaQueryWrapper<EmsProject>().eq(EmsProject::getSpaceId, request.getSpaceId()));
+        if (old == null) {
+            throw new BusinessException("错误的空间id值");
+        }
+        Map<String, String> regionMap = emsSystemDictRegionService.checkAndBuildMap(
+                request.getProvinceCode(), request.getCityCode(), request.getDistrictCode());
+        EmsProject po = new EmsProject();
+        copyRequestToEntity(request, po, regionMap);
+        po.setSpaceId(old.getSpaceId());
+        po.setTenantId(old.getTenantId());
+        fillAuditOnUpdate(po);
+        emsProjectMapper.updateById(po);
+        baseSpaceService.updateProjectSpaceName(old.getSpaceId(), po.getName());
+        replaceDeviceSystems(po.getId(), request.getDeviceSystemList());
+        return toResponse(po, request.getDeviceSystemList());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void remove(Long spaceId) {
+        EmsProject project = emsProjectMapper.selectOne(
+                new LambdaQueryWrapper<EmsProject>().eq(EmsProject::getSpaceId, spaceId));
+        if (project == null) {
+            throw new BusinessException("错误的空间id值");
+        }
+        if (!baseSpaceService.isLeafSpace(project.getSpaceId())) {
+            throw new BusinessException("当前项目下有空间节点不允许删除");
+        }
+        if (hasLoggedUserForProject(project.getId())) {
+            throw new BusinessException("当前项目有关联的用户使用;不允许删除");
+        }
+        emsProjectDeviceSystemMapper.delete(
+                new LambdaQueryWrapper<EmsProjectDeviceSystem>().eq(EmsProjectDeviceSystem::getProjectId, project.getId()));
+        emsProjectMapper.deleteById(project.getId());
+        baseSpaceService.removeById(spaceId);
+    }
+
+    /**
+     * 对应原 UserService.hasLoggedUser(projectId)。接入统一用户/权限模块后可在此查询用户与项目绑定关系。
+     */
+    private boolean hasLoggedUserForProject(Long projectId) {
+        return false;
+    }
+
+    private void validateSaveRequest(EmsProjectSaveRequest request, boolean update) {
+        if (!StringUtils.hasText(request.getName())) {
+            throw new BusinessException("项目名称不能为空");
+        }
+        if (request.getTypeId() == null || !StringUtils.hasText(request.getTypeName())) {
+            throw new BusinessException("项目类型不能为空");
+        }
+    }
+
+    private Long createProjectSpace(String projectName) {
+        LocalDateTime now = LocalDateTime.now();
+        String audit = SecurityUtils.getUsername();
+        BaseSpace space = new BaseSpace();
+        space.setName(projectName);
+        space.setDeep(1);
+        space.setRootId(0L);
+        space.setParentId(0L);
+        space.setType(SPACE_TYPE_PROJECT);
+        space.setPath("");
+        space.setTenantId(SecurityUtils.getTenantId());
+        space.setCreatedBy(audit);
+        space.setUpdatedBy(audit);
+        space.setCreateTime(now);
+        space.setUpdateTime(now);
+        baseSpaceService.save(space);
+        return space.getId();
+    }
+
+    private void copyRequestToEntity(EmsProjectSaveRequest request, EmsProject po, Map<String, String> regionMap) {
+        po.setId(request.getId());
+        po.setName(request.getName());
+        po.setPlatformName(request.getPlatformName());
+        po.setAbbreviation(request.getAbbreviation());
+        po.setArea(request.getArea());
+        po.setCommonArea(request.getCommonArea());
+        po.setAirConditionedArea(request.getAirConditionedArea());
+        po.setResidentPopulation(request.getResidentPopulation());
+        po.setProvinceCode(request.getProvinceCode());
+        po.setCityCode(request.getCityCode());
+        po.setDistrictCode(request.getDistrictCode());
+        po.setProvinceName(regionMap.get(request.getProvinceCode()));
+        po.setCityName(regionMap.get(request.getCityCode()));
+        po.setDistrictName(regionMap.get(request.getDistrictCode()));
+        po.setLocation(request.getLocation());
+        po.setAddress(request.getAddress());
+        po.setTypeId(request.getTypeId());
+        po.setTypeName(request.getTypeName());
+        po.setImage(request.getImage());
+        po.setIntroduction(request.getIntroduction());
+        po.setLogo(request.getLogo());
+        po.setLogoMin(request.getLogoMin());
+    }
+
+    private void fillAuditOnCreate(EmsProject po) {
+        LocalDateTime now = LocalDateTime.now();
+        String uname = SecurityUtils.getUsername();
+        po.setCreatedBy(uname);
+        po.setUpdatedBy(uname);
+        po.setCreateTime(now);
+        po.setUpdateTime(now);
+    }
+
+    private void fillAuditOnUpdate(EmsProject po) {
+        LocalDateTime now = LocalDateTime.now();
+        String uname = SecurityUtils.getUsername();
+        po.setUpdatedBy(uname);
+        po.setUpdateTime(now);
+    }
+
+    private void replaceDeviceSystems(Long projectId, List<Integer> deviceSystemList) {
+        emsProjectDeviceSystemMapper.delete(
+                new LambdaQueryWrapper<EmsProjectDeviceSystem>().eq(EmsProjectDeviceSystem::getProjectId, projectId));
+        if (CollectionUtils.isEmpty(deviceSystemList)) {
+            return;
+        }
+        for (Integer code : deviceSystemList) {
+            if (code == null) {
+                continue;
+            }
+            EmsProjectDeviceSystem row = new EmsProjectDeviceSystem();
+            row.setProjectId(projectId);
+            row.setDeviceSystem(code);
+            LocalDateTime now = LocalDateTime.now();
+            String uname = SecurityUtils.getUsername();
+            row.setUpdatedBy(uname);
+            row.setUpdateTime(now);
+            row.setCreatedBy(uname);
+            row.setCreateTime(now);
+            emsProjectDeviceSystemMapper.insert(row);
+        }
+    }
+
+    private EmsProjectResponse toResponse(EmsProject po, List<Integer> deviceSystemList) {
+        EmsProjectResponse resp = new EmsProjectResponse();
+        resp.setId(po.getId());
+        resp.setSpaceId(po.getSpaceId());
+        resp.setName(po.getName());
+        resp.setAbbreviation(po.getAbbreviation());
+        resp.setArea(po.getArea());
+        resp.setCommonArea(po.getCommonArea());
+        resp.setAirConditionedArea(po.getAirConditionedArea());
+        if (po.getResidentPopulation() != null) {
+            resp.setResidentPopulation(java.math.BigDecimal.valueOf(po.getResidentPopulation()));
+        }
+        resp.setDeviceSystemList(deviceSystemList);
+        resp.setProvinceCode(po.getProvinceCode());
+        resp.setCityCode(po.getCityCode());
+        resp.setDistrictCode(po.getDistrictCode());
+        resp.setProvinceName(po.getProvinceName());
+        resp.setCityName(po.getCityName());
+        resp.setDistrictName(po.getDistrictName());
+        resp.setLocation(po.getLocation());
+        resp.setAddress(po.getAddress());
+        resp.setTypeId(po.getTypeId());
+        resp.setTypeName(po.getTypeName());
+        resp.setImage(po.getImage());
+        resp.setIntroduction(po.getIntroduction());
+        resp.setPlatformName(po.getPlatformName());
+        resp.setLogo(po.getLogo());
+        resp.setLogoMin(po.getLogoMin());
+        resp.setUpdatedBy(po.getUpdatedBy());
+        resp.setCreatedBy(po.getCreatedBy());
+        if (po.getUpdateTime() != null) {
+            resp.setUpdateTime(po.getUpdateTime().toString());
+        }
+        if (po.getCreateTime() != null) {
+            resp.setCreateTime(po.getCreateTime().toString());
+        }
+        return resp;
+    }
+}

+ 19 - 30
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsReportServiceImpl.java

@@ -1,12 +1,13 @@
 package com.usky.ems.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.usky.common.security.utils.SecurityUtils;
+import com.usky.ems.domain.DmpDevice;
 import com.usky.ems.domain.EmsDevice;
 import com.usky.ems.domain.EmsDeviceFunction;
-import com.usky.ems.domain.EmsProject;
+import com.usky.ems.mapper.DmpDeviceMapper;
 import com.usky.ems.mapper.EmsDeviceFunctionMapper;
 import com.usky.ems.mapper.EmsDeviceMapper;
-import com.usky.ems.mapper.EmsProjectMapper;
 import com.usky.ems.service.EmsReportService;
 import com.usky.ems.service.vo.*;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -16,7 +17,6 @@ import org.springframework.util.StringUtils;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.math.BigDecimal;
 import java.nio.charset.StandardCharsets;
 import java.util.*;
 import java.util.stream.Collectors;
@@ -30,18 +30,10 @@ public class EmsReportServiceImpl implements EmsReportService {
     private static final String[] ENERGY_TYPE_NAMES = {"", "电", "水", "气"};
 
     @Autowired
-    private EmsProjectMapper emsProjectMapper;
-    @Autowired
-    private EmsDeviceMapper emsDeviceMapper;
+    private DmpDeviceMapper dmpDeviceMapper;
     @Autowired
     private EmsDeviceFunctionMapper emsDeviceFunctionMapper;
 
-    private Long resolveProjectId(Long projectId) {
-        if (projectId != null) return projectId;
-        List<EmsProject> list = emsProjectMapper.selectList(null);
-        return list.isEmpty() ? null : list.get(0).getId();
-    }
-
     private String energyTypeName(Long energyTypeId) {
         if (energyTypeId == null || energyTypeId < 1 || energyTypeId > 3) return "";
         return ENERGY_TYPE_NAMES[energyTypeId.intValue()];
@@ -49,13 +41,11 @@ public class EmsReportServiceImpl implements EmsReportService {
 
     @Override
     public EmsReportDevicesResponse getEnergyDevices(Long energyTypeId, String keyword, Long projectId) {
-        Long pid = resolveProjectId(projectId);
-        if (pid == null) return new EmsReportDevicesResponse();
-        LambdaQueryWrapper<EmsDevice> q = new LambdaQueryWrapper<EmsDevice>().eq(EmsDevice::getProjectId, pid);
+        LambdaQueryWrapper<DmpDevice> q = new LambdaQueryWrapper<DmpDevice>().eq(DmpDevice::getDeleteFlag,0).eq(DmpDevice::getTenantId, SecurityUtils.getTenantId());
         if (StringUtils.hasText(keyword)) {
-            q.and(w -> w.like(EmsDevice::getName, keyword).or().like(EmsDevice::getNumber, keyword));
+            q.and(w -> w.like(DmpDevice::getDeviceName, keyword));
         }
-        List<EmsDevice> devices = emsDeviceMapper.selectList(q);
+        List<DmpDevice> devices = dmpDeviceMapper.selectList(q);
         return buildReportDevicesResponse(devices, energyTypeId, null);
     }
 
@@ -76,14 +66,13 @@ public class EmsReportServiceImpl implements EmsReportService {
 
     @Override
     public EmsReportDevicesResponse getRegionDevices(Long energyTypeId, Long regionId, String keyword, Long projectId) {
-        Long pid = resolveProjectId(projectId);
-        if (pid == null) return new EmsReportDevicesResponse();
-        LambdaQueryWrapper<EmsDevice> q = new LambdaQueryWrapper<EmsDevice>().eq(EmsDevice::getProjectId, pid);
-        if (regionId != null) q.eq(EmsDevice::getInstallationLocation, regionId);
-        if (StringUtils.hasText(keyword)) {
-            q.and(w -> w.like(EmsDevice::getName, keyword).or().like(EmsDevice::getNumber, keyword));
-        }
-        List<EmsDevice> devices = emsDeviceMapper.selectList(q);
+//        LambdaQueryWrapper<DmpDevice> q = new LambdaQueryWrapper<DmpDevice>().eq(DmpDevice::getDeleteFlag,0).eq(DmpDevice::getTenantId, SecurityUtils.getTenantId());
+//        if (regionId != null) q.eq(EmsDevice::getInstallationLocation, regionId);
+//        if (StringUtils.hasText(keyword)) {
+//            q.and(w -> w.like(EmsDevice::getName, keyword).or().like(EmsDevice::getNumber, keyword));
+//        }
+//        List<DmpDevice> devices = dmpDeviceMapper.selectList(q);
+        List<DmpDevice> devices = new ArrayList<>();
         return buildReportDevicesResponse(devices, energyTypeId, regionId);
     }
 
@@ -118,14 +107,14 @@ public class EmsReportServiceImpl implements EmsReportService {
         doExport(response, format, "collection_report");
     }
 
-    private EmsReportDevicesResponse buildReportDevicesResponse(List<EmsDevice> devices, Long energyTypeId, Long regionId) {
+    private EmsReportDevicesResponse buildReportDevicesResponse(List<DmpDevice> devices, Long energyTypeId, Long regionId) {
         EmsReportDevicesResponse resp = new EmsReportDevicesResponse();
         String typeName = energyTypeName(energyTypeId);
-        for (EmsDevice d : devices) {
+        for (DmpDevice d : devices) {
             EmsReportDeviceItemVO item = new EmsReportDeviceItemVO();
-            item.setId(d.getId());
-            item.setName(d.getName());
-            item.setCode(d.getNumber());
+            item.setId(String.valueOf(d.getId()));
+            item.setName(d.getDeviceName());
+            item.setCode(d.getDeviceId());
             item.setEnergyTypeName(typeName);
             List<EmsDeviceFunction> funcs = emsDeviceFunctionMapper.selectList(
                     new LambdaQueryWrapper<EmsDeviceFunction>().eq(EmsDeviceFunction::getDeviceId, d.getId()));

+ 15 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsSystemDictCodeServiceImpl.java

@@ -0,0 +1,15 @@
+package com.usky.ems.service.impl;
+
+import com.usky.common.mybatis.core.AbstractCrudService;
+import com.usky.ems.domain.EmsSystemDictCode;
+import com.usky.ems.mapper.EmsSystemDictCodeMapper;
+import com.usky.ems.service.EmsSystemDictCodeService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 系统字典编码服务
+ */
+@Service
+public class EmsSystemDictCodeServiceImpl extends AbstractCrudService<EmsSystemDictCodeMapper, EmsSystemDictCode>
+        implements EmsSystemDictCodeService {
+}

+ 61 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsSystemDictRegionServiceImpl.java

@@ -0,0 +1,61 @@
+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.EmsSystemDictRegion;
+import com.usky.ems.mapper.EmsSystemDictRegionMapper;
+import com.usky.ems.service.EmsSystemDictRegionService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * 省市区字典服务
+ */
+@Service
+public class EmsSystemDictRegionServiceImpl implements EmsSystemDictRegionService {
+
+    @Autowired
+    private EmsSystemDictRegionMapper emsSystemDictRegionMapper;
+
+    @Override
+    public Map<String, String> checkAndBuildMap(String provinceCode, String cityCode, String districtCode) {
+        if (!StringUtils.hasText(provinceCode) || !StringUtils.hasText(cityCode) || !StringUtils.hasText(districtCode)) {
+            throw new BusinessException("省市区编码不能为空");
+        }
+        EmsSystemDictRegion province = emsSystemDictRegionMapper.selectOne(
+                new LambdaQueryWrapper<EmsSystemDictRegion>().eq(EmsSystemDictRegion::getCode, provinceCode).last("LIMIT 1"));
+        EmsSystemDictRegion city = emsSystemDictRegionMapper.selectOne(
+                new LambdaQueryWrapper<EmsSystemDictRegion>().eq(EmsSystemDictRegion::getCode, cityCode).last("LIMIT 1"));
+        EmsSystemDictRegion district = emsSystemDictRegionMapper.selectOne(
+                new LambdaQueryWrapper<EmsSystemDictRegion>().eq(EmsSystemDictRegion::getCode, districtCode).last("LIMIT 1"));
+        if (province == null || city == null || district == null) {
+            throw new BusinessException("省市区编码无效");
+        }
+        if (!Objects.equals(city.getParent(), province.getCode())) {
+            throw new BusinessException("市级编码与省级不匹配");
+        }
+        if (!Objects.equals(district.getParent(), city.getCode())) {
+            throw new BusinessException("区级编码与市级不匹配");
+        }
+        Map<String, String> map = new HashMap<>(4);
+        map.put(provinceCode, province.getName());
+        map.put(cityCode, city.getName());
+        map.put(districtCode, district.getName());
+        return map;
+    }
+
+    @Override
+    public List<EmsSystemDictRegion> listByParentCode(String parentCode) {
+        if (!StringUtils.hasText(parentCode)) {
+            throw new BusinessException("父级编码不能为空");
+        }
+        return emsSystemDictRegionMapper.selectList(
+                new LambdaQueryWrapper<EmsSystemDictRegion>().eq(EmsSystemDictRegion::getParent, parentCode));
+    }
+}

+ 15 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsSystemDictValueServiceImpl.java

@@ -0,0 +1,15 @@
+package com.usky.ems.service.impl;
+
+import com.usky.common.mybatis.core.AbstractCrudService;
+import com.usky.ems.domain.EmsSystemDictValue;
+import com.usky.ems.mapper.EmsSystemDictValueMapper;
+import com.usky.ems.service.EmsSystemDictValueService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 系统字典值服务
+ */
+@Service
+public class EmsSystemDictValueServiceImpl extends AbstractCrudService<EmsSystemDictValueMapper, EmsSystemDictValue>
+        implements EmsSystemDictValueService {
+}

+ 43 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/BaseSpaceForestNodeVO.java

@@ -0,0 +1,43 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 空间树节点(对应 base_space)
+ */
+@Data
+public class BaseSpaceForestNodeVO {
+
+    /** 节点 ID(base_space.id) */
+    private Long id;
+
+    /** 名称(base_space.name) */
+    private String name;
+
+    /** 父节点 ID(base_space.parent_id) */
+    private Long parentId;
+
+    /** 空间类型:1项目 2区域 3建筑(base_space.type) */
+    private Integer type;
+
+    /** 根节点 ID(base_space.root_id) */
+    private Long rootId;
+
+    /** 节点路径(base_space.path) */
+    private String path;
+
+    /** 节点路径名称(base_space.path_name) */
+    private String pathName;
+
+    /** 深度(base_space.deep) */
+    private Integer deep;
+
+    /** 父空间名称(便于前端展示) */
+    private String parentSpaceName;
+
+    /** 子节点列表 */
+    private List<BaseSpaceForestNodeVO> children = new ArrayList<>();
+}

Некоторые файлы не были показаны из-за большого количества измененных файлов