SiteDailyElectricityCostTask.java 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. package com.bizmatics.service.job;
  2. import lombok.RequiredArgsConstructor;
  3. import lombok.extern.slf4j.Slf4j;
  4. import org.springframework.boot.ApplicationArguments;
  5. import org.springframework.boot.ApplicationRunner;
  6. import org.springframework.jdbc.core.JdbcTemplate;
  7. import org.springframework.jdbc.core.RowMapper;
  8. import org.springframework.scheduling.annotation.Scheduled;
  9. import org.springframework.stereotype.Service;
  10. import java.math.BigDecimal;
  11. import java.math.RoundingMode;
  12. import java.sql.ResultSet;
  13. import java.sql.SQLException;
  14. import java.time.LocalDate;
  15. import java.time.LocalDateTime;
  16. import java.time.LocalTime;
  17. import java.util.*;
  18. /**
  19. * 日电费计算定时任务
  20. * @author fyc
  21. * @email yuchuan.fu@chinausky.com
  22. * @date 2025/8/22
  23. */
  24. @Slf4j
  25. @Service
  26. @RequiredArgsConstructor
  27. public class SiteDailyElectricityCostTask implements ApplicationRunner {
  28. private final JdbcTemplate jdbcTemplate;
  29. /** 应用启动后立即执行一次 */
  30. @Override
  31. public void run(ApplicationArguments args) {
  32. log.info("立即执行一次日电费记录");
  33. // calculateDailyElectricityCost();
  34. }
  35. // 每天凌晨0点过5分执行,计算前一天的用电费用
  36. @Scheduled(cron = "0 5 0 * * ?")
  37. public void calculateDailyElectricityCost() {
  38. LocalDate yesterday = LocalDate.now().minusDays(1);
  39. // LocalDate yesterday = LocalDate.now();
  40. // LocalDate yesterday = LocalDate.parse("2025-07-19");
  41. log.info("开始计算{}的用电费用", yesterday);
  42. try {
  43. calculateElectricityCostForDate(yesterday);
  44. log.info("{}的用电费用计算完成", yesterday);
  45. } catch (Exception e) {
  46. log.error("计算{}用电费用时发生错误", yesterday, e);
  47. }
  48. }
  49. public void calculateElectricityCostForDate(LocalDate date) {
  50. // 获取所有需要处理的站点和设备
  51. Map<Integer, List<String>> siteDevicesMap = getSiteDevicesMap();
  52. log.info("找到{}个站点需要处理", siteDevicesMap.size());
  53. for (Map.Entry<Integer, List<String>> entry : siteDevicesMap.entrySet()) {
  54. Integer siteId = entry.getKey();
  55. List<String> deviceCodes = entry.getValue();
  56. log.info("处理站点{},包含{}个设备", siteId, deviceCodes.size());
  57. for (String deviceCode : deviceCodes) {
  58. try {
  59. calculateSiteDeviceCost(siteId, deviceCode, date);
  60. log.debug("站点{}设备{}在{}的电费计算完成", siteId, deviceCode, date);
  61. } catch (Exception e) {
  62. log.error("计算站点{}设备{}在{}的电费时出错", siteId, deviceCode, date, e);
  63. }
  64. }
  65. }
  66. }
  67. private Map<Integer, List<String>> getSiteDevicesMap() {
  68. Map<Integer, List<String>> siteDevicesMap = new HashMap<>();
  69. String sql = "SELECT site_id, device_code FROM device WHERE device_type = '1' AND enable = 1";
  70. jdbcTemplate.query(sql, rs -> {
  71. Integer siteId = rs.getInt("site_id");
  72. String deviceCode = rs.getString("device_code");
  73. if (siteId != 0 && deviceCode != null) {
  74. siteDevicesMap.computeIfAbsent(siteId, k -> new ArrayList<>()).add(deviceCode);
  75. }
  76. });
  77. return siteDevicesMap;
  78. }
  79. private void calculateSiteDeviceCost(Integer siteId, String deviceCode, LocalDate date) {
  80. // 获取该站点的时段电价配置
  81. List<TimePriceConfig> timePriceConfigs = getTimePriceConfigs(siteId, date.getMonthValue());
  82. if (timePriceConfigs.isEmpty()) {
  83. log.warn("站点{}在{}年{}月没有电价配置", siteId, date.getYear(), date.getMonthValue());
  84. return;
  85. }
  86. // 获取该设备在指定日期的电力数据
  87. List<ElectricityData> electricityDataList = getElectricityData(deviceCode, date);
  88. if (electricityDataList.size() < 2) {
  89. log.warn("设备{}在{}的电力数据不足,只有{}条记录", deviceCode, date, electricityDataList.size());
  90. return;
  91. }
  92. // 按时间排序电力数据
  93. electricityDataList.sort(Comparator.comparing(ElectricityData::getDataTime));
  94. // 初始化费用统计
  95. BigDecimal sharpPeakCost = BigDecimal.ZERO;
  96. BigDecimal peakCost = BigDecimal.ZERO;
  97. BigDecimal flatCost = BigDecimal.ZERO;
  98. BigDecimal valleyCost = BigDecimal.ZERO;
  99. BigDecimal totalCost = BigDecimal.ZERO;
  100. // 计算每个时间段的电费
  101. for (int i = 1; i < electricityDataList.size(); i++) {
  102. ElectricityData prev = electricityDataList.get(i - 1);
  103. ElectricityData curr = electricityDataList.get(i);
  104. // 计算用电量(当前值减去前一个值)
  105. double usage = curr.getEpp() - prev.getEpp();
  106. if (usage <= 0) {
  107. log.debug("设备{}在{}到{}时间段用电量为负或零,跳过", deviceCode,
  108. prev.getDataTime(), curr.getDataTime());
  109. continue;
  110. }
  111. BigDecimal electricityUsage = BigDecimal.valueOf(usage);
  112. // 确定数据点所在的时间段(取中间时间点)
  113. LocalDateTime startTime = prev.getDataTime();
  114. LocalDateTime endTime = curr.getDataTime();
  115. LocalTime midTime = startTime.plusSeconds(
  116. (endTime.toLocalTime().toSecondOfDay() - startTime.toLocalTime().toSecondOfDay()) / 2
  117. ).toLocalTime();
  118. TimePriceConfig config = findTimePriceConfig(timePriceConfigs, midTime);
  119. if (config == null) {
  120. log.debug("设备{}在时间{}没有找到对应的电价配置", deviceCode, midTime);
  121. continue;
  122. }
  123. // 计算电费:用电量 × 电价
  124. BigDecimal cost = electricityUsage.multiply(config.getElectricityPrice());
  125. totalCost = totalCost.add(cost).setScale(2, RoundingMode.HALF_UP);
  126. switch (config.getPeakValleyAttribute()) {
  127. case 1: // 尖
  128. sharpPeakCost = sharpPeakCost.add(cost).setScale(2, RoundingMode.HALF_UP);
  129. break;
  130. case 2: // 峰
  131. peakCost = peakCost.add(cost).setScale(2, RoundingMode.HALF_UP);
  132. break;
  133. case 3: // 平
  134. flatCost = flatCost.add(cost).setScale(2, RoundingMode.HALF_UP);
  135. break;
  136. case 4: // 谷
  137. valleyCost = valleyCost.add(cost).setScale(2, RoundingMode.HALF_UP);
  138. break;
  139. default:
  140. log.warn("未知的峰谷属性: {}", config.getPeakValleyAttribute());
  141. }
  142. }
  143. // 保存计算结果
  144. saveElectricityRecord(siteId, deviceCode, date, sharpPeakCost, peakCost,
  145. flatCost, valleyCost, totalCost);
  146. log.info("站点{}设备{}在{}的电费计算完成:尖峰{}元,峰{}元,平{}元,谷{}元,总计{}元",
  147. siteId, deviceCode, date, sharpPeakCost, peakCost, flatCost, valleyCost, totalCost);
  148. }
  149. private List<TimePriceConfig> getTimePriceConfigs(Integer siteId, int month) {
  150. String sql = "SELECT price_type, electricity_price, start_time, end_time, " +
  151. "peak_valley_attribute FROM electricity_time_price " +
  152. "WHERE site_id = ? AND month = ? ORDER BY start_time";
  153. return jdbcTemplate.query(sql, new Object[]{siteId, month}, new RowMapper<TimePriceConfig>() {
  154. @Override
  155. public TimePriceConfig mapRow(ResultSet rs, int rowNum) throws SQLException {
  156. TimePriceConfig config = new TimePriceConfig();
  157. config.setPriceType(rs.getInt("price_type"));
  158. config.setElectricityPrice(rs.getBigDecimal("electricity_price"));
  159. // 处理时间字段
  160. java.sql.Time startTime = rs.getTime("start_time");
  161. java.sql.Time endTime = rs.getTime("end_time");
  162. if (startTime != null) config.setStartTime(startTime.toLocalTime());
  163. if (endTime != null) config.setEndTime(endTime.toLocalTime());
  164. config.setPeakValleyAttribute(rs.getInt("peak_valley_attribute"));
  165. return config;
  166. }
  167. });
  168. }
  169. private List<ElectricityData> getElectricityData(String deviceCode, LocalDate date) {
  170. String sql = "SELECT Epp, dataTime FROM ht_analog_data " +
  171. "WHERE deviceName = ? AND freezingTime = ? " +
  172. "ORDER BY dataTime";
  173. return jdbcTemplate.query(sql, new Object[]{deviceCode, date}, new RowMapper<ElectricityData>() {
  174. @Override
  175. public ElectricityData mapRow(ResultSet rs, int rowNum) throws SQLException {
  176. ElectricityData data = new ElectricityData();
  177. data.setEpp(rs.getDouble("Epp"));
  178. java.sql.Timestamp dataTime = rs.getTimestamp("dataTime");
  179. if (dataTime != null) {
  180. data.setDataTime(dataTime.toLocalDateTime());
  181. }
  182. return data;
  183. }
  184. });
  185. }
  186. private TimePriceConfig findTimePriceConfig(List<TimePriceConfig> configs, LocalTime time) {
  187. for (TimePriceConfig config : configs) {
  188. LocalTime startTime = config.getStartTime();
  189. LocalTime endTime = config.getEndTime();
  190. if (startTime != null && endTime != null) {
  191. // 处理跨天的时间段(如23:00-01:00)
  192. if (startTime.isAfter(endTime)) {
  193. if (!time.isBefore(startTime) || time.isBefore(endTime)) {
  194. return config;
  195. }
  196. } else {
  197. if (!time.isBefore(startTime) && time.isBefore(endTime)) {
  198. return config;
  199. }
  200. }
  201. }
  202. }
  203. return null;
  204. }
  205. private void saveElectricityRecord(Integer siteId, String deviceCode, LocalDate date,
  206. BigDecimal sharpPeakCost, BigDecimal peakCost,
  207. BigDecimal flatCost, BigDecimal valleyCost,
  208. BigDecimal totalCost) {
  209. String sql = "INSERT INTO site_electricity_record " +
  210. "(site_id, device_code, date, peak_cost, flat_cost, " +
  211. "valley_cost, sharp_peak_cost, total_cost) " +
  212. "VALUES (?, ?, ?, ?, ?, ?, ?, ?) " +
  213. "ON DUPLICATE KEY UPDATE " +
  214. "peak_cost = VALUES(peak_cost), " +
  215. "flat_cost = VALUES(flat_cost), " +
  216. "valley_cost = VALUES(valley_cost), " +
  217. "sharp_peak_cost = VALUES(sharp_peak_cost), " +
  218. "total_cost = VALUES(total_cost)";
  219. try {
  220. jdbcTemplate.update(sql,
  221. siteId,
  222. deviceCode,
  223. date,
  224. peakCost,
  225. flatCost,
  226. valleyCost,
  227. sharpPeakCost,
  228. totalCost
  229. );
  230. log.debug("成功保存站点{}设备{}的电费记录", siteId, deviceCode);
  231. } catch (Exception e) {
  232. log.error("保存站点{}设备{}的电费记录失败", siteId, deviceCode, e);
  233. // 这里只是记录错误,不会影响其他设备的处理
  234. }
  235. }
  236. // 内部配置类
  237. @lombok.Data
  238. private static class TimePriceConfig {
  239. private int priceType;
  240. private BigDecimal electricityPrice;
  241. private LocalTime startTime;
  242. private LocalTime endTime;
  243. private int peakValleyAttribute;
  244. }
  245. // 电力数据类
  246. @lombok.Data
  247. private static class ElectricityData {
  248. private double epp;
  249. private LocalDateTime dataTime;
  250. }
  251. }