| | |
| | | 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; |
| | |
| | | @Override |
| | | public void syncMeterDataScheduled() { |
| | | try { |
| | | syncMeterDataInternal(); |
| | | syncMeterDataInternal(null, null); |
| | | } catch (Exception e) { |
| | | log.warn("syncMeterDataScheduled failed", e); |
| | | } |
| | |
| | | |
| | | @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 { |
| | |
| | | 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; |
| | | } |
| | |
| | | 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())) { |
| | |
| | | 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; |
| | |
| | | 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) { |