From 3c7399c25c0f35c8aa7cb6af1935e31d1a3f0102 Mon Sep 17 00:00:00 2001
From: doum <doum>
Date: 星期二, 02 六月 2026 17:43:50 +0800
Subject: [PATCH] 新增智能电表、空调管理

---
 server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalBizServiceImpl.java |  168 ++++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 152 insertions(+), 16 deletions(-)

diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalBizServiceImpl.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalBizServiceImpl.java
index c9911dd..2a76435 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalBizServiceImpl.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalBizServiceImpl.java
@@ -52,6 +52,8 @@
     public static final int ACTION_OPEN = 6;
     public static final int ACTION_RECHARGE = 7;
     public static final int ACTION_READ = 8;
+    public static final int ACTION_POWER_PROTECT = 9;
+    public static final int ACTION_POWER_PROTECT_RELEASE = 10;
 
     private static final long FIRST_STATUS_QUERY_DELAY_MS = 30_000L;
     private static final long STATUS_QUERY_MIN_INTERVAL_MS = 3_600_000L;
@@ -62,6 +64,12 @@
     private static final int ELE_READ_TYPE_METER = 3;
     /** queryData 鐢佃〃鐘舵�佽鎯呭姛鑳� ID */
     private static final String QUERY_DATA_FUNCTION_METER_STATUS = "253";
+    /** DataRequest 鍗曟鏈�澶ф潯鏁帮紙鎺ュ彛 limit 涓婇檺 1000锛� */
+    private static final int QUERY_DATA_PAGE_SIZE = 500;
+    /** 鎶勮〃/DataRequest 鏌ヨ鏃堕棿璺ㄥ害涓婇檺锛堝ぉ锛� */
+    private static final int MAX_METER_QUERY_DAYS = 7;
+    /** 鍒嗛〉 total 鍚堢悊涓婇檺锛岄伩鍏嶈鎶婃椂闂存埑褰撲綔鎬绘潯鏁� */
+    private static final int QUERY_DATA_TOTAL_SANITY_MAX = 100_000;
 
     @Autowired
     private YwElectricalMapper ywElectricalMapper;
@@ -164,6 +172,10 @@
                 return doEleControl(e, 10, ACTION_TRIP, user);
             case "close":
                 return doEleControl(e, 11, ACTION_CLOSE, user);
+            case "powerProtect":
+                return doEleControl(e, 63, ACTION_POWER_PROTECT, user);
+            case "powerProtectRelease":
+                return doEleControl(e, 220, ACTION_POWER_PROTECT_RELEASE, user);
             case "openAccount":
                 return doOpenAccount(e, dto, user);
             case "recharge":
@@ -209,7 +221,7 @@
         List<OpenAccountRequest> list = new ArrayList<>();
         list.add(req);
         ElectronicBaseResponse resp = ElectronicToolUtil.eleControl(list);
-        return finishAsync(e, actionType, oprId, "/Api_v2/ele_security/ele_control", reqJson, resp, user);
+        return finishAsync(e, actionType, oprId, "/Api_v2/ele_control", reqJson, resp, user);
     }
 
     private String doOpenAccount(YwElectrical e, YwElectricalOperateDTO dto, LoginUserInfo user) {
@@ -823,7 +835,7 @@
     @Override
     public void syncMeterDataScheduled() {
         try {
-            syncMeterDataInternal();
+            syncMeterDataInternal(null, null);
         } catch (Exception e) {
             log.warn("syncMeterDataScheduled failed", e);
         }
@@ -831,8 +843,36 @@
 
     @Override
     public String syncMeterDataFromPlatform() {
-        MeterDataSyncStats stats = syncMeterDataInternal();
+        MeterDataSyncStats stats = syncMeterDataInternal(null, null);
         return "鎶勮〃鍚屾瀹屾垚锛氭柊澧炪��" + stats.addCount + "銆戞潯锛岃烦杩囬噸澶嶃��" + stats.skipCount + "銆戞潯";
+    }
+
+    @Override
+    public String syncMeterDataFromPlatform(String readTimeBegin, String readTimeEnd) {
+        if (StringUtils.isBlank(readTimeBegin) || StringUtils.isBlank(readTimeEnd)) {
+            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "璇烽�夋嫨鎶勮〃鏃堕棿娈�");
+        }
+        String startTime = readTimeBegin.trim();
+        String endTime = readTimeEnd.trim();
+        validateManualSyncTimeRange(startTime, endTime);
+        String expandedStart = expandStartByOneDay(startTime);
+        MeterDataSyncStats stats = syncMeterDataInternal(expandedStart, endTime);
+        return "鎶勮〃鍚屾瀹屾垚锛氭柊澧炪��" + stats.addCount + "銆戞潯锛岃烦杩囬噸澶嶃��" + stats.skipCount + "銆戞潯";
+    }
+
+    private String expandStartByOneDay(String startTime) {
+        try {
+            Date start = DateUtil.StringToDate(startTime, "yyyy-MM-dd HH:mm:ss");
+            if (start == null) {
+                return startTime;
+            }
+            Calendar cal = Calendar.getInstance();
+            cal.setTime(start);
+            cal.add(Calendar.DAY_OF_MONTH, -1);
+            return DateUtil.formatDate(cal.getTime(), "yyyy-MM-dd HH:mm:ss");
+        } catch (Exception e) {
+            return startTime;
+        }
     }
 
     private static class MeterDataSyncStats {
@@ -840,12 +880,14 @@
         private int skipCount;
     }
 
-    private MeterDataSyncStats syncMeterDataInternal() {
+    private MeterDataSyncStats syncMeterDataInternal(String startTime, String endTime) {
         MeterDataSyncStats stats = new MeterDataSyncStats();
-        String startTime = resolveSyncStartTime();
-        QueryDataRequest param = buildQueryDataRequest(startTime, DateUtil.formatDate(new Date(), "yyyy-MM-dd HH:mm:ss"));
-        log.info("sync meter data, start_time={}, end_time={}", startTime, param.getEnd_time());
-        List<QueryDataInfoResponse> list = fetchQueryDataList(param);
+        String resolvedStart = StringUtils.isNotBlank(startTime) ? startTime : resolveSyncStartTime();
+        String resolvedEnd = StringUtils.isNotBlank(endTime) ? endTime : DateUtil.formatDate(new Date(), "yyyy-MM-dd HH:mm:ss");
+        resolvedStart = capQueryStartTime(resolvedStart, resolvedEnd, MAX_METER_QUERY_DAYS);
+        QueryDataRequest param = buildQueryDataRequest(resolvedStart, resolvedEnd);
+        log.info("sync meter data, start_time={}, end_time={}", resolvedStart, param.getEnd_time());
+        List<QueryDataInfoResponse> list = fetchAllQueryDataList(param);
         if (CollectionUtils.isEmpty(list)) {
             return stats;
         }
@@ -869,6 +911,51 @@
         return stats;
     }
 
+    private void validateManualSyncTimeRange(String startTime, String endTime) {
+        if (!startTime.matches("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}")
+                || !endTime.matches("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}")) {
+            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "鎶勮〃鏃堕棿鏍煎紡涓嶆纭�");
+        }
+        Date start;
+        Date end;
+        try {
+            start = DateUtil.StringToDate(startTime, "yyyy-MM-dd HH:mm:ss");
+            end = DateUtil.StringToDate(endTime, "yyyy-MM-dd HH:mm:ss");
+        } catch (Exception e) {
+            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "鎶勮〃鏃堕棿鏍煎紡涓嶆纭�");
+        }
+        if (start == null || end == null || !start.before(end)) {
+            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "鎶勮〃寮�濮嬫椂闂村繀椤绘棭浜庣粨鏉熸椂闂�");
+        }
+        long diffMs = end.getTime() - start.getTime();
+        if (diffMs > (long) MAX_METER_QUERY_DAYS * 24 * 60 * 60 * 1000) {
+            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "鎶勮〃鏃堕棿娈典笉鑳借秴杩�" + MAX_METER_QUERY_DAYS + "澶�");
+        }
+    }
+
+    private String capQueryStartTime(String startTime, String endTime, int maxDays) {
+        if (StringUtils.isBlank(startTime) || StringUtils.isBlank(endTime)) {
+            return startTime;
+        }
+        try {
+            Date start = DateUtil.StringToDate(startTime, "yyyy-MM-dd HH:mm:ss");
+            Date end = DateUtil.StringToDate(endTime, "yyyy-MM-dd HH:mm:ss");
+            if (start == null || end == null || !start.before(end)) {
+                return startTime;
+            }
+            long maxMs = (long) maxDays * 24 * 60 * 60 * 1000;
+            if (end.getTime() - start.getTime() <= maxMs) {
+                return startTime;
+            }
+            Calendar cal = Calendar.getInstance();
+            cal.setTime(end);
+            cal.add(Calendar.DAY_OF_MONTH, -maxDays);
+            return DateUtil.formatDate(cal.getTime(), "yyyy-MM-dd HH:mm:ss");
+        } catch (Exception e) {
+            return startTime;
+        }
+    }
+
     /** 鍗曡〃鎶勮〃鍚庝粠绗笁鏂规媺鍙栨渶鏂版暟鎹叆搴擄紝杩斿洖鏄惁鏈夋柊璁板綍 */
     private boolean syncMeterDataForElectrical(YwElectrical e) {
         if (e == null || StringUtils.isBlank(e.getAddress())) {
@@ -881,7 +968,7 @@
                 DateUtil.formatDate(new Date(), "yyyy-MM-dd HH:mm:ss"));
         List<QueryDataInfoResponse> list;
         try {
-            list = fetchQueryDataList(param);
+            list = fetchAllQueryDataList(param);
         } catch (BusinessException ex) {
             log.warn("sync meter data for electricalId={} failed: {}", e.getId(), ex.getMessage());
             return false;
@@ -912,17 +999,66 @@
         param.setStart_time(startTime);
         param.setEnd_time(endTime);
         param.setOffset(0);
-        param.setLimit(500);
+        param.setLimit(QUERY_DATA_PAGE_SIZE);
         return param;
     }
 
-    private List<QueryDataInfoResponse> fetchQueryDataList(QueryDataRequest param) {
-        ElectronicDataResponse response = ElectronicToolUtil.queryDataRequest(param);
-        if (!ElectronicToolUtil.isDataApiSuccess(response)) {
-            throw new BusinessException(ResponseStatus.SERVER_ERROR.getCode(),
-                    ElectronicToolUtil.dataApiErrorMessage(response, "鎶勮〃鏁版嵁鍚屾澶辫触"));
+    /** 鎸� DataRequest 鍒嗛〉 total 寰幆鎷夊彇鍏ㄩ儴鎶勮〃鏁版嵁 */
+    private List<QueryDataInfoResponse> fetchAllQueryDataList(QueryDataRequest param) {
+        if (param == null) {
+            return Collections.emptyList();
         }
-        return parseQueryDataList(response);
+        int limit = param.getLimit() > 0 ? param.getLimit() : QUERY_DATA_PAGE_SIZE;
+        List<QueryDataInfoResponse> all = new ArrayList<>();
+        int offset = 0;
+        Integer total = null;
+        int pageNo = 0;
+        while (true) {
+            pageNo++;
+            param.setOffset(offset);
+            param.setLimit(limit);
+            ElectronicDataResponse response = ElectronicToolUtil.queryDataRequest(param);
+            if (!ElectronicToolUtil.isDataApiSuccess(response)) {
+                throw new BusinessException(ResponseStatus.SERVER_ERROR.getCode(),
+                        ElectronicToolUtil.dataApiErrorMessage(response, "鎶勮〃鏁版嵁鍚屾澶辫触"));
+            }
+            List<QueryDataInfoResponse> page = parseQueryDataList(response);
+            if (!CollectionUtils.isEmpty(page)) {
+                all.addAll(page);
+            }
+            if (total == null) {
+                total = resolveQueryTotal(response);
+            }
+            log.info("sync meter data page={}, offset={}, pageSize={}, accumulated={}, total={}",
+                    pageNo, offset, page.size(), all.size(), total);
+            if (total != null && total > 0) {
+                offset += limit;
+                if (offset >= total) {
+                    break;
+                }
+                continue;
+            }
+            if (CollectionUtils.isEmpty(page) || page.size() < limit) {
+                break;
+            }
+            offset += limit;
+            if (pageNo >= 200) {
+                log.warn("sync meter data pagination exceeded safety page limit, accumulated={}", all.size());
+                break;
+            }
+        }
+        return all;
+    }
+
+    private Integer resolveQueryTotal(ElectronicDataResponse response) {
+        if (response == null || response.getTotal() == null) {
+            return null;
+        }
+        int total = response.getTotal();
+        if (total <= 0 || total > QUERY_DATA_TOTAL_SANITY_MAX) {
+            return null;
+        }
+        return total;
     }
 
     private List<QueryDataInfoResponse> parseQueryDataList(ElectronicDataResponse response) {

--
Gitblit v1.9.3