Commit a021b725f754b68f99355879d12b25bce60f1686
1 parent
015d5bc6
feat: 新增eq_kwh多设备能耗查询接口
- 新增按时/日/月聚合的多设备能耗查询接口(/energy/eqKwhByType) - 新增eq_kwh数据概况调试接口(/energy/debug/eqKwhInfo) - 修复日期查询参数格式为ISO标准(T00:00:00) - 规范代码格式与Javadoc注释
Showing
2 changed files
with
543 additions
and
29 deletions
| ... | ... | @@ -164,8 +164,8 @@ public class HealthController { |
| 164 | 164 | * type=2(天): 传startDate和endDate,按日统计 |
| 165 | 165 | * type=3(月): 查本年年初到现在,按月统计 |
| 166 | 166 | * |
| 167 | - * @param dtuSn 设备序列号 | |
| 168 | - * @param type 类型:1-时,2-天,3-月 | |
| 167 | + * @param dtuSn 设备序列号 | |
| 168 | + * @param type 类型:1-时,2-天,3-月 | |
| 169 | 169 | * @param startDate 开始日期 yyyy-MM-dd (type=1,2必填) |
| 170 | 170 | * @param endDate 结束日期 yyyy-MM-dd (type=2必填) |
| 171 | 171 | */ |
| ... | ... | @@ -184,7 +184,7 @@ public class HealthController { |
| 184 | 184 | * |
| 185 | 185 | * @param date 查询日期 yyyy-MM-dd |
| 186 | 186 | * @param pageNo 页码,默认1 |
| 187 | - * @param pageSize 每页条数,默认12 | |
| 187 | + * @param pageSize 每页条数,默认12 | |
| 188 | 188 | */ |
| 189 | 189 | @GetMapping("/energy/timelineStatus") |
| 190 | 190 | public Map<String, Object> energyTimelineStatus( |
| ... | ... | @@ -208,4 +208,32 @@ public class HealthController { |
| 208 | 208 | @RequestParam String endDate) { |
| 209 | 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 | 242 | * type=2(天): 传startDate和endDate,按日统计 |
| 243 | 243 | * type=3(月): 查本年年初到现在,按月统计 |
| 244 | 244 | * |
| 245 | - * @param dtuSn 设备序列号 | |
| 246 | - * @param type 类型:1-时,2-天,3-月 | |
| 245 | + * @param dtuSn 设备序列号 | |
| 246 | + * @param type 类型:1-时,2-天,3-月 | |
| 247 | 247 | * @param startDate 开始日期 yyyy-MM-dd (type=1,2必填) |
| 248 | 248 | * @param endDate 结束日期 yyyy-MM-dd (type=2必填) |
| 249 | 249 | */ |
| ... | ... | @@ -323,7 +323,8 @@ public class EnergySearchService { |
| 323 | 323 | } |
| 324 | 324 | kwhRawData = dataArray; |
| 325 | 325 | } |
| 326 | - } catch (Exception ignored) {} | |
| 326 | + } catch (Exception ignored) { | |
| 327 | + } | |
| 327 | 328 | totalKwhAll = totalKwhAll.add(dayKwh); |
| 328 | 329 | |
| 329 | 330 | String periodKey = "3".equals(type) ? dateStr.substring(0, 7) : dateStr; |
| ... | ... | @@ -455,7 +456,8 @@ public class EnergySearchService { |
| 455 | 456 | if (runStatus == 3) runDuration += duration; // 运行状态 |
| 456 | 457 | } |
| 457 | 458 | } |
| 458 | - } catch (Exception ignored) {} | |
| 459 | + } catch (Exception ignored) { | |
| 460 | + } | |
| 459 | 461 | } |
| 460 | 462 | |
| 461 | 463 | item.put("timelineList", oeeRawData); |
| ... | ... | @@ -475,7 +477,8 @@ public class EnergySearchService { |
| 475 | 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 | 483 | item.put("totalKwh", totalKwh.setScale(2, RoundingMode.HALF_UP)); |
| 481 | 484 | // 稼动率 = 运行时长 / 总时长 |
| ... | ... | @@ -559,10 +562,10 @@ public class EnergySearchService { |
| 559 | 562 | /** |
| 560 | 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 | 570 | * @param startDate 开始日期 yyyy-MM-dd |
| 568 | 571 | * @param endDate 结束日期 yyyy-MM-dd |
| ... | ... | @@ -605,7 +608,9 @@ public class EnergySearchService { |
| 605 | 608 | return buildEqKwhStatisticsResult(deviceStatMap, deviceNameMap); |
| 606 | 609 | } |
| 607 | 610 | |
| 608 | - /** 设备统计内部结构 */ | |
| 611 | + /** | |
| 612 | + * 设备统计内部结构 | |
| 613 | + */ | |
| 609 | 614 | static class DeviceStatResult { |
| 610 | 615 | String dtuSn; |
| 611 | 616 | String deviceName; |
| ... | ... | @@ -620,15 +625,31 @@ public class EnergySearchService { |
| 620 | 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 | 653 | private Map<String, Object> buildEmptyEqKwhStats() { |
| 633 | 654 | return Map.of( |
| 634 | 655 | "code", 200, "msg", "请求成功", "data", |
| ... | ... | @@ -649,7 +670,9 @@ public class EnergySearchService { |
| 649 | 670 | ); |
| 650 | 671 | } |
| 651 | 672 | |
| 652 | - /** 获取能耗设备表所有dtuSn及名称 */ | |
| 673 | + /** | |
| 674 | + * 获取能耗设备表所有dtuSn及名称 | |
| 675 | + */ | |
| 653 | 676 | private Map<String, String> queryAllEnergyDeviceNames() { |
| 654 | 677 | String sql = "SELECT dtuSn, deviceName FROM " + energyTableName + " WHERE corp_code = ? ORDER BY dtuSn"; |
| 655 | 678 | List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql, energyCorpCode); |
| ... | ... | @@ -660,7 +683,9 @@ public class EnergySearchService { |
| 660 | 683 | return result; |
| 661 | 684 | } |
| 662 | 685 | |
| 663 | - /** 构建连续日期列表 */ | |
| 686 | + /** | |
| 687 | + * 构建连续日期列表 | |
| 688 | + */ | |
| 664 | 689 | private List<String> buildDateList(String startDate, String endDate) { |
| 665 | 690 | List<String> result = new ArrayList<>(); |
| 666 | 691 | try { |
| ... | ... | @@ -679,7 +704,9 @@ public class EnergySearchService { |
| 679 | 704 | return result; |
| 680 | 705 | } |
| 681 | 706 | |
| 682 | - /** 从 eq_kwh 表批量查询指定日期范围的所有记录 */ | |
| 707 | + /** | |
| 708 | + * 从 eq_kwh 表批量查询指定日期范围的所有记录 | |
| 709 | + */ | |
| 683 | 710 | private List<Map<String, Object>> queryEqKwhBatch(List<String> dateList) { |
| 684 | 711 | if (dateList.isEmpty()) return Collections.emptyList(); |
| 685 | 712 | |
| ... | ... | @@ -689,14 +716,19 @@ public class EnergySearchService { |
| 689 | 716 | List<Object> params = new ArrayList<>(); |
| 690 | 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 | 723 | sql.deleteCharAt(sql.length() - 1).append(")"); |
| 694 | 724 | sql.append(" ORDER BY dtuSn, use_date"); |
| 695 | 725 | |
| 696 | 726 | return jdbcTemplate.queryForList(sql.toString(), params.toArray()); |
| 697 | 727 | } |
| 698 | 728 | |
| 699 | - /** 按 dtuSn 分组聚合各状态时长 */ | |
| 729 | + /** | |
| 730 | + * 按 dtuSn 分组聚合各状态时长 | |
| 731 | + */ | |
| 700 | 732 | private Map<String, DeviceStatResult> aggregateByDevice(List<Map<String, Object>> rawData, |
| 701 | 733 | Map<String, String> deviceNameMap) { |
| 702 | 734 | Map<String, DeviceStatResult> resultMap = new LinkedHashMap<>(); |
| ... | ... | @@ -743,7 +775,9 @@ public class EnergySearchService { |
| 743 | 775 | return resultMap; |
| 744 | 776 | } |
| 745 | 777 | |
| 746 | - /** 构建最终统计结果 */ | |
| 778 | + /** | |
| 779 | + * 构建最终统计结果 | |
| 780 | + */ | |
| 747 | 781 | private Map<String, Object> buildEqKwhStatisticsResult(Map<String, DeviceStatResult> deviceStatMap, |
| 748 | 782 | Map<String, String> deviceNameMap) { |
| 749 | 783 | // ---- 全局汇总 ---- |
| ... | ... | @@ -756,8 +790,10 @@ public class EnergySearchService { |
| 756 | 790 | for (Map.Entry<String, DeviceStatResult> entry : deviceStatMap.entrySet()) { |
| 757 | 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 | 797 | totalKwhSum += dsr.totalKwh; |
| 762 | 798 | |
| 763 | 799 | // 单设备稼动率 = 运行 / (停机+待机+运行) |
| ... | ... | @@ -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 | 898 | private Map<String, Integer> queryCurrentRunStatus() { |
| 861 | 899 | String sql = "SELECT runStatus, COUNT(*) as cnt FROM " + energyTableName + |
| 862 | 900 | " WHERE corp_code = ? GROUP BY runStatus"; |
| ... | ... | @@ -875,4 +913,452 @@ public class EnergySearchService { |
| 875 | 913 | } |
| 876 | 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 | } | ... | ... |