feat(admin): 管理端接登录 + Bearer 鉴权 + 修雪花ID/探活(适配硬化后的网关)

/admin 加 RequireAdmin 后管理端原来无鉴权会 401。本次打通:
- api.ts:JWT token 存 localStorage + 所有 /admin 调用带 Bearer + 401 清令牌广播登出;
  login/me;gatewayOnline 改用公开 /healthz(原 /billing 已转受保护会误判离线)。
- 修类型:Model.id number → string(模型 id 早已迁雪花字符串)。
- Login 登录门 + App 鉴权门(启动校验 me,无则登录页)+ AppShell 显示用户/登出。

实测(硬化网关):无 token /admin/models → 401;登录拿 token → 200 返回模型(string id)。
dev 未配 ADMIN_USER_IDS 时任意登录账号放行;生产须在白名单。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-06-19 11:09:02 +08:00
parent 8f619c2a62
commit 030dcda9b4
4 changed files with 185 additions and 28 deletions
+29 -1
View File
@@ -1,11 +1,39 @@
import { useEffect, useState } from "react";
import { HashRouter } from "react-router-dom";
import { AppShell } from "./shell/AppShell";
import { Login } from "./Login";
import { me, clearToken, type AuthUser } from "./api";
// 用 HashRouter:纯静态托管/桌面内嵌都能深链,无需服务端路由配置。
export default function App() {
const [user, setUser] = useState<AuthUser | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
me()
.then(setUser)
.catch(() => setUser(null))
.finally(() => setLoading(false));
const onLogout = () => setUser(null);
window.addEventListener("sdx:logout", onLogout);
return () => window.removeEventListener("sdx:logout", onLogout);
}, []);
if (loading) {
return <div className="flex h-screen w-screen items-center justify-center text-sm text-gray-400"></div>;
}
if (!user) {
return <Login onAuthed={setUser} />;
}
return (
<HashRouter>
<AppShell />
<AppShell
user={user}
onLogout={() => {
clearToken();
setUser(null);
}}
/>
</HashRouter>
);
}