--- 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=`,由单独的 `#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`,并在 router 中套 `require_admin_auth`。 - 只读接口也必须走后台鉴权。 - query 参数使用 `Query`。 - 返回 `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/.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 / ``。 - 所有单元格做 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` 报错。 - 后台页面中的中文和 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` 与冷编译超时的修复记录。