解耦账单与充值请求生命周期

为 AI Game Creator 账单读取使用独立 lifecycle token,并在账号切换时失效旧请求。

补充充值弹窗变化期间账单仍可正常收尾的回归测试。
This commit is contained in:
2026-08-05 17:52:05 +08:00
parent f8e308be15
commit bf25b85c7e
2 changed files with 47 additions and 4 deletions
@@ -43,7 +43,10 @@ export function useAccountWallet(currentUserId: string) {
const [nativeRechargePayment, setNativeRechargePayment] =
useState<PlatformProfileRechargeNativePaymentState | null>(null);
const rechargeLifecycleRef = useRef(0);
const walletLedgerLifecycleRef = useRef(0);
const [walletUiOwnerUserId, setWalletUiOwnerUserId] = useState(currentUserId);
const currentUserIdRef = useRef(currentUserId);
currentUserIdRef.current = currentUserId;
const walletOwnerMatchesCurrentUser =
Boolean(currentUserId) && ownerUserId === currentUserId;
const walletUiOwnerMatchesCurrentUser =
@@ -79,6 +82,7 @@ export function useAccountWallet(currentUserId: string) {
useEffect(() => {
rechargeLifecycleRef.current += 1;
walletLedgerLifecycleRef.current += 1;
setWalletUiOwnerUserId(currentUserId);
setWalletLedgerOpen(false);
setWalletLedger(null);
@@ -93,16 +97,23 @@ export function useAccountWallet(currentUserId: string) {
}, [currentUserId]);
async function loadWalletLedger() {
const rechargeLifecycle = rechargeLifecycleRef.current;
const walletLedgerLifecycle = ++walletLedgerLifecycleRef.current;
const snapshotOwnerUserId = currentUserId;
setWalletLedgerLoading(true);
setWalletLedgerError(null);
try {
const ledger = await getClientProfileWalletLedger();
if (rechargeLifecycleRef.current === rechargeLifecycle) {
if (
walletLedgerLifecycleRef.current === walletLedgerLifecycle &&
currentUserIdRef.current === snapshotOwnerUserId
) {
setWalletLedger(ledger);
}
} catch (error) {
if (rechargeLifecycleRef.current !== rechargeLifecycle) {
if (
walletLedgerLifecycleRef.current !== walletLedgerLifecycle ||
currentUserIdRef.current !== snapshotOwnerUserId
) {
return;
}
setWalletLedger(null);
@@ -110,7 +121,10 @@ export function useAccountWallet(currentUserId: string) {
error instanceof Error ? error.message : '读取泥点账单失败',
);
} finally {
if (rechargeLifecycleRef.current === rechargeLifecycle) {
if (
walletLedgerLifecycleRef.current === walletLedgerLifecycle &&
currentUserIdRef.current === snapshotOwnerUserId
) {
setWalletLedgerLoading(false);
}
}
@@ -246,6 +246,35 @@ describe('useWalletStore', () => {
});
});
test('keeps ledger loading independent from the recharge lifecycle', async () => {
let resolveLedger!: (value: { entries: [] }) => void;
clientApi.getClientProfileRechargeCenter.mockResolvedValue({
walletBalance: 18,
mudPointBalance: detailedBalance(18),
});
clientApi.getClientProfileWalletLedger.mockImplementation(
() =>
new Promise((resolve) => {
resolveLedger = resolve;
}),
);
const { result } = renderHook(() => useAccountWallet('user-a'));
await act(async () => undefined);
act(() => {
result.current.openWalletLedger();
result.current.openRecharge();
});
expect(result.current.walletLedgerLoading).toBe(true);
await act(async () => {
resolveLedger({ entries: [] });
});
expect(result.current.walletLedger).toEqual({ entries: [] });
expect(result.current.walletLedgerLoading).toBe(false);
});
test('hides account-owned dialogs and data when the account changes', async () => {
clientApi.getClientProfileRechargeCenter.mockResolvedValue({
walletBalance: 18,