From 2b39c4eb64b194e23f338873e4467eda2b18ccc3 Mon Sep 17 00:00:00 2001
From: zhizhijie <zhizhijie@users.noreply.gitee.com>
Date: 星期二, 22 九月 2026 11:28:56 +0800
Subject: [PATCH] feat(报表生成): 汇总大表导出结果落盘缓存,无新导入即秒回复用
---
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportTaskService.java | 135 +++++++++++++++++++++++++++++++++++++++++---
1 files changed, 124 insertions(+), 11 deletions(-)
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportTaskService.java b/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportTaskService.java
index ac01627..6c430eb 100644
--- a/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportTaskService.java
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportTaskService.java
@@ -8,6 +8,7 @@
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
+import java.io.File;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashMap;
@@ -26,10 +27,11 @@
* 鐩殑锛�2026-09-20 鐢ㄦ埛涓婃姤銆岀偣涓嬭浇瑕佺瓑涓�浼氾紝鏁版嵁澶氫簡浼氫笉浼氬崱姝汇�嶏級锛�
* 1) 鐐瑰嚮鍚庣珛鍒绘湁鍙嶉锛屼笉鍐嶉暱鏃堕棿鏃犲搷搴旓紱
* 2) 鐢熸垚杩囩▼涓嶅崰鐢� HTTP 璇锋眰绾跨▼锛岄伩鍏嶅浜哄悓鏃剁偣鎶婂鍣ㄦ嫋姝伙紙宸ヤ綔绾跨▼鍥哄畾 1 涓紝澶氫綑璇锋眰鏄剧ず銆屾帓闃熶腑銆嶏級锛�
- * 3) 鍚屼竴鎶ヨ〃鏈� 10 鍒嗛挓鍐呯殑缁撴灉鍙鐢紝閬垮厤姣忔閲嶇畻鏁存湰绾� 4 涓囨牸鍏紡銆�
+ * 3) 鍚屼竴鎶ヨ〃鏈熺殑缁撴灉鍙鐢紙**钀界洏淇濆瓨锛岃繘绋嬮噸鍚悗渚濈劧鏈夋晥**锛夛紝閬垮厤姣忔閲嶇畻鏁存湰绾� 4 涓囨牸鍏紡銆�
*
- * 缂撳瓨姝g‘鎬у畧鍗細鍙湁褰撱�岃窛涓婃鐢熸垚涓嶈秴杩� TTL銆嶄笖銆屾湡闂存病鏈変换浣曟柊鐨勬暟鎹鍏ワ紙import_batch 鏈�澶� id 鏈彉锛夈��
- * 鏃舵墠澶嶇敤锛涘鍏ュ嵆澶辨晥锛岄伩鍏嶆妸杩囨湡鎶ヨ〃缁欑敤鎴枫�傚墠绔粛鎻愪緵銆岄噸鏂扮敓鎴愩�嶅己鍒跺埛鏂般��
+ * 缂撳瓨姝g‘鎬у畧鍗細鍙湁褰撱�屾湡闂存病鏈変换浣曟柊鐨勬暟鎹鍏ワ紙import_batch 鏈�澶� id 鏈彉锛夈�嶄笖銆岀粨鏋滀笉瓒呰繃 7 澶┿�嶆椂鎵嶅鐢紱
+ * 瀵煎叆鍗冲け鏁堬紝閬垮厤鎶婅繃鏈熸姤琛ㄧ粰鐢ㄦ埛銆傚墠绔粛鎻愪緵銆岄噸鏂扮敓鎴愩�嶅己鍒堕噸绠楋紙璺宠繃缂撳瓨锛夈��
+ * 钀界洏浣嶇疆锛歿summary.template-dir}/_cache/<key>_v<鏁版嵁鐗堟湰>.xlsx
*/
@Slf4j
@Service
@@ -40,13 +42,41 @@
/** 鐢熸垚缁撴灉淇濈暀鏃堕暱锛氭湡闂村彲鍙嶅涓嬭浇锛岃繃鍚庤嚜鍔ㄦ竻鐞� */
private static final long RESULT_TTL_MS = 30 * 60 * 1000L;
- /** 鍚屼竴鎶ヨ〃鏈熺粨鏋滃鐢ㄦ椂闀匡紙涓旀湡闂存棤鏂板鍏ユ椂鎵嶅鐢級 */
- private static final long CACHE_TTL_MS = 10 * 60 * 1000L;
+ /** 鍚屼竴鎶ヨ〃鏈熺粨鏋滃鐢ㄤ笂闄愶細鍙鏈熼棿娌℃湁鏂板鍏ュ氨涓�鐩存部鐢紱瓒呰繃璇ュぉ鏁颁笉鍐嶅鐢紙闃叉闄堝勾缁撴灉琚綋鏂拌〃鍙戝嚭锛� */
+ private static final long CACHE_MAX_AGE_MS = 7L * 24 * 60 * 60 * 1000;
+ /** 钀界洏缂撳瓨瀛愮洰褰曪紙鐩稿 summary.template-dir锛� */
+ private static final String CACHE_SUBDIR = "_cache";
@Resource
private ReportExportService reportService;
@Resource
private ImportBatchMapper importBatchMapper;
+
+ /** 姹囨�诲伐浣滅翱鐩綍锛堜笌 ReportExportService 鍚岄厤缃級锛岃惤鐩樼紦瀛樻斁鍦ㄥ叾 _cache 瀛愮洰褰� */
+ @org.springframework.beans.factory.annotation.Value("${summary.template-dir:docs/鐢熸垚姹囨�诲ぇ琛▆")
+ private String summaryTemplateDir;
+
+ /** 钀界洏缂撳瓨鐩綍锛涘畾浣嶇瓥鐣ヤ笌妯℃澘涓�鑷达紙閰嶇疆鐩綍 / user.dir 鐩稿 / 涓婄骇鐩綍锛� */
+ private File cacheDir() {
+ String rel = summaryTemplateDir;
+ while (rel.startsWith("./")) rel = rel.substring(2);
+ String[] roots = {rel, System.getProperty("user.dir") + "/" + rel, System.getProperty("user.dir") + "/../" + rel};
+ for (String root : roots) {
+ if (root == null || root.trim().isEmpty()) continue;
+ File base = new File(root);
+ if (base.isDirectory()) {
+ File dir = new File(base, CACHE_SUBDIR);
+ if (!dir.isDirectory() && !dir.mkdirs()) continue;
+ return dir;
+ }
+ }
+ return null;
+ }
+
+ /** 缂撳瓨鏂囦欢鍚嶅墠缂�锛堜笉鍚暟鎹増鏈笌鎵╁睍鍚嶏級锛歬ey 閲岀殑闈炴硶瀛楃缁熶竴鏇挎崲 */
+ private String cacheFilePrefix(String type, String period, String mode, Integer toMonth) {
+ return (cacheKey(type, period, mode, toMonth)).replaceAll("[^0-9A-Za-z_-]", "_");
+ }
private final Map<String, Task> tasks = new ConcurrentHashMap<>();
/** 鍚屾姤琛ㄦ湡鏈�杩戜竴娆℃垚鍔熺粨鏋滐細key -> task */
@@ -64,7 +94,7 @@
private final String period;
private final String mode;
private final Integer toMonth;
- private final long createdAt = System.currentTimeMillis();
+ private volatile long createdAt = System.currentTimeMillis();
/** 鐢熸垚鏃剁殑鏁版嵁鐗堟湰锛坕mport_batch 鏈�澶� id锛夛紝鐢ㄤ簬鍒ゆ柇缂撳瓨鏄惁杩囨湡 */
private final long importVersion;
private volatile String status = "PENDING"; // PENDING/RUNNING/DONE/FAILED
@@ -72,6 +102,8 @@
private volatile String step = "鎺掗槦涓�";
private volatile String error;
private volatile byte[] data;
+ /** 钀界洏缂撳瓨鏂囦欢锛氬唴瀛樻暟鎹娓呯悊鍚庝粛鍙粠杩欓噷鍙栵紙璺ㄨ繘绋嬮噸鍚鐢級 */
+ private volatile File cacheFile;
private volatile String fileName;
private volatile boolean fromCache;
private volatile long finishedAt;
@@ -93,11 +125,24 @@
public String getStatus() { return status; }
- public boolean isDone() { return "DONE".equals(status) && data != null; }
+ public boolean isDone() {
+ return "DONE".equals(status) && (data != null || (cacheFile != null && cacheFile.isFile()));
+ }
public boolean isFromCache() { return fromCache; }
- public byte[] getData() { return data; }
+ public byte[] getData() {
+ if (data != null) return data;
+ File f = cacheFile;
+ if (f != null && f.isFile()) {
+ try {
+ return java.nio.file.Files.readAllBytes(f.toPath());
+ } catch (Exception e) {
+ log.warn("璇诲彇瀵煎嚭缂撳瓨鏂囦欢澶辫触锛歿} {}", f.getAbsolutePath(), e.getMessage());
+ }
+ }
+ return null;
+ }
public String getFileName() { return fileName; }
@@ -135,8 +180,16 @@
Task hit = cache.get(key);
if (cacheUsable(hit)) {
hit.fromCache = true;
- log.info("鎶ヨ〃瀵煎嚭浠诲姟鍛戒腑缂撳瓨锛歿} {}锛堢敓鎴愪簬 {}锛�", type, period, new Date(hit.finishedAt));
+ log.info("鎶ヨ〃瀵煎嚭浠诲姟鍛戒腑鍐呭瓨缂撳瓨锛歿} {}锛堢敓鎴愪簬 {}锛�", type, period, new Date(hit.finishedAt));
return hit;
+ }
+ Task disk = loadFromDisk(key, type, period, mode, toMonth);
+ if (disk != null) {
+ tasks.put(disk.id, disk);
+ cache.put(key, disk);
+ log.info("鎶ヨ〃瀵煎嚭浠诲姟鍛戒腑钀界洏缂撳瓨锛歿} {}锛堢敓鎴愪簬 {}锛屾枃浠� {}锛�", type, period,
+ new Date(disk.finishedAt), disk.cacheFile.getName());
+ return disk;
}
}
Task running = findRunning(key);
@@ -168,6 +221,7 @@
t.status = "DONE";
t.finishedAt = System.currentTimeMillis();
cache.put(cacheKey(t.type, t.period, t.mode, t.toMonth), t);
+ writeToDisk(t, data);
log.info("鎶ヨ〃瀵煎嚭浠诲姟瀹屾垚锛歿} {} 鐢ㄦ椂 {} ms锛寋} 瀛楄妭", t.type, t.period, t.finishedAt - start, data.length);
} catch (Throwable e) {
t.status = "FAILED";
@@ -188,7 +242,7 @@
private boolean cacheUsable(Task t) {
if (t == null || !"DONE".equals(t.status) || t.data == null) return false;
- if (System.currentTimeMillis() - t.finishedAt > CACHE_TTL_MS) return false;
+ if (System.currentTimeMillis() - t.finishedAt > CACHE_MAX_AGE_MS) return false;
long now = importVersion();
if (now < 0 || t.importVersion < 0) return false; // 鍙栦笉鍒版暟鎹増鏈椂涓嶅啋椋庨櫓澶嶇敤
return now == t.importVersion;
@@ -218,7 +272,66 @@
}
for (Iterator<Map.Entry<String, Task>> it = cache.entrySet().iterator(); it.hasNext(); ) {
Task t = it.next().getValue();
- if (t.finishedAt <= 0 || now - t.finishedAt > CACHE_TTL_MS) it.remove();
+ if (t.finishedAt <= 0 || now - t.finishedAt > CACHE_MAX_AGE_MS) it.remove();
+ }
+ File dir = cacheDir();
+ if (dir == null) return;
+ File[] fs = dir.listFiles((d, n) -> n.endsWith(".xlsx"));
+ if (fs == null) return;
+ for (File f : fs) {
+ if (now - f.lastModified() > CACHE_MAX_AGE_MS && !f.delete()) {
+ log.warn("娓呯悊杩囨湡瀵煎嚭缂撳瓨澶辫触锛歿}", f.getAbsolutePath());
+ }
+ }
+ }
+
+ /** 鐢熸垚鎴愬姛鍚庢妸缁撴灉钀界洏锛屼緵杩涚▼閲嶅惎鍚庡鐢紱鍐欑洏澶辫触鍙憡璀︼紝涓嶅奖鍝嶆湰娆″鍑� */
+ private void writeToDisk(Task t, byte[] data) {
+ try {
+ File dir = cacheDir();
+ if (dir == null) return;
+ long version = t.importVersion;
+ if (version < 0) return;
+ String prefix = cacheFilePrefix(t.type, t.period, t.mode, t.toMonth);
+ File target = new File(dir, prefix + "_v" + version + ".xlsx");
+ java.nio.file.Files.write(target.toPath(), data);
+ File[] siblings = dir.listFiles((d, n) -> n.startsWith(prefix + "_v") && n.endsWith(".xlsx"));
+ if (siblings != null) {
+ for (File f : siblings) {
+ if (!f.getName().equals(target.getName()) && !f.delete()) {
+ log.warn("娓呯悊鏃х増鏈鍑虹紦瀛樺け璐ワ細{}", f.getAbsolutePath());
+ }
+ }
+ }
+ } catch (Exception e) {
+ log.warn("瀵煎嚭缁撴灉钀界洏澶辫触锛堜笉褰卞搷鏈瀵煎嚭锛屼粎鏃犳硶璺ㄩ噸鍚鐢級锛歿}", e.getMessage());
+ }
+ }
+
+ /** 浠庤惤鐩樼紦瀛橀噷鎵俱�屽悓涓�鎶ヨ〃鏈� + 鍚屼竴鏁版嵁鐗堟湰 + 鏈秴鏈熴�嶇殑缁撴灉锛涙病鏈夎繑鍥� null */
+ private Task loadFromDisk(String key, String type, String period, String mode, Integer toMonth) {
+ try {
+ File dir = cacheDir();
+ if (dir == null) return null;
+ long version = importVersion();
+ if (version < 0) return null; // 鍙栦笉鍒版暟鎹増鏈椂涓嶅啋椋庨櫓澶嶇敤
+ File f = new File(dir, cacheFilePrefix(type, period, mode, toMonth) + "_v" + version + ".xlsx");
+ if (!f.isFile()) return null;
+ long finished = f.lastModified();
+ if (finished <= 0 || System.currentTimeMillis() - finished > CACHE_MAX_AGE_MS) return null;
+ Task t = new Task(type, period, mode, toMonth, version);
+ t.cacheFile = f;
+ t.createdAt = finished; // 澶嶇敤鍘嗗彶缁撴灉锛氫互鐢熸垚鏃堕棿浣滀负璧风偣锛岄伩鍏� elapsedMs 鍑虹幇璐熸暟
+ t.fileName = "鐢熸垚_閬撹矾杩愯緭閲忔眹鎬昏〃_" + period + ".xlsx";
+ t.status = "DONE";
+ t.percent = 100;
+ t.step = "瀹屾垚";
+ t.finishedAt = finished;
+ t.fromCache = true;
+ return t;
+ } catch (Exception e) {
+ log.warn("璇诲彇钀界洏瀵煎嚭缂撳瓨澶辫触锛歿}", e.getMessage());
+ return null;
}
}
--
Gitblit v1.9.1