Browse Source

Merge branch 'fu-dev' of hanzhengyi/usky-power into master

fuyuchuan 1 day ago
parent
commit
7001675585
34 changed files with 1471 additions and 388 deletions
  1. 19 2
      fiveep-controller/src/main/java/com/bizmatics/controller/web/DeviceController.java
  2. 32 8
      fiveep-controller/src/main/java/com/bizmatics/controller/web/HtAnalogDataController.java
  3. 6 6
      fiveep-controller/src/main/java/com/bizmatics/controller/web/SiteController.java
  4. 25 0
      fiveep-controller/src/main/java/com/bizmatics/controller/web/StarMarkingEquipmentController.java
  5. 2 2
      fiveep-controller/src/main/resources/application-dev.properties
  6. 5 2
      fiveep-controller/src/main/resources/application-prod.properties
  7. 1 1
      fiveep-controller/src/main/resources/application.properties
  8. 3 0
      fiveep-model/src/main/java/com/bizmatics/model/DeviceList.java
  9. 4 3
      fiveep-model/src/main/java/com/bizmatics/model/RtAnalogData.java
  10. 9 2
      fiveep-model/src/main/java/com/bizmatics/model/Site.java
  11. 30 1
      fiveep-model/src/main/java/com/bizmatics/model/SiteElectricityRecord.java
  12. 4 1
      fiveep-persistence/src/main/java/com/bizmatics/persistence/mapper/DeviceMapper.java
  13. 3 1
      fiveep-persistence/src/main/java/com/bizmatics/persistence/mapper/HtAnalogDataMapper.java
  14. 2 0
      fiveep-persistence/src/main/java/com/bizmatics/persistence/mapper/RtAnalogDataMapper.java
  15. 41 10
      fiveep-persistence/src/main/resources/mapper/mysql/DeviceMapper.xml
  16. 6 3
      fiveep-persistence/src/main/resources/mapper/mysql/HtAnalogDataMapper.xml
  17. 92 80
      fiveep-persistence/src/main/resources/mapper/mysql/RtAnalogDataMapper.xml
  18. 16 0
      fiveep-service/pom.xml
  19. 4 2
      fiveep-service/src/main/java/com/bizmatics/service/DeviceService.java
  20. 28 5
      fiveep-service/src/main/java/com/bizmatics/service/HtAnalogDataService.java
  21. 8 4
      fiveep-service/src/main/java/com/bizmatics/service/RtAnalogDataService.java
  22. 3 1
      fiveep-service/src/main/java/com/bizmatics/service/SiteService.java
  23. 40 39
      fiveep-service/src/main/java/com/bizmatics/service/impl/DeviceServiceImpl.java
  24. 598 5
      fiveep-service/src/main/java/com/bizmatics/service/impl/HtAnalogDataServiceImpl.java
  25. 52 50
      fiveep-service/src/main/java/com/bizmatics/service/impl/RtAnalogDataServiceImpl.java
  26. 144 139
      fiveep-service/src/main/java/com/bizmatics/service/impl/SiteServiceImpl.java
  27. 46 20
      fiveep-service/src/main/java/com/bizmatics/service/job/SiteDailyElectricityCostTask.java
  28. 44 0
      fiveep-service/src/main/java/com/bizmatics/service/vo/ElectricityTrendRequestVO.java
  29. 27 0
      fiveep-service/src/main/java/com/bizmatics/service/vo/ElectricityTrendResponseVO.java
  30. 41 0
      fiveep-service/src/main/java/com/bizmatics/service/vo/ElectricityTrendVO.java
  31. 6 0
      fiveep-service/src/main/java/com/bizmatics/service/vo/SiteLoadAnalysisVO.java
  32. 53 0
      fiveep-service/src/main/java/com/bizmatics/service/vo/TimeSharingElectricityRequestVO.java
  33. 76 0
      fiveep-service/src/main/java/com/bizmatics/service/vo/TimeSharingElectricityResponseVO.java
  34. 1 1
      pom.xml

+ 19 - 2
fiveep-controller/src/main/java/com/bizmatics/controller/web/DeviceController.java

@@ -68,6 +68,23 @@ public class DeviceController {
         return ApiResult.success(deviceService.deviceList(siteId));
     }
 
+    /**
+     * 分时用电-设备分页列表查询
+     *
+     * @param siteId     站点ID
+     * @param deviceType 设备类型 默认1、支路设备(183 用电设备), 2、分路设备(171、173用电设备)
+     * @param pageNum    页数
+     * @param pageSize   条数
+     * @return
+     */
+    @GetMapping("list")
+    public ApiResult<List<DeviceList>> branch(@RequestParam(value = "siteId") int siteId,
+                                              @RequestParam(value = "deviceType", required = false, defaultValue = "1") int deviceType,
+                                              @RequestParam(value = "pageNum", required = false, defaultValue = "1") int pageNum,
+                                              @RequestParam(value = "pageSize", required = false, defaultValue = "20") int pageSize) {
+        return ApiResult.success(deviceService.deviceBranch(siteId, deviceType, pageNum, pageSize));
+    }
+
 
     /**
      * 数据管理-同比分析报表-右侧设备查询
@@ -242,7 +259,7 @@ public class DeviceController {
      */
     @Log(title = "设备管理-通信设备", businessType = BusinessType.IMPORT)
     @PostMapping("/deviceImport")
-    public ApiResult<Void> deviceImport(@RequestParam("file") MultipartFile file){
+    public ApiResult<Void> deviceImport(@RequestParam("file") MultipartFile file) {
         deviceService.deviceImport(file);
         return ApiResult.success();
     }
@@ -253,7 +270,7 @@ public class DeviceController {
      * @return
      */
     @GetMapping("deviceBoxList")
-    public ApiResult<List<DeviceOneVo>> deviceBoxList(@RequestParam (required = false) Integer siteId) {
+    public ApiResult<List<DeviceOneVo>> deviceBoxList(@RequestParam(required = false) Integer siteId) {
         return ApiResult.success(deviceService.deviceBoxList(siteId));
     }
 

+ 32 - 8
fiveep-controller/src/main/java/com/bizmatics/controller/web/HtAnalogDataController.java

@@ -1,7 +1,7 @@
 package com.bizmatics.controller.web;
 
-
 import com.bizmatics.common.core.bean.ApiResult;
+import com.bizmatics.common.core.bean.CommonPage;
 import com.bizmatics.model.vo.DataManagementOneVO;
 import com.bizmatics.model.vo.HtAnalogEnergyConsumptionVo;
 import com.bizmatics.model.vo.HtAnalogEnergySegmentedVo;
@@ -9,19 +9,16 @@ import com.bizmatics.model.vo.SingleLoopReportOneVo;
 import com.bizmatics.service.HtAnalogDataService;
 import com.bizmatics.service.aop.BusinessType;
 import com.bizmatics.service.aop.Log;
-import com.bizmatics.service.vo.CommonIcoVO;
-import com.bizmatics.service.vo.HadCountVO;
-import com.bizmatics.service.vo.RealScoreVO;
-import com.bizmatics.service.vo.TimeShareVO;
+import com.bizmatics.service.vo.*;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.format.annotation.DateTimeFormat;
 import org.springframework.web.bind.annotation.*;
 
-import java.time.LocalDate;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.net.URLEncoder;
 import java.time.LocalDateTime;
-import java.time.format.DateTimeFormatter;
-import java.time.temporal.ChronoUnit;
 import java.util.Date;
 import java.util.List;
 
@@ -259,5 +256,32 @@ public class HtAnalogDataController {
         }
         return ApiResult.success(htAnalogDataService.getSegmentedData(siteId, queryPeriod, queryTime, queryType));
     }
+
+    /**
+     * 时间分时电量、电费-表格统计
+     * @param request 请求参数
+     * @return 响应
+     */
+    @PostMapping("page")
+    public ApiResult<CommonPage<TimeSharingElectricityResponseVO>> timeSharingElectricity(@RequestBody TimeSharingElectricityRequestVO request) {
+        return ApiResult.success(htAnalogDataService.timeSharingElectricity(request));
+    }
+
+    /**
+     * 分时用电-电量趋势
+     * @param trendVO 请求参数
+     * @return 响应
+     */
+    @PostMapping("trend")
+    public ApiResult<List<ElectricityTrendResponseVO>> trend(@RequestBody ElectricityTrendRequestVO trendVO) {
+        return ApiResult.success(htAnalogDataService.trend(trendVO));
+    }
+
+    @PostMapping("export")
+    public void export(@RequestBody TimeSharingElectricityRequestVO request, HttpServletResponse response) throws IOException {
+        htAnalogDataService.export(request, response);
+
+    }
+
 }
 

+ 6 - 6
fiveep-controller/src/main/java/com/bizmatics/controller/web/SiteController.java

@@ -174,15 +174,15 @@ public class SiteController {
     /**
      * 站点管理-基础信息-站点负载分析
      * @param siteId 站点ID
-     * @param pageNum 页码
-     * @param pageSize 每页数量
+     * @param startTime 开始时间
+     * @param endTime 结束时间
      * @return 负载分析数据
      */
     @GetMapping("siteLoadAnalysis")
-    public ApiResult<CommonPage<SiteLoadAnalysisVO>> siteLoadAnalysis(@RequestParam(value = "siteId") Integer siteId,
-                                                                      @RequestParam(value = "pageNum", required = false, defaultValue = "1") Integer pageNum,
-                                                                      @RequestParam(value = "pageSize", required = false, defaultValue = "10") Integer pageSize) {
-        return ApiResult.success(siteService.siteLoadAnalysis(siteId, pageNum, pageSize));
+    public ApiResult<List<SiteLoadAnalysisVO>> siteLoadAnalysis(@RequestParam(value = "siteId") Integer siteId,
+                                                                    @RequestParam(value = "startTime", required = false) String startTime,
+                                                                    @RequestParam(value = "endTime", required = false) String endTime) {
+        return ApiResult.success(siteService.siteLoadAnalysis(siteId, startTime, endTime));
     }
 }
 

+ 25 - 0
fiveep-controller/src/main/java/com/bizmatics/controller/web/StarMarkingEquipmentController.java

@@ -0,0 +1,25 @@
+package com.bizmatics.controller.web;
+
+import com.bizmatics.persistence.mapper.HtAnalogDataMapper;
+import com.bizmatics.service.HtAnalogDataService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ *
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2025/9/4
+ */
+@RestController
+@RequestMapping("/starMarkingEquipment")
+public class StarMarkingEquipmentController {
+    @Autowired
+    private HtAnalogDataService htAnalogDataService;
+    @Autowired
+    private HtAnalogDataMapper htAnalogDataMapper;
+
+    // @RequestMapping("/powerLevel")
+    // public
+}

+ 2 - 2
fiveep-controller/src/main/resources/application-dev.properties

@@ -19,10 +19,10 @@ spring.autoconfigure.exclude=com.alibaba.druid.spring.boot.autoconfigure.DruidDa
 spring.datasource.dynamic.primary=usky-power
 #spring.datasource.dynamic.datasource.usky-power.url=jdbc:mysql://usky-cloud-mysql:3306/usky-electricity?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8
 #spring.datasource.dynamic.datasource.usky-power.url=jdbc:mysql://dianli.usky.cn:3306/usky-electricity?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8
-#spring.datasource.dynamic.datasource.usky-power.url=jdbc:mysql://101.133.214.75:3306/usky-electricity?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8
+spring.datasource.dynamic.datasource.usky-power.url=jdbc:mysql://101.133.214.75:3306/usky-electricity?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8
 spring.datasource.dynamic.datasource.usky-power.username=usky
 spring.datasource.dynamic.datasource.usky-power.password=Yt#75Usky
-spring.datasource.dynamic.druid.initial-size=5                                                                       
+spring.datasource.dynamic.druid.initial-size=5
 spring.datasource.dynamic.druid.min-idle=5
 spring.datasource.dynamic.druid.max-active=30
 spring.datasource.dynamic.druid.max-wait=60000

+ 5 - 2
fiveep-controller/src/main/resources/application-prod.properties

@@ -14,11 +14,14 @@ mybatis-plus.configuration.defaultStatementTimeout=3
 mybatis.refresh.enabled=true
 mybatis.refresh.delay-seconds=10
 mybatis.refresh.sleep-seconds=20
+# SQL日志打印
+mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
 # datasource
 spring.autoconfigure.exclude=com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
 spring.datasource.dynamic.primary=usky-power
-spring.datasource.dynamic.datasource.usky-power.url=jdbc:mysql://usky-cloud-mysql:3306/usky-electricity?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8
+#spring.datasource.dynamic.datasource.usky-power.url=jdbc:mysql://usky-cloud-mysql:3306/usky-electricity?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8
 #spring.datasource.dynamic.datasource.usky-power.url=jdbc:mysql://101.133.214.75:3306/usky-electricity?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8
+spring.datasource.dynamic.datasource.usky-power.url=jdbc:mysql://101.133.214.75:3306/usky-electricity?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai
 spring.datasource.dynamic.datasource.usky-power.username=usky
 spring.datasource.dynamic.datasource.usky-power.password=Yt#75Usky
 spring.datasource.dynamic.druid.initial-size=5
@@ -73,7 +76,7 @@ spring.cache.ehcache.config=classpath:ehcache.xml
 # redis
 spring.cache.redis.enabled=true
 spring.redis.database=0
-spring.redis.host=usky-cloud-redist 
+spring.redis.host=usky-cloud-redis
 spring.redis.port=6379
 spring.redis.password=123456
 spring.redis.timeout=10000

+ 1 - 1
fiveep-controller/src/main/resources/application.properties

@@ -1,5 +1,5 @@
 # common
-spring.profiles.active=test
+spring.profiles.active=prod
 spring.application.name=usky-power
 spring.main.banner-mode=off
 mybatis-plus.global-config.banner=false

+ 3 - 0
fiveep-model/src/main/java/com/bizmatics/model/DeviceList.java

@@ -72,6 +72,9 @@ public class DeviceList implements Serializable {
      */
     private Integer enable;
 
+    /**
+     * 设备状态(0 正常,1 离线,77 告警,4 故障)
+     **/
     @TableField(exist = false)
     private Integer deviceStatus;
 

+ 4 - 3
fiveep-model/src/main/java/com/bizmatics/model/RtAnalogData.java

@@ -8,11 +8,12 @@ import lombok.EqualsAndHashCode;
 import lombok.experimental.Accessors;
 
 import java.io.Serializable;
+import java.time.LocalDateTime;
 import java.util.Date;
 
 /**
  * <p>
- * 
+ *
  * </p>
  *
  * @author ya
@@ -23,7 +24,7 @@ import java.util.Date;
 @Accessors(chain = true)
 public class RtAnalogData implements Serializable {
 
-    private static final long serialVersionUID=1L;
+    private static final long serialVersionUID = 1L;
 
     /**
      * 伍继电力test_183消费组模拟量数据表ID
@@ -407,7 +408,7 @@ public class RtAnalogData implements Serializable {
      * 上报时间
      */
     @TableField("dataTime")
-    private Date dataTime;
+    private LocalDateTime dataTime;
 
 
 }

+ 9 - 2
fiveep-model/src/main/java/com/bizmatics/model/Site.java

@@ -3,12 +3,13 @@ package com.bizmatics.model;
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.experimental.Accessors;
 
 import java.io.Serializable;
-import java.util.Date;
+import java.time.LocalDateTime;
 
 /**
  * <p>
@@ -104,7 +105,8 @@ public class Site implements Serializable {
     /**
      * 创建时间
      */
-    private Date createTime;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime createTime;
 
     /**
      * 创建人
@@ -126,4 +128,9 @@ public class Site implements Serializable {
      */
     private Integer tenantId;
 
+    /**
+     * 站点费用状态:ture-正常,false-异常(没有费用配置)
+     */
+    @TableField(exist = false)
+    private boolean costTypeStatus;
 }

+ 30 - 1
fiveep-model/src/main/java/com/bizmatics/model/SiteElectricityRecord.java

@@ -1,10 +1,15 @@
 package com.bizmatics.model;
 
 import java.math.BigDecimal;
+
 import com.baomidou.mybatisplus.annotation.IdType;
+
 import java.time.LocalDate;
+
 import com.baomidou.mybatisplus.annotation.TableId;
+
 import java.io.Serializable;
+
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.experimental.Accessors;
@@ -22,7 +27,7 @@ import lombok.experimental.Accessors;
 @Accessors(chain = true)
 public class SiteElectricityRecord implements Serializable {
 
-    private static final long serialVersionUID=1L;
+    private static final long serialVersionUID = 1L;
 
     /**
      * 站点日电费记录表ID
@@ -70,5 +75,29 @@ public class SiteElectricityRecord implements Serializable {
      */
     private BigDecimal totalCost;
 
+    /**
+     * 尖 用量(Kwh)
+     */
+    private BigDecimal sharpPeak;
+
+    /**
+     * 峰 用量(Kwh)
+     */
+    private BigDecimal peak;
+
+    /**
+     * 平 用量(Kwh)
+     */
+    private BigDecimal flat;
+
+    /**
+     * 谷 用量(Kwh)
+     */
+    private BigDecimal valley;
+
+    /**
+     * 总电量(Kwh)
+     */
+    private BigDecimal totalElectricity;
 
 }

+ 4 - 1
fiveep-persistence/src/main/java/com/bizmatics/persistence/mapper/DeviceMapper.java

@@ -59,5 +59,8 @@ public interface DeviceMapper extends CrudMapper<Device> {
 
     List<DeviceOneVo> deviceBoxList(@Param("siteId") Integer siteId);
 
-
+    List<DeviceList> deviceBranch(@Param("siteId") int siteId,
+                                  @Param("deviceType") int deviceType,
+                                  @Param("pageNum") int pageNum,
+                                  @Param("pageSize") int pageSize);
 }

+ 3 - 1
fiveep-persistence/src/main/java/com/bizmatics/persistence/mapper/HtAnalogDataMapper.java

@@ -181,7 +181,9 @@ public interface HtAnalogDataMapper extends CrudMapper<HtAnalogData> {
                                            @Param("endTime") String endTime,
                                            @Param("table") String table);
 
-    List<HtAnalogData> getP(@Param("deviceCodes") List<String> deviceCodes);
+    List<HtAnalogData> getP(@Param("deviceCodes") List<String> deviceCodes,
+                            @Param("startTime") LocalDateTime startTime,
+                            @Param("endTime") LocalDateTime endTime);
 
     List<Map<String, Object>> aggregateAll(TimeRangeParams params);
 }

+ 2 - 0
fiveep-persistence/src/main/java/com/bizmatics/persistence/mapper/RtAnalogDataMapper.java

@@ -69,4 +69,6 @@ public interface RtAnalogDataMapper extends CrudMapper<RtAnalogData> {
     List<RtAnalogData> getRtAnalogDataList(@Param("deviceCode") String deviceCode,
                                            @Param("table") String table);
 
+    List<RtAnalogData> getP(@Param("deviceCodes") List<String> deviceCodes);
+
 }

+ 41 - 10
fiveep-persistence/src/main/resources/mapper/mysql/DeviceMapper.xml

@@ -4,15 +4,15 @@
 
     <!-- 通用查询映射结果 -->
     <resultMap id="BaseResultMap" type="com.bizmatics.model.Device">
-        <id column="id" property="id" />
-        <result column="device_code" property="deviceCode" />
-        <result column="device_name" property="deviceName" />
-        <result column="site_id" property="siteId" />
-        <result column="device_address" property="deviceAddress" />
-        <result column="device_type" property="deviceType" />
-        <result column="install_time" property="installTime" />
-        <result column="creator" property="creator" />
-        <result column="enable" property="enable" />
+        <id column="id" property="id"/>
+        <result column="device_code" property="deviceCode"/>
+        <result column="device_name" property="deviceName"/>
+        <result column="site_id" property="siteId"/>
+        <result column="device_address" property="deviceAddress"/>
+        <result column="device_type" property="deviceType"/>
+        <result column="install_time" property="installTime"/>
+        <result column="creator" property="creator"/>
+        <result column="enable" property="enable"/>
     </resultMap>
     <select id="selectCount" resultType="java.lang.Integer">
         select count(1)
@@ -44,7 +44,8 @@
         </where>
     </select>
     <select id="list" resultType="com.bizmatics.model.Device">
-        select d.id,d.device_code,d.device_name,d.site_id,d.device_address,d.device_type,d.install_time,ds.device_status as deviceStatus,
+        select d.id,d.device_code,d.device_name,d.site_id,d.device_address,d.device_type,d.install_time,ds.device_status
+        as deviceStatus,
         s.installed_capacity as installedCapacity
         from user_site as us
         inner join device_status as ds
@@ -90,6 +91,36 @@
         </where>
     </select>
 
+    <select id="deviceBranch" resultType="com.bizmatics.model.DeviceList">
+        SELECT
+        a.*, c.rated_voltage, c.rated_current
+        FROM
+        device AS a
+        JOIN device_attribute AS c ON a.device_code = c.monitor_device_code
+        <where>
+            a.enable = 1
+            <if test="siteId != null and siteId != 0">
+                AND a.site_id = #{siteId}
+            </if>
+
+            <choose>
+                <!-- deviceType == 1 -->
+                <when test="deviceType == 1">
+                    AND a.device_type = 1
+                </when>
+                <!-- deviceType == 2 -->
+                <when test="deviceType == 2">
+                    AND a.device_type IN (3,4)
+                </when>
+            </choose>
+        </where>
+
+        <!-- 分页参数 -->
+        <if test="pageNum != null and pageSize != null">
+            LIMIT #{pageSize} OFFSET ${(pageNum - 1) * pageSize}
+        </if>
+    </select>
+
     <select id="CorrespondDeviceList" resultType="com.bizmatics.model.vo.CorrespondDeviceVO">
         SELECT
         a.id,

+ 6 - 3
fiveep-persistence/src/main/resources/mapper/mysql/HtAnalogDataMapper.xml

@@ -1554,11 +1554,14 @@
         SELECT deviceName, MAX(id) AS max_id
         FROM ht_analog_data
         WHERE deviceName IN
-        <foreach item="item" collection="deviceCodes" open="(" separator="," close=")">
-            #{item}
+        <foreach collection="deviceCodes" item="code" open="(" separator="," close=")">
+            #{code}
         </foreach>
+        AND dataTime BETWEEN #{startTime} AND #{endTime}
         GROUP BY deviceName
-        ) latest ON t.deviceName = latest.deviceName AND t.id = latest.max_id
+        ) latest
+        ON t.deviceName = latest.deviceName
+        AND t.id = latest.max_id
     </select>
 
     <select id="aggregateAll" resultType="map">

+ 92 - 80
fiveep-persistence/src/main/resources/mapper/mysql/RtAnalogDataMapper.xml

@@ -20,68 +20,68 @@
     </select>
     <select id="getOneMap" resultType="java.util.Map">
         select
-          `Busot` as '母线停电次数',
-          `COS` as '三相功率因数',
-          `COSa` as 'A相功率因数',
-          `COSb` as 'B相功率因数',
-          `COSc` as 'C相功率因数',
-          `Demand` as '实时有功需量',
-          `DevResetTimes` as '设备复位次数',
-          `DeviceTemp` as '设备温度',
-          `Epn` as '三相反向有功电度',
-          `Epn1` as '尖段反向有功电度',
-          `Epn2` as '峰段反向有功电度',
-          `Epn3` as '平段反向有功电度',
-          `Epn4` as '谷段反向有功电度',
-          `Epp` as '三相正向有功电度',
-          `Epp1` as '尖段正向有功电度',
-          `Epp2` as '峰段正向有功电度',
-          `Epp3` as '平段正向有功电度',
-          `Epp4` as '谷段正向有功电度',
-          `Eqn` as '三相反向无功电度',
-          `Eqp` as '三相正向无功电度',
-          `F` as '频率',
-          `I0` as '零序电流',
-          `I2` as '负序电流',
-          `IHa` as 'A相总谐波电流',
-          `IHb` as 'B相总谐波电流',
-          `IHc` as 'C相总谐波电流',
-          `Ia` as 'A相电流',
-          `Ib` as 'B相电流',
-          `Ic` as 'C相电流',
-          `Ir` as '剩余电流',
-          `LastDayMD` as '昨日有功最大需量',
-          `LastDayMDt` as '昨日有功最大需量发生时间',
-          `P` as '三相总有功功率',
-          `Pa` as 'A相有功功率',
-          `Pb` as 'B相有功功率',
-          `Pc` as 'C相有功功率',
-          `Q` as '三相总无功功率',
-          `Qa` as 'A相无功功率',
-          `Qb` as 'B相无功功率',
-          `Qc` as 'C相无功功率',
-          `SignalIntensity` as '信号强度',
-          `T1` as '第1路温度',
-          `T2` as '第2路温度',
-          `T3` as '第3路温度',
-          `T4` as '第4路温度',
-          `THDUa` as 'A相电压THD',
-          `THDUb` as 'B相电压THD',
-          `THDUc` as 'C相电压THD',
-          `Ua` as 'A相电压',
-          `Uab` as 'AB线电压',
-          `Ub` as 'B相电压',
-          `Ubc` as 'BC线电压',
-          `UblU0` as '零序电压不平衡度',
-          `UblU2` as '负序电压不平衡度',
-          `Uc` as 'C相电压',
-          `Uca` as 'CA线电压',
-          `Udt` as '电压暂降次数',
-          `Ul` as '线路侧电压',
-          `Upt` as '电压暂升次数',
-          `Ust` as '电压短时中断次数',
-          `freezingTime` as '冻结时间',
-          `dataTime` as '上报时间'
+        `Busot` as '母线停电次数',
+        `COS` as '三相功率因数',
+        `COSa` as 'A相功率因数',
+        `COSb` as 'B相功率因数',
+        `COSc` as 'C相功率因数',
+        `Demand` as '实时有功需量',
+        `DevResetTimes` as '设备复位次数',
+        `DeviceTemp` as '设备温度',
+        `Epn` as '三相反向有功电度',
+        `Epn1` as '尖段反向有功电度',
+        `Epn2` as '峰段反向有功电度',
+        `Epn3` as '平段反向有功电度',
+        `Epn4` as '谷段反向有功电度',
+        `Epp` as '三相正向有功电度',
+        `Epp1` as '尖段正向有功电度',
+        `Epp2` as '峰段正向有功电度',
+        `Epp3` as '平段正向有功电度',
+        `Epp4` as '谷段正向有功电度',
+        `Eqn` as '三相反向无功电度',
+        `Eqp` as '三相正向无功电度',
+        `F` as '频率',
+        `I0` as '零序电流',
+        `I2` as '负序电流',
+        `IHa` as 'A相总谐波电流',
+        `IHb` as 'B相总谐波电流',
+        `IHc` as 'C相总谐波电流',
+        `Ia` as 'A相电流',
+        `Ib` as 'B相电流',
+        `Ic` as 'C相电流',
+        `Ir` as '剩余电流',
+        `LastDayMD` as '昨日有功最大需量',
+        `LastDayMDt` as '昨日有功最大需量发生时间',
+        `P` as '三相总有功功率',
+        `Pa` as 'A相有功功率',
+        `Pb` as 'B相有功功率',
+        `Pc` as 'C相有功功率',
+        `Q` as '三相总无功功率',
+        `Qa` as 'A相无功功率',
+        `Qb` as 'B相无功功率',
+        `Qc` as 'C相无功功率',
+        `SignalIntensity` as '信号强度',
+        `T1` as '第1路温度',
+        `T2` as '第2路温度',
+        `T3` as '第3路温度',
+        `T4` as '第4路温度',
+        `THDUa` as 'A相电压THD',
+        `THDUb` as 'B相电压THD',
+        `THDUc` as 'C相电压THD',
+        `Ua` as 'A相电压',
+        `Uab` as 'AB线电压',
+        `Ub` as 'B相电压',
+        `Ubc` as 'BC线电压',
+        `UblU0` as '零序电压不平衡度',
+        `UblU2` as '负序电压不平衡度',
+        `Uc` as 'C相电压',
+        `Uca` as 'CA线电压',
+        `Udt` as '电压暂降次数',
+        `Ul` as '线路侧电压',
+        `Upt` as '电压暂升次数',
+        `Ust` as '电压短时中断次数',
+        `freezingTime` as '冻结时间',
+        `dataTime` as '上报时间'
         from user_site as us
         inner join device as d
         on us.site_id = d.site_id
@@ -89,10 +89,10 @@
         on d.device_code = rad.deviceName
         <where>
             <if test="siteId != null and siteId != 0">
-              us.site_id = #{siteId}
+                us.site_id = #{siteId}
             </if>
             <if test="userId != null and userId != 0">
-              and  us.user_id = #{userId}
+                and us.user_id = #{userId}
             </if>
         </where>
     </select>
@@ -121,7 +121,7 @@
         inner join device as d
         on us.site_id = d.site_id
         <where>
-                and d.enable=1
+            and d.enable=1
             <if test="siteId != null and siteId != 0">
                 and d.site_id = #{siteId}
             </if>
@@ -262,12 +262,12 @@
         FROM
         ht_analog_data
         <where>
-                and deviceName in
-            <foreach item="item"  collection="deviceCode" open="(" separator="," close=")">
+            and deviceName in
+            <foreach item="item" collection="deviceCode" open="(" separator="," close=")">
                 #{item.deviceCode}
             </foreach>
             <if test="endTime != null and startTime != null">
-                and dataTime BETWEEN   #{startTime} and #{endTime}
+                and dataTime BETWEEN #{startTime} and #{endTime}
             </if>
         </where>
         ) t
@@ -412,11 +412,11 @@
         ht_analog_data
         <where>
             and deviceName in
-            <foreach item="item"  collection="deviceCode" open="(" separator="," close=")">
+            <foreach item="item" collection="deviceCode" open="(" separator="," close=")">
                 #{item.deviceCode}
             </foreach>
             <if test="endTime != null and startTime != null">
-                and dataTime BETWEEN   #{startTime} and #{endTime}
+                and dataTime BETWEEN #{startTime} and #{endTime}
             </if>
         </where>
         ) t
@@ -580,11 +580,11 @@
         ht_analog_data
         <where>
             and deviceName in
-            <foreach item="item"  collection="deviceCode" open="(" separator="," close=")">
+            <foreach item="item" collection="deviceCode" open="(" separator="," close=")">
                 #{item.deviceCode}
             </foreach>
             <if test="endTime != null and startTime != null">
-                and dataTime BETWEEN   #{startTime} and #{endTime}
+                and dataTime BETWEEN #{startTime} and #{endTime}
             </if>
         </where>
         ) t
@@ -731,11 +731,11 @@
         ht_analog_data
         <where>
             and deviceName in
-            <foreach item="item"  collection="deviceCode" open="(" separator="," close=")">
+            <foreach item="item" collection="deviceCode" open="(" separator="," close=")">
                 #{item.deviceCode}
             </foreach>
             <if test="endTime != null and startTime != null">
-                and dataTime BETWEEN   #{startTime} and #{endTime}
+                and dataTime BETWEEN #{startTime} and #{endTime}
             </if>
         </where>
         ) t
@@ -827,11 +827,23 @@
 
 
     <select id="getRtAnalogDataList" resultType="com.bizmatics.model.RtAnalogData">
-        SELECT
-            *
-        FROM
-            ${table}
-        WHERE
-            deviceName = #{deviceCode}
+        SELECT *
+        FROM ${table}
+        WHERE deviceName = #{deviceCode}
     </select>
+
+    <select id="getP" resultType="com.bizmatics.model.RtAnalogData">
+        SELECT t.deviceName, t.p, t.dataTime
+        FROM rt_analog_data t
+        INNER JOIN (
+        SELECT deviceName, MAX(id) AS max_id
+        FROM rt_analog_data
+        WHERE deviceName IN
+        <foreach item="item" collection="deviceCodes" open="(" separator="," close=")">
+            #{item}
+        </foreach>
+        GROUP BY deviceName
+        ) latest ON t.deviceName = latest.deviceName AND t.id = latest.max_id
+    </select>
+
 </mapper>

+ 16 - 0
fiveep-service/pom.xml

@@ -112,6 +112,22 @@
             <groupId>com.google.protobuf</groupId>
             <artifactId>protobuf-java</artifactId>
         </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+        </dependency>
+
+        <!-- Apache POI -->
+<!--        <dependency>-->
+<!--            <groupId>org.apache.poi</groupId>-->
+<!--            <artifactId>poi</artifactId>-->
+<!--            <version>5.2.2</version>-->
+<!--        </dependency>-->
+<!--        <dependency>-->
+<!--            <groupId>org.apache.poi</groupId>-->
+<!--            <artifactId>poi-ooxml</artifactId>-->
+<!--            <version>5.4.0</version>-->
+<!--        </dependency>-->
     </dependencies>
 
 

+ 4 - 2
fiveep-service/src/main/java/com/bizmatics/service/DeviceService.java

@@ -37,7 +37,7 @@ public interface DeviceService extends CrudService<Device> {
      */
     DeviceCountVO selectDeviceCountByType(Integer site);
 
-    List<Device> list(Integer userId,Integer siteId,Integer deviceStatus,Date startTime,Date endTime,String type);
+    List<Device> list(Integer userId, Integer siteId, Integer deviceStatus, Date startTime, Date endTime, String type);
 
     List<DeviceList> deviceList(String siteId);
 
@@ -57,7 +57,7 @@ public interface DeviceService extends CrudService<Device> {
 
     void variableCloning(Integer type, String newDeviceCode, String oldDeviceCode, String deviceName);
 
-    List<Device> deviceListOne(Integer siteId,Integer deviceType);
+    List<Device> deviceListOne(Integer siteId, Integer deviceType);
 
     String deviceExport(String deviceName, Integer deviceType, Integer siteId);
 
@@ -67,4 +67,6 @@ public interface DeviceService extends CrudService<Device> {
 
 
     List<DeviceOneVo> deviceBoxList(Integer siteId);
+
+    List<DeviceList> deviceBranch(int siteId, int deviceType, int pageNum, int pageSize);
 }

+ 28 - 5
fiveep-service/src/main/java/com/bizmatics/service/HtAnalogDataService.java

@@ -1,14 +1,14 @@
 package com.bizmatics.service;
 
 import com.bizmatics.common.core.bean.ApiResult;
+import com.bizmatics.common.core.bean.CommonPage;
 import com.bizmatics.common.mvc.base.CrudService;
 import com.bizmatics.model.HtAnalogData;
 import com.bizmatics.model.vo.*;
-import com.bizmatics.service.vo.CommonIcoVO;
-import com.bizmatics.service.vo.HadCountVO;
-import com.bizmatics.service.vo.RealScoreVO;
-import com.bizmatics.service.vo.TimeShareVO;
+import com.bizmatics.service.vo.*;
 
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.util.Date;
@@ -108,7 +108,7 @@ public interface HtAnalogDataService extends CrudService<HtAnalogData> {
     SingleLoopReportOneVo SingleLoopReportData(String deviceCode, Date time, int type);
 
     // 获取设备三相总有功功率值
-    List<HtAnalogData> getP(List<String> deviceCode);
+    List<HtAnalogData> getP(List<String> deviceCode, LocalDateTime startTime, LocalDateTime endTime);
 
     /**
      * 获取设备三相有功功率值
@@ -131,4 +131,27 @@ public interface HtAnalogDataService extends CrudService<HtAnalogData> {
      * @return HtAnalogEnergySegmentedVo
      */
     HtAnalogEnergySegmentedVo getSegmentedData(Integer siteId, String queryPeriod, LocalDateTime queryTime, String queryType);
+
+    /**
+     * 分时电量-表格统计
+     *
+     * @param request
+     * @return
+     */
+    CommonPage<TimeSharingElectricityResponseVO> timeSharingElectricity(TimeSharingElectricityRequestVO request);
+
+    /**
+     * 分时电量-趋势统计
+     *
+     * @param trendVO
+     * @return
+     */
+    List<ElectricityTrendResponseVO> trend(ElectricityTrendRequestVO trendVO);
+
+    /**
+     * 分时电量-导出
+     *
+     * @param request
+     */
+    void export(TimeSharingElectricityRequestVO request, HttpServletResponse response) throws IOException;
 }

+ 8 - 4
fiveep-service/src/main/java/com/bizmatics/service/RtAnalogDataService.java

@@ -2,6 +2,7 @@ package com.bizmatics.service;
 
 import com.bizmatics.common.mvc.base.CrudService;
 import com.bizmatics.model.DeviceAttribute;
+import com.bizmatics.model.HtAnalogData;
 import com.bizmatics.model.RtAnalogData;
 import com.bizmatics.model.SiteDynamicProperties;
 import com.bizmatics.model.vo.EvaluationReporVo;
@@ -35,7 +36,7 @@ public interface RtAnalogDataService extends CrudService<RtAnalogData> {
      * @param siteId 站点id
      * @return
      */
-    List<Map<String,Object>> getOne(Integer siteId);
+    List<Map<String, Object>> getOne(Integer siteId);
 
     /**
      * 实时负荷
@@ -77,7 +78,7 @@ public interface RtAnalogDataService extends CrudService<RtAnalogData> {
      * @param queryType
      * @return
      */
-    List<Map<String,Object>> getDataReport(Integer siteId,Date startTime, Date endTime,String queryType);
+    List<Map<String, Object>> getDataReport(Integer siteId, Date startTime, Date endTime, String queryType);
 
     /**
      *
@@ -87,7 +88,7 @@ public interface RtAnalogDataService extends CrudService<RtAnalogData> {
      * @param queryType
      * @return
      */
-    List<Map<String,Object>> getHistoricalCurve(Integer siteId,Date startTime, Date endTime,String queryType);
+    List<Map<String, Object>> getHistoricalCurve(Integer siteId, Date startTime, Date endTime, String queryType);
 
     /**
      *
@@ -97,11 +98,14 @@ public interface RtAnalogDataService extends CrudService<RtAnalogData> {
      * @param queryType
      * @return
      */
-    String DataReportExport(Integer siteId,Date startTime, Date endTime,String queryType);
+    String DataReportExport(Integer siteId, Date startTime, Date endTime, String queryType);
 
 
     List<RealScoreVO> fillRealScoreDataTwo(List<EvaluationReporVo> evaluationReporVo);
 
     List<RealScoreVO> evaluationReport(int siteId, Date time, int type);
 
+    // 获取三项总有功功率
+    List<RtAnalogData> getP(List<String> deviceCode);
+
 }

+ 3 - 1
fiveep-service/src/main/java/com/bizmatics/service/SiteService.java

@@ -96,8 +96,10 @@ public interface SiteService extends CrudService<Site> {
     /**
      * 站点负载分析
      * @param siteId 站点id
+     * @param startTime 开始时间
+     * @param endTime 结束时间
      * @return 站点负载分析
      */
-    CommonPage<SiteLoadAnalysisVO> siteLoadAnalysis(Integer siteId, Integer pageNum, Integer pageSize);
+    List<SiteLoadAnalysisVO> siteLoadAnalysis(Integer siteId, String startTime, String endTime);
 }
 

+ 40 - 39
fiveep-service/src/main/java/com/bizmatics/service/impl/DeviceServiceImpl.java

@@ -43,10 +43,7 @@ import java.io.FileOutputStream;
 import java.io.IOException;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.Optional;
+import java.util.*;
 
 /**
  * 设备
@@ -143,7 +140,7 @@ public class DeviceServiceImpl extends AbstractCrudService<DeviceMapper, Device>
                     for (int j = 0; j < deviceAnalogVariableList.size(); j++) {
                         if (deviceList.get(i).getId().equals(deviceAnalogVariableList.get(j).getMonitoringEquipment())) {
                             DeviceAnalogVariableList deviceAnalogVariableListThree = new DeviceAnalogVariableList();
-                            deviceAnalogVariableListThree.setVariableCoding(deviceAnalogVariableList.get(j).getVariableCoding()+"_"+deviceList.get(i).getId());
+                            deviceAnalogVariableListThree.setVariableCoding(deviceAnalogVariableList.get(j).getVariableCoding() + "_" + deviceList.get(i).getId());
                             deviceAnalogVariableListThree.setVariableName(deviceAnalogVariableList.get(j).getVariableName());
                             deviceAnalogVariableListThree.setId(deviceAnalogVariableList.get(j).getId());
                             deviceAnalogVariableListTwo.add(deviceAnalogVariableListThree);
@@ -159,7 +156,7 @@ public class DeviceServiceImpl extends AbstractCrudService<DeviceMapper, Device>
     }
 
     @Override
-    public List<DeviceOneVo> deviceBoxList(Integer siteId){
+    public List<DeviceOneVo> deviceBoxList(Integer siteId) {
         List<DeviceOneVo> deviceOneVo = baseMapper.deviceBoxList(siteId);
         return deviceOneVo;
     }
@@ -198,16 +195,16 @@ public class DeviceServiceImpl extends AbstractCrudService<DeviceMapper, Device>
     @Override
     public CommonPage<CorrespondDeviceVO> correspondDeviceList(String deviceName, int size, int current) {
         List<SysUser> tenantDaya = userMapper.getTenantId(SecurityUtils.getLoginUser().getUser().getUserId());
-        if (tenantDaya.size()<0) {
+        if (tenantDaya.size() < 0) {
             throw new BusinessException("无此租户,请联系管理员");
         }
-        List<CorrespondDeviceVO> correspondDeviceListOne = baseMapper.CorrespondDeviceList(deviceName, null, null,tenantDaya.get(0).getTenantId());
+        List<CorrespondDeviceVO> correspondDeviceListOne = baseMapper.CorrespondDeviceList(deviceName, null, null, tenantDaya.get(0).getTenantId());
         int total = 0;
         if (correspondDeviceListOne.size() > 0) {
             total = correspondDeviceListOne.size();
         }
         int startCurrent = (current - 1) * size;
-        List<CorrespondDeviceVO> correspondDeviceList = baseMapper.CorrespondDeviceList(deviceName, startCurrent, size,tenantDaya.get(0).getTenantId());
+        List<CorrespondDeviceVO> correspondDeviceList = baseMapper.CorrespondDeviceList(deviceName, startCurrent, size, tenantDaya.get(0).getTenantId());
         return new CommonPage<>(correspondDeviceList, total, size, current);
     }
 
@@ -231,7 +228,7 @@ public class DeviceServiceImpl extends AbstractCrudService<DeviceMapper, Device>
 
     @Override
     public CommonPage<Device> videoMonitoringDeviceList(String deviceName, Integer deviceType, Integer siteId, Integer size, Integer current) {
-        IPage<Device> page = new Page<Device>(current,size);
+        IPage<Device> page = new Page<Device>(current, size);
         LambdaQueryWrapper<Device> queryWrapper = Wrappers.lambdaQuery();
         queryWrapper.eq(Device::getSiteId, siteId).eq(Device::getEnable, 1);
         if (deviceType != null && deviceType != 0) {
@@ -243,13 +240,13 @@ public class DeviceServiceImpl extends AbstractCrudService<DeviceMapper, Device>
 
         page = this.page(page, queryWrapper);
         this.ToCommonPage(page);
-        return new CommonPage<>(page.getRecords(), page.getTotal(), page.getSize(),page.getCurrent() );
+        return new CommonPage<>(page.getRecords(), page.getTotal(), page.getSize(), page.getCurrent());
     }
 
     @Override
     public void variableCloning(Integer type, String newDeviceCode, String oldDeviceCode, String deviceName) {
         SysUser user = SecurityUtils.getLoginUser().getUser();
-        //查询出旧设备配置
+        // 查询出旧设备配置
         LambdaQueryWrapper<Device> queryWrapper = Wrappers.lambdaQuery();
         queryWrapper.eq(Device::getEnable, 1).eq(Device::getDeviceCode, oldDeviceCode);
         List<Device> deviceList = this.list(queryWrapper);
@@ -258,10 +255,10 @@ public class DeviceServiceImpl extends AbstractCrudService<DeviceMapper, Device>
             LambdaQueryWrapper<Device> queryWrapperFour = Wrappers.lambdaQuery();
             queryWrapperFour.eq(Device::getEnable, 1).eq(Device::getDeviceCode, newDeviceCode);
             List<Device> deviceListrFour = this.list(queryWrapperFour);
-            if (deviceListrFour.size()>0){
-                throw new BusinessException(newDeviceCode+"设备编号重复");
-            }else {
-                //设备表新增
+            if (deviceListrFour.size() > 0) {
+                throw new BusinessException(newDeviceCode + "设备编号重复");
+            } else {
+                // 设备表新增
                 Device device = new Device();
                 device.setEnable(1);
                 device.setInstallTime(new Date());
@@ -277,14 +274,14 @@ public class DeviceServiceImpl extends AbstractCrudService<DeviceMapper, Device>
                 Integer deviceId = device.getId();
                 String deviceCode = device.getDeviceCode();
                 Integer siteId = device.getSiteId();
-                //设备状态表新增
+                // 设备状态表新增
                 DeviceStatus deviceStatus = new DeviceStatus();
                 deviceStatus.setDeviceStatus(1);
                 deviceStatus.setDeviceCode(deviceCode);
                 deviceStatus.setStatusTime(new Date());
                 deviceStatus.setSiteId(siteId);
                 deviceStatusService.save(deviceStatus);
-                //变量配置查询
+                // 变量配置查询
                 LambdaQueryWrapper<DeviceAnalogVariableList> queryWrapperOne = Wrappers.lambdaQuery();
                 queryWrapperOne.eq(DeviceAnalogVariableList::getStatus, 1).eq(DeviceAnalogVariableList::getCommunicationEquipment, deviceList.get(0).getId());
                 List<DeviceAnalogVariableList> deviceAnalogVariableList = deviceAnalogVariableListService.list(queryWrapperOne);
@@ -293,7 +290,7 @@ public class DeviceServiceImpl extends AbstractCrudService<DeviceMapper, Device>
                         DeviceAnalogVariableList deviceAnalogVariableListOne = new DeviceAnalogVariableList();
                         deviceAnalogVariableListOne.setDeviceCode(newDeviceCode);
                         deviceAnalogVariableListOne.setVariableName(deviceAnalogVariableList.get(i).getVariableName());
-                        deviceAnalogVariableListOne.setVariableCoding(newDeviceCode+"_"+deviceAnalogVariableList.get(i).getVariableCoding().split("_")[1]);
+                        deviceAnalogVariableListOne.setVariableCoding(newDeviceCode + "_" + deviceAnalogVariableList.get(i).getVariableCoding().split("_")[1]);
                         deviceAnalogVariableListOne.setMonitoringEquipment(0);
                         deviceAnalogVariableListOne.setCommunicationEquipment(deviceId);
                         deviceAnalogVariableListOne.setDataAddress(deviceAnalogVariableList.get(i).getDataAddress());
@@ -313,7 +310,7 @@ public class DeviceServiceImpl extends AbstractCrudService<DeviceMapper, Device>
             queryWrapperOneA.eq(DeviceAnalogVariableList::getStatus, 1).eq(DeviceAnalogVariableList::getCommunicationEquipment, deviceList.get(0).getId());
             List<DeviceAnalogVariableList> deviceAnalogVariableListOne = deviceAnalogVariableListService.list(queryWrapperOneA);
 
-            //克隆设备查询是否存在
+            // 克隆设备查询是否存在
             LambdaQueryWrapper<Device> queryWrapperTwo = Wrappers.lambdaQuery();
             queryWrapperTwo.eq(Device::getEnable, 1).eq(Device::getDeviceCode, newDeviceCode);
             List<Device> deviceListTwo = this.list(queryWrapperTwo);
@@ -335,7 +332,7 @@ public class DeviceServiceImpl extends AbstractCrudService<DeviceMapper, Device>
                     DeviceAnalogVariableList deviceAnalogVariableList = new DeviceAnalogVariableList();
                     deviceAnalogVariableList.setDeviceCode(newDeviceCode);
                     deviceAnalogVariableList.setVariableName(deviceAnalogVariableListOne.get(i).getVariableName());
-                    deviceAnalogVariableList.setVariableCoding(newDeviceCode+"_"+deviceAnalogVariableListOne.get(i).getVariableCoding().split("_")[1]);
+                    deviceAnalogVariableList.setVariableCoding(newDeviceCode + "_" + deviceAnalogVariableListOne.get(i).getVariableCoding().split("_")[1]);
                     deviceAnalogVariableList.setMonitoringEquipment(0);
                     deviceAnalogVariableList.setCommunicationEquipment(deviceListTwo.get(0).getId());
                     deviceAnalogVariableList.setDataType(deviceAnalogVariableListOne.get(i).getDataType());
@@ -375,7 +372,7 @@ public class DeviceServiceImpl extends AbstractCrudService<DeviceMapper, Device>
                         Page<Device> page = new Page<>(i, 30);
                         LambdaQueryWrapper<Device> queryWrapper = Wrappers.lambdaQuery();
                         queryWrapper.eq(Device::getEnable, 1);
-                        if (siteId!=0){
+                        if (siteId != 0) {
                             queryWrapper.eq(Device::getSiteId, siteId);
                         }
 
@@ -388,7 +385,7 @@ public class DeviceServiceImpl extends AbstractCrudService<DeviceMapper, Device>
 
                         page = this.page(page, queryWrapper);
 
-                        for (int j = 0; j < page.getRecords().size(); j++){
+                        for (int j = 0; j < page.getRecords().size(); j++) {
                             DeviceOne deviceOne = new DeviceOne();
                             deviceOne.setDeviceCode(page.getRecords().get(j).getDeviceCode());
                             deviceOne.setDeviceName(page.getRecords().get(j).getDeviceName());
@@ -438,7 +435,7 @@ public class DeviceServiceImpl extends AbstractCrudService<DeviceMapper, Device>
         Workbook workbook = null;
         File file = null;
         List<SysUser> tenantDaya = userMapper.getTenantId(SecurityUtils.getLoginUser().getUser().getUserId());
-        if (tenantDaya.size()<0) {
+        if (tenantDaya.size() < 0) {
             throw new BusinessException("无此租户,请联系管理员");
         }
         try {
@@ -447,8 +444,8 @@ public class DeviceServiceImpl extends AbstractCrudService<DeviceMapper, Device>
                     (o, i) -> {
                         List<CorrespondDeviceTwoVO> Active = new ArrayList<CorrespondDeviceTwoVO>();
                         int startCurrent = (i - 1) * 30;
-                        List<CorrespondDeviceVO> correspondDeviceList = baseMapper.CorrespondDeviceList(deviceName, startCurrent, 30,tenantDaya.get(0).getTenantId());
-                        for (int j = 0; j <correspondDeviceList.size(); j++){
+                        List<CorrespondDeviceVO> correspondDeviceList = baseMapper.CorrespondDeviceList(deviceName, startCurrent, 30, tenantDaya.get(0).getTenantId());
+                        for (int j = 0; j < correspondDeviceList.size(); j++) {
                             CorrespondDeviceTwoVO correspondDeviceTwoVO = new CorrespondDeviceTwoVO();
                             correspondDeviceTwoVO.setDeviceCode(correspondDeviceList.get(j).getDeviceCode());
                             correspondDeviceTwoVO.setDeviceName(correspondDeviceList.get(j).getDeviceName());
@@ -497,19 +494,19 @@ public class DeviceServiceImpl extends AbstractCrudService<DeviceMapper, Device>
         SysUser user = SecurityUtils.getLoginUser().getUser();
         ImportParams params = new ImportParams();
         params.setHeadRows(1);
-        String err="文件导入失败";
+        String err = "文件导入失败";
         try {
             List<DeviceImportVo> deviceImportVos = ExcelImportUtil.importExcel(multipartFile.getInputStream(),
                     DeviceImportVo.class, params);
-            if (CollectionUtils.isNotEmpty(deviceImportVos)){
-                int rot=0;
-                for (DeviceImportVo deviceImportVo:deviceImportVos) {
+            if (CollectionUtils.isNotEmpty(deviceImportVos)) {
+                int rot = 0;
+                for (DeviceImportVo deviceImportVo : deviceImportVos) {
                     DeviceStatus deviceStatus = new DeviceStatus();
                     Device device = BeanMapperUtils.map(deviceImportVo, Device.class);
                     device.setEnable(1);
                     device.setCreator(user.getUserName());
                     device.setInstallTime(new Date());
-                    try{
+                    try {
                         this.save(device);
                         String deviceCode = device.getDeviceCode();
                         Integer siteId = device.getSiteId();
@@ -518,21 +515,25 @@ public class DeviceServiceImpl extends AbstractCrudService<DeviceMapper, Device>
                         deviceStatus.setStatusTime(new Date());
                         deviceStatus.setSiteId(siteId);
                         deviceStatusService.save(deviceStatus);
-                    }catch (Exception e){
-                        int h=rot+2;
-                        err="文件导入失败,第"+h+"行数据导入失败";
-                        throw  new BusinessException(err);
+                    } catch (Exception e) {
+                        int h = rot + 2;
+                        err = "文件导入失败,第" + h + "行数据导入失败";
+                        throw new BusinessException(err);
                     }
                     rot++;
                 }
-            }else {
-                err="文件不能为空";
+            } else {
+                err = "文件不能为空";
                 throw new BusinessException(err);
             }
-        }catch (Exception e){
+        } catch (Exception e) {
             TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
-            throw  new BusinessException(err);
+            throw new BusinessException(err);
         }
     }
 
+    @Override
+    public List<DeviceList> deviceBranch(int siteId, int deviceType, int pageNum, int pageSize) {
+        return baseMapper.deviceBranch(siteId, deviceType, pageNum, pageSize);
+    }
 }

+ 598 - 5
fiveep-service/src/main/java/com/bizmatics/service/impl/HtAnalogDataServiceImpl.java

@@ -5,13 +5,13 @@ import cn.afterturn.easypoi.excel.entity.ExportParams;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.bizmatics.common.core.bean.CommonPage;
 import com.bizmatics.common.core.exception.BusinessException;
 import com.bizmatics.common.core.util.*;
 import com.bizmatics.common.mvc.base.AbstractCrudService;
 import com.bizmatics.common.spring.util.GlobalUtils;
 import com.bizmatics.common.spring.util.JsonUtils;
 import com.bizmatics.model.*;
-import com.bizmatics.model.utils.TimeRangeParams;
 import com.bizmatics.model.utils.TimeRangeUtils;
 import com.bizmatics.model.vo.*;
 import com.bizmatics.persistence.mapper.*;
@@ -22,25 +22,34 @@ import com.bizmatics.service.util.SecurityUtils;
 import com.bizmatics.service.util.SiteFeeCacheService;
 import com.bizmatics.service.vo.*;
 import com.fasterxml.jackson.core.type.TypeReference;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.poi.ss.usermodel.Workbook;
-import org.checkerframework.checker.units.qual.A;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import javax.servlet.http.HttpServletResponse;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.OutputStream;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
 import java.text.DateFormat;
 import java.text.DecimalFormat;
 import java.text.SimpleDateFormat;
 import java.time.*;
+import java.time.format.DateTimeFormatter;
 import java.time.temporal.ChronoUnit;
 import java.time.temporal.TemporalAdjusters;
 import java.util.*;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -88,6 +97,8 @@ public class HtAnalogDataServiceImpl extends AbstractCrudService<HtAnalogDataMap
 
     private static final BigDecimal ZERO = BigDecimal.ZERO;
 
+    private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+
     @Override
     public HadCountVO selectCount() {
         HadCountVO hadCountVO = new HadCountVO();
@@ -1501,8 +1512,8 @@ public class HtAnalogDataServiceImpl extends AbstractCrudService<HtAnalogDataMap
 
     // 获取设备三相总有功功率值
     @Override
-    public List<HtAnalogData> getP(List<String> deviceCodes) {
-        return htAnalogDataMapper.getP(deviceCodes);
+    public List<HtAnalogData> getP(List<String> deviceCodes, LocalDateTime startTime, LocalDateTime endTime) {
+        return htAnalogDataMapper.getP(deviceCodes, startTime, endTime);
     }
 
     /**
@@ -3028,5 +3039,587 @@ public class HtAnalogDataServiceImpl extends AbstractCrudService<HtAnalogDataMap
                 .reduce(ZERO, BigDecimal::add);
     }
 
+    @Override
+    public CommonPage<TimeSharingElectricityResponseVO> timeSharingElectricity(TimeSharingElectricityRequestVO request) {
+        /* ---------- 1. 参数兜底 ---------- */
+        Integer pageNum = request.getPageNum() == null ? 1 : request.getPageNum();
+        Integer pageSize = request.getPageSize() == null ? 20 : request.getPageSize();
+        if (request.getSiteId() == null) {
+            throw new BusinessException("站点ID不能为空");
+        }
+        if (CollectionUtils.isEmpty(request.getDeviceCodes())) {
+            throw new BusinessException("设备代码列表不能为空");
+        }
+        if (request.getStartTime() == null || request.getEndTime() == null) {
+            throw new BusinessException("起止时间不能为空");
+        }
+
+        /* ---------- 2. 分页查询 ---------- */
+        PageHelper.startPage(pageNum, pageSize);
+        LambdaQueryWrapper<SiteElectricityRecord> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(SiteElectricityRecord::getSiteId, request.getSiteId())
+                .in(SiteElectricityRecord::getDeviceCode, request.getDeviceCodes())
+                .between(SiteElectricityRecord::getDate, request.getStartTime(), request.getEndTime())
+                .orderByAsc(SiteElectricityRecord::getDate, SiteElectricityRecord::getDeviceCode);
+
+        List<SiteElectricityRecord> records = siteElectricityRecordMapper.selectList(wrapper);
+
+        // 3. 按 deviceCode 分组汇总
+        List<TimeSharingElectricityResponseVO> list = records.stream()
+                .collect(Collectors.groupingBy(
+                        SiteElectricityRecord::getDeviceCode,
+                        LinkedHashMap::new,
+                        Collectors.reducing((a, b) -> {
+                            // 累加电量
+                            a.setSharpPeak(a.getSharpPeak().add(b.getSharpPeak()));
+                            a.setPeak(a.getPeak().add(b.getPeak()));
+                            a.setFlat(a.getFlat().add(b.getFlat()));
+                            a.setValley(a.getValley().add(b.getValley()));
+                            a.setTotalElectricity(a.getTotalElectricity().add(b.getTotalElectricity()));
+
+                            // 累加电费
+                            a.setSharpPeakCost(a.getSharpPeakCost().add(b.getSharpPeakCost()));
+                            a.setPeakCost(a.getPeakCost().add(b.getPeakCost()));
+                            a.setFlatCost(a.getFlatCost().add(b.getFlatCost()));
+                            a.setValleyCost(a.getValleyCost().add(b.getValleyCost()));
+                            a.setTotalCost(a.getTotalCost().add(b.getTotalCost()));
+                            return a;
+                        })
+                ))
+                .values().stream()
+                .map(opt -> opt.orElseThrow(() -> new RuntimeException("汇总数据不能为空")))
+                .map(r -> {
+                    TimeSharingElectricityResponseVO vo = new TimeSharingElectricityResponseVO();
+                    vo.setDeviceName(r.getDeviceCode());
+                    vo.setSharpNum(r.getSharpPeak());
+                    vo.setPeakNum(r.getPeak());
+                    vo.setFlatNum(r.getFlat());
+                    vo.setValleyNum(r.getValley());
+                    vo.setTotalNum(r.getTotalElectricity());
+                    vo.setSharpPrice(r.getSharpPeakCost());
+                    vo.setPeakPrice(r.getPeakCost());
+                    vo.setFlatPrice(r.getFlatCost());
+                    vo.setValleyPrice(r.getValleyCost());
+                    vo.setTotalPrice(r.getTotalCost());
+                    vo.setTime(request.getStartTime().toString() +
+                            " - " + request.getEndTime().toString());
+                    return vo;
+                })
+                .collect(Collectors.toList());
+
+        /* ---------- 4. 封装分页结果 ---------- */
+        PageInfo<TimeSharingElectricityResponseVO> pageInfo = new PageInfo<>(list);
+        return new CommonPage<>(list, pageInfo.getTotal(), pageNum, pageSize);
+    }
+
+    @Override
+    public List<ElectricityTrendResponseVO> trend(ElectricityTrendRequestVO request) {
+        // 1. 参数校验
+        if (request.getSiteId() == null) {
+            throw new BusinessException("站点ID不能为空");
+        }
+        if (CollectionUtils.isEmpty(request.getDeviceCodes())) {
+            throw new BusinessException("设备代码列表不能为空");
+        }
+        if (request.getStartTime() == null || request.getEndTime() == null || request.getStartTime().isAfter(request.getEndTime())) {
+            throw new BusinessException("起止时间异常");
+        }
+        if (request.getType() == null || !Arrays.asList(1, 2).contains(request.getType())) {
+            throw new BusinessException("查询类型必须为1(电量)或2(电费)");
+        }
+
+        LocalDate startDate = request.getStartTime();
+        LocalDate endDate = request.getEndTime();
+
+        // 2. 查询数据
+        LambdaQueryWrapper<SiteElectricityRecord> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(SiteElectricityRecord::getSiteId, request.getSiteId())
+                .in(SiteElectricityRecord::getDeviceCode, request.getDeviceCodes())
+                .between(SiteElectricityRecord::getDate, startDate, endDate)
+                .orderByAsc(SiteElectricityRecord::getDeviceCode, SiteElectricityRecord::getDate);
+
+        List<SiteElectricityRecord> records = siteElectricityRecordMapper.selectList(wrapper);
+
+        // 3. 判断维度
+        long days = ChronoUnit.DAYS.between(startDate, endDate) + 1;
+        boolean monthMode = days > 31;
+
+        // 4. 选择字段函数(电量 or 电费)
+        boolean isElectricity = request.getType() == 1;
+        Function<SiteElectricityRecord, BigDecimal> sharpFn = isElectricity ?
+                SiteElectricityRecord::getSharpPeak : SiteElectricityRecord::getSharpPeakCost;
+        Function<SiteElectricityRecord, BigDecimal> peakFn = isElectricity ?
+                SiteElectricityRecord::getPeak : SiteElectricityRecord::getPeakCost;
+        Function<SiteElectricityRecord, BigDecimal> flatFn = isElectricity ?
+                SiteElectricityRecord::getFlat : SiteElectricityRecord::getFlatCost;
+        Function<SiteElectricityRecord, BigDecimal> valleyFn = isElectricity ?
+                SiteElectricityRecord::getValley : SiteElectricityRecord::getValleyCost;
+
+        records.forEach(r -> log.info("id={}, date={}, toString={}", r.getId(), r.getDate(), r.getDate().toString()));
+
+        // 5. 分组汇总
+        Map<String, Map<String, List<SiteElectricityRecord>>> group =
+                records.stream()
+                        .collect(Collectors.groupingBy(
+                                SiteElectricityRecord::getDeviceCode,
+                                LinkedHashMap::new,
+                                Collectors.groupingBy(r -> monthMode ?
+                                                YearMonth.from(r.getDate()).toString() : r.getDate().toString(),
+                                        LinkedHashMap::new,
+                                        Collectors.toList())
+                        ));
+
+
+        // 6. 转 VO
+        List<ElectricityTrendResponseVO> result = new ArrayList<>();
+        group.forEach((deviceCode, timeMap) -> {
+            ElectricityTrendResponseVO dev = new ElectricityTrendResponseVO();
+            dev.setDeviceCode(deviceCode);
+
+            List<ElectricityTrendVO> trendList = new ArrayList<>();
+            timeMap.forEach((timeKey, list) -> {
+
+                if (timeKey.contains("031") || timeKey.contains("-0031")) {
+                    throw new IllegalArgumentException("脏 timeKey 出现:" + timeKey);
+                }
+
+                BigDecimal sharp = list.stream().map(sharpFn).reduce(ZERO, BigDecimal::add);
+                BigDecimal peak = list.stream().map(peakFn).reduce(ZERO, BigDecimal::add);
+                BigDecimal flat = list.stream().map(flatFn).reduce(ZERO, BigDecimal::add);
+                BigDecimal valley = list.stream().map(valleyFn).reduce(ZERO, BigDecimal::add);
+
+                ElectricityTrendVO vo = new ElectricityTrendVO();
+                vo.setTime(timeKey);
+                vo.setSharp(sharp);
+                vo.setPeak(peak);
+                vo.setFlat(flat);
+                vo.setValley(valley);
+                trendList.add(vo);
+            });
+            dev.setData(trendList);
+            result.add(dev);
+        });
+
+        return result;
+    }
+
+    @Override
+    public void export(TimeSharingElectricityRequestVO request, HttpServletResponse response) throws IOException {
+        // 参数校验
+        if (request.getSiteId() == null) {
+            throw new BusinessException("站点ID不能为空");
+        }
+        if (CollectionUtils.isEmpty(request.getDeviceCodes())) {
+            throw new BusinessException("设备代码列表不能为空");
+        }
+        if (request.getStartTime() == null || request.getEndTime() == null) {
+            throw new BusinessException("起止时间不能为空");
+        }
+
+        // 格式化时间(解决之前的时间格式问题)
+        String start = formatter.format(request.getStartTime());
+        String end = formatter.format(request.getEndTime());
+
+        // 查询数据
+        LambdaQueryWrapper<SiteElectricityRecord> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(SiteElectricityRecord::getSiteId, request.getSiteId())
+                .in(SiteElectricityRecord::getDeviceCode, request.getDeviceCodes())
+                .between(SiteElectricityRecord::getDate, request.getStartTime(), request.getEndTime())
+                .orderByAsc(SiteElectricityRecord::getDate, SiteElectricityRecord::getDeviceCode);
+
+        List<SiteElectricityRecord> records = siteElectricityRecordMapper.selectList(wrapper);
+        log.info("查询到的记录数: {}", records.size());
+        if (records.isEmpty()) {
+            throw new BusinessException("没有找到符合条件的数据");
+        }
+
+        // 按设备分组汇总数据
+        List<TimeSharingElectricityResponseVO> dataList = aggregateData(records, start, end);
+
+        // 处理分页
+        List<TimeSharingElectricityResponseVO> pageList = handlePagination(dataList, request);
+
+        // 准备导出信息
+        String deviceTypePrefix = request.getDeviceType() == 1 ? "支路" : "分项";
+        String sheetName = deviceTypePrefix + "分时用电";
+        String fileName = String.format("%s分时用电(%s-%s).xlsx", deviceTypePrefix, start, end);
+        String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name());
+
+        // 导出Excel
+        try (Workbook workbook = new XSSFWorkbook()) {
+            Sheet sheet = workbook.createSheet(sheetName);
+
+            // 创建样式
+            CellStyle titleStyle = createTitleStyle(workbook);    // 新增标题样式
+            CellStyle headerStyle = createHeaderStyle(workbook);
+            CellStyle dataStyle = createDataStyle(workbook);
+            CellStyle percentageStyle = createPercentageStyle(workbook);
+
+            // 构建表格结构
+            int currentRow = 0;
+            currentRow = createTitleRow(sheet, titleStyle, sheetName, currentRow);  // 使用标题样式
+            currentRow = createHeaderRows(sheet, headerStyle, currentRow, deviceTypePrefix);
+
+            // 设置列宽
+            setColumnWidths(sheet);
+
+            // 插入数据
+            currentRow = insertDataIntoSheet(sheet, pageList, dataStyle, percentageStyle, currentRow);
+
+            // 响应处理
+            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+            response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFileName);
+
+            try (OutputStream outputStream = response.getOutputStream()) {
+                workbook.write(outputStream);
+            }
+        } catch (Exception e) {
+            log.error("导出文件失败", e);
+            throw new BusinessException("导出文件失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 创建标题行
+     */
+    private int createTitleRow(Sheet sheet, CellStyle titleStyle, String title, int rowIndex) {
+        Row titleRow = sheet.createRow(rowIndex++);
+        Cell titleCell = titleRow.createCell(0);
+        titleCell.setCellValue(title);
+        titleCell.setCellStyle(titleStyle);
+        // 合并标题行(0行0列到0行11列)
+        sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), titleRow.getRowNum(), 0, 11));
+        return rowIndex;
+    }
+
+    /**
+     * 创建表头行(包含单元格合并逻辑)
+     */
+    /**
+     * 创建表头行(包含单元格合并逻辑,确保所有单元格应用带边框的样式)
+     */
+    private int createHeaderRows(Sheet sheet, CellStyle headerStyle, int rowIndex, String deviceTypePrefix) {
+        // 第一级表头(rowIndex行)
+        Row headerRow = sheet.createRow(rowIndex);
+
+        // 1. 支路名称 - 合并两行(rowIndex和rowIndex+1行,0列)
+        sheet.addMergedRegion(new CellRangeAddress(rowIndex, rowIndex + 1, 0, 0));
+        // 为合并区域的两个行(rowIndex 和 rowIndex+1)的第0列单元格设置样式
+        for (int r = rowIndex; r <= rowIndex + 1; r++) {
+            Row row = sheet.getRow(r);
+            if (row == null) {
+                row = sheet.createRow(r);
+            }
+            Cell cell = row.getCell(0);
+            if (cell == null) {
+                cell = row.createCell(0);
+            }
+            cell.setCellValue(deviceTypePrefix + "名称");
+            cell.setCellStyle(headerStyle);
+        }
+
+        // 2. 尖峰 - 合并两列(rowIndex行,1-2列)
+        sheet.addMergedRegion(new CellRangeAddress(rowIndex, rowIndex, 1, 2));
+        // 为合并区域的两个列(1 和 2)的单元格设置样式
+        for (int c = 1; c <= 2; c++) {
+            Cell cell = headerRow.getCell(c);
+            if (cell == null) {
+                cell = headerRow.createCell(c);
+            }
+            cell.setCellValue("尖峰");
+            cell.setCellStyle(headerStyle);
+        }
+
+        // 3. 峰 - 合并两列(rowIndex行,3-4列)
+        sheet.addMergedRegion(new CellRangeAddress(rowIndex, rowIndex, 3, 4));
+        for (int c = 3; c <= 4; c++) {
+            Cell cell = headerRow.getCell(c);
+            if (cell == null) {
+                cell = headerRow.createCell(c);
+            }
+            cell.setCellValue("峰");
+            cell.setCellStyle(headerStyle);
+        }
+
+        // 4. 平 - 合并两列(rowIndex行,5-6列)
+        sheet.addMergedRegion(new CellRangeAddress(rowIndex, rowIndex, 5, 6));
+        for (int c = 5; c <= 6; c++) {
+            Cell cell = headerRow.getCell(c);
+            if (cell == null) {
+                cell = headerRow.createCell(c);
+            }
+            cell.setCellValue("平");
+            cell.setCellStyle(headerStyle);
+        }
+
+        // 5. 谷 - 合并两列(rowIndex行,7-8列)
+        sheet.addMergedRegion(new CellRangeAddress(rowIndex, rowIndex, 7, 8));
+        for (int c = 7; c <= 8; c++) {
+            Cell cell = headerRow.getCell(c);
+            if (cell == null) {
+                cell = headerRow.createCell(c);
+            }
+            cell.setCellValue("谷");
+            cell.setCellStyle(headerStyle);
+        }
+
+        // 6. 总 - 合并两列(rowIndex行,9-10列)
+        sheet.addMergedRegion(new CellRangeAddress(rowIndex, rowIndex, 9, 10));
+        for (int c = 9; c <= 10; c++) {
+            Cell cell = headerRow.getCell(c);
+            if (cell == null) {
+                cell = headerRow.createCell(c);
+            }
+            cell.setCellValue("总");
+            cell.setCellStyle(headerStyle);
+        }
+
+        // 7. 谷电量占比 - 合并两行(rowIndex和rowIndex+1行,11列)
+        sheet.addMergedRegion(new CellRangeAddress(rowIndex, rowIndex + 1, 11, 11));
+        for (int r = rowIndex; r <= rowIndex + 1; r++) {
+            Row row = sheet.getRow(r);
+            if (row == null) {
+                row = sheet.createRow(r);
+            }
+            Cell cell = row.getCell(11);
+            if (cell == null) {
+                cell = row.createCell(11);
+            }
+            cell.setCellValue("谷电量占比");
+            cell.setCellStyle(headerStyle);
+        }
+
+        // 第二级表头(rowIndex+1行)
+        rowIndex++;
+        Row subHeaderRow = sheet.createRow(rowIndex);
+        String[] subHeaders = {"", "电量(kWh)", "电费(元)", "电量(kWh)", "电费(元)",
+                "电量(kWh)", "电费(元)", "电量(kWh)", "电费(元)",
+                "电量(kWh)", "电费(元)", ""};
+        for (int i = 0; i < subHeaders.length; i++) {
+            Cell cell = subHeaderRow.createCell(i);
+            cell.setCellValue(subHeaders[i]);
+            cell.setCellStyle(headerStyle);
+        }
+
+        return rowIndex + 1;
+    }
+
+    /**
+     * 设置列宽(增加标题行宽度)
+     */
+    private void setColumnWidths(Sheet sheet) {
+        // 为不同列设置更合理的宽度,第0列(支路名称)宽度增加
+        int[] columnWidths = {25, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 18};
+        for (int i = 0; i < columnWidths.length; i++) {
+            sheet.setColumnWidth(i, columnWidths[i] * 256);
+        }
+    }
+
+    // 创建标题样式(带边框、宋体,24号)
+    private CellStyle createTitleStyle(Workbook workbook) {
+        CellStyle style = workbook.createCellStyle();
+        // 设置边框
+        style.setBorderTop(BorderStyle.THIN);
+        style.setBorderBottom(BorderStyle.THIN);
+        style.setBorderLeft(BorderStyle.THIN);
+        style.setBorderRight(BorderStyle.THIN);
+        style.setAlignment(HorizontalAlignment.CENTER);
+        style.setVerticalAlignment(VerticalAlignment.CENTER);
+
+        Font font = workbook.createFont();
+        font.setFontName("宋体");
+        font.setFontHeightInPoints((short) 24);
+        font.setBold(true);
+        style.setFont(font);
+        return style;
+    }
+
+    // 创建表头样式(带边框、宋体,12号,加粗)
+    private CellStyle createHeaderStyle(Workbook workbook) {
+        CellStyle style = workbook.createCellStyle();
+        // 设置边框
+        style.setBorderTop(BorderStyle.THIN);
+        style.setBorderBottom(BorderStyle.THIN);
+        style.setBorderLeft(BorderStyle.THIN);
+        style.setBorderRight(BorderStyle.THIN);
+        style.setAlignment(HorizontalAlignment.CENTER);
+        style.setVerticalAlignment(VerticalAlignment.CENTER);
+
+        Font font = workbook.createFont();
+        font.setFontName("宋体");
+        font.setFontHeightInPoints((short) 12);
+        font.setBold(true);
+        style.setFont(font);
+        return style;
+    }
+
+    // 创建数据行样式(带边框、宋体,12号)
+    private CellStyle createDataStyle(Workbook workbook) {
+        CellStyle style = workbook.createCellStyle();
+        // 设置边框
+        style.setBorderTop(BorderStyle.THIN);
+        style.setBorderBottom(BorderStyle.THIN);
+        style.setBorderLeft(BorderStyle.THIN);
+        style.setBorderRight(BorderStyle.THIN);
+        style.setAlignment(HorizontalAlignment.CENTER);
+        style.setVerticalAlignment(VerticalAlignment.CENTER);
+
+        Font font = workbook.createFont();
+        font.setFontName("宋体");
+        font.setFontHeightInPoints((short) 12);
+        style.setFont(font);
+        return style;
+    }
+
+    // 创建百分比样式(带边框、宋体,12号,百分比格式)
+    private CellStyle createPercentageStyle(Workbook workbook) {
+        CellStyle style = createDataStyle(workbook);
+        DataFormat format = workbook.createDataFormat();
+        style.setDataFormat(format.getFormat("0.00%"));
+        return style;
+    }
+
+    /**
+     * 汇总数据
+     */
+    private List<TimeSharingElectricityResponseVO> aggregateData(List<SiteElectricityRecord> records, String start, String end) {
+        return records.stream()
+                .collect(Collectors.groupingBy(
+                        SiteElectricityRecord::getDeviceCode,
+                        LinkedHashMap::new,
+                        Collectors.reducing((a, b) -> {
+                            // 累加电量
+                            a.setSharpPeak(a.getSharpPeak().add(b.getSharpPeak()));
+                            a.setPeak(a.getPeak().add(b.getPeak()));
+                            a.setFlat(a.getFlat().add(b.getFlat()));
+                            a.setValley(a.getValley().add(b.getValley()));
+                            a.setTotalElectricity(a.getTotalElectricity().add(b.getTotalElectricity()));
+
+                            // 累加电费
+                            a.setSharpPeakCost(a.getSharpPeakCost().add(b.getSharpPeakCost()));
+                            a.setPeakCost(a.getPeakCost().add(b.getPeakCost()));
+                            a.setFlatCost(a.getFlatCost().add(b.getFlatCost()));
+                            a.setValleyCost(a.getValleyCost().add(b.getValleyCost()));
+                            a.setTotalCost(a.getTotalCost().add(b.getTotalCost()));
+                            return a;
+                        })
+                ))
+                .values().stream()
+                .map(opt -> opt.orElseThrow(() -> new RuntimeException("汇总数据不能为空")))
+                .map(r -> {
+                    TimeSharingElectricityResponseVO vo = new TimeSharingElectricityResponseVO();
+                    vo.setDeviceName(r.getDeviceCode());
+                    vo.setSharpNum(r.getSharpPeak());
+                    vo.setPeakNum(r.getPeak());
+                    vo.setFlatNum(r.getFlat());
+                    vo.setValleyNum(r.getValley());
+                    vo.setTotalNum(r.getTotalElectricity());
+                    vo.setSharpPrice(r.getSharpPeakCost());
+                    vo.setPeakPrice(r.getPeakCost());
+                    vo.setFlatPrice(r.getFlatCost());
+                    vo.setValleyPrice(r.getValleyCost());
+                    vo.setTotalPrice(r.getTotalCost());
+                    vo.setTime(start + " - " + end);
+                    return vo;
+                })
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * 处理分页
+     */
+    private List<TimeSharingElectricityResponseVO> handlePagination(List<TimeSharingElectricityResponseVO> dataList,
+                                                                    TimeSharingElectricityRequestVO request) {
+        int total = dataList.size();
+        int startIndex = (request.getPageNum() - 1) * request.getPageSize();
+        int endIndex = Math.min(startIndex + request.getPageSize(), total);
+
+        log.info("总记录数: {}, 起始索引: {}, 结束索引: {}", total, startIndex, endIndex);
+
+        if (startIndex > total) {
+            throw new BusinessException("起始索引无效");
+        }
+
+        return dataList.subList(startIndex, endIndex);
+    }
+
+    /**
+     * 插入数据到工作表
+     */
+    private int insertDataIntoSheet(Sheet sheet, List<TimeSharingElectricityResponseVO> dataList,
+                                    CellStyle dataStyle, CellStyle percentageStyle, int startRow) {
+        int currentRow = startRow;
+
+        for (TimeSharingElectricityResponseVO vo : dataList) {
+            Row row = sheet.createRow(currentRow++);
+            int cellIndex = 0;
+
+            // 支路名称
+            createCell(row, cellIndex++, vo.getDeviceName(), dataStyle);
+
+            // 尖峰数据
+            createCell(row, cellIndex++, vo.getSharpNum(), dataStyle);
+            createCell(row, cellIndex++, vo.getSharpPrice(), dataStyle);
+
+            // 峰数据
+            createCell(row, cellIndex++, vo.getPeakNum(), dataStyle);
+            createCell(row, cellIndex++, vo.getPeakPrice(), dataStyle);
+
+            // 平数据
+            createCell(row, cellIndex++, vo.getFlatNum(), dataStyle);
+            createCell(row, cellIndex++, vo.getFlatPrice(), dataStyle);
+
+            // 谷数据
+            createCell(row, cellIndex++, vo.getValleyNum(), dataStyle);
+            createCell(row, cellIndex++, vo.getValleyPrice(), dataStyle);
+
+            // 总数据
+            createCell(row, cellIndex++, vo.getTotalNum(), dataStyle);
+            createCell(row, cellIndex++, vo.getTotalPrice(), dataStyle);
+
+            // 谷电量占比(处理除零情况)
+            BigDecimal valleyRatio = calculateValleyRatio(vo);
+            createCell(row, cellIndex++, valleyRatio, percentageStyle);
+        }
+
+        return currentRow;
+    }
+
+    /**
+     * 创建单元格并设置值和样式
+     */
+    private void createCell(Row row, int cellIndex, Object value, CellStyle style) {
+        Cell cell = row.createCell(cellIndex);
+        cell.setCellStyle(style);
+
+        if (value == null) {
+            cell.setCellValue("");
+            return;
+        }
+
+        // 根据值类型设置单元格内容
+        if (value instanceof BigDecimal) {
+            cell.setCellValue(((BigDecimal) value).doubleValue());
+        } else if (value instanceof Number) {
+            cell.setCellValue(((Number) value).doubleValue());
+        } else {
+            cell.setCellValue(value.toString());
+        }
+    }
+
+    /**
+     * 计算谷电量占比(处理除零情况)
+     */
+    private BigDecimal calculateValleyRatio(TimeSharingElectricityResponseVO vo) {
+        try {
+            if (vo.getTotalNum() == null || vo.getTotalNum().compareTo(BigDecimal.ZERO) == 0) {
+                return BigDecimal.ZERO;
+            }
+            return vo.getValleyNum().divide(vo.getTotalNum(), 4, RoundingMode.HALF_UP);
+        } catch (Exception e) {
+            log.warn("计算谷电量占比失败", e);
+            return BigDecimal.ZERO;
+        }
+    }
 
 }

+ 52 - 50
fiveep-service/src/main/java/com/bizmatics/service/impl/RtAnalogDataServiceImpl.java

@@ -131,7 +131,7 @@ public class RtAnalogDataServiceImpl extends AbstractCrudService<RtAnalogDataMap
     public List<Map<String, Object>> getOne(Integer siteId) {
         Integer userId = SecurityUtils.getLoginUser().getUser().getUserId().intValue();
         List<Map<String, Object>> list = new ArrayList<>();
-        Map<String, Object> radMap = baseMapper.getOneMap(userId,siteId);
+        Map<String, Object> radMap = baseMapper.getOneMap(userId, siteId);
         Optional.ofNullable(radMap).ifPresent(rad -> {
             for (String name : rad.keySet()) {
                 Map<String, Object> map = new HashMap<>();
@@ -151,27 +151,27 @@ public class RtAnalogDataServiceImpl extends AbstractCrudService<RtAnalogDataMap
 
     @Override
     public RealScoreVO realScore(String deviceCode) {
-        //查询设备
+        // 查询设备
         LambdaQueryWrapper<Device> deviceQuery = Wrappers.lambdaQuery();
         deviceQuery.eq(Device::getDeviceCode, deviceCode);
         Device device = deviceService.getOne(deviceQuery);
         Optional.ofNullable(device).orElseThrow(() -> new BusinessException("设备不存在"));
 
         String table = "rt_analog_data";
-        if (device.getDeviceType().equals("1")){
+        if (device.getDeviceType().equals("1")) {
             table = "rt_analog_data";
-        }else if (device.getDeviceType().equals("4")){
+        } else if (device.getDeviceType().equals("4")) {
             table = "rt_analog_173_data";
         }
 
-        List<RtAnalogData> rtAnalogDataList = baseMapper.getRtAnalogDataList(deviceCode,table);
+        List<RtAnalogData> rtAnalogDataList = baseMapper.getRtAnalogDataList(deviceCode, table);
         RtAnalogData rtAnalogData = new RtAnalogData();
-        if (rtAnalogDataList.size()>0){
+        if (rtAnalogDataList.size() > 0) {
             rtAnalogData = rtAnalogDataList.get(0);
-        }else {
+        } else {
             new BusinessException("设备实时信息不存在");
         }
-        //查询sd
+        // 查询sd
         LambdaQueryWrapper<SiteDynamicProperties> sdQuery = Wrappers.lambdaQuery();
         sdQuery.eq(SiteDynamicProperties::getSiteId, device.getSiteId());
         SiteDynamicProperties siteDynamicProperties = siteDynamicPropertiesService.getOne(sdQuery);
@@ -212,7 +212,7 @@ public class RtAnalogDataServiceImpl extends AbstractCrudService<RtAnalogDataMap
 
     @Override
     public RealScoreOneVO realScoreOne(String deviceCode) {
-        //查询设备
+        // 查询设备
         LambdaQueryWrapper<Device> deviceQuery = Wrappers.lambdaQuery();
         deviceQuery.eq(Device::getDeviceCode, deviceCode);
         Device device = deviceService.getOne(deviceQuery);
@@ -220,21 +220,21 @@ public class RtAnalogDataServiceImpl extends AbstractCrudService<RtAnalogDataMap
 
 
         String table = "rt_analog_data";
-        if (device.getDeviceType().equals("1")){
+        if (device.getDeviceType().equals("1")) {
             table = "rt_analog_data";
-        }else if (device.getDeviceType().equals("4")){
+        } else if (device.getDeviceType().equals("4")) {
             table = "rt_analog_173_data";
         }
 
-        List<RtAnalogData> rtAnalogDataList = baseMapper.getRtAnalogDataList(deviceCode,table);
+        List<RtAnalogData> rtAnalogDataList = baseMapper.getRtAnalogDataList(deviceCode, table);
         RtAnalogData rtAnalogData = new RtAnalogData();
-        if (rtAnalogDataList.size()>0){
+        if (rtAnalogDataList.size() > 0) {
             rtAnalogData = rtAnalogDataList.get(0);
-        }else {
+        } else {
             new BusinessException("设备实时信息不存在");
         }
 
-        //查询sd
+        // 查询sd
         LambdaQueryWrapper<SiteDynamicProperties> sdQuery = Wrappers.lambdaQuery();
         sdQuery.eq(SiteDynamicProperties::getSiteId, device.getSiteId());
         SiteDynamicProperties siteDynamicProperties = siteDynamicPropertiesService.getOne(sdQuery);
@@ -269,14 +269,14 @@ public class RtAnalogDataServiceImpl extends AbstractCrudService<RtAnalogDataMap
         checkList.add(realScoreVo.getIb());
         checkList.add(realScoreVo.getIc());
 
-        //电流不平衡
+        // 电流不平衡
         realScoreVo.setElBalun(checkBalun(checkList));
 
         checkList.clear();
         checkList.add(realScoreVo.getUa());
         checkList.add(realScoreVo.getUb());
         checkList.add(realScoreVo.getUc());
-        //ABC三相电压占比
+        // ABC三相电压占比
         Double max = checkList.stream().max(Double::compareTo).get();
         if (max == 0.00) {
             realScoreVo.setUaPercentage(0.00);
@@ -289,11 +289,11 @@ public class RtAnalogDataServiceImpl extends AbstractCrudService<RtAnalogDataMap
         }
 
 
-        //电压不平衡
+        // 电压不平衡
         realScoreVo.setVtBalun(checkBalun(checkList));
-        //当前频率
+        // 当前频率
         realScoreVo.setF(realScoreVo.getF());
-        //频率偏差
+        // 频率偏差
         if (realScoreVo.getF() == 0.00) {
             realScoreVo.setFdeviation(0.00);
         } else {
@@ -333,26 +333,26 @@ public class RtAnalogDataServiceImpl extends AbstractCrudService<RtAnalogDataMap
         checkList.add(realScoreVo.getIa());
         checkList.add(realScoreVo.getIb());
         checkList.add(realScoreVo.getIc());
-        //电流不平衡
+        // 电流不平衡
         realScoreVo.setElBalun(checkBalun(checkList));
         checkList.clear();
         checkList.add(realScoreVo.getUa());
         checkList.add(realScoreVo.getUb());
         checkList.add(realScoreVo.getUc());
-        //电压不平衡
+        // 电压不平衡
         realScoreVo.setVtBalun(checkBalun(checkList));
-        //电压合格率
+        // 电压合格率
         double voltageLevel = Double.parseDouble(siteDynamicProperties.getVoltageLevel());
         realScoreVo.setUaQualified(Arith.sub(rtAnalogData.getUa(), voltageLevel));
         realScoreVo.setUbQualified(Arith.sub(rtAnalogData.getUb(), voltageLevel));
         realScoreVo.setUcQualified(Arith.sub(rtAnalogData.getUc(), voltageLevel));
-        //电流负载率
+        // 电流负载率
         Double ratedCurrent = deviceAttribute.getRatedCurrent();
         realScoreVo.setIaLoad(Arith.div(realScoreVo.getIa(), ratedCurrent));
         realScoreVo.setIbLoad(Arith.div(realScoreVo.getIb(), ratedCurrent));
         realScoreVo.setIcLoad(Arith.div(realScoreVo.getIc(), ratedCurrent));
-        //计算分数
-        //电压分数
+        // 计算分数
+        // 电压分数
         realScoreVo.setUaQ(computeUScore(realScoreVo.getUaQualified(), voltageLevel));
         realScoreVo.setUbQ(computeUScore(realScoreVo.getUbQualified(), voltageLevel));
         realScoreVo.setUcQ(computeUScore(realScoreVo.getUcQualified(), voltageLevel));
@@ -362,7 +362,7 @@ public class RtAnalogDataServiceImpl extends AbstractCrudService<RtAnalogDataMap
         } else {
             realScoreVo.setUQ(false);
         }
-        //电流分数
+        // 电流分数
         realScoreVo.setIaLoadQ(realScoreVo.getIaLoad() <= 0.8);
         realScoreVo.setIbLoadQ(realScoreVo.getIbLoad() <= 0.8);
         realScoreVo.setIcLoadQ(realScoreVo.getIcLoad() <= 0.8);
@@ -372,17 +372,17 @@ public class RtAnalogDataServiceImpl extends AbstractCrudService<RtAnalogDataMap
         } else {
             realScoreVo.setILoadQ(false);
         }
-        //电压平衡分数
+        // 电压平衡分数
         realScoreVo.setElBalunQ(realScoreVo.getElBalun() <= 0.15);
         if (realScoreVo.getElBalunQ()) {
             score += 20;
         }
-        //电流平衡分数
+        // 电流平衡分数
         realScoreVo.setVtBalunQ(realScoreVo.getVtBalun() <= 0.15);
         if (realScoreVo.getVtBalunQ()) {
             score += 20;
         }
-        //功率
+        // 功率
         realScoreVo.setCosQ(realScoreVo.getCos() >= 0.9);
         if (realScoreVo.getCosQ()) {
             score += 20;
@@ -1181,8 +1181,6 @@ public class RtAnalogDataServiceImpl extends AbstractCrudService<RtAnalogDataMap
     }
 
 
-
-
     @Override
     public List<RealScoreVO> evaluationReport(int siteId, Date time, int type) {
         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
@@ -1203,13 +1201,13 @@ public class RtAnalogDataServiceImpl extends AbstractCrudService<RtAnalogDataMap
             }
         }
         LambdaQueryWrapper<Device> queryWrapper1 = Wrappers.lambdaQuery();
-        queryWrapper1.eq(Device::getSiteId,siteId);
+        queryWrapper1.eq(Device::getSiteId, siteId);
         List<Device> deviceList = deviceService.list(queryWrapper1);
         List<String> fieldDisplayOne183 = new ArrayList();
         List<String> fieldDisplayOne171 = new ArrayList();
         List<String> fieldDisplayOne173 = new ArrayList();
         if (deviceList.size() > 0) {
-            for(int j = 0; j < deviceList.size(); j++){
+            for (int j = 0; j < deviceList.size(); j++) {
                 if (deviceList.get(j).getDeviceType().equals("1")) {
                     fieldDisplayOne183.add(deviceList.get(j).getDeviceCode());
                 } else if (deviceList.get(j).getDeviceType().equals("3")) {
@@ -1221,11 +1219,11 @@ public class RtAnalogDataServiceImpl extends AbstractCrudService<RtAnalogDataMap
         }
         List<EvaluationReporVo> evaluationReporVo1 = new ArrayList<>();
         List<EvaluationReporVo> evaluationReporVo2 = new ArrayList<>();
-        if (fieldDisplayOne183.size()>0){
-            evaluationReporVo1 = baseMapper.getEvaluationReportList(siteId, startTime, endtime,"ht_analog_data",fieldDisplayOne183);
+        if (fieldDisplayOne183.size() > 0) {
+            evaluationReporVo1 = baseMapper.getEvaluationReportList(siteId, startTime, endtime, "ht_analog_data", fieldDisplayOne183);
         }
-        if (fieldDisplayOne173.size()>0){
-            evaluationReporVo2 = baseMapper.getEvaluationReportList(siteId, startTime, endtime,"ht_analog_173_data",fieldDisplayOne173);
+        if (fieldDisplayOne173.size() > 0) {
+            evaluationReporVo2 = baseMapper.getEvaluationReportList(siteId, startTime, endtime, "ht_analog_173_data", fieldDisplayOne173);
         }
 
         List<EvaluationReporVo> evaluationReporVo = Stream.of(evaluationReporVo1, evaluationReporVo2).flatMap(Collection::stream).distinct().collect(Collectors.toList());
@@ -1262,8 +1260,8 @@ public class RtAnalogDataServiceImpl extends AbstractCrudService<RtAnalogDataMap
      */
     public Boolean computeUScoreOne(Double qualified, Double voltageLevel) {
 //        double mul = Arith.mul(qualified, voltageLevel);
-        Double voltageLevels = voltageLevel+(voltageLevel / 100 * 7);
-        Double voltageLevelx = voltageLevel-(voltageLevel / 100 * 7);
+        Double voltageLevels = voltageLevel + (voltageLevel / 100 * 7);
+        Double voltageLevelx = voltageLevel - (voltageLevel / 100 * 7);
         if (voltageLevelx <= qualified && voltageLevels >= qualified) {
             return true;
         }
@@ -1285,26 +1283,26 @@ public class RtAnalogDataServiceImpl extends AbstractCrudService<RtAnalogDataMap
                 checkList.add(realScoreVo.getIa());
                 checkList.add(realScoreVo.getIb());
                 checkList.add(realScoreVo.getIc());
-                //电流不平衡
+                // 电流不平衡
                 realScoreVo.setElBalun(checkBalun(checkList));
                 checkList.clear();
                 checkList.add(realScoreVo.getUa());
                 checkList.add(realScoreVo.getUb());
                 checkList.add(realScoreVo.getUc());
-                //电压不平衡
+                // 电压不平衡
                 realScoreVo.setVtBalun(checkBalun(checkList));
-                //电压合格率
+                // 电压合格率
                 double voltageLevel = Double.parseDouble(evaluationReporVo.get(i).getVoltageLevel());
                 realScoreVo.setUaQualified(Arith.sub(evaluationReporVo.get(i).getUa(), voltageLevel));
                 realScoreVo.setUbQualified(Arith.sub(evaluationReporVo.get(i).getUb(), voltageLevel));
                 realScoreVo.setUcQualified(Arith.sub(evaluationReporVo.get(i).getUc(), voltageLevel));
-                //电流负载率
+                // 电流负载率
                 Double ratedCurrent = evaluationReporVo.get(i).getRatedCurrent();
                 realScoreVo.setIaLoad(Arith.div(realScoreVo.getIa(), ratedCurrent));
                 realScoreVo.setIbLoad(Arith.div(realScoreVo.getIb(), ratedCurrent));
                 realScoreVo.setIcLoad(Arith.div(realScoreVo.getIc(), ratedCurrent));
-                //计算分数
-                //电压分数
+                // 计算分数
+                // 电压分数
                 realScoreVo.setUaQ(computeUScoreOne(realScoreVo.getUa(), voltageLevel));
                 realScoreVo.setUbQ(computeUScoreOne(realScoreVo.getUb(), voltageLevel));
                 realScoreVo.setUcQ(computeUScoreOne(realScoreVo.getUc(), voltageLevel));
@@ -1314,7 +1312,7 @@ public class RtAnalogDataServiceImpl extends AbstractCrudService<RtAnalogDataMap
                 } else {
                     realScoreVo.setUQ(false);
                 }
-                //电流分数
+                // 电流分数
                 realScoreVo.setIaLoadQ(realScoreVo.getIaLoad() <= 0.8);
                 realScoreVo.setIbLoadQ(realScoreVo.getIbLoad() <= 0.8);
                 realScoreVo.setIcLoadQ(realScoreVo.getIcLoad() <= 0.8);
@@ -1324,17 +1322,17 @@ public class RtAnalogDataServiceImpl extends AbstractCrudService<RtAnalogDataMap
                 } else {
                     realScoreVo.setILoadQ(false);
                 }
-                //电压平衡分数
+                // 电压平衡分数
                 realScoreVo.setElBalunQ(realScoreVo.getElBalun() <= 0.15);
                 if (realScoreVo.getElBalunQ()) {
                     score += 20;
                 }
-                //电流平衡分数
+                // 电流平衡分数
                 realScoreVo.setVtBalunQ(realScoreVo.getVtBalun() <= 0.15);
                 if (realScoreVo.getVtBalunQ()) {
                     score += 20;
                 }
-                //功率
+                // 功率
                 realScoreVo.setCosQ(realScoreVo.getCos() <= 0.15);
                 if (realScoreVo.getCosQ()) {
                     score += 20;
@@ -1394,4 +1392,8 @@ public class RtAnalogDataServiceImpl extends AbstractCrudService<RtAnalogDataMap
 //    }
 
 
+    @Override
+    public List<RtAnalogData> getP(List<String> deviceCodes) {
+        return baseMapper.getP(deviceCodes);
+    }
 }

+ 144 - 139
fiveep-service/src/main/java/com/bizmatics/service/impl/SiteServiceImpl.java

@@ -11,10 +11,7 @@ import com.bizmatics.common.mvc.base.AbstractCrudService;
 import com.bizmatics.model.*;
 import com.bizmatics.model.system.SysUser;
 import com.bizmatics.model.vo.PlatformAreaVo;
-import com.bizmatics.persistence.mapper.AlarmPowerMapper;
-import com.bizmatics.persistence.mapper.DeviceMapper;
-import com.bizmatics.persistence.mapper.PlatformAreaMapper;
-import com.bizmatics.persistence.mapper.SiteMapper;
+import com.bizmatics.persistence.mapper.*;
 import com.bizmatics.persistence.mapper.system.SysUserMapper;
 import com.bizmatics.service.*;
 import com.bizmatics.service.enums.DeviceStatusCode;
@@ -30,6 +27,8 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
 import java.util.*;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -65,6 +64,10 @@ public class SiteServiceImpl extends AbstractCrudService<SiteMapper, Site> imple
     private SysUserMapper userMapper;
     @Autowired
     private HtAnalogDataService htAnalogDataService;
+    @Autowired
+    private ElectricityRateConfigMapper electricityRateConfigMapper;
+    @Autowired
+    private RtAnalogDataService rtAnalogDataService;
 
     @Override
     public DeviceCountVO selectCount() {
@@ -84,15 +87,28 @@ public class SiteServiceImpl extends AbstractCrudService<SiteMapper, Site> imple
     public List<Site> list(String name) {
         Integer userId = SecurityUtils.getLoginUser().getUser().getUserId().intValue();
         List<SysUser> tenantDaya = userMapper.getTenantId(SecurityUtils.getLoginUser().getUser().getUserId());
-        if (tenantDaya.size() < 0) {
+        if (tenantDaya.isEmpty()) {
             throw new BusinessException("无此租户,请联系管理员");
         }
         List<Site> siteList = new ArrayList<>();
-        if (tenantDaya.get(0).getUserType().equals("01")) {
+        if ("01".equals(tenantDaya.get(0).getUserType())) {
             siteList = baseMapper.listOne(name, tenantDaya.get(0).getTenantId());
-        } else if (tenantDaya.get(0).getUserType().equals("00")) {
+        } else if ("00".equals(tenantDaya.get(0).getUserType())) {
             siteList = baseMapper.list(userId, name, tenantDaya.get(0).getTenantId());
         }
+
+        // 查询是否存在费用
+        List<Integer> siteIds = siteList.stream()
+                .map(Site::getId)
+                .collect(Collectors.toList());
+
+        Set<Integer> exists = electricityRateConfigMapper.selectBatchIds(siteIds)
+                .stream()
+                .map(ElectricityRateConfig::getSiteId)
+                .collect(Collectors.toSet());
+
+        siteList.forEach(site -> site.setCostTypeStatus(exists.contains(site.getId())));
+
         return siteList;
     }
 
@@ -194,7 +210,7 @@ public class SiteServiceImpl extends AbstractCrudService<SiteMapper, Site> imple
         }
         SysUser user = SecurityUtils.getLoginUser().getUser();
         Site site1 = siteVOT.getSite();
-        site1.setCreateTime(new Date());
+        site1.setCreateTime(LocalDateTime.now());
         site1.setCreator(user.getUserName());
         site1.setCompanyCode(11111);
         site1.setEnable(1);
@@ -363,149 +379,138 @@ public class SiteServiceImpl extends AbstractCrudService<SiteMapper, Site> imple
     }
 
     @Override
-    public CommonPage<SiteLoadAnalysisVO> siteLoadAnalysis(Integer siteId, Integer pageNum, Integer pageSize) {
-        List<SiteLoadAnalysisVO> records = new ArrayList<>();
-        LambdaQueryWrapper<Site> siteQuery = Wrappers.lambdaQuery();
-        siteQuery.eq(Site::getId, siteId);
-        Site site = baseMapper.selectOne(siteQuery);
-        log.info("站点信息:{}", site);
+    public List<SiteLoadAnalysisVO> siteLoadAnalysis(Integer siteId,
+                                                     String startTime,
+                                                     String endTime) {
+
+        SiteLoadAnalysisVO siteRow = new SiteLoadAnalysisVO();
+
+        /* ---------- 1. 站点&动态属性 ---------- */
+        Site site = Optional.ofNullable(
+                        baseMapper.selectById(siteId))
+                .orElseGet(() -> {
+                    log.warn("站点 {} 不存在", siteId);
+                    return null;
+                });
         if (site == null) {
-            return new CommonPage<>();
+            return Collections.singletonList(siteRow);
         }
 
-        // 站点动态属性查询
-        LambdaQueryWrapper<SiteDynamicProperties> siteDynamicPropertiesQuery = Wrappers.lambdaQuery();
-        siteDynamicPropertiesQuery.eq(SiteDynamicProperties::getSiteId, siteId);
-        SiteDynamicProperties siteDynamicProperties = siteDynamicPropertiesService.getOne(siteDynamicPropertiesQuery);
-        log.info("站点动态属性信息:{}", siteDynamicProperties);
-        if (siteDynamicProperties == null) {
-            log.error("站点{}动态属性不存在", siteId);
+        SiteDynamicProperties sdp = siteDynamicPropertiesService.lambdaQuery()
+                .eq(SiteDynamicProperties::getSiteId, siteId)
+                .one();
+        if (sdp == null) {
+            log.warn("站点 {} 动态属性缺失", siteId);
         }
 
-        // 存入接口返回
-        SiteLoadAnalysisVO siteLoadAnalysisVO = new SiteLoadAnalysisVO();
-        siteLoadAnalysisVO.setId(site.getId());
-        siteLoadAnalysisVO.setName(site.getSiteName());
-        // 额定容量=装机容量
-        siteLoadAnalysisVO.setRatedCapacity(Double.valueOf(site.getInstalledCapacity()));
-        // 额定电压
-        try {
-            siteLoadAnalysisVO.setRatedVoltage(StringUtils.isBlank(siteDynamicProperties.getVoltageLevel()) ? 0.0 : Double.parseDouble(siteDynamicProperties.getVoltageLevel()) / 1000.0);
-        } catch (NumberFormatException e) {
-            log.error("站点{}  电压等级(单位:V)格式转换错误{} ,已重置为0.0", siteDynamicProperties.getSiteId(), e);
-            siteLoadAnalysisVO.setRatedVoltage(0.0);
-        }
-        // 先插入部分站点信息
-        records.add(0, siteLoadAnalysisVO);
-
-        List<DeviceList> deviceList = deviceService.deviceList(String.valueOf(siteId));
-        log.info("设备信息list:{}", deviceList);
-
-        // 类型为1的设备为站点的有功功率(总和)
-        List<DeviceList> typeOneDevices = new ArrayList<>();
-        List<DeviceList> typeOtherDevices = new ArrayList<>();
-        List<String> deviceCodeList = new ArrayList<>();
-        // Map<String, Double> pMap = new HashMap<>();
-        Double p = 0.0;
-
-        if (!deviceList.isEmpty()) {
-            for (DeviceList device : deviceList) {
-                String deviceType = device.getDeviceType();
-                if ("1".equals(deviceType)) {
-                    typeOneDevices.add(device);
-                } else if (!"2".equals(deviceType)) {
-                    typeOtherDevices.add(device);
-                }
-                deviceCodeList.add(device.getDeviceCode());
-            }
-        } else {
-            log.error("站点{}无设备", siteId);
-            return new CommonPage<>(records, records.size(), pageSize, pageNum);
+        /* ---------- 2. 设备 ---------- */
+        List<DeviceList> devices = deviceService.deviceList(String.valueOf(siteId));
+        if (CollectionUtils.isEmpty(devices)) {
+            log.warn("站点 {} 无设备", siteId);
+            return Collections.singletonList(siteRow);
         }
 
-        // pMap = (htAnalogDataService.getP(deviceCodeList)).stream().collect(Collectors.toMap(HtAnalogData::getDeviceName, HtAnalogData::getP));
-        List<HtAnalogData> pList = htAnalogDataService.getP(deviceCodeList);
-        if (pList.isEmpty()) {
-            log.error("站点{},设备编号{} 无有功功率数据", siteId, deviceCodeList);
-            for (DeviceList device : deviceList) {
-                SiteLoadAnalysisVO deviceLoadAnalysis = new SiteLoadAnalysisVO();
-                deviceLoadAnalysis.setName(device.getDeviceName());
-                deviceLoadAnalysis.setId(null);
-                deviceLoadAnalysis.setCode(device.getDeviceCode());
-                deviceLoadAnalysis.setRatedCapacity(0.0);
-                deviceLoadAnalysis.setRatedVoltage(0.0);
-                deviceLoadAnalysis.setP(0.0);
-                deviceLoadAnalysis.setLoadRate(0.0);
-                deviceLoadAnalysis.setDataTime(null);
-                records.add(deviceLoadAnalysis);
-            }
-            return new CommonPage<>(records, records.size(), pageSize, pageNum);
-        }
-        for (DeviceList device : deviceList) {
-            log.info("设备信息:{}", device);
-            SiteLoadAnalysisVO deviceLoadAnalysis = new SiteLoadAnalysisVO();
-            deviceLoadAnalysis.setId(device.getId());
-            deviceLoadAnalysis.setName(device.getDeviceName());
-            deviceLoadAnalysis.setCode(device.getDeviceCode());
-            double ratedCapacity = device.getRatedCurrent() * device.getRatedVoltage();
-            log.info("额定容量:{}", ratedCapacity);
-            deviceLoadAnalysis.setRatedCapacity(ratedCapacity);
-            deviceLoadAnalysis.setRatedVoltage(device.getRatedVoltage());
-
-            LocalDateTime dateTime = pList.stream()
-                    .filter(ht -> ht.getDeviceName().equals(device.getDeviceCode()))
-                    .findFirst()
-                    .map(HtAnalogData::getDataTime)
-                    .orElse(null);
-            deviceLoadAnalysis.setDataTime(dateTime);
-
-            // deviceLoadAnalysis.setP(pMap.get(device.getDeviceCode()));
-
-            Double m = pList.stream()
-                    .filter(ht -> ht.getDeviceName().equals(device.getDeviceCode()))
-                    .findFirst()
-                    .map(HtAnalogData::getP)
-                    .orElse(0.0);
-            deviceLoadAnalysis.setP(m);
-
-            if (ratedCapacity != 0.0) {
-                deviceLoadAnalysis.setLoadRate(Arith.div(m, ratedCapacity, 3));
-            } else {
-                deviceLoadAnalysis.setLoadRate(0.0);
+        // 提前过滤并分组
+        Map<Boolean, List<DeviceList>> group = devices.stream()
+                .collect(Collectors.partitioningBy(d -> "1".equals(d.getDeviceType())));
+        List<DeviceList> typeOneDevices = group.get(true);
+        List<DeviceList> typeOtherDevices = group.get(false).stream()
+                .filter(d -> !"2".equals(d.getDeviceType()))
+                .collect(Collectors.toList());
+
+        List<String> deviceCodes = devices.stream()
+                .map(DeviceList::getDeviceCode)
+                .collect(Collectors.toList());
+
+        /* ---------- 3. 有功功率数据 ---------- */
+        // 解析时间
+        DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+        LocalDateTime start = null, end = null;
+        if (StringUtils.isNotBlank(startTime) && StringUtils.isNotBlank(endTime)) {
+            try {
+                start = LocalDateTime.parse(startTime, fmt);
+                end = LocalDateTime.parse(endTime, fmt);
+            } catch (DateTimeParseException ex) {
+                throw new BusinessException("时间格式错误: " + ex.getMessage());
             }
-            records.add(deviceLoadAnalysis);
         }
 
-        // 没有的话则是其他类型的总和(类型2 视频监控除外)
-        if (!typeOneDevices.isEmpty()) {
-            for (DeviceList device : typeOneDevices) {
-                Double m = pList.stream()
-                        .filter(ht -> ht.getDeviceName().equals(device.getDeviceCode()))
-                        .findFirst()
-                        .map(HtAnalogData::getP)
-                        .orElse(0.0);
-                p += m;
-            }
-        } else {
-            for (DeviceList device : typeOtherDevices) {
-                Double m = pList.stream()
-                        .filter(ht -> ht.getDeviceName().equals(device.getDeviceCode()))
-                        .findFirst()
-                        .map(HtAnalogData::getP)
-                        .orElse(0.0);
-                p += m;
-            }
-        }
-        records.get(0).setP(p);
+        // 统一获取数据并转 Map
+        Map<String, ? extends Number> pMap = start != null
+                ? htAnalogDataService.getP(deviceCodes, start, end)
+                .stream()
+                .collect(Collectors.toMap(HtAnalogData::getDeviceName,
+                        HtAnalogData::getP,
+                        (v1, v2) -> v1))
+                : rtAnalogDataService.getP(deviceCodes)
+                .stream()
+                .collect(Collectors.toMap(RtAnalogData::getDeviceName,
+                        RtAnalogData::getP,
+                        (v1, v2) -> v1));
+
+        /* ---------- 4. 组装结果 ---------- */
+        // 站点根节点
+        siteRow.setId(site.getId());
+        siteRow.setName(site.getSiteName());
+        siteRow.setRatedCapacity(Optional.ofNullable(site.getInstalledCapacity()).map(Double::valueOf).orElse(0.0));
+        siteRow.setRatedVoltage(
+                Optional.ofNullable(sdp)
+                        .map(SiteDynamicProperties::getVoltageLevel)
+                        .filter(StringUtils::isNotBlank)
+                        .map(v -> Double.parseDouble(v) / 1000.0)
+                        .orElse(0.0));
+        siteRow.setDataTime(site.getCreateTime());
+
+        // 汇总功率、负载率用
+        double totalP = (typeOneDevices.isEmpty() ? typeOtherDevices : typeOneDevices)
+                .stream()
+                .mapToDouble(d -> Optional.ofNullable(pMap.get(d.getDeviceCode()))
+                        .map(Number::doubleValue)
+                        .orElse(0.0))
+                .sum();
+        siteRow.setP(totalP);
+        siteRow.setLoadRate(siteRow.getRatedCapacity() == 0
+                ? 0.0
+                : Arith.div(totalP, siteRow.getRatedCapacity(), 3));
+
+        /* --- 构造 children --- */
+        LocalDateTime finalStart = start, finalEnd = end;
+        List<SiteLoadAnalysisVO> children = devices.stream()
+                .map(d -> {
+                    SiteLoadAnalysisVO dev = new SiteLoadAnalysisVO();
+                    dev.setId(d.getId());
+                    dev.setName(d.getDeviceName());
+                    dev.setCode(d.getDeviceCode());
+
+                    double ratedCap = d.getRatedCurrent() * d.getRatedVoltage();
+                    dev.setRatedCapacity(ratedCap);
+                    dev.setRatedVoltage(d.getRatedVoltage());
+
+                    double p = Optional.ofNullable(pMap.get(d.getDeviceCode()))
+                            .map(Number::doubleValue)
+                            .orElse(0.0);
+                    dev.setP(p);
+                    dev.setLoadRate(ratedCap == 0 ? 0.0 : Arith.div(p, ratedCap, 3));
+
+                    // 数据时间
+                    if (finalStart != null) {
+                        htAnalogDataService.getP(deviceCodes, finalStart, finalEnd).stream()
+                                .filter(h -> h.getDeviceName().equals(d.getDeviceCode()))
+                                .findFirst()
+                                .ifPresent(h -> dev.setDataTime(h.getDataTime()));
+                    } else {
+                        rtAnalogDataService.getP(deviceCodes).stream()
+                                .filter(h -> h.getDeviceName().equals(d.getDeviceCode()))
+                                .findFirst()
+                                .ifPresent(h -> dev.setDataTime(h.getDataTime()));
+                    }
+                    return dev;
+                })
+                .collect(Collectors.toList());
 
-        // 负载率
-        if (records.get(0).getRatedCapacity() != 0.0) {
-            records.get(0).setLoadRate(Arith.div(p, records.get(0).getRatedCapacity(), 3));
-        } else {
-            records.get(0).setLoadRate(0.0);
-        }
+        siteRow.setChildren(children);
 
-        return new CommonPage<>(records, records.size(), pageNum, pageSize);
+        return Collections.singletonList(siteRow);
     }
 
 }

+ 46 - 20
fiveep-service/src/main/java/com/bizmatics/service/job/SiteDailyElectricityCostTask.java

@@ -34,17 +34,20 @@ public class SiteDailyElectricityCostTask implements ApplicationRunner {
     /** 应用启动后立即执行一次 */
     @Override
     public void run(ApplicationArguments args) {
-        log.info("立即执行一次日电费记录");
+        log.info("立即执行一次日电量电费记录");
         // calculateDailyElectricityCost();
     }
 
     // 每天凌晨0点过5分执行,计算前一天的用电费用
-    @Scheduled(cron = "0 5 0 * * ?")
+    //@Scheduled(cron = "0 5 0 * * ?")
+
+    // 每小时第5分钟执行一次
+    @Scheduled(cron = "0 5 * * * ?")
     public void calculateDailyElectricityCost() {
-        LocalDate yesterday = LocalDate.now().minusDays(1);
-        // LocalDate yesterday = LocalDate.now();
+        // LocalDate yesterday = LocalDate.now().minusDays(1);
+        LocalDate yesterday = LocalDate.now();
         // LocalDate yesterday = LocalDate.parse("2025-07-19");
-        log.info("开始计算{}的用电费用", yesterday);
+        // log.info("开始计算{}的用电费用", yesterday);
 
         try {
             calculateElectricityCostForDate(yesterday);
@@ -80,7 +83,7 @@ public class SiteDailyElectricityCostTask implements ApplicationRunner {
     private Map<Integer, List<String>> getSiteDevicesMap() {
         Map<Integer, List<String>> siteDevicesMap = new HashMap<>();
 
-        String sql = "SELECT site_id, device_code FROM device WHERE device_type = '1' AND enable = 1";
+        String sql = "SELECT site_id, device_code FROM device WHERE device_type IN ('1','3','4') AND enable = 1";
 
         jdbcTemplate.query(sql, rs -> {
             Integer siteId = rs.getInt("site_id");
@@ -121,6 +124,13 @@ public class SiteDailyElectricityCostTask implements ApplicationRunner {
         BigDecimal valleyCost = BigDecimal.ZERO;
         BigDecimal totalCost = BigDecimal.ZERO;
 
+        // 初始化用电量
+        BigDecimal sharpPeak = BigDecimal.ZERO;
+        BigDecimal peak = BigDecimal.ZERO;
+        BigDecimal flat = BigDecimal.ZERO;
+        BigDecimal valley = BigDecimal.ZERO;
+        BigDecimal totalElectricity = BigDecimal.ZERO;
+
         // 计算每个时间段的电费
         for (int i = 1; i < electricityDataList.size(); i++) {
             ElectricityData prev = electricityDataList.get(i - 1);
@@ -154,19 +164,24 @@ public class SiteDailyElectricityCostTask implements ApplicationRunner {
             // 计算电费:用电量 × 电价
             BigDecimal cost = electricityUsage.multiply(config.getElectricityPrice());
             totalCost = totalCost.add(cost).setScale(2, RoundingMode.HALF_UP);
+            totalElectricity = totalElectricity.add(electricityUsage);
 
             switch (config.getPeakValleyAttribute()) {
                 case 1: // 尖
                     sharpPeakCost = sharpPeakCost.add(cost).setScale(2, RoundingMode.HALF_UP);
+                    sharpPeak = sharpPeak.add(electricityUsage);
                     break;
                 case 2: // 峰
                     peakCost = peakCost.add(cost).setScale(2, RoundingMode.HALF_UP);
+                    peak = peak.add(electricityUsage);
                     break;
                 case 3: // 平
                     flatCost = flatCost.add(cost).setScale(2, RoundingMode.HALF_UP);
+                    flat = flat.add(electricityUsage);
                     break;
                 case 4: // 谷
                     valleyCost = valleyCost.add(cost).setScale(2, RoundingMode.HALF_UP);
+                    valley = valley.add(electricityUsage);
                     break;
                 default:
                     log.warn("未知的峰谷属性: {}", config.getPeakValleyAttribute());
@@ -175,10 +190,15 @@ public class SiteDailyElectricityCostTask implements ApplicationRunner {
 
         // 保存计算结果
         saveElectricityRecord(siteId, deviceCode, date, sharpPeakCost, peakCost,
-                flatCost, valleyCost, totalCost);
-
-        log.info("站点{}设备{}在{}的电费计算完成:尖峰{}元,峰{}元,平{}元,谷{}元,总计{}元",
-                siteId, deviceCode, date, sharpPeakCost, peakCost, flatCost, valleyCost, totalCost);
+                flatCost, valleyCost, totalCost, sharpPeak, peak, flat, valley, totalElectricity);
+
+        log.info("站点{}设备{}在{}的电费计算完成:尖峰{}元({}kWh),峰{}元({}kWh),平{}元({}kWh),谷{}元({}kWh),总计{}元({}kwh)",
+                siteId, deviceCode, date,
+                sharpPeakCost, sharpPeak,
+                peakCost, peak,
+                flatCost, flat,
+                valleyCost, valley,
+                totalCost, totalElectricity);
     }
 
     private List<TimePriceConfig> getTimePriceConfigs(Integer siteId, int month) {
@@ -250,17 +270,18 @@ public class SiteDailyElectricityCostTask implements ApplicationRunner {
     private void saveElectricityRecord(Integer siteId, String deviceCode, LocalDate date,
                                        BigDecimal sharpPeakCost, BigDecimal peakCost,
                                        BigDecimal flatCost, BigDecimal valleyCost,
-                                       BigDecimal totalCost) {
+                                       BigDecimal totalCost, BigDecimal sharpPeak,
+                                       BigDecimal peak, BigDecimal flat, BigDecimal valley,
+                                       BigDecimal totalElectricity) {
         String sql = "INSERT INTO site_electricity_record " +
-                "(site_id, device_code, date, peak_cost, flat_cost, " +
-                "valley_cost, sharp_peak_cost, total_cost) " +
-                "VALUES (?, ?, ?, ?, ?, ?, ?, ?) " +
+                "(site_id, device_code, date, peak_cost, flat_cost, valley_cost, sharp_peak_cost, total_cost, " +
+                "sharp_peak, peak, flat, valley, total_electricity) " +
+                "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) " +
                 "ON DUPLICATE KEY UPDATE " +
-                "peak_cost = VALUES(peak_cost), " +
-                "flat_cost = VALUES(flat_cost), " +
-                "valley_cost = VALUES(valley_cost), " +
-                "sharp_peak_cost = VALUES(sharp_peak_cost), " +
-                "total_cost = VALUES(total_cost)";
+                "peak_cost = VALUES(peak_cost), flat_cost = VALUES(flat_cost), valley_cost = VALUES(valley_cost), " +
+                "sharp_peak_cost = VALUES(sharp_peak_cost), total_cost = VALUES(total_cost), " +
+                "sharp_peak = VALUES(sharp_peak), peak = VALUES(peak), " +
+                "flat = VALUES(flat), valley = VALUES(valley), total_electricity = VALUES(total_electricity)";
 
         try {
             jdbcTemplate.update(sql,
@@ -271,7 +292,12 @@ public class SiteDailyElectricityCostTask implements ApplicationRunner {
                     flatCost,
                     valleyCost,
                     sharpPeakCost,
-                    totalCost
+                    totalCost,
+                    sharpPeak,
+                    peak,
+                    flat,
+                    valley,
+                    totalElectricity
             );
             log.debug("成功保存站点{}设备{}的电费记录", siteId, deviceCode);
         } catch (Exception e) {

+ 44 - 0
fiveep-service/src/main/java/com/bizmatics/service/vo/ElectricityTrendRequestVO.java

@@ -0,0 +1,44 @@
+package com.bizmatics.service.vo;
+
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDate;
+import java.util.List;
+
+/**
+ *
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2025/9/5
+ */
+@Data
+public class ElectricityTrendRequestVO {
+
+    /**
+     * 站点id
+     */
+    private Integer siteId;
+
+    /**
+     * 查询类型 1:电量,2:费用
+     */
+    private Integer type;
+
+    /**
+     * 开始时间
+     */
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    private LocalDate startTime;
+
+    /**
+     * 结束时间
+     */
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    private LocalDate endTime;
+
+    /**
+     * 设备编码列表
+     */
+    private List<String> deviceCodes;
+}

+ 27 - 0
fiveep-service/src/main/java/com/bizmatics/service/vo/ElectricityTrendResponseVO.java

@@ -0,0 +1,27 @@
+package com.bizmatics.service.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ *
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2025/9/5
+ */
+@Data
+public class ElectricityTrendResponseVO {
+
+    /**
+     * 设备名称
+     */
+    private String deviceCode;
+
+    /**
+     * 设备数据
+     */
+    private List<ElectricityTrendVO> data;
+}

+ 41 - 0
fiveep-service/src/main/java/com/bizmatics/service/vo/ElectricityTrendVO.java

@@ -0,0 +1,41 @@
+package com.bizmatics.service.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ *
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2025/9/5
+ */
+@Data
+public class ElectricityTrendVO {
+
+    /**
+     * 时间
+     */
+    private String time;
+
+    /**
+     * 尖
+     */
+    private BigDecimal sharp;
+
+    /**
+     * 峰
+     */
+    private BigDecimal peak;
+
+    /**
+     * 平
+     */
+    private BigDecimal flat;
+
+    /**
+     * 谷
+     */
+    private BigDecimal valley;
+
+}

+ 6 - 0
fiveep-service/src/main/java/com/bizmatics/service/vo/SiteLoadAnalysisVO.java

@@ -1,11 +1,14 @@
 package com.bizmatics.service.vo;
 
 import com.bizmatics.service.config.CustomerDoubleSerialize;
+import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import lombok.Data;
 
 import java.time.LocalDateTime;
+import java.util.ArrayList;
 import java.util.Date;
+import java.util.List;
 
 /**
  *
@@ -60,4 +63,7 @@ public class SiteLoadAnalysisVO {
      * 上报时间
      */
     private LocalDateTime dataTime;
+
+    @JsonInclude(JsonInclude.Include.NON_EMPTY)
+    private List<SiteLoadAnalysisVO> children = new ArrayList<>();
 }

+ 53 - 0
fiveep-service/src/main/java/com/bizmatics/service/vo/TimeSharingElectricityRequestVO.java

@@ -0,0 +1,53 @@
+package com.bizmatics.service.vo;
+
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDate;
+import java.util.List;
+
+/**
+ *
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2025/9/4
+ */
+@Data
+public class TimeSharingElectricityRequestVO {
+    /**
+     * 站点id
+     */
+    private Integer siteId;
+
+    /**
+     * 设备类型 1:支路 2:分项
+     */
+    private Integer deviceType;
+
+    /**
+     * 开始时间
+     */
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    private LocalDate startTime;
+
+    /**
+     * 结束时间
+     */
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    private LocalDate endTime;
+
+    /**
+     * 页数
+     */
+    private Integer pageNum;
+
+    /**
+     * 每页数量
+     */
+    private Integer pageSize;
+
+    /**
+     * 设备编码
+     */
+    private List<String> deviceCodes;
+}

+ 76 - 0
fiveep-service/src/main/java/com/bizmatics/service/vo/TimeSharingElectricityResponseVO.java

@@ -0,0 +1,76 @@
+package com.bizmatics.service.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ *
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2025/9/4
+ */
+@Data
+public class TimeSharingElectricityResponseVO {
+
+    /**
+     * 设备名称
+     */
+    private String deviceName;
+
+    /**
+     * 尖 电量
+     */
+    private BigDecimal sharpNum;
+
+    /**
+     * 峰 电量
+     */
+    private BigDecimal peakNum;
+
+    /**
+     * 平 电量
+     */
+    private BigDecimal flatNum;
+
+    /**
+     * 谷 电量
+     */
+    private BigDecimal valleyNum;
+
+    /**
+     * 总 电量
+     */
+    private BigDecimal totalNum;
+
+    /**
+     * 尖 电费
+     */
+    private BigDecimal sharpPrice;
+
+    /**
+     * 峰 电费
+     */
+    private BigDecimal peakPrice;
+
+    /**
+     * 平 电费
+     */
+    private BigDecimal flatPrice;
+
+    /**
+     * 谷 电费
+     */
+    private BigDecimal valleyPrice;
+
+    /**
+     * 总 电费
+     */
+    private BigDecimal totalPrice;
+
+    /**
+     * 数据时间区间
+     * 也用于导出文件名
+     */
+    private String time;
+}

+ 1 - 1
pom.xml

@@ -50,7 +50,7 @@
         <!--apache-->
         <commons-lang3.version>3.11</commons-lang3.version>
         <commons-beanutils.version>1.9.4</commons-beanutils.version>
-        <commons-io.version>2.6</commons-io.version>
+        <commons-io.version>2.11.0</commons-io.version>
         <okhttp3.version>4.8.1</okhttp3.version>
         <openCsv.version>4.6</openCsv.version>