perf: 优化快照接口——缓存预序列化 JSON,消除重复序列化

- Caffeine 缓存改为存储 bundle JSON 字符串而非 SiteSnapshot 对象(减少内存占用)
- 新增 getActiveBundleJson 方法,SnapshotController 直接使用缓存 JSON
- bundle JSON 新增 checksum 字段,ETag 比较无需额外查库
- 删除 SnapshotController.toJson 重复序列化代码
- publish 时复用已序列化的 JSON 上传 CDN
This commit is contained in:
mediabot-pt
2026-07-07 11:28:28 +08:00
parent 9f94b53753
commit 820b180d87
3 changed files with 42 additions and 49 deletions

View File

@@ -1,5 +1,6 @@
package com.par.api.controller;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.par.core.dto.ApiResponse;
import com.par.core.entity.SiteSnapshot;
@@ -32,7 +33,7 @@ public class SnapshotController {
/**
* 获取站点当前生效配置快照(供 mediabot 使用)
* 支持 ETag (If-None-Match) 实现增量更新
* 优先从数据库读取,数据库不可用时回退到七牛云 CDN
* 优先从 Caffeine 缓存读取,缓存未命中查数据库并回退七牛云 CDN
*/
@GetMapping("/{siteId}")
public ResponseEntity<?> getSnapshot(
@@ -40,9 +41,9 @@ public class SnapshotController {
@RequestHeader(value = "If-None-Match", required = false) String ifNoneMatch) {
try {
SiteSnapshot active = snapshotService.getActive(siteId);
String bundleJson = snapshotService.getActiveBundleJson(siteId);
if (active == null) {
if (bundleJson == null || bundleJson.isEmpty()) {
// 尝试从云存储 CDN 回退
String cached = storageService.read("snapshots/" + siteId + ".json");
if (cached != null && !cached.isBlank()) {
@@ -53,18 +54,18 @@ public class SnapshotController {
return ResponseEntity.notFound().build();
}
// ETag 检查
if (ifNoneMatch != null && ifNoneMatch.equals(active.getChecksum())) {
// ETag 检查:从 JSON 中提取 checksum
String etag = extractChecksum(bundleJson);
if (ifNoneMatch != null && etag != null && ifNoneMatch.equals(etag)) {
return ResponseEntity.status(304)
.header(HttpHeaders.ETAG, active.getChecksum())
.header(HttpHeaders.ETAG, etag)
.build();
}
String json = toJson(active);
return ResponseEntity.ok()
.header(HttpHeaders.ETAG, active.getChecksum())
.header(HttpHeaders.ETAG, etag)
.contentType(MediaType.APPLICATION_JSON)
.body(json);
.body(bundleJson);
} catch (Exception e) {
log.error("获取快照失败 siteId={}", siteId, e);
@@ -99,39 +100,13 @@ public class SnapshotController {
return ApiResponse.success(result);
}
private String toJson(SiteSnapshot snapshot) {
/** 从 bundle JSON 中提取 checksum 字段,用于 ETag 比较 */
private String extractChecksum(String bundleJson) {
try {
Map<String, Object> bundle = new LinkedHashMap<>();
bundle.put("siteId", snapshot.getSiteId());
bundle.put("generatedAt", snapshot.getPublishedAt() != null
? snapshot.getPublishedAt().toString()
: java.time.LocalDateTime.now().toString());
if (snapshot.getScript() != null) {
bundle.put("script", snapshot.getScript());
bundle.put("scriptVersion", snapshot.getScriptVersion());
bundle.put("scriptSource", snapshot.getScriptSource());
}
if (snapshot.getPages() != null) {
@SuppressWarnings("unchecked")
var pages = om.readValue(snapshot.getPages(), java.util.List.class);
bundle.put("pages", pages);
bundle.put("pagesVersion", snapshot.getPagesVersion());
bundle.put("pagesSource", snapshot.getPagesSource());
}
if (snapshot.getRules() != null) {
@SuppressWarnings("unchecked")
var rules = om.readValue(snapshot.getRules(), java.util.List.class);
bundle.put("riskRules", rules);
bundle.put("riskRulesVersion", snapshot.getRulesVersion());
}
return om.writeValueAsString(bundle);
JsonNode node = om.readTree(bundleJson);
return node.has("checksum") ? node.get("checksum").asText() : null;
} catch (Exception e) {
log.error("序列化快照失败 siteId={}", snapshot.getSiteId(), e);
return "{}";
return null;
}
}
}