From 31f2433c4b477b03b4b7fde1fce616c9c4d8ae04 Mon Sep 17 00:00:00 2001 From: chenhang Date: Mon, 15 Jun 2026 17:39:50 +0800 Subject: [PATCH] refactor: auto-init guest chat quota on first read, clean up logs - Remove unused `quotaWarningThreshold` and `warningThreshold` getter from `GuestChatQuota` DTO - Apply get-or-init pattern in `ChatStorage.getGuestDailyChatQuota` and `getGuestTotalQuota`: when no existing value, initialize with DTO defaults (`threshold` = max per day, `totalQuotaDefault`) and return the seeded data - Fall back to original `null` result if initialization write fails - Consolidate logging strategy: drop per-actor trace logs in `chat-machine.ts`, keep business-decision logs in 3 assign actions, move all actor ENTRY/API/DONE chains to `chat-machine.actors.ts` and helper load logs to `chat-machine.helpers.ts` --- src/data/dto/chat/guest_chat_quota.ts | 11 -- src/data/storage/chat/chat_storage.ts | 43 +++++- src/stores/chat/chat-machine.ts | 202 ++++++-------------------- src/stores/chat/chat-state.ts | 2 - 4 files changed, 88 insertions(+), 170 deletions(-) diff --git a/src/data/dto/chat/guest_chat_quota.ts b/src/data/dto/chat/guest_chat_quota.ts index 7a6cfc25..8bd7204d 100644 --- a/src/data/dto/chat/guest_chat_quota.ts +++ b/src/data/dto/chat/guest_chat_quota.ts @@ -11,9 +11,7 @@ export class GuestChatQuota { // 静态常量 static readonly maxQuotaPerDay = 40; static readonly maxQuotaPerDayTest = 4; - static readonly quotaWarningThreshold = 20; - static readonly quotaWarningThresholdTest = 2; static readonly defaultTotalQuota = 50; static readonly defaultTotalQuotaDev = 5; static readonly quotaExhausted = 0; @@ -49,15 +47,6 @@ export class GuestChatQuota { return process.env.NODE_ENV !== "production"; } - /** - * 获取当前环境的配额警告阈值 - */ - static get warningThreshold(): number { - return GuestChatQuota.isDevelopment - ? GuestChatQuota.quotaWarningThresholdTest - : GuestChatQuota.quotaWarningThreshold; - } - /** * 获取当前环境的每日最大配额 */ diff --git a/src/data/storage/chat/chat_storage.ts b/src/data/storage/chat/chat_storage.ts index b68b4df2..561c1027 100644 --- a/src/data/storage/chat/chat_storage.ts +++ b/src/data/storage/chat/chat_storage.ts @@ -18,9 +18,11 @@ import { GuestChatQuotaSchema, type GuestChatQuotaData, } from "@/data/schemas/chat/guest_chat_quota"; +import { GuestChatQuota as GuestChatQuotaDto } from "@/data/dto/chat/guest_chat_quota"; import { z } from "zod"; -import { type Result as ResultT } from "@/utils/result"; +import { type Result as ResultT, Result } from "@/utils/result"; +import { formatDate } from "@/utils/date"; import { SpAsyncUtil } from "@/utils/storage"; import { StorageKeys } from "../storage_keys"; import type { IChatStorage } from "./ichat_storage"; @@ -41,7 +43,27 @@ export class ChatStorage implements IChatStorage { // ---- guest daily chat quota ---- getGuestDailyChatQuota(): Promise> { - return SpAsyncUtil.getJson(StorageKeys.guestChatQuota, GuestChatQuotaSchema); + // get-or-init:**有**返**有**,**无**则**用** DTO 默认**值**(`threshold` = max per day)**初**始**化**并**返**回** + return SpAsyncUtil.getJson( + StorageKeys.guestChatQuota, + GuestChatQuotaSchema, + ).then(async (existing) => { + if (existing.success && existing.data != null) { + return existing; + } + const today = formatDate(new Date()); + const initResult = await this.setGuestDailyChatQuota( + GuestChatQuotaDto.threshold, + today, + ); + if (Result.isErr(initResult)) { + return existing; // **初**始**化**失**败** → **返**原**始** null + } + return { + success: true, + data: { remaining: GuestChatQuotaDto.threshold, date: today }, + }; + }); } setGuestDailyChatQuota(remaining: number, date: string): Promise> { @@ -56,7 +78,22 @@ export class ChatStorage implements IChatStorage { // ---- guest total quota ---- getGuestTotalQuota(): Promise> { - return SpAsyncUtil.getJson(StorageKeys.guestTotalQuotaRemaining, z.number()); + // get-or-init:**有**返**有**,**无**则**用** DTO 默认**值**(`totalQuotaDefault`)**初**始**化**并**返**回** + return SpAsyncUtil.getJson( + StorageKeys.guestTotalQuotaRemaining, + z.number(), + ).then(async (existing) => { + if (existing.success && existing.data != null) { + return existing; + } + const initResult = await this.setGuestTotalQuota( + GuestChatQuotaDto.totalQuotaDefault, + ); + if (Result.isErr(initResult)) { + return existing; + } + return { success: true, data: GuestChatQuotaDto.totalQuotaDefault }; + }); } setGuestTotalQuota(remaining: number): Promise> { diff --git a/src/stores/chat/chat-machine.ts b/src/stores/chat/chat-machine.ts index 94864343..8c7d6828 100644 --- a/src/stores/chat/chat-machine.ts +++ b/src/stores/chat/chat-machine.ts @@ -47,9 +47,11 @@ * - `chat-machine.helpers.ts` —— 纯函数 + 数据加载(readGuestQuota + readAndSyncHistory) * - `chat-machine.actors.ts` —— 5 个 XState actor(loadQuota + loadHistory + sendMessage + loadMoreHistory + chatWebSocket) * - * **调试日志**(保留 —— 用户要求"不要将其删除"): - * - 命名约定:`[]` 前缀标识来源 - * - 4 actor **全链日志**(ENTRY / API / DONE / 关键决策)—— 排查 actor **没**被调时**一眼**看到 + * **日**志**策略**(**清**理**后**): + * - **不**在**状**态**机**文**件**中**打**追踪**日**志**(actor / helper / action **本**身**有** ENTRY / DONE **日**志**,**就**够**排查**了) + * - `appendUserMessage` / `appendUserImage` / `appendOrUpdateAISentence` 这 3 **个** action `assign` **内**部**保**留** `console.log`(**记**录** action **本**身**的**业**务**决**策**,**比**如** quota 减**少** / isFromAI 标**记**) + * - **所**有** actor ENTRY / API / DONE **全**链**日**志**放**在** `chat-machine.actors.ts` + * - **所**有** helper 数据**加**载**日**志**(`readGuestQuota` / `readAndSyncHistory` 3 **步**)放**在** `chat-machine.helpers.ts` */ import { setup, assign } from "xstate"; @@ -95,9 +97,11 @@ export const chatMachine = setup({ actions: { appendUserMessage: assign(({ context, event }) => { if (event.type !== "ChatSendMessage") return {}; + const newQuota = context.wsConnected ? context.guestRemainingQuota : Math.max(0, context.guestRemainingQuota - 1); + console.log("[chat-machine] appendUserMessage", { contentLength: event.content.length, contentPreview: event.content.slice(0, 50), @@ -109,6 +113,7 @@ export const chatMachine = setup({ newQuota, isReplyingAI: true, }); + return { messages: [ ...context.messages, @@ -195,26 +200,6 @@ export const chatMachine = setup({ setWsConnected: assign({ wsConnected: true }), clearWsConnected: assign({ wsConnected: false }), - - logSendMessageReceived: ({ event, context }) => - console.log("[chat-machine] SendMessage received", { - contentLength: event.type === "ChatSendMessage" ? event.content.length : 0, - isReplyingAI: context.isReplyingAI, - wsConnected: context.wsConnected, - currentMessagesCount: context.messages.length, - }), - - logSendImageReceived: ({ event, context }) => - console.log("[chat-machine] SendImage received", { - hasImageBase64: event.type === "ChatSendImage" ? !!event.imageBase64 : false, - wsConnected: context.wsConnected, - }), - - logLoadMoreHistoryReceived: ({ context }) => - console.log("[chat-machine] LoadMoreHistory received", { - currentOffset: context.historyOffset, - hasMore: context.hasMore, - }), }, }).createMachine({ id: "chat", @@ -248,17 +233,11 @@ export const chatMachine = setup({ id: "loadQuota", src: "loadQuota", onDone: { - actions: assign(({ event }) => { - console.log("[chat-machine] guestSession.initializing.loadQuota.onDone", { - remaining: event.output.remaining, - total: event.output.total, - }); - return { - guestRemainingQuota: event.output.remaining, - guestTotalQuota: event.output.total, - quotaLoaded: true, - }; - }), + actions: assign(({ event }) => ({ + guestRemainingQuota: event.output.remaining, + guestTotalQuota: event.output.total, + quotaLoaded: true, + })), }, onError: { // 失败**也**标 loaded,**不**卡 init(**游**客**可**能 quota 服务**不**可**用**,**让** UI **进** ready) @@ -272,23 +251,12 @@ export const chatMachine = setup({ id: "loadHistory", src: "loadHistory", onDone: { - actions: assign(({ event }) => { - console.log( - "[chat-machine] guestSession.initializing.loadHistory.onDone", - { - finalCount: event.output.messages.length, - hasMore: event.output.hasMore, - newOffset: event.output.newOffset, - localOverwritten: event.output.localOverwritten, - }, - ); - return { - messages: event.output.messages, - hasMore: event.output.hasMore, - historyOffset: event.output.newOffset, - historyLoaded: true, - }; - }), + actions: assign(({ event }) => ({ + messages: event.output.messages, + hasMore: event.output.hasMore, + historyOffset: event.output.newOffset, + historyLoaded: true, + })), }, onError: { // 失败**也**标 loaded,**不**卡 init(**让** UI **还**是**能**进** ready,**屏**幕**可**能**空**) @@ -316,9 +284,9 @@ export const chatMachine = setup({ guard: ({ context }) => context.guestRemainingQuota <= 0, actions: "incrementQuotaExceeded", }, - // 3. 两**个**配**额**都** OK + **内**容**非**空** → **发**送** + // 3. 两**个**配**额**都** OK + **内**容**非**空** → **发**送**(**只**用** appendUserMessage **自**己**的**日**志**) { - actions: ["logSendMessageReceived", "appendUserMessage"], + actions: "appendUserMessage", guard: ({ event }) => event.content.trim().length > 0, target: "sending", }, @@ -335,7 +303,7 @@ export const chatMachine = setup({ actions: "incrementQuotaExceeded", }, { - actions: ["logSendImageReceived", "appendUserImage"], + actions: "appendUserImage", target: "sending", }, ], @@ -349,11 +317,6 @@ export const chatMachine = setup({ }, }, sending: { - entry: ({ event }) => - console.log("[chat-machine] → guestSession.sending", { - contentLength: event.type === "ChatSendMessage" ? event.content.length : 0, - contentPreview: event.type === "ChatSendMessage" ? event.content.slice(0, 50) : "", - }), invoke: { src: "sendMessageHttp", input: ({ event }) => ({ @@ -361,28 +324,14 @@ export const chatMachine = setup({ }), onDone: { target: "ready", - actions: [ - assign(({ context, event }) => ({ - messages: [...context.messages, event.output.reply], - isReplyingAI: false, - })), - ({ context, event }) => - console.log("[chat-machine] guestSession.sending.onDone", { - newReplyContentLength: event.output.reply.content.length, - totalMessagesCount: context.messages.length + 1, - isReplyingAI: false, - }), - ], + actions: assign(({ context, event }) => ({ + messages: [...context.messages, event.output.reply], + isReplyingAI: false, + })), }, onError: { target: "ready", - actions: [ - assign({ isReplyingAI: false }), - ({ event }) => - console.error("[chat-machine] guestSession.sending.onError", { - error: event.error instanceof Error ? event.error.message : String(event.error), - }), - ], + actions: assign({ isReplyingAI: false }), }, }, }, @@ -416,24 +365,13 @@ export const chatMachine = setup({ // 任务 2:拉 history(**只**调**用** loadHistoryActor,**不**再**用** loadMoreHistoryActor) src: "loadHistory", onDone: { - actions: assign(({ event }) => { - console.log( - "[chat-machine] userSession.initializing.loadHistory.onDone", - { - finalCount: event.output.messages.length, - hasMore: event.output.hasMore, - newOffset: event.output.newOffset, - localOverwritten: event.output.localOverwritten, - }, - ); - return { - messages: event.output.messages, - isLoadingMore: false, - hasMore: event.output.hasMore, - historyOffset: event.output.newOffset, - historyLoaded: true, - }; - }), + actions: assign(({ event }) => ({ + messages: event.output.messages, + isLoadingMore: false, + hasMore: event.output.hasMore, + historyOffset: event.output.newOffset, + historyLoaded: true, + })), }, onError: { // 失败**也**标 loaded,**不**卡 init @@ -447,22 +385,14 @@ export const chatMachine = setup({ ready: { on: { ChatSendMessage: { - actions: ["logSendMessageReceived", "appendUserMessage"], + actions: "appendUserMessage", guard: ({ event }) => event.content.trim().length > 0, target: "sending", }, ChatSendImage: { - actions: ["logSendImageReceived", "appendUserImage"], + actions: "appendUserImage", }, ChatLoadMoreHistory: { - actions: [ - "logLoadMoreHistoryReceived", - ({ context }) => - console.log("[chat-machine] userSession.ready.ChatLoadMoreHistory received", { - currentOffset: context.historyOffset, - hasMore: context.hasMore, - }), - ], target: "loadingMore", }, ChatLogout: "#chat.idle", @@ -474,12 +404,6 @@ export const chatMachine = setup({ }, }, sending: { - entry: ({ event }) => - console.log("[chat-machine] → userSession.sending", { - contentLength: event.type === "ChatSendMessage" ? event.content.length : 0, - contentPreview: event.type === "ChatSendMessage" ? event.content.slice(0, 50) : "", - wsConnected: true, - }), invoke: { src: "sendMessageHttp", input: ({ event }) => ({ @@ -487,63 +411,33 @@ export const chatMachine = setup({ }), onDone: { target: "ready", - actions: [ - assign(({ context, event }) => ({ - messages: [...context.messages, event.output.reply], - isReplyingAI: false, - })), - ({ context, event }) => - console.log("[chat-machine] userSession.sending.onDone", { - newReplyContentLength: event.output.reply.content.length, - totalMessagesCount: context.messages.length + 1, - isReplyingAI: false, - }), - ], + actions: assign(({ context, event }) => ({ + messages: [...context.messages, event.output.reply], + isReplyingAI: false, + })), }, onError: { target: "ready", - actions: [ - assign({ isReplyingAI: false }), - ({ event }) => - console.error("[chat-machine] userSession.sending.onError", { - error: event.error instanceof Error ? event.error.message : String(event.error), - }), - ], + actions: assign({ isReplyingAI: false }), }, }, }, loadingMore: { - entry: ({ context }) => - console.log("[chat-machine] → userSession.loadingMore", { offset: context.historyOffset }), invoke: { src: "loadMoreHistory", input: ({ context }) => ({ offset: context.historyOffset }), onDone: { target: "ready", - actions: [ - assign(({ context, event }) => ({ - messages: [...event.output.messages, ...context.messages], - isLoadingMore: false, - hasMore: event.output.hasMore, - historyOffset: event.output.newOffset, - })), - ({ event }) => - console.log("[chat-machine] userSession.loadingMore.onDone", { - newMessagesCount: event.output.messages.length, - newOffset: event.output.newOffset, - hasMore: event.output.hasMore, - }), - ], + actions: assign(({ context, event }) => ({ + messages: [...event.output.messages, ...context.messages], + isLoadingMore: false, + hasMore: event.output.hasMore, + historyOffset: event.output.newOffset, + })), }, onError: { target: "ready", - actions: [ - assign({ isLoadingMore: false }), - ({ event }) => - console.error("[chat-machine] userSession.loadingMore.onError", { - error: event.error instanceof Error ? event.error.message : String(event.error), - }), - ], + actions: assign({ isLoadingMore: false }), }, }, }, diff --git a/src/stores/chat/chat-state.ts b/src/stores/chat/chat-state.ts index 090fd5d5..bc81015b 100644 --- a/src/stores/chat/chat-state.ts +++ b/src/stores/chat/chat-state.ts @@ -29,8 +29,6 @@ export interface ChatState { */ wsConnected: boolean; /** 游客剩余配额(次/天)—— **仅**游客业务展示用 - * - default 0 = "未初始化" / "已耗尽"(同码,靠 `isGuest` 区分) - * - 非游客:永不被赋值(业务事实"无消息限制") */ guestRemainingQuota: number; /** 游客总配额(仅游客展示用) */