|
@@ -0,0 +1,145 @@
|
|
|
|
|
+package com.etotem.cfc.service;
|
|
|
|
|
+
|
|
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
|
|
+import com.etotem.cfc.entity.HealthReport;
|
|
|
|
|
+import com.etotem.cfc.mapper.HealthReportMapper;
|
|
|
|
|
+import com.fasterxml.jackson.databind.JsonNode;
|
|
|
|
|
+import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
|
|
+import org.slf4j.Logger;
|
|
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
|
|
+import org.springframework.scheduling.annotation.Scheduled;
|
|
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
|
|
+
|
|
|
|
|
+import javax.annotation.Resource;
|
|
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
|
|
+import java.util.Date;
|
|
|
|
|
+import java.util.List;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 真实报告日期提取 + 历史回填。
|
|
|
|
|
+ * PDF 解析阶段将「报告日期/采样日期」写入 payload.reportDate 后,
|
|
|
|
|
+ * 入库逻辑(HealthReportService 既有 reportDate 修正)自动生效;
|
|
|
|
|
+ * 本类只负责:(1) 从 payload 提取真实日期(供回填/校验);
|
|
|
|
|
+ * (2) 扫描旧报告幂等回填 report_date(无法提取置 NULL,不伪装成上传时间)。
|
|
|
|
|
+ */
|
|
|
|
|
+@Service
|
|
|
|
|
+public class ReportDateExtractor {
|
|
|
|
|
+
|
|
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(ReportDateExtractor.class);
|
|
|
|
|
+
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private HealthReportMapper healthReportMapper;
|
|
|
|
|
+
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private ObjectMapper objectMapper = new ObjectMapper();
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 从 payload JSON 中提取真实报告日期。
|
|
|
|
|
+ * 取值顺序:summary.reportDate → 顶层 reportDate → indicators[].collectionDate(取第一个非空)。
|
|
|
|
|
+ * 支持 yyyy-MM-dd / yyyy/MM/dd;无法提取返回 null。
|
|
|
|
|
+ */
|
|
|
|
|
+ public java.sql.Date extractReportDate(String payloadJson) {
|
|
|
|
|
+ if (payloadJson == null || payloadJson.isEmpty()) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ JsonNode root = objectMapper.readTree(payloadJson);
|
|
|
|
|
+ String dateStr = null;
|
|
|
|
|
+ JsonNode summary = root.get("summary");
|
|
|
|
|
+ if (summary != null && summary.isObject() && summary.hasNonNull("reportDate")) {
|
|
|
|
|
+ dateStr = summary.get("reportDate").asText();
|
|
|
|
|
+ }
|
|
|
|
|
+ if (dateStr == null || dateStr.trim().isEmpty()) {
|
|
|
|
|
+ if (root.hasNonNull("reportDate")) {
|
|
|
|
|
+ dateStr = root.get("reportDate").asText();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ java.sql.Date parsed = parseDate(dateStr);
|
|
|
|
|
+ if (parsed != null) {
|
|
|
|
|
+ return parsed;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 回退:indicators[].collectionDate 取第一个非空
|
|
|
|
|
+ JsonNode indicators = root.get("indicators");
|
|
|
|
|
+ if (indicators != null && indicators.isArray()) {
|
|
|
|
|
+ for (JsonNode ind : indicators) {
|
|
|
|
|
+ if (ind.hasNonNull("collectionDate")) {
|
|
|
|
|
+ java.sql.Date d = parseDate(ind.get("collectionDate").asText());
|
|
|
|
|
+ if (d != null) {
|
|
|
|
|
+ return d;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("ReportDateExtractor 解析 payload 失败: {}", e.getMessage());
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private java.sql.Date parseDate(String dateStr) {
|
|
|
|
|
+ if (dateStr == null || dateStr.trim().isEmpty()) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ String s = dateStr.trim();
|
|
|
|
|
+ try {
|
|
|
|
|
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
|
|
|
|
+ Date d = sdf.parse(s);
|
|
|
|
|
+ return new java.sql.Date(d.getTime());
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
|
|
|
|
|
+ Date d = sdf.parse(s);
|
|
|
|
|
+ return new java.sql.Date(d.getTime());
|
|
|
|
|
+ } catch (Exception e2) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 历史回填:扫描 report_date 缺失或疑似上传时间的旧报告(且 payload 含 reportDate 字段),
|
|
|
|
|
+ * 提取真实日期并回填;无法提取置 NULL。分批(500/批)、幂等可重跑。
|
|
|
|
|
+ *
|
|
|
|
|
+ * @return 本轮处理的总行数
|
|
|
|
|
+ */
|
|
|
|
|
+ public int backfillHistoricalReports() {
|
|
|
|
|
+ int total = 0;
|
|
|
|
|
+ while (true) {
|
|
|
|
|
+ List<HealthReport> batch = healthReportMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<HealthReport>()
|
|
|
|
|
+ .isNull(HealthReport::getReportDate)
|
|
|
|
|
+ .like(HealthReport::getPayloadJson, "reportDate")
|
|
|
|
|
+ .last("LIMIT 500"));
|
|
|
|
|
+ if (batch.isEmpty()) {
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ for (HealthReport report : batch) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ java.sql.Date realDate = extractReportDate(report.getPayloadJson());
|
|
|
|
|
+ if (realDate != null) {
|
|
|
|
|
+ report.setReportDate(realDate);
|
|
|
|
|
+ healthReportMapper.updateById(report);
|
|
|
|
|
+ log.info("回填报告真实日期 reportId={}, reportDate={}", report.getId(), realDate);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 置 NULL 并清 payload 中可提取性:无法提取的不再反复处理
|
|
|
|
|
+ report.setReportDate(null);
|
|
|
|
|
+ healthReportMapper.updateById(report);
|
|
|
|
|
+ log.info("报告日期无法提取,置 NULL reportId={}", report.getId());
|
|
|
|
|
+ }
|
|
|
|
|
+ total++;
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("回填报告日期失败 reportId={}: {}", report.getId(), e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ log.info("ReportDateExtractor 历史回填完成,共处理 {} 行", total);
|
|
|
|
|
+ return total;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 每日 03:10 自动回填(幂等可重跑,连跑两次结果一致) */
|
|
|
|
|
+ @Scheduled(cron = "0 10 3 * * ?")
|
|
|
|
|
+ public void scheduledBackfill() {
|
|
|
|
|
+ backfillHistoricalReports();
|
|
|
|
|
+ }
|
|
|
|
|
+}
|