hanzhengyi 3 dienas atpakaļ
vecāks
revīzija
99ea39b852
41 mainītis faili ar 1051 papildinājumiem un 100 dzēšanām
  1. 61 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsAnalysisController.java
  2. 30 30
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsApiV1Controller.java
  3. 37 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsAuthController.java
  4. 106 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsReportController.java
  5. 19 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsAnalysisService.java
  6. 14 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsAuthService.java
  7. 32 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsReportService.java
  8. 61 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsAnalysisServiceImpl.java
  9. 30 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsAuthServiceImpl.java
  10. 164 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsReportServiceImpl.java
  11. 14 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsCategoryRatioItemVO.java
  12. 17 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsCollectionRealtimeItemVO.java
  13. 16 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsCollectionRealtimeRequest.java
  14. 12 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsCollectionRealtimeResponse.java
  15. 15 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsCompareRequest.java
  16. 14 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsCompareResponse.java
  17. 14 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsCompareSeriesItemVO.java
  18. 12 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsCompareValueVO.java
  19. 18 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsEnergyStatisticsItemVO.java
  20. 14 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsEnergyStatisticsRequest.java
  21. 14 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsEnergyStatisticsResponse.java
  22. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsLoginRequest.java
  23. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsLoginResponse.java
  24. 18 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsRegionAnalysisItemVO.java
  25. 12 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsRegionAnalysisResponse.java
  26. 12 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsReportAttributeVO.java
  27. 17 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsReportDeviceItemVO.java
  28. 12 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsReportDevicesResponse.java
  29. 14 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsTrendCategoryResponse.java
  30. 18 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsTrendIndicatorsResponse.java
  31. 14 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsTrendItemVO.java
  32. 14 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsTrendResponse.java
  33. 23 0
      service-sas/service-sas-biz/src/main/java/com/usky/sas/controller/web/SasPatrolEventController.java
  34. 23 0
      service-sas/service-sas-biz/src/main/java/com/usky/sas/controller/web/SasUsbEventController.java
  35. 10 0
      service-sas/service-sas-biz/src/main/java/com/usky/sas/service/SasMapDeviceService.java
  36. 10 0
      service-sas/service-sas-biz/src/main/java/com/usky/sas/service/SasPatrolEventService.java
  37. 10 0
      service-sas/service-sas-biz/src/main/java/com/usky/sas/service/SasUsbEventService.java
  38. 15 0
      service-sas/service-sas-biz/src/main/java/com/usky/sas/service/impl/SasMapDeviceServiceImpl.java
  39. 57 70
      service-sas/service-sas-biz/src/main/java/com/usky/sas/service/impl/SasMapServiceImpl.java
  40. 19 0
      service-sas/service-sas-biz/src/main/java/com/usky/sas/service/impl/SasPatrolEventServiceImpl.java
  41. 19 0
      service-sas/service-sas-biz/src/main/java/com/usky/sas/service/impl/SasUsbEventServiceImpl.java

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

@@ -0,0 +1,61 @@
+package com.usky.ems.controller.web;
+
+import com.usky.common.core.bean.ApiResult;
+import com.usky.ems.service.EmsAnalysisService;
+import com.usky.ems.service.vo.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 能耗分析接口
+ * 趋势、指标、分类占比、区域分析、对比分析
+ */
+@RestController
+@RequestMapping("/prod-api/service-ems")
+public class EmsAnalysisController {
+
+    @Autowired
+    private EmsAnalysisService emsAnalysisService;
+
+    @GetMapping("/analysis/trend")
+    public ApiResult<EmsTrendResponse> getTrend(
+            @RequestParam(required = false) Long projectId,
+            @RequestParam String timeDimension,
+            @RequestParam String timeValue,
+            @RequestParam(required = false) Long energyTypeId) {
+        return ApiResult.success(emsAnalysisService.getTrend(projectId, timeDimension, timeValue, energyTypeId));
+    }
+
+    @GetMapping("/analysis/trend/indicators")
+    public ApiResult<EmsTrendIndicatorsResponse> getTrendIndicators(
+            @RequestParam(required = false) Long projectId,
+            @RequestParam String timeDimension,
+            @RequestParam String timeValue,
+            @RequestParam(required = false) Long energyTypeId) {
+        return ApiResult.success(emsAnalysisService.getTrendIndicators(projectId, timeDimension, timeValue, energyTypeId));
+    }
+
+    @GetMapping("/analysis/trend/category")
+    public ApiResult<EmsTrendCategoryResponse> getTrendCategory(
+            @RequestParam(required = false) Long projectId,
+            @RequestParam String timeDimension,
+            @RequestParam String timeValue,
+            @RequestParam(required = false) Long energyTypeId) {
+        return ApiResult.success(emsAnalysisService.getTrendCategory(projectId, timeDimension, timeValue, energyTypeId));
+    }
+
+    @GetMapping("/analysis/region")
+    public ApiResult<EmsRegionAnalysisResponse> getRegionAnalysis(
+            @RequestParam(required = false) Long projectId,
+            @RequestParam(required = false) String regionIds,
+            @RequestParam String timeDimension,
+            @RequestParam String timeValue,
+            @RequestParam(required = false) Long energyTypeId) {
+        return ApiResult.success(emsAnalysisService.getRegionAnalysis(projectId, regionIds, timeDimension, timeValue, energyTypeId));
+    }
+
+    @PostMapping("/analysis/compare")
+    public ApiResult<EmsCompareResponse> getCompare(@RequestBody EmsCompareRequest request) {
+        return ApiResult.success(emsAnalysisService.getCompare(request));
+    }
+}

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

@@ -12,39 +12,39 @@ import org.springframework.web.bind.annotation.*;
 import java.util.List;
 
 /**
- * 能源能耗系统 API v1
+ * 能源能耗系统 API
  *
- * 对应 API 文档基础路径:/api/v1
- * GET  /api/v1/overview/project           获取项目信息
- * GET  /api/v1/overview/summary            获取项目数据概括
- * GET  /api/v1/model/structure/tree        获取项目层级树
- * GET  /api/v1/model/energy-type/list      能源类型列表
- * POST /api/v1/model/building              新增建筑
- * PUT  /api/v1/model/building/{id}        编辑建筑
- * DELETE /api/v1/model/building/{id}      删除建筑
- * POST /api/v1/model/region                新增区域
- * PUT  /api/v1/model/region/{id}           编辑区域
- * DELETE /api/v1/model/region/{id}         删除区域
- * POST /api/v1/model/floor                 新增楼层
- * PUT  /api/v1/model/floor/{id}            编辑楼层
- * DELETE /api/v1/model/floor/{id}          删除楼层
- * POST /api/v1/model/gateway               新增网关
- * PUT  /api/v1/model/gateway/{id}         编辑网关
- * DELETE /api/v1/model/gateway/{id}        删除网关
- * POST /api/v1/model/channel               新增通道
- * PUT  /api/v1/model/channel/{id}          编辑通道
- * DELETE /api/v1/model/channel/{id}        删除通道
- * POST /api/v1/model/device                新增设备
- * PUT  /api/v1/model/device/{id}           编辑设备
- * DELETE /api/v1/model/device/{id}         删除设备
- * POST /api/v1/model/attribute-point      新增属性点位
- * PUT  /api/v1/model/attribute-point/{id}  编辑属性点位
- * DELETE /api/v1/model/attribute-point/{id} 删除属性点位
- * GET  /api/v1/device/gateway/list         网关列表(分页)
- * GET  /api/v1/device/gateway/{id}         网关详情
+ * 对应 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("/api/v1")
+@RequestMapping("/prod-api/service-ems")
 public class EmsApiV1Controller {
 
     @Autowired

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

@@ -0,0 +1,37 @@
+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("退出成功");
+    }
+}

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

@@ -0,0 +1,106 @@
+package com.usky.ems.controller.web;
+
+import com.usky.common.core.bean.ApiResult;
+import com.usky.ems.service.EmsReportService;
+import com.usky.ems.service.vo.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 统计报表接口
+ * 能源报表、区域报表、采集报表
+ */
+@RestController
+@RequestMapping("/prod-api/service-ems")
+public class EmsReportController {
+
+    @Autowired
+    private EmsReportService emsReportService;
+
+    // ---------- 能源报表 ----------
+    @GetMapping("/report/energy/devices")
+    public ApiResult<EmsReportDevicesResponse> getEnergyDevices(
+            @RequestParam Long energyTypeId,
+            @RequestParam(required = false) String keyword,
+            @RequestParam(required = false) Long projectId) {
+        return ApiResult.success(emsReportService.getEnergyDevices(energyTypeId, keyword, projectId));
+    }
+
+    @PostMapping("/report/energy/statistics")
+    public ApiResult<EmsEnergyStatisticsResponse> getEnergyStatistics(@RequestBody EmsEnergyStatisticsRequest request) {
+        return ApiResult.success(emsReportService.getEnergyStatistics(request));
+    }
+
+    @GetMapping("/report/energy/export")
+    public void exportEnergy(
+            @RequestParam String deviceIds,
+            @RequestParam(required = false) String attributePointIds,
+            @RequestParam String timeDimension,
+            @RequestParam String timeValue,
+            @RequestParam(required = false, defaultValue = "excel") String format,
+            HttpServletResponse response) {
+        emsReportService.exportEnergy(deviceIds, attributePointIds, timeDimension, timeValue, format, response);
+    }
+
+    // ---------- 区域报表 ----------
+    @GetMapping("/report/region/devices")
+    public ApiResult<EmsReportDevicesResponse> getRegionDevices(
+            @RequestParam Long energyTypeId,
+            @RequestParam(required = false) Long regionId,
+            @RequestParam(required = false) String keyword,
+            @RequestParam(required = false) Long projectId) {
+        return ApiResult.success(emsReportService.getRegionDevices(energyTypeId, regionId, keyword, projectId));
+    }
+
+    @PostMapping("/report/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")
+    public void exportRegion(
+            @RequestParam String deviceIds,
+            @RequestParam(required = false) String attributePointIds,
+            @RequestParam String timeDimension,
+            @RequestParam String timeValue,
+            @RequestParam(required = false) String regionIds,
+            @RequestParam(required = false, defaultValue = "excel") String format,
+            HttpServletResponse response) {
+        emsReportService.exportRegion(deviceIds, attributePointIds, timeDimension, timeValue, regionIds, format, response);
+    }
+
+    // ---------- 采集报表 ----------
+    @GetMapping("/report/collection/devices")
+    public ApiResult<EmsReportDevicesResponse> getCollectionDevices(
+            @RequestParam Long energyTypeId,
+            @RequestParam(required = false) String keyword,
+            @RequestParam(required = false) Long projectId) {
+        return ApiResult.success(emsReportService.getCollectionDevices(energyTypeId, keyword, projectId));
+    }
+
+    @PostMapping("/report/collection/realtime")
+    public ApiResult<EmsCollectionRealtimeResponse> getCollectionRealtime(@RequestBody EmsCollectionRealtimeRequest request) {
+        return ApiResult.success(emsReportService.getCollectionRealtime(request));
+    }
+
+    @PostMapping("/report/collection/statistics")
+    public ApiResult<EmsEnergyStatisticsResponse> getCollectionStatistics(@RequestBody EmsEnergyStatisticsRequest request) {
+        return ApiResult.success(emsReportService.getCollectionStatistics(request));
+    }
+
+    @GetMapping("/report/collection/export")
+    public void exportCollection(
+            @RequestParam String deviceIds,
+            @RequestParam(required = false) String attributePointIds,
+            @RequestParam String timeDimension,
+            @RequestParam String timeValue,
+            @RequestParam(required = false, defaultValue = "excel") String format,
+            HttpServletResponse response) {
+        emsReportService.exportCollection(deviceIds, attributePointIds, timeDimension, timeValue, format, response);
+    }
+}

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

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

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

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

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

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

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

@@ -0,0 +1,61 @@
+package com.usky.ems.service.impl;
+
+import com.usky.ems.service.EmsAnalysisService;
+import com.usky.ems.service.vo.*;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+
+/**
+ * 能耗分析服务实现(占位数据,后续对接时序/聚合数据)
+ */
+@Service
+public class EmsAnalysisServiceImpl implements EmsAnalysisService {
+
+    private static final String[] ENERGY_TYPE_NAMES = {"", "电", "水", "气"};
+
+    @Override
+    public EmsTrendResponse getTrend(Long projectId, String timeDimension, String timeValue, Long energyTypeId) {
+        EmsTrendResponse resp = new EmsTrendResponse();
+        resp.setTimeDimension(timeDimension);
+        resp.setTimeValue(timeValue);
+        return resp;
+    }
+
+    @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);
+        return resp;
+    }
+
+    @Override
+    public EmsTrendCategoryResponse getTrendCategory(Long projectId, String timeDimension, String timeValue, Long energyTypeId) {
+        EmsTrendCategoryResponse resp = new EmsTrendCategoryResponse();
+        resp.setTimeDimension(timeDimension);
+        resp.setTimeValue(timeValue);
+        return resp;
+    }
+
+    @Override
+    public EmsRegionAnalysisResponse getRegionAnalysis(Long projectId, String regionIds, String timeDimension, String timeValue, Long energyTypeId) {
+        return new EmsRegionAnalysisResponse();
+    }
+
+    @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] : "");
+        }
+        if (request != null) resp.setDimension(request.getTimeDimension());
+        return resp;
+    }
+}

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

@@ -0,0 +1,30 @@
+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 失效
+    }
+}

+ 164 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsReportServiceImpl.java

@@ -0,0 +1,164 @@
+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.domain.EmsProject;
+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;
+import org.springframework.stereotype.Service;
+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;
+
+/**
+ * 统计报表服务实现(设备列表基于 leo 设备与属性点位,统计与导出为占位/预留)
+ */
+@Service
+public class EmsReportServiceImpl implements EmsReportService {
+
+    private static final String[] ENERGY_TYPE_NAMES = {"", "电", "水", "气"};
+
+    @Autowired
+    private EmsProjectMapper emsProjectMapper;
+    @Autowired
+    private EmsDeviceMapper emsDeviceMapper;
+    @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()];
+    }
+
+    @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);
+        if (StringUtils.hasText(keyword)) {
+            q.and(w -> w.like(EmsDevice::getName, keyword).or().like(EmsDevice::getNumber, keyword));
+        }
+        List<EmsDevice> devices = emsDeviceMapper.selectList(q);
+        return buildReportDevicesResponse(devices, energyTypeId, null);
+    }
+
+    @Override
+    public EmsEnergyStatisticsResponse getEnergyStatistics(EmsEnergyStatisticsRequest request) {
+        EmsEnergyStatisticsResponse resp = new EmsEnergyStatisticsResponse();
+        if (request != null) {
+            resp.setTimeDimension(request.getTimeDimension());
+            resp.setTimeValue(request.getTimeValue());
+        }
+        return resp;
+    }
+
+    @Override
+    public void exportEnergy(String deviceIds, String attributePointIds, String timeDimension, String timeValue, String format, HttpServletResponse response) {
+        doExport(response, format, "energy_report");
+    }
+
+    @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);
+        return buildReportDevicesResponse(devices, energyTypeId, regionId);
+    }
+
+    @Override
+    public EmsEnergyStatisticsResponse getRegionStatistics(EmsEnergyStatisticsRequest request, List<Long> regionIds) {
+        return getEnergyStatistics(request);
+    }
+
+    @Override
+    public void exportRegion(String deviceIds, String attributePointIds, String timeDimension, String timeValue, String regionIds, String format, HttpServletResponse response) {
+        doExport(response, format, "region_report");
+    }
+
+    @Override
+    public EmsReportDevicesResponse getCollectionDevices(Long energyTypeId, String keyword, Long projectId) {
+        return getEnergyDevices(energyTypeId, keyword, projectId);
+    }
+
+    @Override
+    public EmsCollectionRealtimeResponse getCollectionRealtime(EmsCollectionRealtimeRequest request) {
+        EmsCollectionRealtimeResponse resp = new EmsCollectionRealtimeResponse();
+        return resp;
+    }
+
+    @Override
+    public EmsEnergyStatisticsResponse getCollectionStatistics(EmsEnergyStatisticsRequest request) {
+        return getEnergyStatistics(request);
+    }
+
+    @Override
+    public void exportCollection(String deviceIds, String attributePointIds, String timeDimension, String timeValue, String format, HttpServletResponse response) {
+        doExport(response, format, "collection_report");
+    }
+
+    private EmsReportDevicesResponse buildReportDevicesResponse(List<EmsDevice> devices, Long energyTypeId, Long regionId) {
+        EmsReportDevicesResponse resp = new EmsReportDevicesResponse();
+        String typeName = energyTypeName(energyTypeId);
+        for (EmsDevice d : devices) {
+            EmsReportDeviceItemVO item = new EmsReportDeviceItemVO();
+            item.setId(d.getId());
+            item.setName(d.getName());
+            item.setCode(d.getNumber());
+            item.setEnergyTypeName(typeName);
+            List<EmsDeviceFunction> funcs = emsDeviceFunctionMapper.selectList(
+                    new LambdaQueryWrapper<EmsDeviceFunction>().eq(EmsDeviceFunction::getDeviceId, d.getId()));
+            List<EmsReportAttributeVO> attrs = funcs.stream().map(f -> {
+                EmsReportAttributeVO a = new EmsReportAttributeVO();
+                a.setId(f.getId());
+                a.setName(f.getName());
+                a.setCode(f.getIdentifier());
+                a.setUnit("");
+                return a;
+            }).collect(Collectors.toList());
+            item.setAttributes(attrs);
+            resp.getDevices().add(item);
+        }
+        return resp;
+    }
+
+    private void doExport(HttpServletResponse response, String format, String fileName) {
+        try {
+            if ("csv".equalsIgnoreCase(format != null ? format : "excel")) {
+                response.setContentType("text/csv;charset=UTF-8");
+                response.setHeader("Content-Disposition", "attachment; filename=" + fileName + ".csv");
+                try (OutputStream os = response.getOutputStream()) {
+                    os.write("\uFEFF".getBytes(StandardCharsets.UTF_8));
+                    os.write("时间维度,时间值\n".getBytes(StandardCharsets.UTF_8));
+                }
+            } else {
+                response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+                response.setHeader("Content-Disposition", "attachment; filename=" + fileName + ".xlsx");
+                response.getOutputStream().flush();
+            }
+        } catch (IOException e) {
+            throw new RuntimeException("导出失败", e);
+        }
+    }
+}

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

@@ -0,0 +1,14 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class EmsCategoryRatioItemVO {
+
+    private String energyTypeName;
+    private BigDecimal ratio;
+    private BigDecimal usage;
+    private String unit;
+}

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

@@ -0,0 +1,17 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Data
+public class EmsCollectionRealtimeItemVO {
+
+    private String deviceId;
+    private Long pointId;
+    private String pointName;
+    private BigDecimal value;
+    private String unit;
+    private LocalDateTime collectedAt;
+}

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

@@ -0,0 +1,16 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class EmsCollectionRealtimeRequest {
+
+    private List<String> deviceIds;
+    private List<Long> attributePointIds;
+    private String timeDimension;
+    private String timeValue;
+    private String startTime;
+    private String endTime;
+}

+ 12 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsCollectionRealtimeResponse.java

@@ -0,0 +1,12 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+public class EmsCollectionRealtimeResponse {
+
+    private List<EmsCollectionRealtimeItemVO> items = new ArrayList<>();
+}

+ 15 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsCompareRequest.java

@@ -0,0 +1,15 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+@Data
+public class EmsCompareRequest {
+
+    private Long energyTypeId;
+    private List<String> deviceIds;
+    private String timeDimension;
+    private List<String> timeValues;
+}

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

@@ -0,0 +1,14 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+public class EmsCompareResponse {
+
+    private String energyTypeName;
+    private String dimension;
+    private List<EmsCompareSeriesItemVO> series = new ArrayList<>();
+}

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

@@ -0,0 +1,14 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+public class EmsCompareSeriesItemVO {
+
+    private String deviceId;
+    private String deviceName;
+    private List<EmsCompareValueVO> values = new ArrayList<>();
+}

+ 12 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsCompareValueVO.java

@@ -0,0 +1,12 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class EmsCompareValueVO {
+
+    private String timeValue;
+    private BigDecimal usage;
+}

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

@@ -0,0 +1,18 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+@Data
+public class EmsEnergyStatisticsItemVO {
+
+    private String deviceId;
+    private String deviceName;
+    private Long pointId;
+    private String pointName;
+    private String unit;
+    private BigDecimal value;
+    private List<Object> statistics;
+}

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

@@ -0,0 +1,14 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class EmsEnergyStatisticsRequest {
+
+    private List<String> deviceIds;
+    private List<Long> attributePointIds;
+    private String timeDimension;
+    private String timeValue;
+}

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

@@ -0,0 +1,14 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+public class EmsEnergyStatisticsResponse {
+
+    private String timeDimension;
+    private String timeValue;
+    private List<EmsEnergyStatisticsItemVO> items = new ArrayList<>();
+}

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

@@ -0,0 +1,10 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+@Data
+public class EmsLoginRequest {
+
+    private String username;
+    private String password;
+}

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

@@ -0,0 +1,10 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+@Data
+public class EmsLoginResponse {
+
+    private String accessToken;
+    private Long expiresIn;
+}

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

@@ -0,0 +1,18 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class EmsRegionAnalysisItemVO {
+
+    private Long regionId;
+    private String regionName;
+    private BigDecimal area;
+    private BigDecimal totalUsage;
+    private BigDecimal areaUsage;
+    private String unit;
+    private String timeDimension;
+    private String timeValue;
+}

+ 12 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsRegionAnalysisResponse.java

@@ -0,0 +1,12 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+public class EmsRegionAnalysisResponse {
+
+    private List<EmsRegionAnalysisItemVO> items = new ArrayList<>();
+}

+ 12 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsReportAttributeVO.java

@@ -0,0 +1,12 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+@Data
+public class EmsReportAttributeVO {
+
+    private Long id;
+    private String name;
+    private String code;
+    private String unit;
+}

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

@@ -0,0 +1,17 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+public class EmsReportDeviceItemVO {
+
+    private String id;
+    private String name;
+    private String code;
+    private String energyTypeName;
+    private String regionName;
+    private List<EmsReportAttributeVO> attributes = new ArrayList<>();
+}

+ 12 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsReportDevicesResponse.java

@@ -0,0 +1,12 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+public class EmsReportDevicesResponse {
+
+    private List<EmsReportDeviceItemVO> devices = new ArrayList<>();
+}

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

@@ -0,0 +1,14 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+public class EmsTrendCategoryResponse {
+
+    private String timeDimension;
+    private String timeValue;
+    private List<EmsCategoryRatioItemVO> categoryRatio = new ArrayList<>();
+}

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

@@ -0,0 +1,18 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class EmsTrendIndicatorsResponse {
+
+    private String timeDimension;
+    private String timeValue;
+    private BigDecimal totalUsage;
+    private BigDecimal standardCoal;
+    private BigDecimal carbonEmission;
+    private BigDecimal areaUsage;
+    private BigDecimal perCapitaUsage;
+    private Object rating;
+}

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

@@ -0,0 +1,14 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class EmsTrendItemVO {
+
+    private String timeLabel;
+    private BigDecimal usage;
+    private BigDecimal standardCoal;
+    private BigDecimal carbonEmission;
+}

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

@@ -0,0 +1,14 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+public class EmsTrendResponse {
+
+    private String timeDimension;
+    private String timeValue;
+    private List<EmsTrendItemVO> trend = new ArrayList<>();
+}

+ 23 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/controller/web/SasPatrolEventController.java

@@ -8,7 +8,10 @@ import com.usky.sas.service.vo.MapInfoVo;
 import lombok.Data;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
@@ -22,6 +25,8 @@ import java.util.List;
  * GET  /prod-api/service-sas/event/patrol/map/tree  获取地图树(仅 type=1008)
  * GET  /prod-api/service-sas/event/patrol/map/info  获取地图详情(仅 type=1008)
  * POST /prod-api/service-sas/event/patrol/map       新增地图(type 固定为 1008 实时电子巡检)
+ * PUT  /prod-api/service-sas/event/patrol/map/{id}  修改地图(仅 type=1008)
+ * DELETE /prod-api/service-sas/event/patrol/map/{id} 删除地图(仅 type=1008)
  */
 @RestController
 @RequestMapping("/event/patrol")
@@ -58,6 +63,24 @@ public class SasPatrolEventController {
         return ApiResult.success(vo);
     }
 
+    /**
+     * 修改地图(仅实时电子巡检 type=1008 的地图)
+     */
+    @PutMapping("/map/{id}")
+    public ApiResult<Void> updateMap(@PathVariable("id") String id, @RequestBody MapSaveRequest request) {
+        sasPatrolEventService.updateMap(id, request);
+        return ApiResult.success();
+    }
+
+    /**
+     * 删除地图(仅实时电子巡检 type=1008 的地图)
+     */
+    @DeleteMapping("/map/{id}")
+    public ApiResult<Void> deleteMap(@PathVariable("id") String id) {
+        sasPatrolEventService.deleteMap(id);
+        return ApiResult.success();
+    }
+
     @Data
     public static class AddMapResponse {
         private String id;

+ 23 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/controller/web/SasUsbEventController.java

@@ -8,7 +8,10 @@ import com.usky.sas.service.vo.MapInfoVo;
 import lombok.Data;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
@@ -22,6 +25,8 @@ import java.util.List;
  * GET  /prod-api/service-sas/event/usb/map/tree   获取地图树(仅 type=1003)
  * GET  /prod-api/service-sas/event/usb/map/info  获取地图详情(仅 type=1003)
  * POST /prod-api/service-sas/event/usb/map       新增地图(type 固定为 1003 视频导出防护)
+ * PUT  /prod-api/service-sas/event/usb/map/{id} 修改地图(仅 type=1003)
+ * DELETE /prod-api/service-sas/event/usb/map/{id} 删除地图(仅 type=1003)
  */
 @RestController
 @RequestMapping("/event/usb")
@@ -58,6 +63,24 @@ public class SasUsbEventController {
         return ApiResult.success(vo);
     }
 
+    /**
+     * 修改地图(仅视频导出防护 type=1003 的地图)
+     */
+    @PutMapping("/map/{id}")
+    public ApiResult<Void> updateMap(@PathVariable("id") String id, @RequestBody MapSaveRequest request) {
+        sasUsbEventService.updateMap(id, request);
+        return ApiResult.success();
+    }
+
+    /**
+     * 删除地图(仅视频导出防护 type=1003 的地图)
+     */
+    @DeleteMapping("/map/{id}")
+    public ApiResult<Void> deleteMap(@PathVariable("id") String id) {
+        sasUsbEventService.deleteMap(id);
+        return ApiResult.success();
+    }
+
     @Data
     public static class AddMapResponse {
         private String id;

+ 10 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/service/SasMapDeviceService.java

@@ -0,0 +1,10 @@
+package com.usky.sas.service;
+
+import com.usky.common.mybatis.core.CrudService;
+import com.usky.sas.domain.SasMapDevice;
+
+/**
+ * 电子地图设备点位 Service
+ */
+public interface SasMapDeviceService extends CrudService<SasMapDevice> {
+}

+ 10 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/service/SasPatrolEventService.java

@@ -33,4 +33,14 @@ public interface SasPatrolEventService extends CrudService<SasPatrolEvent> {
      * @return 地图详情(含背景图 URL、设备点位及设备信息),非巡检地图返回 null
      */
     MapInfoVo getMapInfo(String mapId);
+
+    /**
+     * 修改实时电子巡检模块下的地图,仅允许 type=1008 的地图
+     */
+    void updateMap(String id, MapSaveRequest request);
+
+    /**
+     * 删除实时电子巡检模块下的地图,仅允许 type=1008 的地图
+     */
+    void deleteMap(String id);
 }

+ 10 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/service/SasUsbEventService.java

@@ -33,4 +33,14 @@ public interface SasUsbEventService extends CrudService<SasUsbEvent> {
      * @return 地图详情(含背景图 URL、设备点位及设备信息),非 USB 地图返回 null
      */
     MapInfoVo getMapInfo(String mapId);
+
+    /**
+     * 修改视频导出防护(USB)模块下的地图,仅允许 type=1003 的地图
+     */
+    void updateMap(String id, MapSaveRequest request);
+
+    /**
+     * 删除视频导出防护(USB)模块下的地图,仅允许 type=1003 的地图
+     */
+    void deleteMap(String id);
 }

+ 15 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/service/impl/SasMapDeviceServiceImpl.java

@@ -0,0 +1,15 @@
+package com.usky.sas.service.impl;
+
+import com.usky.common.mybatis.core.AbstractCrudService;
+import com.usky.sas.domain.SasMapDevice;
+import com.usky.sas.mapper.SasMapDeviceMapper;
+import com.usky.sas.service.SasMapDeviceService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 电子地图设备点位 Service 实现
+ */
+@Service
+public class SasMapDeviceServiceImpl extends AbstractCrudService<SasMapDeviceMapper, SasMapDevice>
+        implements SasMapDeviceService {
+}

+ 57 - 70
service-sas/service-sas-biz/src/main/java/com/usky/sas/service/impl/SasMapServiceImpl.java

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 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.mybatis.core.AbstractCrudService;
 import com.usky.sas.domain.SasMapDevice;
 import com.usky.sas.domain.SasMaps;
@@ -17,6 +18,7 @@ import com.usky.sas.mapper.SasPicMapper;
 import com.usky.sas.mapper.SasDeviceMapper;
 import com.usky.sas.common.util.GetIpUtils;
 import com.usky.sas.service.SasMapService;
+import com.usky.sas.service.SasMapDeviceService;
 import com.usky.sas.service.SasPicService;
 import com.usky.sas.service.SasPicSourceService;
 import com.usky.sas.service.vo.MapDeviceBindRequest;
@@ -35,8 +37,10 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 
-import com.usky.common.core.exception.BusinessException;
-
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.StrUtil;
+import lombok.extern.slf4j.Slf4j;
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.util.Collections;
@@ -49,12 +53,16 @@ import java.util.UUID;
 import java.util.stream.Collectors;
 
 @Service
+@Slf4j
 public class SasMapServiceImpl extends AbstractCrudService<SasMapsMapper, SasMaps>
         implements SasMapService {
 
     @Autowired
     private SasMapDeviceMapper mapDeviceMapper;
 
+    @Autowired
+    private SasMapDeviceService mapDeviceService;
+
     @Autowired
     private SasPicMapper sasPicMapper;
 
@@ -236,21 +244,19 @@ public class SasMapServiceImpl extends AbstractCrudService<SasMapsMapper, SasMap
 
     @Override
     public void saveMapInfo(MapSaveWithDevicesRequest request) {
-        // 保存或更新地图基础信息
+        log.info("请求参数:request={}", request);
+        // 1. 保存或更新地图基础信息
         SasMaps map = null;
-        if (request.getId() != null && !request.getId().isEmpty()) {
+        if (StrUtil.isNotBlank(request.getId())) {
             map = this.getById(request.getId());
         }
         LocalDateTime now = LocalDateTime.now();
         if (map == null) {
             map = new SasMaps();
-            String id = request.getId() != null && !request.getId().isEmpty()
-                    ? request.getId()
-                    : UUID.randomUUID().toString();
+            String id = StrUtil.isNotBlank(request.getId()) ? request.getId() : IdUtil.fastSimpleUUID();
             map.setId(id);
             map.setCreateTime(now);
         }
-        // 复用 applyMap 逻辑
         MapSaveRequest saveReq = new MapSaveRequest();
         saveReq.setName(request.getName());
         saveReq.setType(request.getType());
@@ -265,79 +271,60 @@ public class SasMapServiceImpl extends AbstractCrudService<SasMapsMapper, SasMap
         this.saveOrUpdate(map);
 
         String mapId = map.getId();
+        List<MapSaveWithDevicesRequest.MapDeviceItem> deviceItems = request.getDevices();
 
-        // 处理地图下设备点位:删除已不存在的,新增或更新现有
+        // 2. 查询该地图下已有设备点位,删除“数据库有而本次请求没有”
         LambdaQueryWrapper<SasMapDevice> query = new LambdaQueryWrapper<>();
         query.eq(SasMapDevice::getMapId, mapId);
-        List<SasMapDevice> dbDevices = mapDeviceMapper.selectList(query);
-
-        Set<String> dbIds = new HashSet<>();
-        if (dbDevices != null) {
-            for (SasMapDevice d : dbDevices) {
-                if (d.getId() != null) {
-                    dbIds.add(d.getId());
-                }
+        List<SasMapDevice> dbList = mapDeviceService.list(query);
+        if (CollUtil.isNotEmpty(dbList)) {
+            Set<String> dbIds = dbList.stream().map(SasMapDevice::getId).collect(Collectors.toSet());
+            Set<String> newIds = deviceItems == null ? Collections.emptySet()
+                    : deviceItems.stream()
+                            .map(MapSaveWithDevicesRequest.MapDeviceItem::getId)
+                            .filter(StrUtil::isNotBlank)
+                            .collect(Collectors.toSet());
+            dbIds.removeAll(newIds);
+            if (CollUtil.isNotEmpty(dbIds)) {
+                mapDeviceService.removeByIds(dbIds);
             }
         }
 
-        Set<String> newIds = new HashSet<>();
-        if (request.getDevices() != null) {
-            request.getDevices().stream()
-                    .map(MapSaveWithDevicesRequest.MapDeviceItem::getId)
-                    .filter(id -> id != null && !id.isEmpty())
-                    .forEach(newIds::add);
-        }
-
-        // 需要删除的设备ID = 数据库已有 - 新请求中的
-        dbIds.removeAll(newIds);
-        if (!dbIds.isEmpty()) {
-            mapDeviceMapper.deleteBatchIds(dbIds);
-        }
-
-        // 新增或更新设备点位
-        if (request.getDevices() != null && !request.getDevices().isEmpty()) {
-            for (MapSaveWithDevicesRequest.MapDeviceItem item : request.getDevices()) {
-                SasMapDevice entity;
-                boolean exists = item.getId() != null && !item.getId().isEmpty();
-                if (exists) {
-                    entity = mapDeviceMapper.selectById(item.getId());
-                    if (entity == null) {
-                        entity = new SasMapDevice();
-                        entity.setId(item.getId());
-                        entity.setCreateTime(now);
-                    }
-                } else {
-                    entity = new SasMapDevice();
-                    entity.setId(UUID.randomUUID().toString());
-                    entity.setCreateTime(now);
+        // 3. 新增或更新设备点位:id 为空则生成 UUID,统一设置 mapId 后批量保存
+        if (CollUtil.isNotEmpty(deviceItems)) {
+            List<SasMapDevice> entities = deviceItems.stream()
+                    .map(item -> toMapDeviceEntity(item, mapId, now))
+                    .collect(Collectors.toList());
+            entities.forEach(entity -> {
+                if (StrUtil.isBlank(entity.getId())) {
+                    entity.setId(IdUtil.fastSimpleUUID());
                 }
                 entity.setMapId(mapId);
-                entity.setDeviceId(item.getDeviceId());
-                entity.setImgId(item.getImgId());
-                entity.setType(item.getType());
-                if (item.getX() != null) {
-                    entity.setX(BigDecimal.valueOf(item.getX()));
-                } else {
-                    entity.setX(null);
-                }
-                if (item.getY() != null) {
-                    entity.setY(BigDecimal.valueOf(item.getY()));
-                } else {
-                    entity.setY(null);
-                }
-                entity.setWidth(item.getWidth());
-                entity.setHeight(item.getHeight());
-                entity.setAngle(item.getAngle());
-                entity.setText(item.getText());
                 entity.setUpdateTime(now);
+            });
+            mapDeviceService.saveOrUpdateBatch(entities);
+        }
+    }
 
-                if (exists && mapDeviceMapper.selectById(entity.getId()) != null) {
-                    mapDeviceMapper.updateById(entity);
-                } else {
-                    mapDeviceMapper.insert(entity);
-                }
-            }
+    /** 将请求中的设备点位项转为实体(id 可为空,由 saveMapInfo 统一补全) */
+    private SasMapDevice toMapDeviceEntity(MapSaveWithDevicesRequest.MapDeviceItem item, String mapId, LocalDateTime now) {
+        SasMapDevice entity = new SasMapDevice();
+        if (StrUtil.isNotBlank(item.getId())) {
+            entity.setId(item.getId());
         }
+        entity.setMapId(mapId);
+        entity.setDeviceId(item.getDeviceId());
+        entity.setImgId(item.getImgId());
+        entity.setType(item.getType());
+        entity.setX(item.getX() != null ? BigDecimal.valueOf(item.getX()) : null);
+        entity.setY(item.getY() != null ? BigDecimal.valueOf(item.getY()) : null);
+        entity.setWidth(item.getWidth());
+        entity.setHeight(item.getHeight());
+        entity.setAngle(item.getAngle());
+        entity.setText(item.getText());
+        entity.setCreateTime(now);
+        entity.setUpdateTime(now);
+        return entity;
     }
 
     @Override

+ 19 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/service/impl/SasPatrolEventServiceImpl.java

@@ -41,4 +41,23 @@ public class SasPatrolEventServiceImpl extends AbstractCrudService<SasPatrolEven
         }
         return vo;
     }
+
+    @Override
+    public void updateMap(String id, MapSaveRequest request) {
+        MapInfoVo vo = sasMapService.getMapInfo(id);
+        if (vo == null || vo.getType() == null || vo.getType() != SystemTypeCodeEnum.patrol.getCode()) {
+            throw new IllegalArgumentException("仅支持修改实时电子巡检模块的地图");
+        }
+        request.setType(SystemTypeCodeEnum.patrol.getCode());
+        sasMapService.update(id, request);
+    }
+
+    @Override
+    public void deleteMap(String id) {
+        MapInfoVo vo = sasMapService.getMapInfo(id);
+        if (vo == null || vo.getType() == null || vo.getType() != SystemTypeCodeEnum.patrol.getCode()) {
+            throw new IllegalArgumentException("仅支持删除实时电子巡检模块的地图");
+        }
+        sasMapService.delete(id);
+    }
 }

+ 19 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/service/impl/SasUsbEventServiceImpl.java

@@ -41,4 +41,23 @@ public class SasUsbEventServiceImpl extends AbstractCrudService<SasUsbEventMappe
         }
         return vo;
     }
+
+    @Override
+    public void updateMap(String id, MapSaveRequest request) {
+        MapInfoVo vo = sasMapService.getMapInfo(id);
+        if (vo == null || vo.getType() == null || vo.getType() != SystemTypeCodeEnum.usbalarm.getCode()) {
+            throw new IllegalArgumentException("仅支持修改视频导出防护(USB)模块的地图");
+        }
+        request.setType(SystemTypeCodeEnum.usbalarm.getCode());
+        sasMapService.update(id, request);
+    }
+
+    @Override
+    public void deleteMap(String id) {
+        MapInfoVo vo = sasMapService.getMapInfo(id);
+        if (vo == null || vo.getType() == null || vo.getType() != SystemTypeCodeEnum.usbalarm.getCode()) {
+            throw new IllegalArgumentException("仅支持删除视频导出防护(USB)模块的地图");
+        }
+        sasMapService.delete(id);
+    }
 }