docs: 设计NapCat WebUI Gateway

This commit is contained in:
sunlei 2026-06-24 10:05:58 +08:00
parent 98c8568184
commit cdded5b754
2 changed files with 707 additions and 0 deletions

View File

@ -0,0 +1,386 @@
# QQBot NapCat WebUI Gateway Design
## Background
QQBot already manages NapCat containers, WebUI tokens, login sessions, runtime
profile evidence, and split account status inside the API. Admin can observe and
drive the curated login flow, but it cannot open the original NapCat WebUI for a
specific account without manually exposing container ports and tokens.
The new requirement is to open the original NapCat WebUI from Admin for one
QQBot account, with full WebUI operation capability, while keeping NapCat
container addresses, WebUI tokens, credentials, and host topology server-side.
## Goals
- Add a QQBot account-row operation that opens the selected account's NapCat
WebUI in a second-level Admin route page.
- Implement NapCat WebUI access through an independent
`kt-napcat-webui-gateway` service, not through direct browser access to
NapCat container ports.
- Bind gateway access sessions to the Admin route page lifecycle.
- Allow full NapCat WebUI operations inside the embedded page.
- Keep NapCat WebUI tokens, NapCat credentials, container IPs, host ports, and
NAS topology out of browser-visible payloads.
- Record auditable evidence for session creation, activation, heartbeat,
revocation, expiration, and proxy failures.
## Non-goals
- Do not reimplement the NapCat WebUI as native Admin components.
- Do not make the first version read-only.
- Do not expose NapCat container ports to the public network.
- Do not let the browser call arbitrary upstream URLs through the gateway.
- Do not merge this gateway into the existing login SSE state machine.
## User Experience
Admin adds a new WebUI action on the QQBot account list connection/action area.
Clicking it navigates to:
```text
/qqbot/account/:accountId/napcat-webui
```
The route page is a remote console, not a drawer. It has:
- a compact Admin-owned header with back navigation, account identity, container
status, session status, refresh session, and close session actions;
- one full-height iframe that loads the gateway URL;
- a failure state for unauthorized, missing container, WebUI offline, session
expired, or gateway unavailable;
- no page-level body scrolling beyond the existing Vben layout content area.
The route page owns the gateway session. Opening the same WebUI from the account
list always creates a new route-bound session. Page refresh creates a new
session and lets the previous session expire or be revoked by heartbeat timeout.
## High-level Architecture
```text
Admin account list
-> Admin route /qqbot/account/:accountId/napcat-webui
-> API: create route-bound gateway session
-> Gateway: store active session and NapCat target metadata
-> Admin iframe: gateway bootstrap URL
-> Gateway: proxy NapCat WebUI HTML, assets, APIs, and WebSocket traffic
-> NapCat WebUI container
```
The API remains the authority for Admin authentication and QQBot account
binding. The Gateway remains the authority for active WebUI proxy sessions and
NapCat WebUI credential exchange.
## Service Boundaries
### API service
The API service owns Admin-facing authorization and account resolution:
- verify the Admin JWT and QQBot account permissions;
- resolve account, selfId, primary NapCat binding, container id/name, WebUI
base URL, and WebUI token from existing persistence;
- reject missing, offline, or non-owned NapCat targets before a gateway session
is created;
- call the Gateway internal API with a service-to-service secret;
- return only an opaque `sessionId`, `iframeUrl`, and safe display metadata to
Admin;
- proxy heartbeat/revoke calls from Admin to the Gateway internal API;
- write or forward audit events.
The API must not return `webuiToken`, NapCat credential, upstream host, upstream
port, Docker container IP, or NAS SSH details.
### Gateway service
`kt-napcat-webui-gateway` is deployed as a separate process, image, and K8s
Deployment. It owns:
- active session storage and TTL enforcement;
- one-time iframe bootstrap tickets;
- NapCat WebUI credential login using the existing `sha256(token + ".napcat")`
contract;
- proxying HTTP methods, static assets, WebUI API calls, redirects, cookies, and
WebSocket upgrade traffic;
- session heartbeat, revocation, expiration, and cleanup;
- gateway-side audit events and structured logs.
The Gateway only proxies to the target already stored in the session. It never
accepts a browser-supplied upstream URL.
### Admin frontend
Admin owns the route UI and lifecycle:
- navigate from account list action to the second-level route page;
- create a session when the page mounts;
- load the returned iframe URL only after session creation succeeds;
- send heartbeat while the route is mounted;
- revoke the session on route leave, explicit close, component unmount, and
best-effort browser unload;
- render expired/revoked/error states without blank iframes.
## Gateway Session Lifecycle
Sessions are route-bound leases:
```text
created -> active -> revoked
created -> expired
active -> expired
created/active -> failed
```
Creation flow:
1. Admin route mounts and calls `POST /qqbot/napcat/webui/session`.
2. API authorizes the current Admin user and resolves the selected account's
active NapCat target.
3. API calls Gateway internal `POST /internal/sessions` with safe account
metadata plus encrypted or service-internal target credentials.
4. Gateway creates an active-session record with TTL and a one-time bootstrap
ticket.
5. API returns the public iframe bootstrap URL.
6. The iframe loads the bootstrap URL. Gateway redeems the one-time ticket,
sets an HttpOnly path-scoped gateway cookie, and redirects to the session
WebUI root.
7. Gateway marks the session active after the first successful proxied WebUI
response.
Heartbeat and cleanup:
- Admin sends heartbeat every 15 to 30 seconds while the route is mounted.
- Gateway updates `lastSeenAt`.
- If no heartbeat arrives for 60 to 90 seconds, Gateway expires the session.
- Route leave, explicit close, and component unmount call revoke.
- `beforeunload` uses a best-effort sendBeacon revoke, but correctness relies on
TTL timeout because browser unload is not guaranteed.
Concurrent access policy:
- One active WebUI session is allowed per Admin user plus account.
- Creating a new session for the same Admin user and account revokes the older
session.
- Other Admin users require their own authorization and separate sessions.
Expired or revoked sessions return HTTP 410 for iframe/proxy requests and a
small gateway error page that tells the Admin page to reopen the route session.
## Public and Internal Contracts
### API Admin contracts
```text
POST /qqbot/napcat/webui/session
Body: { accountId: string }
Result: {
sessionId: string;
iframeUrl: string;
expiresAt: number;
account: { id: string; selfId: string; nickname?: string };
container: { id: string; name: string; webuiStatus: string };
}
POST /qqbot/napcat/webui/session/:sessionId/heartbeat
Result: { sessionId: string; expiresAt: number; status: "active" }
POST /qqbot/napcat/webui/session/:sessionId/revoke
Result: true
```
The Admin API responses contain only display-safe metadata.
### Gateway public contracts
```text
GET /napcat-webui/session/:sessionId/bootstrap?ticket=...
GET /napcat-webui/session/:sessionId/webui/*
POST /napcat-webui/session/:sessionId/webui/*
WS /napcat-webui/session/:sessionId/webui/*
```
The bootstrap ticket is short-lived and one-time. After redemption, the browser
uses only a path-scoped gateway session cookie. The iframe URL should not keep a
long-lived bearer token in the address bar.
### Gateway internal contracts
```text
POST /internal/sessions
POST /internal/sessions/:sessionId/heartbeat
POST /internal/sessions/:sessionId/revoke
GET /internal/health
```
Internal endpoints require a service-to-service secret or mTLS-equivalent
cluster boundary. They are not exposed through the public Caddy/Admin route.
## Proxy Behavior
The Gateway must support:
- all HTTP methods used by the NapCat WebUI;
- JSON and binary payloads;
- static assets;
- redirects with `Location` rewritten back under the gateway session prefix;
- cookies rewritten to a session-scoped path and never exposed across accounts;
- WebSocket upgrade traffic if NapCat WebUI uses it;
- `Cache-Control: no-store` for sensitive proxied responses;
- removal or replacement of upstream frame headers that would block Admin's
iframe, while preserving safe CSP around the gateway route.
If NapCat WebUI emits absolute `/api` or `/webui` paths, Gateway rewrites the
HTML and response headers so browser requests remain under:
```text
/napcat-webui/session/:sessionId/webui/
```
The proxy must reject path traversal, encoded upstream URL injection, and any
request that escapes the bound session prefix.
## Security Model
Full NapCat WebUI operations are allowed. Authorization is therefore explicit:
an Admin user who can open the route for an account can fully operate that
account's NapCat WebUI.
Security controls:
- Admin JWT is checked by API before session creation.
- Gateway session is opaque, short-lived, and bound to Admin user id, account id,
selfId, container id, client metadata, and route lease.
- NapCat WebUI token and credential never leave server-side services.
- Browser-visible URLs contain at most a one-time bootstrap ticket.
- Gateway cookies are HttpOnly, Secure, SameSite, and path-scoped to the
session.
- Public gateway requests cannot select upstream hosts or ports.
- Internal API-to-Gateway calls are authenticated.
- Audit records include user id, account id, selfId, container id, session id,
event type, client IP, user agent, timestamps, and safe error summaries.
## Data Model
Active runtime session state lives in Redis so Gateway replicas can share it:
```text
napcat:webui:session:{sessionId}
status
adminUserId
accountId
selfId
containerId
containerName
upstreamBaseUrl
encryptedWebuiToken or encrypted credential material
createdAt
activeAt
lastSeenAt
expiresAt
revokedAt
```
Gateway audit history is persisted in MySQL, either through the API service or a
Gateway-owned table managed from the API repository:
```text
qqbot_napcat_webui_gateway_audit
id
session_id
admin_user_id
account_id
self_id
container_id
event_type
client_ip
user_agent
detail_json
create_time
```
No WebUI token, NapCat credential, QQ password, captcha ticket, QR payload, or
raw proxied response is stored in audit rows.
## Deployment
The first implementation keeps the service source in the API repository but
builds and deploys it as a separate service:
- independent app entry for `kt-napcat-webui-gateway`;
- independent Dockerfile or Docker target;
- Jenkins build and push for the Gateway image;
- K8s Deployment and Service;
- Caddy/Admin domain route such as:
```text
https://admin.kwitsukasa.top/napcat-webui/*
```
The route points to the Gateway service, while the existing API remains under
`/api/*`. Local development should provide an equivalent Vite or Caddy proxy so
Admin can test the iframe route without exposing NapCat container ports.
## Error Handling
- Missing account or no active NapCat binding: Admin route shows a clear empty
state with a link back to the account list.
- Container offline or WebUI unavailable: session creation fails before iframe
load and shows the runtime evidence returned by API.
- Gateway cannot authenticate to NapCat WebUI: session fails with a sanitized
error and an audit event.
- Session expired or revoked: iframe receives HTTP 410 and Admin page offers a
one-click session reopen.
- Gateway target proxy timeout: render an error overlay and keep route controls
available.
- API and Gateway clocks must not be used as the only correctness boundary;
Redis TTL and heartbeat timestamps drive cleanup.
## Testing Strategy
Backend/API:
- unit test account authorization and session creation DTOs;
- unit test no secret fields are returned to Admin;
- integration test API creates and revokes Gateway sessions through a mocked
internal Gateway client;
- audit tests for create, active, heartbeat, revoke, expire, and proxy failure.
Gateway:
- unit test bootstrap ticket redemption and one-time use;
- unit test session TTL, heartbeat, revoke, and concurrent session revocation;
- proxy tests for path rewriting, cookie scoping, redirect rewriting, and
forbidden upstream URL injection;
- WebSocket upgrade smoke if NapCat WebUI requires it.
Admin:
- route test for account-row WebUI action navigation;
- lifecycle test for mount create, heartbeat, unmount revoke;
- dark-theme and layout tests for the remote console page;
- Playwright smoke that opens the route, waits for iframe shell load, leaves the
route, and verifies revoke was called.
Online:
- deploy API and Gateway through Jenkins/K8s;
- verify Gateway health and route availability under the Admin domain;
- open the WebUI page for account `1914728559`;
- confirm the iframe loads the original NapCat WebUI without exposing tokens or
container ports in the browser URL;
- perform one safe WebUI operation such as reading login status or opening a
settings page;
- leave the route and verify the session is revoked or expires by heartbeat
timeout.
## Completion Criteria
- Admin has a working second-level NapCat WebUI route per QQBot account.
- The Gateway is independently deployed and observable.
- Full NapCat WebUI operation works through the iframe route.
- Browser-visible data contains no NapCat token, credential, container IP, host
port, NAS SSH route, or QQ password.
- Session lifecycle follows the Admin route lifecycle and cleans up on route
leave or heartbeat timeout.
- API, Gateway, Admin tests and online smoke evidence are recorded before the
feature is considered complete.

View File

@ -0,0 +1,321 @@
# QQBot NapCat WebUI Gateway 设计
## 背景
QQBot 当前已经由 API 管理 NapCat 容器、WebUI token、登录 session、运行态证据和账号拆分状态。Admin 可以使用定制过的登录弹窗完成扫码、验证码和新设备流程,但还不能从某个 QQBot 账号定点打开原版 NapCat WebUI。
本次目标是在 Admin 中打开指定账号对应的原版 NapCat WebUI并允许完整操作同时不把 NapCat 容器端口、WebUI token、Credential 或 NAS 拓扑暴露给浏览器。
## 目标
- 在 QQBot 账号列表的账号连接列或操作区增加 WebUI 入口。
- 点击入口后进入二级路由页面,而不是抽屉。
- 使用独立 `kt-napcat-webui-gateway` 微服务承载 NapCat WebUI 代理。
- Gateway session 跟随二级页面生命周期创建、心跳和销毁。
- iframe 内允许 NapCat WebUI 完整操作。
- 浏览器侧不暴露 NapCat token、Credential、容器 IP、宿主端口或 NAS 内网信息。
- 记录 session 创建、激活、心跳、撤销、过期和代理失败审计证据。
## 非目标
- 不把 NapCat WebUI 重写成 Admin 原生组件。
- 第一版不做只读限制。
- 不直接公开 NapCat 容器端口。
- 不允许浏览器通过 Gateway 任意代理外部 URL。
- 不把该 Gateway 合并到现有 NapCat 登录 SSE 状态机。
## 页面形态
Admin 新增二级路由:
```text
/qqbot/account/:accountId/napcat-webui
```
QQBot 账号列表的 WebUI 按钮跳转到该路由。页面是远程控制台,而不是抽屉。页面结构:
- 顶部是 Admin 自己的轻量控制栏返回账号列表、账号信息、容器状态、session 状态、刷新 session、关闭 session。
- 主体是全高 iframe加载 Gateway 返回的 NapCat WebUI URL。
- 未授权、无容器、WebUI 离线、session 过期、Gateway 不可用时显示明确错误态。
- 页面高度遵循 Vben Layout 内容区,不额外制造 body 滚动。
刷新页面会创建新 session旧 session 通过主动 revoke 或 heartbeat 超时清理。重复从账号列表打开同一账号时创建新 session不复用旧 session。
## 总体架构
```text
Admin 账号列表
-> Admin 二级路由 /qqbot/account/:accountId/napcat-webui
-> API 创建 route-bound Gateway session
-> Gateway 保存活跃 session 与 NapCat 目标信息
-> Admin iframe 打开 Gateway bootstrap URL
-> Gateway 反代 NapCat WebUI HTML、静态资源、API 和 WebSocket
-> NapCat WebUI 容器
```
API 是 Admin 鉴权和 QQBot 账号绑定的权威。Gateway 是 WebUI 代理 session 和 NapCat WebUI Credential 交换的权威。
## 服务边界
### API 服务
API 负责 Admin 侧鉴权和账号解析:
- 校验 Admin JWT 与 QQBot 账号权限。
- 根据账号解析 selfId、主 NapCat 绑定、容器 ID/名称、WebUI base URL 和 WebUI token。
- 在创建 Gateway session 前拒绝无容器、容器离线、WebUI 不可用或无权限的请求。
- 使用服务间密钥调用 Gateway 内部接口。
- 只把 `sessionId`、`iframeUrl` 和安全展示字段返回给 Admin。
- 代理 Admin 发来的 heartbeat/revoke 到 Gateway 内部接口。
- 写入或转发审计事件。
API 不返回 `webuiToken`、NapCat Credential、容器 IP、宿主端口、Docker 网络或 NAS SSH 信息。
### Gateway 服务
`kt-napcat-webui-gateway` 独立进程、独立镜像、独立 K8s Deployment。它负责
- 活跃 session 存储与 TTL 管理。
- 一次性 iframe bootstrap ticket。
- 按 NapCat 现有契约使用 `sha256(token + ".napcat")` 登录 WebUI 并换取 Credential。
- 反代 HTTP 方法、静态资源、WebUI API、重定向、Cookie 和 WebSocket upgrade。
- session heartbeat、revoke、expire 和清理。
- Gateway 侧审计和结构化日志。
Gateway 只代理 session 内已绑定的目标容器,不接受浏览器传入的 upstream URL。
### Admin 前端
Admin 负责路由页面和生命周期:
- 账号列表 WebUI 按钮跳转到二级路由。
- 页面 mounted 时创建 session。
- session 创建成功后再加载 iframe。
- 页面 mounted 期间持续 heartbeat。
- 路由离开、显式关闭、组件 unmount、浏览器卸载时尽量 revoke。
- session 过期或失败时展示可读错误态,避免 iframe 白屏。
## Gateway Session 生命周期
session 是页面生命周期租约:
```text
created -> active -> revoked
created -> expired
active -> expired
created/active -> failed
```
创建流程:
1. Admin 二级页面 mounted调用 `POST /qqbot/napcat/webui/session`
2. API 校验当前 Admin 用户并解析目标账号的 NapCat 容器。
3. API 调用 Gateway 内部 `POST /internal/sessions`,传入安全账号元数据和服务端目标凭据。
4. Gateway 创建带 TTL 的 session并生成一次性 bootstrap ticket。
5. API 返回公开 iframe bootstrap URL。
6. iframe 打开 bootstrap URLGateway 兑换一次性 ticket设置 HttpOnly、路径隔离的 gateway cookie并跳转到 session WebUI 根路径。
7. Gateway 在第一次成功代理 WebUI 响应后将 session 标记为 active。
心跳和清理:
- Admin 页面 mounted 时每 15 到 30 秒发送 heartbeat。
- Gateway 更新 `lastSeenAt`
- 60 到 90 秒无 heartbeat 时 Gateway 自动 expire。
- 路由离开、显式关闭、组件 unmount 时主动 revoke。
- `beforeunload` 使用 sendBeacon 尽量 revoke正确性仍依赖 TTL因为浏览器卸载不保证执行。
并发策略:
- 同一个 Admin 用户对同一个 QQBot 账号只允许一个活跃 WebUI session。
- 创建新 session 时撤销旧 session。
- 不同 Admin 用户需要各自鉴权并创建独立 session。
revoked 或 expired 后Gateway 对 iframe/proxy 请求返回 410并展示“会话已关闭请重新打开”的轻量错误页。
## 接口契约
### API Admin 接口
```text
POST /qqbot/napcat/webui/session
Body: { accountId: string }
Result: {
sessionId: string;
iframeUrl: string;
expiresAt: number;
account: { id: string; selfId: string; nickname?: string };
container: { id: string; name: string; webuiStatus: string };
}
POST /qqbot/napcat/webui/session/:sessionId/heartbeat
Result: { sessionId: string; expiresAt: number; status: "active" }
POST /qqbot/napcat/webui/session/:sessionId/revoke
Result: true
```
这些接口只返回展示安全字段。
### Gateway 公开接口
```text
GET /napcat-webui/session/:sessionId/bootstrap?ticket=...
GET /napcat-webui/session/:sessionId/webui/*
POST /napcat-webui/session/:sessionId/webui/*
WS /napcat-webui/session/:sessionId/webui/*
```
bootstrap ticket 短期有效且只能使用一次。ticket 兑换后,浏览器只依赖路径隔离的 gateway cookie不在地址栏长期保留 bearer token。
### Gateway 内部接口
```text
POST /internal/sessions
POST /internal/sessions/:sessionId/heartbeat
POST /internal/sessions/:sessionId/revoke
GET /internal/health
```
内部接口需要服务间密钥或等价的集群内边界,不通过公开 Admin 域暴露。
## 代理行为
Gateway 必须支持:
- NapCat WebUI 使用的全部 HTTP 方法。
- JSON 和二进制 payload。
- 静态资源。
- `Location` 重写到 Gateway session 前缀下。
- Cookie path 改写到当前 session避免跨账号串用。
- WebSocket upgrade。
- 敏感响应使用 `Cache-Control: no-store`
- 处理或替换会阻止 Admin iframe 的 upstream frame header同时保留 Gateway 自身安全 CSP。
如果 NapCat WebUI 输出绝对 `/api``/webui` 路径Gateway 需要重写 HTML 和响应头,让浏览器请求始终留在:
```text
/napcat-webui/session/:sessionId/webui/
```
Gateway 必须拒绝路径穿越、编码 URL 注入和任何逃逸当前 session 前缀的请求。
## 安全模型
iframe 内允许 NapCat WebUI 完整操作。因此权限语义必须直接明确:能进入该账号 NapCat WebUI 二级页面的 Admin 用户,就拥有该账号 NapCat WebUI 的完整操作权。
安全控制:
- API 创建 session 前校验 Admin JWT。
- Gateway session 绑定 Admin 用户 ID、账号 ID、selfId、容器 ID、客户端信息和页面租约。
- NapCat WebUI token 与 Credential 只存在服务端。
- 浏览器可见 URL 最多包含一次性 bootstrap ticket。
- Gateway cookie 使用 HttpOnly、Secure、SameSite并按 session path 隔离。
- 公开 Gateway 请求不能选择 upstream host 或 port。
- API 到 Gateway 的内部调用必须鉴权。
- 审计记录用户、账号、selfId、容器、session、事件类型、IP、UA、时间和安全错误摘要。
## 数据模型
活跃 session 使用 Redis 保存,便于 Gateway 多副本共享:
```text
napcat:webui:session:{sessionId}
status
adminUserId
accountId
selfId
containerId
containerName
upstreamBaseUrl
encryptedWebuiToken or encrypted credential material
createdAt
activeAt
lastSeenAt
expiresAt
revokedAt
```
审计历史持久化到 MySQL可由 API 写入,也可以由 Gateway 通过 API 仓库管理的表写入:
```text
qqbot_napcat_webui_gateway_audit
id
session_id
admin_user_id
account_id
self_id
container_id
event_type
client_ip
user_agent
detail_json
create_time
```
审计表不保存 WebUI token、NapCat Credential、QQ 密码、验证码 ticket、二维码内容或原始代理响应。
## 部署
第一版源码放在 API 仓库,但作为独立服务构建和部署:
- 独立 `kt-napcat-webui-gateway` app entry。
- 独立 Dockerfile 或 Docker build target。
- Jenkins 构建并推送 Gateway 镜像。
- K8s 独立 Deployment 和 Service。
- Caddy/Admin 域增加路由,例如:
```text
https://admin.kwitsukasa.top/napcat-webui/*
```
该路由指向 Gateway 服务,现有 API 继续位于 `/api/*`。本地开发需要等价 Vite 或 Caddy proxy避免为了 iframe 调试而暴露 NapCat 容器端口。
## 错误处理
- 账号不存在或无有效 NapCat 绑定:页面显示空状态并提供返回账号列表入口。
- 容器离线或 WebUI 不可用session 创建前失败,并展示 API 返回的运行态证据。
- Gateway 无法登录 NapCat WebUIsession failed展示脱敏错误并写审计。
- session 过期或撤销iframe 收到 410Admin 页面提供重新打开按钮。
- Gateway 代理目标超时:显示错误覆盖层,保留页面顶部控制栏。
- API/Gateway 不只依赖本机时钟判断正确性,清理以 Redis TTL 和 heartbeat 时间戳为准。
## 验证策略
API
- 单测账号权限和 session 创建 DTO。
- 单测 Admin 响应不包含敏感字段。
- 集成测试 API 通过 mock Gateway client 创建、心跳和撤销 session。
- 审计测试覆盖 create、active、heartbeat、revoke、expire、proxy failure。
Gateway
- 单测 bootstrap ticket 兑换和一次性使用。
- 单测 session TTL、heartbeat、revoke 和同用户同账号并发 session 撤销。
- 代理测试覆盖路径重写、Cookie 隔离、重定向重写、禁止 upstream URL 注入。
- 如果 NapCat WebUI 使用 WebSocket则增加 WebSocket upgrade smoke。
Admin
- 账号行 WebUI 按钮路由跳转测试。
- 页面生命周期测试mounted 创建 session、heartbeat、unmount revoke。
- 暗色主题和 Layout 高度测试。
- Playwright smoke打开二级路由、等待 iframe shell 加载、离开路由、确认 revoke 被调用。
线上:
- Jenkins/K8s 部署 API 和 Gateway。
- 验证 Gateway health 与 Admin 域 `/napcat-webui/*` 路由。
- 打开账号 `1914728559` 的 WebUI 页面。
- 确认 iframe 加载原版 NapCat WebUI浏览器 URL 不暴露 token、Credential、容器端口。
- 执行一个安全 WebUI 操作,例如读取登录状态或打开设置页。
- 离开路由后确认 session revoke 或 heartbeat 超时回收。
## 完成标准
- Admin 支持按 QQBot 账号打开二级 NapCat WebUI 页面。
- Gateway 独立部署且可观测。
- iframe 中完整 NapCat WebUI 操作可用。
- 浏览器侧不暴露 NapCat token、Credential、容器 IP、宿主端口、NAS SSH 路由或 QQ 密码。
- session 生命周期跟随 Admin 二级页面,路由离开或 heartbeat 超时后会清理。
- API、Gateway、Admin 测试和线上 smoke 证据齐全后才算完成。