226 lines
14 KiB
Markdown
226 lines
14 KiB
Markdown
---
|
||
name: genarrative-admin-backoffice
|
||
short_description: 在 Genarrative/百梦后台新增或修改管理页、后台只读/写接口、导出能力时使用。
|
||
description: 在 Genarrative/百梦后台新增或修改管理页、后台 BFF 接口、shared-contracts/admin DTO、admin-web 路由导航、Excel/表格导出与验证发布时使用。
|
||
version: 1.0.0
|
||
author: Hermes Agent
|
||
license: MIT
|
||
metadata:
|
||
hermes:
|
||
tags: [Genarrative, 百梦后台, admin-web, 后台接口, Excel导出, Rust, Axum, SpacetimeDB]
|
||
related_skills: [genarrative-play-type-integration]
|
||
---
|
||
|
||
# Genarrative / 百梦后台管理功能接入流程
|
||
|
||
用于在 Genarrative 项目中新增或修改百梦后台管理端能力,包括后台页面、后台 API、管理端 DTO、导航路由、表格明细、导出、鉴权与验证。
|
||
|
||
## 适用场景
|
||
|
||
- 新增百梦后台页面或导航项,例如“埋点数据”“任务配置”“邀请码”。
|
||
- 新增 `/admin/api/*` 接口。
|
||
- 修改 `apps/admin-web` 的后台页面、API client、路由、Shell 导航。
|
||
- 在后台展示 SpacetimeDB 表明细或统计数据。
|
||
- 新增“总览 → 单表查询”这类表统计跳转与查询页联动能力时,优先复用现有总览页的表统计作为入口,不另造第二套表目录。
|
||
- 后台导出 CSV / Excel / `.xls` 表格文件。
|
||
- 后台数据页中与业务事件、任务、登录等链路相关的问题,不能只看后台页面;要追到对应前台/API/reducer 写入点,确认“数据何时产生”。例如排查 `daily_login` 时,不要假设它一定由认证登录接口写入;先核对当前分支实现。历史实现曾在 `GET /api/profile/tasks` 打开任务中心时写入、`POST /api/profile/tasks/{task_id}/claim` 领奖时兜底写入;后续方案A把“任务中心读取写埋点”拆出为独立 procedure,任务中心只读取/刷新进度,登录成功链路应显式调用每日登录埋点入口。
|
||
|
||
## 标准落地顺序
|
||
|
||
### 0. 先确认现有后台入口
|
||
|
||
在新增后台页或回答“后台某个数据在哪里”前,先核对是否已有入口,避免重复造页:
|
||
|
||
- 数据库表统计当前在后台“总览”页,不是独立页面:`apps/admin-web/src/pages/AdminOverviewPage.tsx` 的“表统计”面板。
|
||
- 表统计行可直接跳转到表查询页:点击后设置 `window.location.hash = #tables?table=<tableName>`,由单独的 `#tables` 页接收参数并查询。
|
||
- `#tables` 页应在首次加载和 `hashchange` 时都重新读取 `table` 参数,避免只在初次 mount 时生效。
|
||
- 前端通过 `apps/admin-web/src/api/adminApiClient.ts` 的 `getAdminOverview(token)` 请求 `GET /admin/api/overview`。
|
||
- 后端路由在 `server-rs/crates/api-server/src/app.rs` 挂载 `/admin/api/overview`,handler 为 `admin_overview`。
|
||
- 表统计逻辑在 `server-rs/crates/api-server/src/admin.rs` 的 `fetch_database_overview`:先读 SpacetimeDB schema 表名,再逐表执行 `SELECT COUNT(*) AS row_count FROM {table_name}`;private 或当前身份不可见会显示“不可统计(private 或当前身份不可见)”。
|
||
- DTO 在 `server-rs/crates/shared-contracts/src/admin.rs` 的 `AdminOverviewResponse` / `AdminDatabaseOverviewPayload` / `AdminDatabaseTableStatPayload`,前端对应类型在 `apps/admin-web/src/api/adminApiTypes.ts`。
|
||
- 如果本次需求是“每张表都能查”,优先新增 `GET /admin/api/database/tables` 与 `GET /admin/api/database/tables/{tableName}/rows` 两个只读接口,并在前端新建统一的表查询页,而不是把查询逻辑塞回总览页。
|
||
|
||
### 1. 先补技术方案文档
|
||
|
||
项目要求工程修改前先检查/补充落地文档。若没有明确文档,先写到 `docs/technical/`,至少说明:
|
||
|
||
- 后台页面目标。
|
||
- 后端接口路径、鉴权、query/body、response。
|
||
- 数据来源和是否修改 SpacetimeDB schema。
|
||
- 前端页面字段、筛选项、导出格式。
|
||
- 验收命令。
|
||
|
||
示例参考:
|
||
|
||
- `references/admin-tracking-events-export-2026-05-07.md`
|
||
- `references/admin-database-table-query-2026-05-08.md`
|
||
|
||
### 2. 后端 DTO 放 shared-contracts/admin
|
||
|
||
文件:
|
||
|
||
- `server-rs/crates/shared-contracts/src/admin.rs`
|
||
|
||
做法:
|
||
|
||
- 新增 request/query/response DTO。
|
||
- 使用 `#[serde(rename_all = "camelCase")]`。
|
||
- 添加中文注释。
|
||
- 字段名与前端管理端类型保持一致。
|
||
|
||
如果 `apps/admin-web` 当前没有直接消费 Rust shared-contracts 生成物,还要同步:
|
||
|
||
- `apps/admin-web/src/api/adminApiTypes.ts`
|
||
|
||
### 3. 后端 handler 放 api-server/admin.rs
|
||
|
||
文件:
|
||
|
||
- `server-rs/crates/api-server/src/admin.rs`
|
||
- `server-rs/crates/api-server/src/app.rs`
|
||
|
||
要求:
|
||
|
||
- Handler 使用 `Extension(_admin): Extension<AuthenticatedAdmin>`,并在 router 中套 `require_admin_auth`。
|
||
- 只读接口也必须走后台鉴权。
|
||
- query 参数使用 `Query<T>`。
|
||
- 返回 `json_success_body(Some(&request_context), payload)`。
|
||
- 在 `app.rs` 挂到 `/admin/api/...`。
|
||
|
||
### 4. 读取 SpacetimeDB 表明细时优先 HTTP SQL 只读
|
||
|
||
适合后台只读运营页:
|
||
|
||
- 不改表结构。
|
||
- 不新增 reducer。
|
||
- API Server 通过 SpacetimeDB HTTP SQL 读取真实数据。
|
||
|
||
注意:
|
||
|
||
- SQL 字段固定白名单,不要 `SELECT *`。
|
||
- 用户输入只允许有限筛选字段,手动 trim、白名单枚举、字符串转义。
|
||
- limit 必须 clamp,例如默认 200、最大 1000。
|
||
- SpacetimeDB 2.2 HTTP SQL 不支持 `ORDER BY`;如果后台需要倒序展示明细,SQL 中不要拼 `ORDER BY`,先查有限 `LIMIT`,再在 api-server 内按时间字段排序,否则会返回 `HTTP 400 Unsupported: SELECT ... ORDER BY ... LIMIT ...`。
|
||
- 如果 HTTP SQL 返回 `no such table ... If the table exists, it may be marked private`,不要急着改表名或新增 reducer;先确认本地 CLI 是否以当前 standalone 的 identity/token 登录。清空本地数据库或重建 standalone 后,旧 CLI token 可能看不到 private table。按“本地 private table SQL 权限修复”流程用 `/v1/identity` 获取 token,再 `spacetime login --token` 登录。
|
||
- SQL 解析要兼容 SpacetimeDB HTTP SQL 的 statement array + rows 形态。
|
||
- SpacetimeDB HTTP SQL 读取 private table 时,enum / Option / Timestamp 可能以 SATS 原始 JSON 返回,例如 `scope_kind=[3,[]]`、`Some("user")=[0,"user"]`、`None=[1,[]]`、`Timestamp=[1778207451731746]`。后台列表、详情弹窗和 Excel 导出不要直接展示这些原始形态;应在 api-server 解析层或前端展示层转换为人可读值:enum 映射为业务字符串,Option 的 None 显示 `-`,微秒级 Timestamp 格式化为本地可读时间。
|
||
- 可复用已有 `/v1/database/{db}/sql` 请求风格和 token 配置。
|
||
|
||
### 5. 前端接入 admin-web
|
||
|
||
常改文件:
|
||
|
||
- `apps/admin-web/src/api/adminApiTypes.ts`
|
||
- `apps/admin-web/src/api/adminApiClient.ts`
|
||
- `apps/admin-web/src/app/adminRoutes.ts`
|
||
- `apps/admin-web/src/app/AdminShell.tsx`
|
||
- `apps/admin-web/src/app/AdminApp.tsx`
|
||
- `apps/admin-web/src/pages/<AdminXxxPage>.tsx`
|
||
- `apps/admin-web/src/styles/admin.css`
|
||
|
||
接入步骤:
|
||
|
||
1. 在 `adminApiTypes.ts` 增加 query/entry/list 类型。
|
||
2. 在 `adminApiClient.ts` 增加 API 方法;用 `URLSearchParams` 拼非空 query。
|
||
3. 在 `adminRoutes.ts` 增加 route id、label、hash。
|
||
4. 在 `AdminShell.tsx` 增加 route icon,`routeIcons` 必须覆盖全部 `AdminRouteId`。
|
||
5. 在 `AdminApp.tsx` import 并按 routeId 渲染页面。
|
||
6. 新增页面组件,保持 UI 简洁,不写大段规则说明。
|
||
7. 如果页面通过 hash 携带子参数,路由解析和页内参数解析要分开:`resolveAdminRoute()` 只负责路由片段,页面组件自己解析 `?table=` 之类的查询参数;同时要监听 `hashchange`,避免切页后参数不同步。
|
||
8. 列表行点击跳转优先用 hash,不要额外引入全局路由库或重新发明一套页面状态系统。
|
||
|
||
## Excel 导出推荐做法
|
||
|
||
后台运营导出不一定要引入 `xlsx` 依赖;简单表格可用浏览器端 HTML table + `.xls`:
|
||
|
||
- Blob MIME:`application/vnd.ms-excel;charset=utf-8`
|
||
- 文件扩展名:`.xls`
|
||
- 文本前加 UTF-8 BOM / `<meta charset="UTF-8">`。
|
||
- 所有单元格做 HTML escape。
|
||
- ID、大数字、日期类字段使用 `mso-number-format:'\@';` 保持文本格式,避免 Excel 科学计数法。
|
||
- 导出当前筛选结果,避免后端新增 Excel 库依赖。
|
||
|
||
## 本地启动与联调
|
||
|
||
后台改完后如需本地查看页面和接口,优先按本次联调范围选择脚本:
|
||
|
||
```bash
|
||
# 只看后台页面 + api-server,不要求 SpacetimeDB 真实数据
|
||
npm run api-server
|
||
npm run admin-web:dev -- --host 127.0.0.1
|
||
|
||
# 完整 Rust 本地栈:SpacetimeDB + 发布模块 + api-server + 主站 + 后台
|
||
npm run dev
|
||
```
|
||
|
||
验证地址通常为:
|
||
|
||
- `npm run api-server` 单独启动:api-server `http://127.0.0.1:3100/healthz`,后台前端 `http://127.0.0.1:5173/admin/`。
|
||
- `npm run dev` 完整栈:SpacetimeDB `http://127.0.0.1:3101/v1/ping`,api-server `http://127.0.0.1:8082/healthz`,主站 `http://127.0.0.1:3000/`,后台 `http://127.0.0.1:3102/admin/`。
|
||
|
||
注意:
|
||
|
||
- `npm run api-server` 首次启动可能先编译 Rust,后台进程短时间内无完整日志;等待编译完成后再查端口。
|
||
- 不要默认用 `3200` 验证 api-server;当前脚本环境变量常见为 `GENARRATIVE_API_PORT=3100`。不确定时用 `ss -ltnp | grep api-server` 或读取进程环境核对,敏感值输出必须打码。
|
||
- `admin-web` 的 `/` 可能返回 302 跳转到 `/admin/`;验证前端时直接请求 `/admin/`。
|
||
- api-server 启动日志中 SpacetimeDB `127.0.0.1:3101` 连接被拒绝,不一定代表 api-server 没起来;只表示依赖的本地 SpacetimeDB 不可用。后台中需要读 SpacetimeDB 的页面(如埋点明细、表查询)要等 SpacetimeDB 可用后才能返回真实数据。
|
||
- `npm run dev` 依赖 `spacetime` CLI;先用 `command -v spacetime && spacetime --version` 确认可用。
|
||
- 本地和人工排障不再使用 `spacetime --root-dir`。如果看到 `bin/current/spacetimedb-cli` 缺失类错误,优先确认是否仍在运行旧脚本或旧发布包;本地开发应使用 `npm run dev` / `npm run dev:rust`,通过项目脚本和 `--data-dir` 隔离 SpacetimeDB 数据目录,不再把用户级 SpacetimeDB 安装同步到项目目录。
|
||
|
||
- `scripts/dev-rust-stack.sh` 默认 `api timeout: 300s`. 合并 master 后首次 Rust 依赖/工作区重编译可能超过 300s,导致完整 `npm run dev` 在 api-server 就绪前超时并回收 SpacetimeDB。先让 Rust 编译完成,或临时用 `bash scripts/dev-rust-stack.sh --skip-spacetime --skip-publish --api-timeout-seconds 900` 预热 api-server 编译;之后再重新跑完整 `npm run dev`。
|
||
- 用户贴出的 Hermes background watch 通知可能来自已退出的旧 session。先用 `process poll` 查该 session 状态,再判断是否需要处理;不要把旧失败误判成当前服务失败。
|
||
|
||
## 测试与验证
|
||
|
||
常用命令:
|
||
|
||
```bash
|
||
# Rust 格式化检查
|
||
cd server-rs
|
||
cargo fmt -p api-server -p shared-contracts --check
|
||
|
||
# 后端相关测试,按测试名过滤
|
||
cargo test -p api-server admin_tracking -- --nocapture
|
||
|
||
# 前端后台类型检查 / 构建
|
||
cd ..
|
||
npm run admin-web:typecheck
|
||
npm run admin-web:build
|
||
|
||
# 中文/编码检查
|
||
npm run check:encoding
|
||
|
||
# diff 空白检查
|
||
git diff --check
|
||
```
|
||
|
||
如果 `npm run admin-web:typecheck` 报 `Cannot find module .../node_modules/typescript/bin/tsc`,说明当前 worktree 未安装 npm 依赖;先运行:
|
||
|
||
```bash
|
||
npm install
|
||
```
|
||
|
||
不要把该错误误判成 TypeScript 代码错误。
|
||
|
||
## 常见坑
|
||
|
||
1. 只在 `app.rs` import handler 不够,必须实际 `.route(...)` 挂载,并套 `require_admin_auth`。
|
||
2. `cargo fmt --manifest-path server-rs/Cargo.toml` 在该 workspace 可能报 `Failed to find targets`;进入 `server-rs` 后用 `cargo fmt --all` 或 `cargo fmt -p api-server -p shared-contracts --check`。
|
||
3. `cargo fmt --all` 可能格式化不相关 Rust 文件;提交前用 `git status` 检查并 revert 非本任务文件。
|
||
4. patch 工具对 Rust 单文件 lint 可能用 Rust 2015 edition 误报 `async fn is not permitted in Rust 2015`;以 `cargo test/check` 为准。
|
||
5. `adminRoutes` 新增 route id 后,`AdminShell.routeIcons` 必须同步,否则 TypeScript 会因 `satisfies Record<AdminRouteId, ...>` 报错。
|
||
- 后台页面中的中文和 JSON 预览要避免整文件重写导致编码问题;修改后运行 `npm run check:encoding`。
|
||
- 后台数据页移动端要保证表格横向滚动,不要让整页布局撑坏。
|
||
- 若用户追问“之前不是说要把 npm run dev 修好吗”这类已承诺的 dev 启动问题,不要只解释;先复现 `npm run dev`,再按启动日志修脚本并验证到服务就绪。WSL/Linux 下本地开发应走 `spacetime start --data-dir=server-rs/.spacetimedb/local/data` 这一类数据目录隔离,不再用项目级 `--root-dir`,详见 `references/dev-rust-stack-startup-2026-05-08.md`。
|
||
- 涉及敏感配置、token、密码、连接串时,输出和文档中统一写 `[REDACTED]`。
|
||
|
||
## 参考资料
|
||
|
||
- `references/admin-database-table-query-2026-05-08.md`:本次后台数据库表查询接入的实现要点、校验规则与验证结果。
|
||
- `references/admin-tracking-events-export-2026-05-07.md`:本次新增后台“埋点数据”页、SpacetimeDB HTTP SQL 只读明细、前端 `.xls` 导出的实现细节。
|
||
- `references/private-table-sql-token-refresh.md`:本地清库/重建 standalone 后,用 `/v1/identity` + `spacetime login --token` 刷新 CLI token,以便 HTTP SQL 读取 private table。
|
||
- `references/spacetimedb-http-sql-sats-display.md`:通过 HTTP SQL 读取 private table 时,enum / Option / Timestamp 的 SATS 原始 rows 如何转换为后台列表、详情和 Excel 可读值。
|
||
- `references/daily-login-tracking-trigger-points.md`:排查后台 `daily_login` 埋点为何不是登录接口写入,而是任务中心读取/领奖兜底写入的触发点记录。
|
||
- `references/daily-login-auth-closure.md`:将方案A拆出的每日登录埋点入口接入真实认证成功链路时的推荐接入点、非阻断语义、测试和提交注意事项。
|
||
- `references/dev-rust-stack-startup-2026-05-08.md`:`npm run dev` / `scripts/dev-rust-stack.sh` 在 WSL/Linux 下改用用户级 SpacetimeDB CLI、项目数据目录和显式 publish server,避免项目级 `--root-dir` 与冷编译超时的修复记录。
|