# LinkChat Web 管理后台

本模块属于 LinkChat 主仓库。只提供 Web 管理界面，不需要定制 CLI。

生产地址：<https://chat.linkchat.online/admin/>，也支持 <https://linkchat.online/admin/>。使用已有管理员账号登录；普通用户不能访问管理接口。

## 功能

- 运行概况：用户、在线人数、频道、消息、有效 API Key、封禁用户、进行中游戏。
- 用户：搜索、封禁/解封、ADMIN / MEMBER 角色调整。禁止修改自己的角色或封禁自己；其他管理员必须先降为普通成员才能封禁。
- 频道：搜索、改名、查看成员、添加/移除成员、删除及关联清理。
- 消息：全站搜索、详情、单条或勾选批量撤回。保留引用关系并写入实时事件，清理提醒与表情、取消待发邮件；删除没有其他消息引用的服务端附件文件。
- API Key：查看归属、前缀、状态、到期时间并撤销；不展示凭据明文或认证哈希。
- Spire：查看房间、玩家、阶段、层数；结束游戏、删除已结束存档。账号积分和角色胜场只读，删除存档保留这些进度。
- 操作记录：记录管理操作、操作人、目标、参数及时间，与成功写入同事务提交。

角色变更注销目标用户会话；封禁同时撤销其 API Key，解封不恢复已撤销凭据。服务端仍必须使用支持封禁校验的本仓库后端，不能退回不识别 `BANNED` 角色的旧后端。

## 界面与筛选

界面延续 AstroPaper 的等宽字体、纸白/灰蓝主题、蓝/橙强调色和波浪导航。顶部提供概况、用户、Chat、Forum、API Key、Spire、操作记录七个入口；Chat 包含频道和消息，Forum 包含版块、帖子、回复和举报，API Key 和 Spire 也使用二级标签。列表固定展示业务字段，详情在右侧抽屉打开，手机使用折叠菜单和全屏详情。

搜索、筛选、分页保存在 URL hash，例如 `/admin/#/messages?channel_id=1&status=normal`，支持刷新和浏览器前进后退恢复。默认每页 30 条，可选 50 / 100 条。批量撤回与永久删除仅针对本页，刷新、翻页或改变条件会清空勾选。消息、帖子、回复支持批量永久删除；每批最多 100 条，整批校验、同事务提交。

频道成员通过昵称、账号或 ID 搜索后选择添加；直接在成员行移除。删除频道需要输入完整频道名称确认。用户角色使用下拉选择，并明确展示操作影响。

列表接口保持 `rows / total / offset / limit` 与 `id / label / details / actions` 结构，新增筛选如下：

| 资源 | 可选条件 |
| --- | --- |
| users | `role=ADMIN/MEMBER/BANNED` |
| channels | `owner_id` |
| messages | `channel_id`、`user_id`、`status=normal/recalled`、`date_from`、`date_to` |
| api-keys | `user_id`、`status=valid/expired/revoked` |
| spire | `owner_id`、`status=active/finished` |
| audit | `actor_id`、`action`（如 `users.ban`）、`date_from`、`date_to` |

各条件与 `q` 组合生效，服务端先筛选再统计及分页。不支持的条件或非法值返回 422。日期采用 `YYYY-MM-DD` 和已有服务端时间口径，结束日期包含全天。关联名称及成员数按页批量查询，关联用户缺失时保留 ID。概况与最近操作独立加载和重试。

## API Key 治理与 API 调用记录

「API Key → API 调用」集中展示请求时间、API Key/所有人、操作、路径、来源 IP、状态码、结果、耗时和关联 ID。可按 API Key 类型/ID、用户、频道、房间、请求方式、结果、状态码、日期筛选；关键词搜索请求 ID、幂等键/动作 ID、连接 ID、接口、操作、摘要及名称。API Key 详情提供权限、授权范围、最近调用时间、保留期内记录数/异常数/限流数，并能直接打开该 API Key 的调用记录。

- HTTP：全量记录 `/api/v1/` 请求（包括认证失败）及携带 API Key 的 `/api/` 请求；普通 Cookie/会话调用不进入 API Key 日志。
- WebSocket：记录机器 API Key 连接/断开、每个客户端动作和 SYNC 请求，按 actionId/requestId 匹配回执。相同动作 ID 的重试仍各自成条。PING/PONG 和服务端主动推送不作为调用；未收到回执的动作标记“结果未知”。
- HTTP Request ID 与响应 `X-Request-ID` 一致；幂等键、动作/批次 ID 单独保存，可追踪重复请求。批量动作提前停止显示“部分执行”；重启前未完成记录标记“结果未知”，不推断业务是否已经提交。
- 按管理员要求，详情按原始值保存查询参数、完整请求/响应头（包括认证信息）、请求体及返回结果，不做字段或文本脱敏。重复请求头会保留。列表不携带大正文，调用列表与详情都仅管理员可读。
- 请求/响应单侧默认上限 256 KiB；超过上限、未被处理器读取或连接中断都会标注原因和已观察字节数。正常 JSON 保留原始字段值；无效 JSON/文本按 UTF-8 原文展示，非 UTF-8 二进制以 Base64 保存。调用详情为历史快照。
- 默认保留 30 天，启动及每小时分批删除过期记录；查询立即排除超期记录。可通过 `API_AUDIT_RETENTION_DAYS`（1–3650）与 `API_AUDIT_BODY_LIMIT`（4096–4194304 字节）调整。
- 新增 `api_call_audit` 表，由服务启动幂等创建，既有业务表不变。历史调用不能追溯补录。日志写入使用独立事务，正常业务不会因日志写入失败被重复执行或回滚；采集异常会在服务器日志及调用页显示本次运行的失败计数，提示记录可能不完整。

管理员只读接口：`GET /api/admin/api-calls`、`GET /api/admin/api-calls/{id}`。列表保留标准分页结构并附带采集健康信息；详情增加 `payloads`，普通账号对两者均无权访问。

## 本地运行

按仓库根目录 README 配置并启动 FastAPI 后端，然后打开 `http://127.0.0.1:8000/admin/`。静态 HTML、CSS、JavaScript 和字体由 FastAPI 提供，无需前端构建。启用 Spire 管理需要安装 `extensions/spire` 并设置 `LINKCHAT_GAMES_ENABLED=true`。

后台沿用原账号体系，不开放管理员注册。全新环境仍按原项目规则将首个注册账号设为管理员；注册需要配置 SMTP。测试账号仅在隔离测试数据库中创建。

## 部署

前后端应同批发布，本轮启动会自动创建 `api_call_audit` 审计表，无需手工修改既有业务表。将完整 `admin-web/`（包括 `config.js`）与 `linkchat-backend/` 放在同级目录，或通过 `ADMIN_WEB_DIR` 指定其路径。每个发布目录都必须包含 `admin-web/`：缺少时后端照常启动，但 `/admin/` 返回 404，启动日志记录 `admin web not mounted` 警告。发布后检查 `curl -sI https://chat.linkchat.online/admin/` 返回 200。保留原数据库、上传目录和 `.env`，通过 `DATABASE_URL` / `UPLOAD_DIR` 配置。启动仍只用一个 Uvicorn worker，避免游戏锁、连接和在线状态跨进程失效。启动自动增加 `admin_audit` 表。

Nginx 将 `/admin/` 和 `/api/` 转发到后端，模板见 `linkchat-backend/deploy/nginx-linkchat.conf`。正常聊天 Web 和 CLI 继续使用原接口，无需定制客户端。

当前服务器通过 `/etc/systemd/system/linkchat-backend.service.d/zz-linkchat-admin.conf` 从 `/opt/linkchat-admin/current/linkchat-backend` 加载包含后台的后端；这是运行时发布目录，源码归属本 LinkChat 仓库。工作目录、Python 环境、数据库和上传目录仍位于 `/opt/linkchat-backend`。最初上线备份保留在 `/opt/linkchat-admin/backups/`。

## 测试

从 LinkChat 仓库根目录执行：

```sh
cd linkchat-backend
DATABASE_URL=sqlite:// PYTHONPATH=../extensions/spire .venv/bin/python -B -m unittest discover -s tests -q
cd ..
npm install --prefix .qa --no-save playwright@1.64.1
LINKCHAT_TEST_PYTHON="$PWD/linkchat-backend/.venv/bin/python" bash scripts/test-web.sh
```

浏览器测试需要本机 Chrome，自动建立和清理临时 SQLite 数据库，验证普通账号拒绝访问、管理员登录、明暗主题、移动端布局、频道成员搜索/添加/移除和改名、删除确认、组合筛选、分页及历史恢复、批量撤回、角色变更、封禁/解封、Spire、进度、审计、抽屉焦点和退出。覆盖 1440、1024、768、390px 明暗主题，新增统一 Forum 创建、编辑、置顶锁帖、级联删除、批量删除确认、用户资料与账号删除、API Key 权限即时生效和消息编辑删除验证。并通过真实 Agent 请求验证 API 调用筛选、详情、原始值与 API Key 跳转。截图保存在忽略目录 `.qa/screenshots/`。

## Chat / Forum 统一管理与数据库级操作

论坛已并入 `/admin/#/forum-boards`，与 Chat 共用登录、导航、筛选分页、详情抽屉、编辑确认和操作记录。旧 `/admin/forum.html` 自动跳转到统一后台。已有 `/api/admin/forum/*` 接口保留兼容。

| 资源 | 操作 |
| --- | --- |
| 用户 | 编辑昵称及本站头像路径；封禁、解封、调整角色；永久删除账号 |
| 频道 | 编辑名称和说明、转移所有者、管理成员、删除频道及消息 |
| 消息 | 编辑正文、撤回、单条或批量永久删除 |
| 论坛版块 | 创建、编辑名称/说明/排序、归档；永久删除版块及所有帖子回复 |
| 论坛帖子 | 编辑标题、正文、所属版块；置顶、锁定、软删除、单条或批量永久删除 |
| 论坛回复 | 编辑正文、软删除、单条或批量永久删除 |
| 举报 | 分页筛选、定位帖子或回复、处理或驳回并填写说明 |
| API Key | 修改名称、到期时间、六项权限；撤销。已撤销的 Key 不能恢复授权 |

永久删除必须输入目标完整 ID；批量删除输入 `DELETE 数量`，服务端再次验证。编辑只允许对应表单字段，不能通过附加字段改变作者、主键或凭据。普通账号和机器 API Key 无管理员访问权。所有修改与审计记录在同一事务提交，操作成功前不会删除磁盘文件。

消息永久删除会清理表情、提醒、邮件和投递记录，并保留发送回执墓碑以避免重试重复发送。实时推送与断线补齐将删除标记转换成现有客户端支持的撤回占位，不保存原正文。服务器附件只有在没有其他消息引用时才删除。论坛永久删除会清理点赞、收藏、关注、提醒、举报和关联附件；其他回复保留并解除对被删回复的引用，发布幂等回执作为墓碑保留。软删除内容仍可由管理员检查。

删除用户时，先转移其所有频道，并退出或结束其参与的进行中游戏。不能删除自己或其他管理员；其他管理员须先降级。账号、登录会话、API Key、频道成员关系、私人通知和互动关系会被删除；历史发言、游戏存档和审计记录继续保留，作者 ID 作为历史标识，发言显示“已删除用户”。已有 Chat 和游戏连接会断开。此操作不是对历史内容、附件及审计快照的隐私擦除。

统一列表接口为 `GET /api/admin/forum-boards|forum-posts|forum-replies|forum-reports`，沿用 `rows / total / offset / limit`。帖子支持 `board_id / user_id / status / date_from / date_to`，回复与举报支持 `post_id / user_id / status / date_from / date_to`，版块支持 `status=active/archived`。内容状态为 `normal/deleted`；举报为 `open/resolved/dismissed`。

写入沿用 `POST /api/admin/{resource}/{id}/{operation}`，参数为 `values`；批量目标用 `bulk` 和 `ids`。新增操作包括 `edit / purge / transfer / create / hide / pin / unpin / lock / unlock / resolve`，由资源限定。创建版块使用 `forum-boards/new/create`。`purge` 的 `values.confirmation` 必须与目标 ID 或 `DELETE 数量` 一致。新增界面无需构建，后端无新增表或列；应将 `admin-web/` 和后端同批更新。
