|
...
|
...
|
@@ -545,4 +545,334 @@ public class EnergySearchService { |
|
545
|
545
|
sb.append(s).append("秒");
|
|
546
|
546
|
return sb.toString();
|
|
547
|
547
|
}
|
|
|
548
|
+
|
|
|
549
|
+ /**
|
|
|
550
|
+ * 格式化时长为 xxx.xx 时
|
|
|
551
|
+ */
|
|
|
552
|
+ private static String formatDurationToHours(long totalSeconds) {
|
|
|
553
|
+ double hours = totalSeconds / 3600.0;
|
|
|
554
|
+ return String.format("%.2f时", hours);
|
|
|
555
|
+ }
|
|
|
556
|
+
|
|
|
557
|
+ // ==================== eq_kwh 综合统计查询 ====================
|
|
|
558
|
+
|
|
|
559
|
+ /**
|
|
|
560
|
+ * 查询 eq_kwh 表的综合统计数据
|
|
|
561
|
+ * 返回:
|
|
|
562
|
+ * 1. 所有设备的总的稼动率 + 每个状态的时间(xxx.xx时)
|
|
|
563
|
+ * 2. 当前设备的运行状态
|
|
|
564
|
+ * 3. 异常机台排名(待机+停机时间最长往下排, xx时xx分xx秒)
|
|
|
565
|
+ * 4. 每个设备统计时间内的总的0/1/2/3的时间(xx时xx分xx秒)
|
|
|
566
|
+ *
|
|
|
567
|
+ * @param startDate 开始日期 yyyy-MM-dd
|
|
|
568
|
+ * @param endDate 结束日期 yyyy-MM-dd
|
|
|
569
|
+ */
|
|
|
570
|
+ public Map<String, Object> queryEqKwhStatistics(String startDate, String endDate) {
|
|
|
571
|
+ log.info("========== [eq_kwh综合统计] startDate={}, endDate={} ==========", startDate, endDate);
|
|
|
572
|
+
|
|
|
573
|
+ if (!StringUtils.hasText(startDate) || !StringUtils.hasText(endDate)) {
|
|
|
574
|
+ return Map.of(
|
|
|
575
|
+ "code", 400, "msg", "参数错误: startDate和endDate必填",
|
|
|
576
|
+ "data", Map.of()
|
|
|
577
|
+ );
|
|
|
578
|
+ }
|
|
|
579
|
+
|
|
|
580
|
+ // 1. 构建日期列表
|
|
|
581
|
+ List<String> dateList = buildDateList(startDate, endDate);
|
|
|
582
|
+ if (dateList.isEmpty()) {
|
|
|
583
|
+ return buildEmptyEqKwhStats();
|
|
|
584
|
+ }
|
|
|
585
|
+ log.info("日期范围共 {} 天: {} ~ {}", dateList.size(), dateList.get(0), dateList.get(dateList.size() - 1));
|
|
|
586
|
+
|
|
|
587
|
+ // 2. 获取所有设备列表及名称
|
|
|
588
|
+ Map<String, String> deviceNameMap = queryAllEnergyDeviceNames();
|
|
|
589
|
+
|
|
|
590
|
+ // 3. 从 eq_kwh 表批量查询所有设备在指定日期范围内的数据
|
|
|
591
|
+ List<Map<String, Object>> allRawData = queryEqKwhBatch(dateList);
|
|
|
592
|
+ log.info("eq_kwh 批量查询返回 {} 条原始记录", allRawData.size());
|
|
|
593
|
+
|
|
|
594
|
+ // 4. 按 dtuSn 分组聚合数据,计算每个设备的各状态时长
|
|
|
595
|
+ Map<String, DeviceStatResult> deviceStatMap = aggregateByDevice(allRawData, deviceNameMap);
|
|
|
596
|
+
|
|
|
597
|
+ // 确保所有设备都在结果中(无数据的设为0)
|
|
|
598
|
+ for (String sn : deviceNameMap.keySet()) {
|
|
|
599
|
+ if (!deviceStatMap.containsKey(sn)) {
|
|
|
600
|
+ deviceStatMap.put(sn, new DeviceStatResult(sn, deviceNameMap.getOrDefault(sn, "")));
|
|
|
601
|
+ }
|
|
|
602
|
+ }
|
|
|
603
|
+
|
|
|
604
|
+ // 5. 计算全局汇总
|
|
|
605
|
+ return buildEqKwhStatisticsResult(deviceStatMap, deviceNameMap);
|
|
|
606
|
+ }
|
|
|
607
|
+
|
|
|
608
|
+ /** 设备统计内部结构 */
|
|
|
609
|
+ static class DeviceStatResult {
|
|
|
610
|
+ String dtuSn;
|
|
|
611
|
+ String deviceName;
|
|
|
612
|
+ long status0; // 离线时长(秒)
|
|
|
613
|
+ long status1; // 停机时长(秒)
|
|
|
614
|
+ long status2; // 待机时长(秒)
|
|
|
615
|
+ long status3; // 运行时长(秒)
|
|
|
616
|
+ double totalKwh; // 总用电量
|
|
|
617
|
+
|
|
|
618
|
+ DeviceStatResult(String dtuSn, String deviceName) {
|
|
|
619
|
+ this.dtuSn = dtuSn;
|
|
|
620
|
+ this.deviceName = deviceName;
|
|
|
621
|
+ }
|
|
|
622
|
+
|
|
|
623
|
+ /** 总时长(秒) */
|
|
|
624
|
+ long totalDuration() { return status0 + status1 + status2 + status3; }
|
|
|
625
|
+ /** 异常时长 = 停机 + 待机 */
|
|
|
626
|
+ long abnormalDuration() { return status1 + status2; }
|
|
|
627
|
+ /** 稼动分母 = 停机 + 待机 + 运行 (排除离线) */
|
|
|
628
|
+ long activeDuration() { return status1 + status2 + status3; }
|
|
|
629
|
+ }
|
|
|
630
|
+
|
|
|
631
|
+ /** 构建空结果 */
|
|
|
632
|
+ private Map<String, Object> buildEmptyEqKwhStats() {
|
|
|
633
|
+ return Map.of(
|
|
|
634
|
+ "code", 200, "msg", "请求成功", "data",
|
|
|
635
|
+ Map.of(
|
|
|
636
|
+ "summary", Map.of(
|
|
|
637
|
+ "availabilityRate", "0.00%",
|
|
|
638
|
+ "totalStatusDuration", Map.of(
|
|
|
639
|
+ "status0", Map.of("durationFormatted", "0时0分0秒", "durationSeconds", 0L, "durationHours", "0.00时"),
|
|
|
640
|
+ "status1", Map.of("durationFormatted", "0时0分0秒", "durationSeconds", 0L, "durationHours", "0.00时"),
|
|
|
641
|
+ "status2", Map.of("durationFormatted", "0时0分0秒", "durationSeconds", 0L, "durationHours", "0.00时"),
|
|
|
642
|
+ "status3", Map.of("durationFormatted", "0时0分0秒", "durationSeconds", 0L, "durationHours", "0.00时")
|
|
|
643
|
+ )
|
|
|
644
|
+ ),
|
|
|
645
|
+ "currentStatus", Map.of("0", 0, "1", 0, "2", 0, "3", 0),
|
|
|
646
|
+ "abnormalRanking", List.of(),
|
|
|
647
|
+ "deviceList", List.of()
|
|
|
648
|
+ )
|
|
|
649
|
+ );
|
|
|
650
|
+ }
|
|
|
651
|
+
|
|
|
652
|
+ /** 获取能耗设备表所有dtuSn及名称 */
|
|
|
653
|
+ private Map<String, String> queryAllEnergyDeviceNames() {
|
|
|
654
|
+ String sql = "SELECT dtuSn, deviceName FROM " + energyTableName + " WHERE corp_code = ? ORDER BY dtuSn";
|
|
|
655
|
+ List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql, energyCorpCode);
|
|
|
656
|
+ Map<String, String> result = new LinkedHashMap<>(rows.size());
|
|
|
657
|
+ for (Map<String, Object> row : rows) {
|
|
|
658
|
+ result.put((String) row.get("dtuSn"), (String) row.get("deviceName"));
|
|
|
659
|
+ }
|
|
|
660
|
+ return result;
|
|
|
661
|
+ }
|
|
|
662
|
+
|
|
|
663
|
+ /** 构建连续日期列表 */
|
|
|
664
|
+ private List<String> buildDateList(String startDate, String endDate) {
|
|
|
665
|
+ List<String> result = new ArrayList<>();
|
|
|
666
|
+ try {
|
|
|
667
|
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
|
|
668
|
+ Calendar cur = Calendar.getInstance();
|
|
|
669
|
+ cur.setTime(sdf.parse(startDate));
|
|
|
670
|
+ Calendar endCal = Calendar.getInstance();
|
|
|
671
|
+ endCal.setTime(sdf.parse(endDate));
|
|
|
672
|
+ while (!cur.after(endCal)) {
|
|
|
673
|
+ result.add(sdf.format(cur.getTime()));
|
|
|
674
|
+ cur.add(Calendar.DAY_OF_MONTH, 1);
|
|
|
675
|
+ }
|
|
|
676
|
+ } catch (Exception e) {
|
|
|
677
|
+ log.error("日期解析失败: {} ~ {}", startDate, endDate, e);
|
|
|
678
|
+ }
|
|
|
679
|
+ return result;
|
|
|
680
|
+ }
|
|
|
681
|
+
|
|
|
682
|
+ /** 从 eq_kwh 表批量查询指定日期范围的所有记录 */
|
|
|
683
|
+ private List<Map<String, Object>> queryEqKwhBatch(List<String> dateList) {
|
|
|
684
|
+ if (dateList.isEmpty()) return Collections.emptyList();
|
|
|
685
|
+
|
|
|
686
|
+ StringBuilder sql = new StringBuilder(
|
|
|
687
|
+ "SELECT dtuSn, use_date, description FROM " + eqKwhTableName +
|
|
|
688
|
+ " WHERE corp_code = ? AND use_date IN (");
|
|
|
689
|
+ List<Object> params = new ArrayList<>();
|
|
|
690
|
+ params.add(energyCorpCode);
|
|
|
691
|
+
|
|
|
692
|
+ for (String d : dateList) { sql.append("?,"); params.add(d + " 00:00:00"); }
|
|
|
693
|
+ sql.deleteCharAt(sql.length() - 1).append(")");
|
|
|
694
|
+ sql.append(" ORDER BY dtuSn, use_date");
|
|
|
695
|
+
|
|
|
696
|
+ return jdbcTemplate.queryForList(sql.toString(), params.toArray());
|
|
|
697
|
+ }
|
|
|
698
|
+
|
|
|
699
|
+ /** 按 dtuSn 分组聚合各状态时长 */
|
|
|
700
|
+ private Map<String, DeviceStatResult> aggregateByDevice(List<Map<String, Object>> rawData,
|
|
|
701
|
+ Map<String, String> deviceNameMap) {
|
|
|
702
|
+ Map<String, DeviceStatResult> resultMap = new LinkedHashMap<>();
|
|
|
703
|
+
|
|
|
704
|
+ for (Map<String, Object> row : rawData) {
|
|
|
705
|
+ String sn = (String) row.get("dtuSn");
|
|
|
706
|
+ String desc = row.get("description") != null ? String.valueOf(row.get("description")) : "";
|
|
|
707
|
+
|
|
|
708
|
+ DeviceStatResult dsr = resultMap.computeIfAbsent(sn,
|
|
|
709
|
+ k -> new DeviceStatResult(k, deviceNameMap.getOrDefault(k, "")));
|
|
|
710
|
+
|
|
|
711
|
+ if (!StringUtils.hasText(desc)) continue;
|
|
|
712
|
+
|
|
|
713
|
+ try {
|
|
|
714
|
+ JSONArray dataArray = JSON.parseArray(desc);
|
|
|
715
|
+ if (dataArray == null) continue;
|
|
|
716
|
+
|
|
|
717
|
+ for (int i = 0; i < dataArray.size(); i++) {
|
|
|
718
|
+ JSONObject item = dataArray.getJSONObject(i);
|
|
|
719
|
+ if (item == null) continue;
|
|
|
720
|
+
|
|
|
721
|
+ // 用电量
|
|
|
722
|
+ Double value = item.getDouble("value");
|
|
|
723
|
+ if (value != null) dsr.totalKwh += value;
|
|
|
724
|
+
|
|
|
725
|
+ // 各状态时长: 0-离线, 1-停机, 2-待机, 3-运行
|
|
|
726
|
+ for (int sk = 0; sk <= 3; sk++) {
|
|
|
727
|
+ Long dur = item.getLong(String.valueOf(sk));
|
|
|
728
|
+ if (dur != null && dur > 0) {
|
|
|
729
|
+ switch (sk) {
|
|
|
730
|
+ case 0 -> dsr.status0 += dur;
|
|
|
731
|
+ case 1 -> dsr.status1 += dur;
|
|
|
732
|
+ case 2 -> dsr.status2 += dur;
|
|
|
733
|
+ case 3 -> dsr.status3 += dur;
|
|
|
734
|
+ }
|
|
|
735
|
+ }
|
|
|
736
|
+ }
|
|
|
737
|
+ }
|
|
|
738
|
+ } catch (Exception e) {
|
|
|
739
|
+ log.warn("解析eq_kwh description异常 - dtuSn:{}", sn, e);
|
|
|
740
|
+ }
|
|
|
741
|
+ }
|
|
|
742
|
+
|
|
|
743
|
+ return resultMap;
|
|
|
744
|
+ }
|
|
|
745
|
+
|
|
|
746
|
+ /** 构建最终统计结果 */
|
|
|
747
|
+ private Map<String, Object> buildEqKwhStatisticsResult(Map<String, DeviceStatResult> deviceStatMap,
|
|
|
748
|
+ Map<String, String> deviceNameMap) {
|
|
|
749
|
+ // ---- 全局汇总 ----
|
|
|
750
|
+ long totalS0 = 0, totalS1 = 0, totalS2 = 0, totalS3 = 0;
|
|
|
751
|
+ double totalKwhSum = 0;
|
|
|
752
|
+
|
|
|
753
|
+ // ---- 设备详情列表 ----
|
|
|
754
|
+ List<Map<String, Object>> deviceList = new ArrayList<>(deviceStatMap.size());
|
|
|
755
|
+
|
|
|
756
|
+ for (Map.Entry<String, DeviceStatResult> entry : deviceStatMap.entrySet()) {
|
|
|
757
|
+ DeviceStatResult dsr = entry.getValue();
|
|
|
758
|
+
|
|
|
759
|
+ totalS0 += dsr.status0; totalS1 += dsr.status1;
|
|
|
760
|
+ totalS2 += dsr.status2; totalS3 += dsr.status3;
|
|
|
761
|
+ totalKwhSum += dsr.totalKwh;
|
|
|
762
|
+
|
|
|
763
|
+ // 单设备稼动率 = 运行 / (停机+待机+运行)
|
|
|
764
|
+ long active = dsr.activeDuration();
|
|
|
765
|
+ double devRate = active > 0 ? Math.round(dsr.status3 * 10000.0 / active) / 100.0 : 0.0;
|
|
|
766
|
+
|
|
|
767
|
+ Map<String, Object> devItem = new LinkedHashMap<>();
|
|
|
768
|
+ devItem.put("dtuSn", dsr.dtuSn);
|
|
|
769
|
+ devItem.put("deviceName", dsr.deviceName);
|
|
|
770
|
+ // 各状态时长 - 格式化为 xx时xx分xx秒
|
|
|
771
|
+ devItem.put("status0", Map.of(
|
|
|
772
|
+ "durationFormatted", formatDuration(dsr.status0),
|
|
|
773
|
+ "durationSeconds", dsr.status0,
|
|
|
774
|
+ "durationHours", formatDurationToHours(dsr.status0)
|
|
|
775
|
+ ));
|
|
|
776
|
+ devItem.put("status1", Map.of(
|
|
|
777
|
+ "durationFormatted", formatDuration(dsr.status1),
|
|
|
778
|
+ "durationSeconds", dsr.status1,
|
|
|
779
|
+ "durationHours", formatDurationToHours(dsr.status1)
|
|
|
780
|
+ ));
|
|
|
781
|
+ devItem.put("status2", Map.of(
|
|
|
782
|
+ "durationFormatted", formatDuration(dsr.status2),
|
|
|
783
|
+ "durationSeconds", dsr.status2,
|
|
|
784
|
+ "durationHours", formatDurationToHours(dsr.status2)
|
|
|
785
|
+ ));
|
|
|
786
|
+ devItem.put("status3", Map.of(
|
|
|
787
|
+ "durationFormatted", formatDuration(dsr.status3),
|
|
|
788
|
+ "durationSeconds", dsr.status3,
|
|
|
789
|
+ "durationHours", formatDurationToHours(dsr.status3)
|
|
|
790
|
+ ));
|
|
|
791
|
+ devItem.put("totalDurationFormatted", formatDuration(dsr.totalDuration()));
|
|
|
792
|
+ devItem.put("totalDurationSeconds", dsr.totalDuration());
|
|
|
793
|
+ devItem.put("totalDurationHours", formatDurationToHours(dsr.totalDuration()));
|
|
|
794
|
+ devItem.put("totalKwh", Math.round(dsr.totalKwh * 100.0) / 100.0);
|
|
|
795
|
+ devItem.put("availabilityRate", String.format("%.2f%%", devRate));
|
|
|
796
|
+ devItem.put("availabilityRateValue", devRate);
|
|
|
797
|
+ // 异常时长用于排序(内部字段,后面移除)
|
|
|
798
|
+ devItem.put("_abnormalDur", dsr.abnormalDuration());
|
|
|
799
|
+
|
|
|
800
|
+ deviceList.add(devItem);
|
|
|
801
|
+ }
|
|
|
802
|
+
|
|
|
803
|
+ // ---- ① 总稼动率 ----
|
|
|
804
|
+ long totalActive = totalS1 + totalS2 + totalS3;
|
|
|
805
|
+ double overallAvailabilityRate = totalActive > 0
|
|
|
806
|
+ ? Math.round(totalS3 * 10000.0 / totalActive) / 100.0 : 0.0;
|
|
|
807
|
+
|
|
|
808
|
+ // ---- ② 当前设备运行状态(从energy表查) ----
|
|
|
809
|
+ Map<String, Integer> currentStatus = queryCurrentRunStatus();
|
|
|
810
|
+
|
|
|
811
|
+ // ---- ③ 异常机台排名(按 停机+待机 降序) ----
|
|
|
812
|
+ deviceList.sort((a, b) -> Long.compare(
|
|
|
813
|
+ ((Number) b.get("_abnormalDur")).longValue(),
|
|
|
814
|
+ ((Number) a.get("_abnormalDur")).longValue()
|
|
|
815
|
+ ));
|
|
|
816
|
+ // 移除辅助字段
|
|
|
817
|
+ for (Map<String, Object> d : deviceList) {
|
|
|
818
|
+ d.remove("_abnormalDur");
|
|
|
819
|
+ }
|
|
|
820
|
+
|
|
|
821
|
+ return Map.of(
|
|
|
822
|
+ "code", 200, "msg", "请求成功", "data",
|
|
|
823
|
+ Map.of(
|
|
|
824
|
+ "summary", Map.of(
|
|
|
825
|
+ "availabilityRate", String.format("%.2f%%", overallAvailabilityRate),
|
|
|
826
|
+ "availabilityRateValue", overallAvailabilityRate,
|
|
|
827
|
+ "totalDevices", deviceNameMap.size(),
|
|
|
828
|
+ "totalKwh", Math.round(totalKwhSum * 100.0) / 100.0,
|
|
|
829
|
+ "totalStatusDuration", Map.of(
|
|
|
830
|
+ "status0", Map.of(
|
|
|
831
|
+ "durationFormatted", formatDuration(totalS0),
|
|
|
832
|
+ "durationSeconds", totalS0,
|
|
|
833
|
+ "durationHours", formatDurationToHours(totalS0)
|
|
|
834
|
+ ),
|
|
|
835
|
+ "status1", Map.of(
|
|
|
836
|
+ "durationFormatted", formatDuration(totalS1),
|
|
|
837
|
+ "durationSeconds", totalS1,
|
|
|
838
|
+ "durationHours", formatDurationToHours(totalS1)
|
|
|
839
|
+ ),
|
|
|
840
|
+ "status2", Map.of(
|
|
|
841
|
+ "durationFormatted", formatDuration(totalS2),
|
|
|
842
|
+ "durationSeconds", totalS2,
|
|
|
843
|
+ "durationHours", formatDurationToHours(totalS2)
|
|
|
844
|
+ ),
|
|
|
845
|
+ "status3", Map.of(
|
|
|
846
|
+ "durationFormatted", formatDuration(totalS3),
|
|
|
847
|
+ "durationSeconds", totalS3,
|
|
|
848
|
+ "durationHours", formatDurationToHours(totalS3)
|
|
|
849
|
+ )
|
|
|
850
|
+ )
|
|
|
851
|
+ ),
|
|
|
852
|
+ "currentStatus", currentStatus,
|
|
|
853
|
+ "abnormalRanking", deviceList,
|
|
|
854
|
+ "deviceList", deviceList
|
|
|
855
|
+ )
|
|
|
856
|
+ );
|
|
|
857
|
+ }
|
|
|
858
|
+
|
|
|
859
|
+ /** 查询当前设备运行状态分布(从energy表) runStatus: 0-离线, 1-停机, 2-待机, 3-运行 */
|
|
|
860
|
+ private Map<String, Integer> queryCurrentRunStatus() {
|
|
|
861
|
+ String sql = "SELECT runStatus, COUNT(*) as cnt FROM " + energyTableName +
|
|
|
862
|
+ " WHERE corp_code = ? GROUP BY runStatus";
|
|
|
863
|
+ List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql, energyCorpCode);
|
|
|
864
|
+
|
|
|
865
|
+ int s0 = 0, s1 = 0, s2 = 0, s3 = 0;
|
|
|
866
|
+ for (Map<String, Object> row : rows) {
|
|
|
867
|
+ String key = String.valueOf(row.get("runStatus"));
|
|
|
868
|
+ int cnt = ((Number) row.get("cnt")).intValue();
|
|
|
869
|
+ switch (key) {
|
|
|
870
|
+ case "0" -> s0 = cnt;
|
|
|
871
|
+ case "1" -> s1 = cnt;
|
|
|
872
|
+ case "2" -> s2 = cnt;
|
|
|
873
|
+ case "3" -> s3 = cnt;
|
|
|
874
|
+ }
|
|
|
875
|
+ }
|
|
|
876
|
+ return Map.of("0", s0, "1", s1, "2", s2, "3", s3);
|
|
|
877
|
+ }
|
|
548
|
878
|
} |
...
|
...
|
|