feat: 电商工作台进度与生成记录健壮性优化
This commit is contained in:
@@ -38,6 +38,20 @@ export interface SaveGenerationRecordResult {
|
||||
id: string;
|
||||
}
|
||||
|
||||
// 同一 clientRecordId 的保存去重:套图主流程、backgroundTaskRunner、useGenerationTasks
|
||||
// 三处都可能对同一条终态任务调用 saveGenerationRecord,SSE 重复推送 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",
|
||||
|
||||
Reference in New Issue
Block a user