feat: 完整游戏运维功能

This commit is contained in:
npc0-hue
2026-07-18 09:04:01 +08:00
parent f3b14b7945
commit 48b8ad8d6c
187 changed files with 16607 additions and 1140 deletions
+15
View File
@@ -25,9 +25,24 @@ describe("session store helpers", () => {
it("returns null without a stored API session token", async () => {
stubWindowStorage(new Map());
vi.stubGlobal("fetch", vi.fn(async () => jsonResponse({ code: "unauthorized" }, 401)));
await expect(loadCurrentUser()).resolves.toBeNull();
});
it("restores an HttpOnly cookie session without a script-readable token", async () => {
const storage = new Map<string, string>();
stubWindowStorage(storage);
const fetchMock = vi.fn(async (_input: RequestInfo | URL, init?: RequestInit) => {
expect(new Headers(init?.headers).has("Authorization")).toBe(false);
expect(init?.credentials).toBe("same-origin");
return jsonResponse({ id: "user-admin", displayName: "Operator", status: "active", roles: ["platform-admin"] });
});
vi.stubGlobal("fetch", fetchMock);
await expect(loadCurrentUser()).resolves.toMatchObject({ id: "user-admin", source: "api" });
expect(storage.size).toBe(0);
});
it("loads current user with a stored API bearer token", async () => {
const storage = new Map([["platform-web.session.apiToken", "session-token"]]);
stubWindowStorage(storage);
+11 -4
View File
@@ -1,6 +1,6 @@
import { useEffect, useState } from "react";
import { platformApiClient, setPlatformApiSessionToken } from "../api/client";
import { platformApiClient, setPlatformApiAuthFailureHandler, setPlatformApiSessionToken } from "../api/client";
import type {
AuthSessionResponse,
CurrentUserResponse,
@@ -59,9 +59,6 @@ export interface SessionState {
export async function loadCurrentUser(): Promise<CurrentUserView | null> {
const storedToken = readStoredSessionToken();
setPlatformApiSessionToken(storedToken);
if (!storedToken) {
return null;
}
try {
const response = await platformApiClient.getCurrentUser();
return currentUserFromResponse(response, "api");
@@ -93,6 +90,16 @@ export function useSession(): SessionState {
};
}, []);
useEffect(() => {
setPlatformApiAuthFailureHandler(() => {
persistSessionToken(null);
setUser(undefined);
setAuthUnavailable(false);
setAuth({ mode: "login", pending: false, error: "会话已失效,请重新登录。" });
});
return () => setPlatformApiAuthFailureHandler(null);
}, []);
async function login(request: LoginRequest) {
setAuth({ mode: "login", pending: true });
try {