feat: 电商工作台进度与生成记录健壮性优化

This commit is contained in:
2026-06-15 10:24:31 +08:00
parent e1fdbe5f9b
commit 0b2d6b901f
10 changed files with 533 additions and 102 deletions
+44
View File
@@ -38,6 +38,20 @@ export interface SaveGenerationRecordResult {
id: string;
}
// 同一 clientRecordId 的保存去重:套图主流程、backgroundTaskRunner、useGenerationTasks
// 三处都可能对同一条终态任务调用 saveGenerationRecordSSE 重复推送 completed 时
// 单个 poller 内也会重复触发。这里做客户端幂等:in-flight 合流 + 成功后短期拦截,
// 避免后端在缺少去重时插入重复记录。
const inFlightSaves = new Map<string, Promise<SaveGenerationRecordResult>>();
const recentlySavedAt = new Map<string, number>();
const SAVE_DEDUPE_WINDOW_MS = 60_000;
function pruneRecentlySaved(now: number): void {
for (const [id, savedAt] of recentlySavedAt) {
if (now - savedAt > SAVE_DEDUPE_WINDOW_MS) recentlySavedAt.delete(id);
}
}
function readPendingRecords(): SaveGenerationRecordInput[] {
try {
const raw = window.localStorage.getItem(PENDING_RECORDS_KEY);
@@ -60,6 +74,36 @@ function writePendingRecord(input: SaveGenerationRecordInput): void {
}
export async function saveGenerationRecord(input: SaveGenerationRecordInput): Promise<SaveGenerationRecordResult> {
const now = Date.now();
pruneRecentlySaved(now);
const recordId = input.clientRecordId;
if (recordId) {
const inFlight = inFlightSaves.get(recordId);
if (inFlight) return inFlight;
const savedAt = recentlySavedAt.get(recordId);
if (savedAt !== undefined && now - savedAt <= SAVE_DEDUPE_WINDOW_MS) {
// 终态记录只需落库一次;窗口内的重复调用直接视为已保存。
return { source: "server", id: recordId };
}
}
const promise = saveGenerationRecordInternal(input);
if (recordId) {
inFlightSaves.set(recordId, promise);
void promise
.then((result) => {
if (result.source === "server") recentlySavedAt.set(recordId, Date.now());
})
.catch(() => undefined)
.finally(() => {
inFlightSaves.delete(recordId);
});
}
return promise;
}
async function saveGenerationRecordInternal(input: SaveGenerationRecordInput): Promise<SaveGenerationRecordResult> {
try {
const response = await serverRequest<{ id?: string | number }>("ai/generation-records", {
method: "POST",