Files
Genarrative/.hermes/skills/genarrative-admin-backoffice/SKILL.md
kdletters 10ed4fa051
Some checks failed
CI / verify (push) Has been cancelled
docs: clarify SpacetimeDB root-dir usage
2026-05-11 14:27:33 +08:00

226 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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` 与冷编译超时的修复记录。