Commit a021b725f754b68f99355879d12b25bce60f1686

Authored by 杨鸣坤
1 parent 015d5bc6

feat: 新增eq_kwh多设备能耗查询接口

- 新增按时/日/月聚合的多设备能耗查询接口(/energy/eqKwhByType)
- 新增eq_kwh数据概况调试接口(/energy/debug/eqKwhInfo)
- 修复日期查询参数格式为ISO标准(T00:00:00)
- 规范代码格式与Javadoc注释
@@ -164,8 +164,8 @@ public class HealthController { @@ -164,8 +164,8 @@ public class HealthController {
164 * type=2(天): 传startDate和endDate,按日统计 164 * type=2(天): 传startDate和endDate,按日统计
165 * type=3(月): 查本年年初到现在,按月统计 165 * type=3(月): 查本年年初到现在,按月统计
166 * 166 *
167 - * @param dtuSn 设备序列号  
168 - * @param type 类型:1-时,2-天,3-月 167 + * @param dtuSn 设备序列号
  168 + * @param type 类型:1-时,2-天,3-月
169 * @param startDate 开始日期 yyyy-MM-dd (type=1,2必填) 169 * @param startDate 开始日期 yyyy-MM-dd (type=1,2必填)
170 * @param endDate 结束日期 yyyy-MM-dd (type=2必填) 170 * @param endDate 结束日期 yyyy-MM-dd (type=2必填)
171 */ 171 */
@@ -184,7 +184,7 @@ public class HealthController { @@ -184,7 +184,7 @@ public class HealthController {
184 * 184 *
185 * @param date 查询日期 yyyy-MM-dd 185 * @param date 查询日期 yyyy-MM-dd
186 * @param pageNo 页码,默认1 186 * @param pageNo 页码,默认1
187 - * @param pageSize 每页条数,默认12 187 + * @param pageSize 每页条数,默认12
188 */ 188 */
189 @GetMapping("/energy/timelineStatus") 189 @GetMapping("/energy/timelineStatus")
190 public Map<String, Object> energyTimelineStatus( 190 public Map<String, Object> energyTimelineStatus(
@@ -208,4 +208,32 @@ public class HealthController { @@ -208,4 +208,32 @@ public class HealthController {
208 @RequestParam String endDate) { 208 @RequestParam String endDate) {
209 return energySearchService.queryEqKwhStatistics(startDate, endDate); 209 return energySearchService.queryEqKwhStatistics(startDate, endDate);
210 } 210 }
  211 +
  212 + /**
  213 + * 查询eq_kwh多设备能耗数据(按 时/日/月 聚合)
  214 + * type=1 (时): 传startDate,返回该日所有设备的原始用电量明细
  215 + * type=2 (日):
  216 + * - 传startDate+endDate: 按此范围每日统计每台设备能耗
  217 + * - 只传startDate: 自动取当月1号~月末,按日统计
  218 + * type=3 (月): 自动取本年1月~当前月,按月统计每台能耗数据
  219 + *
  220 + * @param type 类型: 1-时, 2-日, 3-月
  221 + * @param startDate 开始日期 yyyy-MM-dd
  222 + * @param endDate 结束日期 yyyy-MM-dd (type=2时可选)
  223 + */
  224 + @GetMapping("/energy/eqKwhByType")
  225 + public Map<String, Object> eqKwhByType(
  226 + @RequestParam(defaultValue = "1") String type,
  227 + @RequestParam(required = false) String startDate,
  228 + @RequestParam(required = false) String endDate) {
  229 + return energySearchService.queryEqKwhByType(type, startDate, endDate);
  230 + }
  231 +
  232 + /**
  233 + * 调试:查看eq_kwh表中的数据概况
  234 + */
  235 + @GetMapping("/energy/debug/eqKwhInfo")
  236 + public Map<String, Object> debugEqKwhInfo() {
  237 + return energySearchService.debugEqKwhInfo();
  238 + }
211 } 239 }
@@ -242,8 +242,8 @@ public class EnergySearchService { @@ -242,8 +242,8 @@ public class EnergySearchService {
242 * type=2(天): 传startDate和endDate,按日统计 242 * type=2(天): 传startDate和endDate,按日统计
243 * type=3(月): 查本年年初到现在,按月统计 243 * type=3(月): 查本年年初到现在,按月统计
244 * 244 *
245 - * @param dtuSn 设备序列号  
246 - * @param type 类型:1-时,2-天,3-月 245 + * @param dtuSn 设备序列号
  246 + * @param type 类型:1-时,2-天,3-月
247 * @param startDate 开始日期 yyyy-MM-dd (type=1,2必填) 247 * @param startDate 开始日期 yyyy-MM-dd (type=1,2必填)
248 * @param endDate 结束日期 yyyy-MM-dd (type=2必填) 248 * @param endDate 结束日期 yyyy-MM-dd (type=2必填)
249 */ 249 */
@@ -323,7 +323,8 @@ public class EnergySearchService { @@ -323,7 +323,8 @@ public class EnergySearchService {
323 } 323 }
324 kwhRawData = dataArray; 324 kwhRawData = dataArray;
325 } 325 }
326 - } catch (Exception ignored) {} 326 + } catch (Exception ignored) {
  327 + }
327 totalKwhAll = totalKwhAll.add(dayKwh); 328 totalKwhAll = totalKwhAll.add(dayKwh);
328 329
329 String periodKey = "3".equals(type) ? dateStr.substring(0, 7) : dateStr; 330 String periodKey = "3".equals(type) ? dateStr.substring(0, 7) : dateStr;
@@ -455,7 +456,8 @@ public class EnergySearchService { @@ -455,7 +456,8 @@ public class EnergySearchService {
455 if (runStatus == 3) runDuration += duration; // 运行状态 456 if (runStatus == 3) runDuration += duration; // 运行状态
456 } 457 }
457 } 458 }
458 - } catch (Exception ignored) {} 459 + } catch (Exception ignored) {
  460 + }
459 } 461 }
460 462
461 item.put("timelineList", oeeRawData); 463 item.put("timelineList", oeeRawData);
@@ -475,7 +477,8 @@ public class EnergySearchService { @@ -475,7 +477,8 @@ public class EnergySearchService {
475 if (value != null) totalKwh = totalKwh.add(BigDecimal.valueOf(value)); 477 if (value != null) totalKwh = totalKwh.add(BigDecimal.valueOf(value));
476 } 478 }
477 } 479 }
478 - } catch (Exception ignored) {} 480 + } catch (Exception ignored) {
  481 + }
479 482
480 item.put("totalKwh", totalKwh.setScale(2, RoundingMode.HALF_UP)); 483 item.put("totalKwh", totalKwh.setScale(2, RoundingMode.HALF_UP));
481 // 稼动率 = 运行时长 / 总时长 484 // 稼动率 = 运行时长 / 总时长
@@ -559,10 +562,10 @@ public class EnergySearchService { @@ -559,10 +562,10 @@ public class EnergySearchService {
559 /** 562 /**
560 * 查询 eq_kwh 表的综合统计数据 563 * 查询 eq_kwh 表的综合统计数据
561 * 返回: 564 * 返回:
562 - * 1. 所有设备的总的稼动率 + 每个状态的时间(xxx.xx时)  
563 - * 2. 当前设备的运行状态  
564 - * 3. 异常机台排名(待机+停机时间最长往下排, xx时xx分xx秒)  
565 - * 4. 每个设备统计时间内的总的0/1/2/3的时间(xx时xx分xx秒) 565 + * 1. 所有设备的总的稼动率 + 每个状态的时间(xxx.xx时)
  566 + * 2. 当前设备的运行状态
  567 + * 3. 异常机台排名(待机+停机时间最长往下排, xx时xx分xx秒)
  568 + * 4. 每个设备统计时间内的总的0/1/2/3的时间(xx时xx分xx秒)
566 * 569 *
567 * @param startDate 开始日期 yyyy-MM-dd 570 * @param startDate 开始日期 yyyy-MM-dd
568 * @param endDate 结束日期 yyyy-MM-dd 571 * @param endDate 结束日期 yyyy-MM-dd
@@ -605,7 +608,9 @@ public class EnergySearchService { @@ -605,7 +608,9 @@ public class EnergySearchService {
605 return buildEqKwhStatisticsResult(deviceStatMap, deviceNameMap); 608 return buildEqKwhStatisticsResult(deviceStatMap, deviceNameMap);
606 } 609 }
607 610
608 - /** 设备统计内部结构 */ 611 + /**
  612 + * 设备统计内部结构
  613 + */
609 static class DeviceStatResult { 614 static class DeviceStatResult {
610 String dtuSn; 615 String dtuSn;
611 String deviceName; 616 String deviceName;
@@ -620,15 +625,31 @@ public class EnergySearchService { @@ -620,15 +625,31 @@ public class EnergySearchService {
620 this.deviceName = deviceName; 625 this.deviceName = deviceName;
621 } 626 }
622 627
623 - /** 总时长(秒) */  
624 - long totalDuration() { return status0 + status1 + status2 + status3; }  
625 - /** 异常时长 = 停机 + 待机 */  
626 - long abnormalDuration() { return status1 + status2; }  
627 - /** 稼动分母 = 停机 + 待机 + 运行 (排除离线) */  
628 - long activeDuration() { return status1 + status2 + status3; } 628 + /**
  629 + * 总时长(秒)
  630 + */
  631 + long totalDuration() {
  632 + return status0 + status1 + status2 + status3;
  633 + }
  634 +
  635 + /**
  636 + * 异常时长 = 停机 + 待机
  637 + */
  638 + long abnormalDuration() {
  639 + return status1 + status2;
  640 + }
  641 +
  642 + /**
  643 + * 稼动分母 = 停机 + 待机 + 运行 (排除离线)
  644 + */
  645 + long activeDuration() {
  646 + return status1 + status2 + status3;
  647 + }
629 } 648 }
630 649
631 - /** 构建空结果 */ 650 + /**
  651 + * 构建空结果
  652 + */
632 private Map<String, Object> buildEmptyEqKwhStats() { 653 private Map<String, Object> buildEmptyEqKwhStats() {
633 return Map.of( 654 return Map.of(
634 "code", 200, "msg", "请求成功", "data", 655 "code", 200, "msg", "请求成功", "data",
@@ -649,7 +670,9 @@ public class EnergySearchService { @@ -649,7 +670,9 @@ public class EnergySearchService {
649 ); 670 );
650 } 671 }
651 672
652 - /** 获取能耗设备表所有dtuSn及名称 */ 673 + /**
  674 + * 获取能耗设备表所有dtuSn及名称
  675 + */
653 private Map<String, String> queryAllEnergyDeviceNames() { 676 private Map<String, String> queryAllEnergyDeviceNames() {
654 String sql = "SELECT dtuSn, deviceName FROM " + energyTableName + " WHERE corp_code = ? ORDER BY dtuSn"; 677 String sql = "SELECT dtuSn, deviceName FROM " + energyTableName + " WHERE corp_code = ? ORDER BY dtuSn";
655 List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql, energyCorpCode); 678 List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql, energyCorpCode);
@@ -660,7 +683,9 @@ public class EnergySearchService { @@ -660,7 +683,9 @@ public class EnergySearchService {
660 return result; 683 return result;
661 } 684 }
662 685
663 - /** 构建连续日期列表 */ 686 + /**
  687 + * 构建连续日期列表
  688 + */
664 private List<String> buildDateList(String startDate, String endDate) { 689 private List<String> buildDateList(String startDate, String endDate) {
665 List<String> result = new ArrayList<>(); 690 List<String> result = new ArrayList<>();
666 try { 691 try {
@@ -679,7 +704,9 @@ public class EnergySearchService { @@ -679,7 +704,9 @@ public class EnergySearchService {
679 return result; 704 return result;
680 } 705 }
681 706
682 - /** 从 eq_kwh 表批量查询指定日期范围的所有记录 */ 707 + /**
  708 + * 从 eq_kwh 表批量查询指定日期范围的所有记录
  709 + */
683 private List<Map<String, Object>> queryEqKwhBatch(List<String> dateList) { 710 private List<Map<String, Object>> queryEqKwhBatch(List<String> dateList) {
684 if (dateList.isEmpty()) return Collections.emptyList(); 711 if (dateList.isEmpty()) return Collections.emptyList();
685 712
@@ -689,14 +716,19 @@ public class EnergySearchService { @@ -689,14 +716,19 @@ public class EnergySearchService {
689 List<Object> params = new ArrayList<>(); 716 List<Object> params = new ArrayList<>();
690 params.add(energyCorpCode); 717 params.add(energyCorpCode);
691 718
692 - for (String d : dateList) { sql.append("?,"); params.add(d + " 00:00:00"); } 719 + for (String d : dateList) {
  720 + sql.append("?,");
  721 + params.add(d + "T00:00:00");
  722 + }
693 sql.deleteCharAt(sql.length() - 1).append(")"); 723 sql.deleteCharAt(sql.length() - 1).append(")");
694 sql.append(" ORDER BY dtuSn, use_date"); 724 sql.append(" ORDER BY dtuSn, use_date");
695 725
696 return jdbcTemplate.queryForList(sql.toString(), params.toArray()); 726 return jdbcTemplate.queryForList(sql.toString(), params.toArray());
697 } 727 }
698 728
699 - /** 按 dtuSn 分组聚合各状态时长 */ 729 + /**
  730 + * 按 dtuSn 分组聚合各状态时长
  731 + */
700 private Map<String, DeviceStatResult> aggregateByDevice(List<Map<String, Object>> rawData, 732 private Map<String, DeviceStatResult> aggregateByDevice(List<Map<String, Object>> rawData,
701 Map<String, String> deviceNameMap) { 733 Map<String, String> deviceNameMap) {
702 Map<String, DeviceStatResult> resultMap = new LinkedHashMap<>(); 734 Map<String, DeviceStatResult> resultMap = new LinkedHashMap<>();
@@ -743,7 +775,9 @@ public class EnergySearchService { @@ -743,7 +775,9 @@ public class EnergySearchService {
743 return resultMap; 775 return resultMap;
744 } 776 }
745 777
746 - /** 构建最终统计结果 */ 778 + /**
  779 + * 构建最终统计结果
  780 + */
747 private Map<String, Object> buildEqKwhStatisticsResult(Map<String, DeviceStatResult> deviceStatMap, 781 private Map<String, Object> buildEqKwhStatisticsResult(Map<String, DeviceStatResult> deviceStatMap,
748 Map<String, String> deviceNameMap) { 782 Map<String, String> deviceNameMap) {
749 // ---- 全局汇总 ---- 783 // ---- 全局汇总 ----
@@ -756,8 +790,10 @@ public class EnergySearchService { @@ -756,8 +790,10 @@ public class EnergySearchService {
756 for (Map.Entry<String, DeviceStatResult> entry : deviceStatMap.entrySet()) { 790 for (Map.Entry<String, DeviceStatResult> entry : deviceStatMap.entrySet()) {
757 DeviceStatResult dsr = entry.getValue(); 791 DeviceStatResult dsr = entry.getValue();
758 792
759 - totalS0 += dsr.status0; totalS1 += dsr.status1;  
760 - totalS2 += dsr.status2; totalS3 += dsr.status3; 793 + totalS0 += dsr.status0;
  794 + totalS1 += dsr.status1;
  795 + totalS2 += dsr.status2;
  796 + totalS3 += dsr.status3;
761 totalKwhSum += dsr.totalKwh; 797 totalKwhSum += dsr.totalKwh;
762 798
763 // 单设备稼动率 = 运行 / (停机+待机+运行) 799 // 单设备稼动率 = 运行 / (停机+待机+运行)
@@ -856,7 +892,9 @@ public class EnergySearchService { @@ -856,7 +892,9 @@ public class EnergySearchService {
856 ); 892 );
857 } 893 }
858 894
859 - /** 查询当前设备运行状态分布(从energy表) runStatus: 0-离线, 1-停机, 2-待机, 3-运行 */ 895 + /**
  896 + * 查询当前设备运行状态分布(从energy表) runStatus: 0-离线, 1-停机, 2-待机, 3-运行
  897 + */
860 private Map<String, Integer> queryCurrentRunStatus() { 898 private Map<String, Integer> queryCurrentRunStatus() {
861 String sql = "SELECT runStatus, COUNT(*) as cnt FROM " + energyTableName + 899 String sql = "SELECT runStatus, COUNT(*) as cnt FROM " + energyTableName +
862 " WHERE corp_code = ? GROUP BY runStatus"; 900 " WHERE corp_code = ? GROUP BY runStatus";
@@ -875,4 +913,452 @@ public class EnergySearchService { @@ -875,4 +913,452 @@ public class EnergySearchService {
875 } 913 }
876 return Map.of("0", s0, "1", s1, "2", s2, "3", s3); 914 return Map.of("0", s0, "1", s1, "2", s2, "3", s3);
877 } 915 }
  916 +
  917 + // ==================== eq_kwh 多设备时/日/月查询 ====================
  918 +
  919 + /**
  920 + * 查询 eq_kwh 表的多设备能耗数据(按 时/日/月 聚合)
  921 + * <p>
  922 + * type=1 (时): 只需传 startDate,返回该日所有设备的原始用电量明细(kwhList)
  923 + * type=2 (日):
  924 + * - 若传了 startDate+endDate: 按此范围每日统计每台设备能耗
  925 + * - 若只传 startDate: 自动补全为当月的第1天~最后一天
  926 + * type=3 (月): 自动取本年1月~当前月,按月统计每台设备能耗
  927 + *
  928 + * @param type 查询类型: 1-时, 2-日, 3-月
  929 + * @param startDate 开始日期 yyyy-MM-dd (type=1,2必填; type=3可选)
  930 + * @param endDate 结束日期 yyyy-MM-dd (type=2可选)
  931 + */
  932 + public Map<String, Object> queryEqKwhByType(String type, String startDate, String endDate) {
  933 + log.info("========== [eq_kwh多设备查询] type={}, startDate={}, endDate={} ==========", type, startDate, endDate);
  934 +
  935 + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
  936 + List<String> dateList = new ArrayList<>();
  937 +
  938 + // 根据type构建日期列表 + 实际起止日期
  939 + String actualStart;
  940 + String actualEnd;
  941 +
  942 + if ("1".equals(type)) {
  943 + // type=1: 单日查询,直接返回原始kwh数据
  944 + if (!StringUtils.hasText(startDate)) {
  945 + return Map.of("code", 400, "msg", "参数错误: type=1时startDate必填");
  946 + }
  947 + dateList.add(startDate);
  948 + actualStart = startDate;
  949 + actualEnd = startDate;
  950 + } else if ("2".equals(type)) {
  951 + // type=2: 按日统计
  952 + if (!StringUtils.hasText(startDate)) {
  953 + return Map.of("code", 400, "msg", "参数错误: type=2时startDate必填");
  954 + }
  955 + if (StringUtils.hasText(endDate)) {
  956 + // 有结束日期,直接用传入范围
  957 + try {
  958 + Date start = sdf.parse(startDate);
  959 + Date end = sdf.parse(endDate);
  960 + Calendar cur = Calendar.getInstance();
  961 + cur.setTime(start);
  962 + while (!cur.getTime().after(end)) {
  963 + dateList.add(sdf.format(cur.getTime()));
  964 + cur.add(Calendar.DAY_OF_MONTH, 1);
  965 + }
  966 + actualStart = startDate;
  967 + actualEnd = endDate;
  968 + } catch (Exception e) {
  969 + return Map.of("code", 400, "msg", "日期格式错误: " + startDate + " ~ " + endDate);
  970 + }
  971 + } else {
  972 + // 只有开始日期 -> 自动取所在月的1号~最后一天
  973 + try {
  974 + Date startD = sdf.parse(startDate);
  975 + Calendar cal = Calendar.getInstance();
  976 + cal.setTime(startD);
  977 + cal.set(Calendar.DAY_OF_MONTH, 1); // 月初
  978 + actualStart = sdf.format(cal.getTime());
  979 + cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH)); // 月末
  980 + actualEnd = sdf.format(cal.getTime());
  981 +
  982 + Calendar cur = Calendar.getInstance();
  983 + cur.setTime(sdf.parse(actualStart));
  984 + while (!cur.after(cal)) { // cur <= actualEnd
  985 + dateList.add(sdf.format(cur.getTime()));
  986 + cur.add(Calendar.DAY_OF_MONTH, 1);
  987 + }
  988 + } catch (Exception e) {
  989 + return Map.of("code", 400, "msg", "日期格式错误: " + startDate);
  990 + }
  991 + }
  992 + } else if ("3".equals(type)) {
  993 + // type=3: 按月统计
  994 + if (StringUtils.hasText(startDate) && StringUtils.hasText(endDate)) {
  995 + // 有起止日期:按传入范围按月聚合
  996 + try {
  997 + Date start = sdf.parse(startDate);
  998 + Date end = sdf.parse(endDate);
  999 + actualStart = startDate;
  1000 + actualEnd = endDate;
  1001 + Calendar cur = Calendar.getInstance();
  1002 + cur.setTime(start);
  1003 + while (!cur.getTime().after(end)) {
  1004 + dateList.add(sdf.format(cur.getTime()));
  1005 + cur.add(Calendar.DAY_OF_MONTH, 1);
  1006 + }
  1007 + } catch (Exception e) {
  1008 + return Map.of("code", 400, "msg", "日期格式错误: " + startDate + " ~ " + endDate);
  1009 + }
  1010 + } else {
  1011 + // 无日期:默认本年1月1日 ~ 今天
  1012 + Calendar now = Calendar.getInstance();
  1013 + int year = now.get(Calendar.YEAR);
  1014 + Calendar startCal = Calendar.getInstance();
  1015 + startCal.set(year, Calendar.JANUARY, 1);
  1016 + actualStart = sdf.format(startCal.getTime());
  1017 + actualEnd = sdf.format(now.getTime());
  1018 +
  1019 + Calendar cur = Calendar.getInstance();
  1020 + cur.setTime(startCal.getTime());
  1021 + while (!cur.getTime().after(now.getTime())) {
  1022 + dateList.add(sdf.format(cur.getTime()));
  1023 + cur.add(Calendar.DAY_OF_MONTH, 1);
  1024 + }
  1025 + }
  1026 + } else {
  1027 + return Map.of("code", 400, "msg", "参数错误: type只支持1/2/3");
  1028 + }
  1029 +
  1030 + log.info("实际日期范围: {} ~ {}, 共{}天", actualStart, actualEnd, dateList.size());
  1031 +
  1032 + // 获取所有设备列表及名称
  1033 + Map<String, String> deviceNameMap = queryAllEnergyDeviceNames();
  1034 +
  1035 + // 从 eq_kwh 批量查询所有记录
  1036 + List<Map<String, Object>> allRawData = queryEqKwhBatch(dateList);
  1037 + log.info("eq_kwh 批量查询返回 {} 条原始记录", allRawData.size());
  1038 +
  1039 + // 构建结果
  1040 + return buildEqKwhByTypeResult(type, dateList, allRawData, deviceNameMap, actualStart, actualEnd);
  1041 + }
  1042 +
  1043 + /**
  1044 + * 构建 type=1/2/3 的多设备查询结果
  1045 + */
  1046 + private Map<String, Object> buildEqKwhByTypeResult(String type, List<String> dateList,
  1047 + List<Map<String, Object>> allRawData,
  1048 + Map<String, String> deviceNameMap,
  1049 + String actualStart, String actualEnd) {
  1050 + // 按 dtuSn+日期 建立快速查找Map: key="dtuSn|yyyy-MM-dd" -> JSONArray(kwh原始数据)
  1051 + Map<String, Object> rawDataMap = new LinkedHashMap<>();
  1052 + for (Map<String, Object> row : allRawData) {
  1053 + String sn = (String) row.get("dtuSn");
  1054 + String ud = String.valueOf(row.get("use_date"));
  1055 + String desc = row.get("description") != null ? String.valueOf(row.get("description")) : "";
  1056 + if (StringUtils.hasText(desc)) {
  1057 + try {
  1058 + // use_date 可能是 "2026-05-21T00:00:00" 或 "2026-05-21 00:00:00",统一截取前10位作为纯日期key
  1059 + String dateKey = ud.length() > 10 ? ud.substring(0, 10) : ud;
  1060 + rawDataMap.put(sn + "|" + dateKey, JSON.parseArray(desc));
  1061 + } catch (Exception ignored) {
  1062 + }
  1063 + }
  1064 + }
  1065 +
  1066 + // ---- type=1: 返回所有设备的原始kwh明细 ----
  1067 + if ("1".equals(type)) {
  1068 + return buildType1Result(dateList.get(0), rawDataMap, deviceNameMap);
  1069 + }
  1070 +
  1071 + // ---- type=2: 按日统计 ----
  1072 + if ("2".equals(type)) {
  1073 + return buildType2Result(dateList, rawDataMap, deviceNameMap, actualStart, actualEnd);
  1074 + }
  1075 +
  1076 + // ---- type=3: 按月统计 ----
  1077 + if ("3".equals(type)) {
  1078 + return buildType3Result(dateList, rawDataMap, deviceNameMap, actualStart, actualEnd);
  1079 + }
  1080 +
  1081 + return Map.of("code", 500, "msg", "未知type");
  1082 + }
  1083 +
  1084 + /**
  1085 + * type=1 (时): 单日查询 - 返回每个设备的 kwh 原始数据 + 当日汇总
  1086 + */
  1087 + private Map<String, Object> buildType1Result(String dateStr, Map<String, Object> rawDataMap,
  1088 + Map<String, String> deviceNameMap) {
  1089 + List<Map<String, Object>> list = new ArrayList<>();
  1090 + BigDecimal totalKwhAll = BigDecimal.ZERO;
  1091 +
  1092 + for (String sn : deviceNameMap.keySet()) {
  1093 + String mapKey = sn + "|" + dateStr;
  1094 + Object rawObj = rawDataMap.getOrDefault(mapKey, Collections.emptyList());
  1095 + @SuppressWarnings("unchecked")
  1096 + JSONArray dataArray = rawObj instanceof JSONArray ? (JSONArray) rawObj : new JSONArray();
  1097 +
  1098 + // 统计当日总用电量
  1099 + BigDecimal dayKwh = BigDecimal.ZERO;
  1100 + for (int i = 0; i < dataArray.size(); i++) {
  1101 + JSONObject item = dataArray.getJSONObject(i);
  1102 + if (item != null && item.getDouble("value") != null) {
  1103 + dayKwh = dayKwh.add(BigDecimal.valueOf(item.getDouble("value")));
  1104 + }
  1105 + }
  1106 + totalKwhAll = totalKwhAll.add(dayKwh);
  1107 +
  1108 + Map<String, Object> item = new LinkedHashMap<>();
  1109 + item.put("dtuSn", sn);
  1110 + item.put("deviceName", deviceNameMap.getOrDefault(sn, ""));
  1111 + item.put("date", dateStr);
  1112 + item.put("kwhList", dataArray);
  1113 + item.put("totalKwh", dayKwh.setScale(2, RoundingMode.HALF_UP));
  1114 + list.add(item);
  1115 + }
  1116 +
  1117 + return Map.of(
  1118 + "code", 200, "msg", "请求成功",
  1119 + "type", "1",
  1120 + "date", dateStr,
  1121 + "totalDevices", deviceNameMap.size(),
  1122 + "totalKwh", totalKwhAll.setScale(2, RoundingMode.HALF_UP),
  1123 + "list", list
  1124 + );
  1125 + }
  1126 +
  1127 + /**
  1128 + * type=2 (日): 按设备统计 - 每个设备一行,包含该设备每天的能耗明细
  1129 + */
  1130 + private Map<String, Object> buildType2Result(List<String> dateList, Map<String, Object> rawDataMap,
  1131 + Map<String, String> deviceNameMap,
  1132 + String actualStart, String actualEnd) {
  1133 + BigDecimal grandTotalKwh = BigDecimal.ZERO;
  1134 + List<Map<String, Object>> deviceList = new ArrayList<>(deviceNameMap.size());
  1135 +
  1136 + for (String sn : deviceNameMap.keySet()) {
  1137 + Map<String, Object> devItem = new LinkedHashMap<>();
  1138 + devItem.put("dtuSn", sn);
  1139 + devItem.put("deviceName", deviceNameMap.getOrDefault(sn, ""));
  1140 +
  1141 + // 该设备的每日数据列表
  1142 + List<Map<String, Object>> dailyDataList = new ArrayList<>(dateList.size());
  1143 + BigDecimal devTotalKwh = BigDecimal.ZERO;
  1144 + long devTotalDur = 0;
  1145 + Map<Integer, Long> devStatusAll = initStatusMap();
  1146 +
  1147 + for (String d : dateList) {
  1148 + String mapKey = sn + "|" + d;
  1149 + Object rawObj = rawDataMap.getOrDefault(mapKey, Collections.emptyList());
  1150 + @SuppressWarnings("unchecked")
  1151 + JSONArray dataArray = rawObj instanceof JSONArray ? (JSONArray) rawObj : new JSONArray();
  1152 +
  1153 + BigDecimal dayKwh = BigDecimal.ZERO;
  1154 + long dayDur = 0;
  1155 + Map<Integer, Long> dayStatus = initStatusMap();
  1156 +
  1157 + for (int i = 0; i < dataArray.size(); i++) {
  1158 + JSONObject item = dataArray.getJSONObject(i);
  1159 + if (item == null) continue;
  1160 +
  1161 + Double value = item.getDouble("value");
  1162 + if (value != null) dayKwh = dayKwh.add(BigDecimal.valueOf(value));
  1163 +
  1164 + for (int sk = 0; sk <= 3; sk++) {
  1165 + Long dur = item.getLong(String.valueOf(sk));
  1166 + if (dur != null && dur > 0) {
  1167 + dayDur += dur;
  1168 + dayStatus.merge(sk, dur, Long::sum);
  1169 + devTotalDur += dur;
  1170 + devStatusAll.merge(sk, dur, Long::sum);
  1171 + }
  1172 + }
  1173 + }
  1174 +
  1175 + devTotalKwh = devTotalKwh.add(dayKwh);
  1176 +
  1177 + Map<String, Object> dayEntry = new LinkedHashMap<>();
  1178 + dayEntry.put("date", d);
  1179 + dayEntry.put("totalKwh", dayKwh.setScale(2, RoundingMode.HALF_UP));
  1180 + dayEntry.put("totalDurationFormatted", formatDuration(dayDur));
  1181 + dayEntry.put("totalDurationSeconds", dayDur);
  1182 + dayEntry.put("statusStats", buildStatusStats(dayStatus, dayDur));
  1183 + dailyDataList.add(dayEntry);
  1184 + }
  1185 +
  1186 + grandTotalKwh = grandTotalKwh.add(devTotalKwh);
  1187 +
  1188 + devItem.put("totalKwh", devTotalKwh.setScale(2, RoundingMode.HALF_UP));
  1189 + devItem.put("totalDurationFormatted", formatDuration(devTotalDur));
  1190 + devItem.put("totalDurationSeconds", devTotalDur);
  1191 + devItem.put("statusStats", buildStatusStats(devStatusAll, devTotalDur));
  1192 + devItem.put("dailyData", dailyDataList);
  1193 +
  1194 + deviceList.add(devItem);
  1195 + }
  1196 +
  1197 + return Map.of(
  1198 + "code", 200, "msg", "请求成功",
  1199 + "type", "2",
  1200 + "actualStartDate", actualStart,
  1201 + "actualEndDate", actualEnd,
  1202 + "totalDays", dateList.size(),
  1203 + "totalDevices", deviceList.size(),
  1204 + "grandTotalKwh", grandTotalKwh.setScale(2, RoundingMode.HALF_UP),
  1205 + "list", deviceList
  1206 + );
  1207 + }
  1208 +
  1209 + /**
  1210 + * type=3 (月): 按设备统计 - 每个设备一行,包含该设备每月的能耗明细
  1211 + */
  1212 + private Map<String, Object> buildType3Result(List<String> dateList, Map<String, Object> rawDataMap,
  1213 + Map<String, String> deviceNameMap,
  1214 + String actualStart, String actualEnd) {
  1215 + BigDecimal grandTotalKwh = BigDecimal.ZERO;
  1216 + List<Map<String, Object>> deviceList = new ArrayList<>(deviceNameMap.size());
  1217 +
  1218 + for (String sn : deviceNameMap.keySet()) {
  1219 + Map<String, Object> devItem = new LinkedHashMap<>();
  1220 + devItem.put("dtuSn", sn);
  1221 + devItem.put("deviceName", deviceNameMap.getOrDefault(sn, ""));
  1222 +
  1223 + // 该设备的按月聚合数据
  1224 + Map<String, Map<String, Object>> monthAgg = new LinkedHashMap<>();
  1225 +
  1226 + for (String d : dateList) {
  1227 + String monthKey = d.substring(0, 7);
  1228 +
  1229 + monthAgg.computeIfAbsent(monthKey, k -> {
  1230 + Map<String, Object> m = new LinkedHashMap<>();
  1231 + m.put("month", k);
  1232 + m.put("label", Integer.parseInt(k.substring(5)) + "月");
  1233 + m.put("totalKwh", BigDecimal.ZERO);
  1234 + m.put("totalDurationSeconds", 0L);
  1235 + m.put("statusDurationMap", initStatusMap());
  1236 + return m;
  1237 + });
  1238 +
  1239 + String mapKey = sn + "|" + d;
  1240 + Object rawObj = rawDataMap.getOrDefault(mapKey, Collections.emptyList());
  1241 + @SuppressWarnings("unchecked")
  1242 + JSONArray dataArray = rawObj instanceof JSONArray ? (JSONArray) rawObj : new JSONArray();
  1243 +
  1244 + Map<String, Object> mEntry = monthAgg.get(monthKey);
  1245 +
  1246 + for (int i = 0; i < dataArray.size(); i++) {
  1247 + JSONObject item = dataArray.getJSONObject(i);
  1248 + if (item == null) continue;
  1249 +
  1250 + Double value = item.getDouble("value");
  1251 + if (value != null) {
  1252 + BigDecimal valBd = BigDecimal.valueOf(value);
  1253 + mEntry.put("totalKwh", ((BigDecimal) mEntry.get("totalKwh")).add(valBd));
  1254 + }
  1255 +
  1256 + for (int sk = 0; sk <= 3; sk++) {
  1257 + Long dur = item.getLong(String.valueOf(sk));
  1258 + if (dur != null && dur > 0) {
  1259 + mEntry.put("totalDurationSeconds", (Long) mEntry.get("totalDurationSeconds") + dur);
  1260 + @SuppressWarnings("unchecked")
  1261 + Map<Integer, Long> sMap = (Map<Integer, Long>) mEntry.get("statusDurationMap");
  1262 + sMap.merge(sk, dur, Long::sum);
  1263 + }
  1264 + }
  1265 + }
  1266 + }
  1267 +
  1268 + // 构建该设备的月度数据列表
  1269 + List<Map<String, Object>> monthlyDataList = new ArrayList<>(monthAgg.size());
  1270 + BigDecimal devTotalKwh = BigDecimal.ZERO;
  1271 + long devTotalDur = 0;
  1272 + Map<Integer, Long> devStatusAll = initStatusMap();
  1273 +
  1274 + for (Map.Entry<String, Map<String, Object>> me : monthAgg.entrySet()) {
  1275 + Map<String, Object> m = me.getValue();
  1276 + long mDur = (Long) m.get("totalDurationSeconds");
  1277 + BigDecimal mKwh = (BigDecimal) m.get("totalKwh");
  1278 + @SuppressWarnings("unchecked")
  1279 + Map<Integer, Long> mStatus = (Map<Integer, Long>) m.get("statusDurationMap");
  1280 +
  1281 + devTotalKwh = devTotalKwh.add(mKwh);
  1282 + devTotalDur += mDur;
  1283 + for (int sk = 0; sk <= 3; sk++) {
  1284 + long sd = mStatus.getOrDefault(sk, 0L);
  1285 + if (sd > 0) devStatusAll.merge(sk, sd, Long::sum);
  1286 + }
  1287 +
  1288 + Map<String, Object> monthEntry = new LinkedHashMap<>();
  1289 + monthEntry.put("month", m.get("month"));
  1290 + monthEntry.put("label", m.get("label"));
  1291 + monthEntry.put("totalKwh", mKwh.setScale(2, RoundingMode.HALF_UP));
  1292 + monthEntry.put("totalDurationFormatted", formatDuration(mDur));
  1293 + monthEntry.put("totalDurationSeconds", mDur);
  1294 + monthEntry.put("statusStats", buildStatusStats(mStatus, mDur));
  1295 + monthlyDataList.add(monthEntry);
  1296 + }
  1297 +
  1298 + grandTotalKwh = grandTotalKwh.add(devTotalKwh);
  1299 +
  1300 + devItem.put("totalKwh", devTotalKwh.setScale(2, RoundingMode.HALF_UP));
  1301 + devItem.put("totalDurationFormatted", formatDuration(devTotalDur));
  1302 + devItem.put("totalDurationSeconds", devTotalDur);
  1303 + devItem.put("statusStats", buildStatusStats(devStatusAll, devTotalDur));
  1304 + devItem.put("monthlyData", monthlyDataList);
  1305 +
  1306 + deviceList.add(devItem);
  1307 + }
  1308 +
  1309 + return Map.of(
  1310 + "code", 200, "msg", "请求成功",
  1311 + "type", "3",
  1312 + "year", actualStart.substring(0, 4),
  1313 + "actualStartDate", actualStart,
  1314 + "actualEndDate", actualEnd,
  1315 + "totalMonths", dateList.isEmpty() ? 0 :
  1316 + (dateList.stream().map(d -> d.substring(0, 7)).distinct().toList()).size(),
  1317 + "totalDevices", deviceList.size(),
  1318 + "grandTotalKwh", grandTotalKwh.setScale(2, RoundingMode.HALF_UP),
  1319 + "list", deviceList
  1320 + );
  1321 + }
  1322 +
  1323 + /** 调试接口: 查看eq_kwh表中的数据概况 */
  1324 + public Map<String, Object> debugEqKwhInfo() {
  1325 + // 总记录数
  1326 + Long totalCount = jdbcTemplate.queryForObject(
  1327 + "SELECT COUNT(*) FROM " + eqKwhTableName + " WHERE corp_code = ?", Long.class, energyCorpCode);
  1328 +
  1329 + // 按日期分组统计
  1330 + List<Map<String, Object>> dateStats = jdbcTemplate.queryForList(
  1331 + "SELECT use_date, COUNT(*) as cnt FROM " + eqKwhTableName +
  1332 + " WHERE corp_code = ? GROUP BY use_date ORDER BY use_date DESC LIMIT 20",
  1333 + energyCorpCode);
  1334 +
  1335 + // 按设备分组统计
  1336 + List<Map<String, Object>> deviceStats = jdbcTemplate.queryForList(
  1337 + "SELECT dtuSn, COUNT(*) as cnt FROM " + eqKwhTableName +
  1338 + " WHERE corp_code = ? GROUP BY dtuSn ORDER BY dtuSn",
  1339 + energyCorpCode);
  1340 +
  1341 + // 最新一条记录的 description 预览
  1342 + Map<String, Object> lastRecord = null;
  1343 + try {
  1344 + String sql = "SELECT dtuSn, use_date, LEFT(description, 500) AS desc_preview, LENGTH(description) as desc_len " +
  1345 + "FROM " + eqKwhTableName + " WHERE corp_code = ? ORDER BY use_date DESC LIMIT 1";
  1346 + lastRecord = jdbcTemplate.queryForMap(sql, energyCorpCode);
  1347 + } catch (Exception ignored) {}
  1348 +
  1349 + // energy 表设备数
  1350 + Long deviceCount = jdbcTemplate.queryForObject(
  1351 + "SELECT COUNT(*) FROM " + energyTableName + " WHERE corp_code = ?", Long.class, energyCorpCode);
  1352 +
  1353 + return Map.of(
  1354 + "code", 200, "msg", "请求成功",
  1355 + "tableName", eqKwhTableName,
  1356 + "corpCode", energyCorpCode,
  1357 + "totalRecords", totalCount != null ? totalCount : 0,
  1358 + "deviceCountInEnergyTable", deviceCount != null ? deviceCount : 0,
  1359 + "latestDateRecords", dateStats,
  1360 + "perDeviceRecordCount", deviceStats,
  1361 + "lastRecordPreview", lastRecord
  1362 + );
  1363 + }
878 } 1364 }