This commit is contained in:
2026-05-08 22:07:05 +08:00
61 changed files with 4364 additions and 202 deletions

View File

@@ -0,0 +1,94 @@
# 后台数据库表查询技术方案2026-05-08
## 背景
后台“总览”页已经通过 `/admin/api/overview` 展示 SpacetimeDB 表统计,但只能看到表名、行数和统计状态。运营和排障时需要从统计行直接进入单表查询页,按基础条件快速查看真实行数据。
## 目标
- 在后台新增“表查询”页,支持所有 schema 表的只读查询。
- “总览 / 表统计”中的每一行可点击跳转到对应表的查询页。
- 提供基础查询能力表选择、关键词搜索、JSON 条件过滤、条数限制、刷新、查看行详情。
- 不修改 SpacetimeDB 表结构,不新增 reducer不引入写操作。
## 后续增强
- 查询页增加“重置条件”快捷操作,便于运营快速回到默认筛选状态。
- 行详情支持一键复制完整 JSON减少人工选中复制的操作成本。
- 查询页顶部增加轻量摘要,显示当前选表和可见列数,方便移动端快速确认上下文。
## 后端接口
### `GET /admin/api/database/tables`
鉴权:沿用 `require_admin_auth`
数据来源SpacetimeDB schema HTTP API。
响应:
```json
{
"tables": ["tracking_event", "user_account"],
"fetchErrors": []
}
```
### `GET /admin/api/database/tables/{tableName}/rows`
鉴权:沿用 `require_admin_auth`
Query
- `limit`:默认 100范围 1-500。
- `search`:可选,前端关键词;后端返回行后在 JSON 文本中大小写不敏感过滤。
- `filters`:可选 JSON object 字符串,例如 `{"user_id":"u1","enabled":true}`;后端返回行后按字段等值过滤。
响应:
```json
{
"tableName": "tracking_event",
"columns": ["event_id", "event_key"],
"rows": [
{
"cells": {
"event_id": "event-1",
"event_key": "daily_login"
},
"raw": ["event-1", "daily_login"]
}
],
"totalReturned": 1,
"limit": 100
}
```
实现约束:
- 表名必须来自 schema 且通过标识符安全校验,避免任意 SQL 注入。
- SQL 固定为 `SELECT * FROM {tableName} LIMIT {limit}`SpacetimeDB 2.2 HTTP SQL 不拼 `ORDER BY`
- 用户输入不直接拼入 SQL关键词和条件在 API Server 内存中过滤。
- private 表或 token 不可见时返回后台可读错误信息。
- SpacetimeDB SQL 行和 SATS 值统一转成人可读 JSONOption None 为 nullSome 展开为内部值Timestamp 单元素数组展开为内部值enum 可保留 tag/name 或原始数组文本。
## 前端页面
路由:`#tables`,导航名“表查询”。
页面能力:
- 表选择下拉,支持 URL hash `#tables?table=xxx` 直达指定表。
- 查询表单表名、关键词、JSON 条件、条数。
- 查询结果表格横向滚动,移动端不撑坏布局。
- 每行提供“详情”按钮,以独立弹层展示完整 JSON。
- 总览表统计行点击后跳转到 `#tables?table={tableName}`
## 验收
- `cd server-rs && cargo fmt -p api-server -p shared-contracts --check`
- `cd server-rs && cargo test -p api-server admin_database -- --nocapture`
- `npm run admin-web:typecheck`
- `npm run admin-web:build`
- `npm run check:encoding`
- `git diff --check`

View File

@@ -0,0 +1,73 @@
# 登录成功每日登录埋点闭环方案2026-05-08
## 背景
后台“埋点数据”需要能看到真实登录触发的 `daily_login` 埋点。此前方案 A 已把“读取任务中心时顺手写每日登录埋点”拆成独立 SpacetimeDB procedure
- `record_daily_login_tracking_event_and_return`
- `spacetime-client` 方法:`record_daily_login_tracking_event(user_id)`
但认证成功链路还没有调用该方法,因此当前只完成了“任务中心读取不污染登录埋点”,没有完成“用户真实登录写入每日登录埋点”。
## 现象
用户已经登录、cookie 未过期时,直接打开网页并不会触发每日登录埋点。原因是前端恢复登录态只读取 `/api/auth/me`,这条链路不会主动走 refresh cookie 续期,因此后端新的埋点写入点不会被触发。
## 修复思路
`AuthGate` 恢复已登录会话时,先主动调用一次 refresh 接口轮换 refresh cookie再调用 `/api/auth/me` 读取当前会话。这样无论本地 access token 是否仍然有效,打开页面都会进入 refresh 续期链路,从而触发后端的 `daily_login` 埋点写入。
## 目标
在用户认证成功并创建 refresh session / access token 后,异步尝试写入每日登录埋点。
覆盖入口:
- 手机验证码登录:`POST /api/auth/phone/login`
- 密码入口登录:`POST /api/auth/entry`
- 重置密码后自动登录:`POST /api/auth/password/reset`
- 微信 OAuth callback 登录:`GET /api/auth/wechat/callback`
- 微信绑定手机号后激活/登录态刷新:`POST /api/auth/wechat/bind-phone`
- refresh cookie 续期:`POST /api/auth/session/refresh`
## 设计约束
1. 埋点写入不能阻断登录成功响应。
2. 只有认证成功并已创建会话后,或 refresh session rotate 成功并签发新 access token 后才记录。
3. 失败只记 warning继续返回 token / cookie。
4. 写入统一收口,避免多个登录 handler 各自拼 procedure 调用。
5. 不修改 SpacetimeDB 表结构,不需要更新 `migration.rs`
## 实现方案
新增 `api-server` 内部 helper
```rust
record_daily_login_tracking_event_after_auth_success(
state: &AppState,
request_context: &RequestContext,
user_id: &str,
login_method: AuthLoginMethod,
).await;
```
该 helper
- 调用 `state.spacetime_client().record_daily_login_tracking_event(user_id.to_string()).await`
- 成功时记录 info
- 失败时记录 warn并明确“登录流程继续”
在各登录入口 `create_auth_session` 成功后调用该 helper。refresh cookie 续期在 `rotate_session``sign_access_token_for_user` 成功后调用同一个 helper`login_method` 使用 refresh session 上保存的 `issued_by_provider`,避免把续期统一误标成 password。
## 验收
- `cargo test -p api-server auth_session -- --nocapture`
- `cargo check -p api-server`
- `cargo check -p spacetime-client`
- `npm run check:encoding`
- `git diff --check`
- `npm run test -- AuthGate.test.tsx`
## 注意
`npm run dev` 是长期运行进程;如需本地 smoke应启动后用 `/healthz` 和后台页面验证,不要等待该命令退出。