refactor: 合并三级缓存为统一缓存 + 重命名 saveDraft → savePreview
1. 三个 Caffeine Cache 合并为一个 jsonCache,key 用 :published/:draft 后缀区分 2. saveDraft → savePreview,更准确反映「预览版」语义(非临时草稿) 3. getPreviewBundleJson 去掉独立缓存,直接查 DB(preview 是 draft+published 逻辑并集,缓存意义不大) 4. 前端按钮「保存草稿」→「保存预览版」,API 路径 /save-draft → /save-preview
This commit is contained in:
@@ -17,20 +17,20 @@ public interface SiteSnapshotService {
|
||||
Map<String, Object> buildSnapshot(String siteId);
|
||||
|
||||
/**
|
||||
* 更新站点的 draft 快照(任意配置变更后调用)
|
||||
* 更新站点的预览版快照(任意配置变更后调用)
|
||||
* 内容未变化(checksum 相同)则不产生新版本
|
||||
* @return 新快照或 null(内容未变)
|
||||
*/
|
||||
SiteSnapshot updateSnapshot(String siteId);
|
||||
|
||||
/**
|
||||
* 手动保存草稿快照(管理员触发,强制生成新 draft)
|
||||
* 手动保存预览版快照(管理员触发)
|
||||
* @return 新快照或 null
|
||||
*/
|
||||
SiteSnapshot saveDraft(String siteId);
|
||||
SiteSnapshot savePreview(String siteId);
|
||||
|
||||
/**
|
||||
* 发布指定站点的快照(draft → published)
|
||||
* 发布指定站点的快照(预览版 → 正式版)
|
||||
* 同时上传到七牛云 CDN 作为灾备
|
||||
*/
|
||||
SiteSnapshot publish(String siteId);
|
||||
@@ -41,22 +41,21 @@ public interface SiteSnapshotService {
|
||||
SiteSnapshot getActive(String siteId);
|
||||
|
||||
/**
|
||||
* 获取站点当前生效快照的预序列化 JSON(带 Caffeine 缓存)
|
||||
* 获取站点当前生效快照的预序列化 JSON(带缓存)
|
||||
* 供 SnapshotController 使用,避免重复序列化
|
||||
* @return JSON 字符串,空串表示无快照
|
||||
*/
|
||||
String getActiveBundleJson(String siteId);
|
||||
|
||||
/**
|
||||
* 获取站点最新 draft 快照的预序列化 JSON(预览版,带 Caffeine 缓存)
|
||||
* 获取站点最新预览版快照的预序列化 JSON(带缓存)
|
||||
* @param siteId 站点标识
|
||||
* @return JSON 字符串,空串表示无 draft
|
||||
* @return JSON 字符串,空串表示无预览版
|
||||
*/
|
||||
String getDraftBundleJson(String siteId);
|
||||
|
||||
/**
|
||||
* 获取预览版快照 JSON(draft 优先,无 draft 时 fallback 到 published)
|
||||
* 即:正式版 + 预览版 的并集
|
||||
* 获取预览快照 JSON(预览版优先,无预览版时 fallback 到正式版)
|
||||
* @param siteId 站点标识
|
||||
* @return JSON 字符串,空串表示无任何快照
|
||||
*/
|
||||
|
||||
@@ -43,23 +43,14 @@ public class SiteSnapshotServiceImpl implements SiteSnapshotService {
|
||||
private final CachedStorageService storageService;
|
||||
private static final ObjectMapper om = new ObjectMapper();
|
||||
|
||||
/** 已发布快照 JSON 缓存:key=siteId, value=预序列化的 bundle JSON */
|
||||
private final Cache<String, String> bundleJsonCache = Caffeine.newBuilder()
|
||||
/** 统一快照 JSON 缓存:key = siteId + ":published" | siteId + ":draft",value = 预序列化 bundle JSON */
|
||||
private final Cache<String, String> jsonCache = Caffeine.newBuilder()
|
||||
.expireAfterWrite(Duration.ofMinutes(10))
|
||||
.maximumSize(500)
|
||||
.maximumSize(1000)
|
||||
.build();
|
||||
|
||||
/** draft 快照 JSON 缓存:key=siteId, value=预序列化的 bundle JSON */
|
||||
private final Cache<String, String> draftJsonCache = Caffeine.newBuilder()
|
||||
.expireAfterWrite(Duration.ofMinutes(5))
|
||||
.maximumSize(500)
|
||||
.build();
|
||||
|
||||
/** preview 快照 JSON 缓存:draft 优先 fallback published */
|
||||
private final Cache<String, String> previewJsonCache = Caffeine.newBuilder()
|
||||
.expireAfterWrite(Duration.ofMinutes(5))
|
||||
.maximumSize(500)
|
||||
.build();
|
||||
private static String pubKey(String siteId) { return siteId + ":published"; }
|
||||
private static String draftKey(String siteId) { return siteId + ":draft"; }
|
||||
|
||||
@Override
|
||||
public Map<String, Object> buildSnapshot(String siteId) {
|
||||
@@ -134,18 +125,15 @@ public class SiteSnapshotServiceImpl implements SiteSnapshotService {
|
||||
}
|
||||
|
||||
try {
|
||||
// 计算 checksum
|
||||
String contentJson = om.writeValueAsString(data);
|
||||
String checksum = HmacUtil.sha256(contentJson);
|
||||
|
||||
// 查询最新 draft
|
||||
SiteSnapshot latestDraft = snapshotMapper.selectLatestDraft(siteId);
|
||||
if (latestDraft != null && checksum.equals(latestDraft.getChecksum())) {
|
||||
log.debug("站点 {} 配置未变化,跳过快照生成", siteId);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 确定新版本号
|
||||
int newVersion = (latestDraft != null) ? latestDraft.getVersion() + 1 : 1;
|
||||
|
||||
SiteSnapshot snapshot = new SiteSnapshot();
|
||||
@@ -157,10 +145,9 @@ public class SiteSnapshotServiceImpl implements SiteSnapshotService {
|
||||
|
||||
snapshotMapper.insert(snapshot);
|
||||
|
||||
// 刷新 draft 和 preview 缓存(审批通过后异步生成 draft,需立即可被预览查询到)
|
||||
// 刷新缓存:审批通过后异步生成的预览版,需立即可被 ?preview=true 查询到
|
||||
String bundleJson = toBundleJson(snapshot);
|
||||
draftJsonCache.put(siteId, bundleJson);
|
||||
previewJsonCache.put(siteId, bundleJson);
|
||||
jsonCache.put(draftKey(siteId), bundleJson);
|
||||
|
||||
log.info("快照已更新: siteId={}, version={}, checksum={}", siteId, newVersion, checksum.substring(0, 8));
|
||||
return snapshot;
|
||||
@@ -172,10 +159,10 @@ public class SiteSnapshotServiceImpl implements SiteSnapshotService {
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public SiteSnapshot saveDraft(String siteId) {
|
||||
public SiteSnapshot savePreview(String siteId) {
|
||||
Map<String, Object> data = buildSnapshot(siteId);
|
||||
if (data == null) {
|
||||
log.warn("站点 {} 无有效配置,无法保存草稿", siteId);
|
||||
log.warn("站点 {} 无有效配置,无法保存预览版", siteId);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -186,7 +173,7 @@ public class SiteSnapshotServiceImpl implements SiteSnapshotService {
|
||||
// 查询最新 draft,内容相同则跳过
|
||||
SiteSnapshot latestDraft = snapshotMapper.selectLatestDraft(siteId);
|
||||
if (latestDraft != null && checksum.equals(latestDraft.getChecksum())) {
|
||||
log.info("站点 {} 草稿内容未变化,跳过保存", siteId);
|
||||
log.info("站点 {} 预览版内容未变化,跳过保存", siteId);
|
||||
return latestDraft;
|
||||
}
|
||||
|
||||
@@ -201,15 +188,14 @@ public class SiteSnapshotServiceImpl implements SiteSnapshotService {
|
||||
|
||||
snapshotMapper.insert(snapshot);
|
||||
|
||||
// 刷新 draft 和 preview 缓存
|
||||
// 刷新缓存
|
||||
String bundleJson = toBundleJson(snapshot);
|
||||
draftJsonCache.put(siteId, bundleJson);
|
||||
previewJsonCache.put(siteId, bundleJson);
|
||||
jsonCache.put(draftKey(siteId), bundleJson);
|
||||
|
||||
log.info("草稿已保存: siteId={}, version={}, checksum={}", siteId, newVersion, checksum.substring(0, 8));
|
||||
log.info("预览版已保存: siteId={}, version={}, checksum={}", siteId, newVersion, checksum.substring(0, 8));
|
||||
return snapshot;
|
||||
} catch (Exception e) {
|
||||
log.error("保存草稿失败 siteId={}", siteId, e);
|
||||
log.error("保存预览版失败 siteId={}", siteId, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -237,7 +223,7 @@ public class SiteSnapshotServiceImpl implements SiteSnapshotService {
|
||||
public SiteSnapshot publish(String siteId) {
|
||||
SiteSnapshot draft = snapshotMapper.selectLatestDraft(siteId);
|
||||
if (draft == null) {
|
||||
log.warn("站点 {} 无 draft 快照可发布", siteId);
|
||||
log.warn("站点 {} 无预览版快照可发布", siteId);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -255,10 +241,9 @@ public class SiteSnapshotServiceImpl implements SiteSnapshotService {
|
||||
|
||||
// 序列化并刷新缓存
|
||||
String bundleJson = toBundleJson(draft);
|
||||
bundleJsonCache.put(siteId, bundleJson);
|
||||
// 发布后 draft/preview 缓存已过时
|
||||
draftJsonCache.invalidate(siteId);
|
||||
previewJsonCache.invalidate(siteId);
|
||||
jsonCache.put(pubKey(siteId), bundleJson);
|
||||
// 发布后 draft 缓存已过时(该记录不再是 draft)
|
||||
jsonCache.invalidate(draftKey(siteId));
|
||||
|
||||
// 上传到七牛云 CDN 作为灾备(复用已序列化的 JSON)
|
||||
try {
|
||||
@@ -285,42 +270,42 @@ public class SiteSnapshotServiceImpl implements SiteSnapshotService {
|
||||
|
||||
@Override
|
||||
public String getActiveBundleJson(String siteId) {
|
||||
return bundleJsonCache.get(siteId, key -> {
|
||||
SiteSnapshot active = snapshotMapper.selectLatestPublished(key);
|
||||
return jsonCache.get(pubKey(siteId), key -> {
|
||||
SiteSnapshot active = snapshotMapper.selectLatestPublished(siteId);
|
||||
if (active == null) {
|
||||
log.debug("站点 {} 无 published 快照", key);
|
||||
log.debug("站点 {} 无 published 快照", siteId);
|
||||
return ""; // 空字符串表示无数据,Caffeine 会缓存
|
||||
}
|
||||
log.debug("缓存未命中,序列化 siteId={}", key);
|
||||
log.debug("缓存未命中,序列化 siteId={}", siteId);
|
||||
return toBundleJson(active);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDraftBundleJson(String siteId) {
|
||||
return draftJsonCache.get(siteId, key -> {
|
||||
SiteSnapshot draft = snapshotMapper.selectLatestDraft(key);
|
||||
return jsonCache.get(draftKey(siteId), key -> {
|
||||
SiteSnapshot draft = snapshotMapper.selectLatestDraft(siteId);
|
||||
if (draft == null) {
|
||||
log.debug("站点 {} 无 draft 快照", key);
|
||||
return ""; // 空字符串表示无数据,Caffeine 会缓存
|
||||
log.debug("站点 {} 无预览版快照", siteId);
|
||||
return "";
|
||||
}
|
||||
log.debug("draft 缓存未命中,序列化 siteId={}", key);
|
||||
log.debug("预览版缓存未命中 siteId={}", siteId);
|
||||
return toBundleJson(draft);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPreviewBundleJson(String siteId) {
|
||||
// draft 优先,无 draft 则取 published — 一次查询
|
||||
return previewJsonCache.get(siteId, key -> {
|
||||
SiteSnapshot snapshot = snapshotMapper.selectLatestWithDraft(key);
|
||||
if (snapshot == null) {
|
||||
log.debug("站点 {} 无任何快照", key);
|
||||
return "";
|
||||
}
|
||||
log.debug("预览缓存未命中 siteId={} status={}", key, snapshot.getStatus());
|
||||
return toBundleJson(snapshot);
|
||||
});
|
||||
// draft 优先,无 draft 则 fallback 到 published — 一次查询
|
||||
// preview 不使用独立缓存,因为它是 draft + published 的逻辑并集;
|
||||
// 当 draft 或 published 任一变化时,需要同时感知,直接查 DB 更准确
|
||||
SiteSnapshot snapshot = snapshotMapper.selectLatestWithDraft(siteId);
|
||||
if (snapshot == null) {
|
||||
log.debug("站点 {} 无任何快照", siteId);
|
||||
return "";
|
||||
}
|
||||
log.debug("预览查询 siteId={} status={}", siteId, snapshot.getStatus());
|
||||
return toBundleJson(snapshot);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user