Files
Genarrative/.hermes/plans/2026-05-04_032321-invite-code-validity-and-admin-confirmation.md
kdletters 9f3e34e81a feat: add invite code validity controls
- Add invite code starts/expires fields across contracts, API, Spacetime bindings, and admin UI
- Enforce pending/expired invite code redemption behavior and expose admin status
- Add admin write-operation confirmation guard and documentation
- Add invite code contract/runtime tests
2026-05-04 13:54:40 +08:00

272 lines
9.4 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.
# 邀请码有效期与后台二次确认实施计划
> **For Hermes:** 按 plan 模式,仅输出并保存实施计划,不直接改业务代码。
**Goal:** 为邀请码新增开始日期与截止日期,并让后台所有会修改数据的操作在提交前增加二次确认,降低误操作风险。
**Architecture:**
邀请码仍作为“用户稳定邀请身份码”保留,不做停用删除;在数据层增加 `starts_at` / `expires_at`,前台填写邀请码时按时间窗校验,后台列表与编辑页展示状态。后台所有写操作统一先弹二次确认,再真正调用 API避免对兑换码、邀请码、任务配置等管理动作误触发。
**Tech Stack:**
Rust / SpacetimeDB / Axum / shared-contracts / TS + React 的 admin-web。
---
## 当前上下文
- 邀请码当前只有 `user_id``invite_code``metadata_json``created_at``updated_at`,没有状态字段。
- 目前后台存在邀请码管理入口,但没有停用能力,也没有有效期概念。
- 邀请码用于 `redeem_profile_referral_invite_code` 时的实时校验,适合增加“时间窗”而不是“禁用删除”。
- 后台已存在兑换码、任务配置等可写操作;本次要求把所有后台操作统一加二次确认,包括新增、编辑、禁用、删除等写入口。
---
## 设计原则
1. **邀请码不做软删除**:保留历史记录和邀请链路。
2. **有效期由时间窗推导**
- `starts_at` 为空表示立即生效。
- `expires_at` 为空表示长期有效。
3. **前台只拒绝新绑定**:已绑定关系不回溯修改。
4. **后台写操作统一确认**:所有会触发 POST / PATCH / DELETE 的管理动作,在真正提交前必须弹出二次确认。
5. **尽量少改接口语义**:优先在现有 admin upsert/list 体系内扩展字段,而不是新增一套并行 API。
---
## 方案概要
### 邀请码时间窗
新增字段:
- `starts_at: Option<Timestamp>`
- `expires_at: Option<Timestamp>`
校验规则:
- 当前时间 `< starts_at`:返回“邀请码未生效”
- 当前时间 `>= expires_at`:返回“邀请码已过期”
- 其他情况允许填写
建议把状态展示为:
- 未生效
- 有效
- 已过期
- 长期有效(两个字段都为空或仅无截止)
### 后台二次确认
对 admin-web 所有管理动作统一加确认弹窗/对话框,至少覆盖:
- 兑换码新增/更新
- 兑换码停用
- 邀请码新增/更新
- 任务配置新增/更新
- 任务配置停用
- 其他后续新增的后台写操作
确认文案要求:
- 显示对象标识(如 code / inviteCode / taskId
- 显示操作类型(新增 / 更新 / 停用)
- 明确提醒“该操作会立即影响线上数据”
- 允许取消返回,不调用 API
---
## 预期修改文件
### 1. 服务端领域与契约
- `server-rs/crates/spacetime-module/src/runtime/profile.rs`
- `ProfileInviteCode` 表结构新增开始/截止字段
- 邀请码 upsert 逻辑写入时间窗
- 邀请码 redeem 逻辑增加时间窗校验
- 邀请中心快照补充时间窗/状态
- `server-rs/crates/spacetime-module/src/migration.rs`
- 兼容旧表数据,给旧邀请码补默认空值
- `server-rs/crates/shared-contracts/src/runtime*.rs` 或对应生成/手写契约文件
- `AdminUpsertProfileInviteCodeRequest` 扩展字段
- `ProfileInviteCodeAdminResponse` 扩展字段
- 如需要,增加时间窗相关状态枚举或派生字段
- `server-rs/crates/spacetime-client/src/module_bindings/*`
- 重新生成 bindings
- mapper 补齐新字段
### 2. API Server
- `server-rs/crates/api-server/src/runtime_profile.rs`
- 接收/转发邀请码时间窗参数
- 返回新增字段给后台
- 如需要,调整校验错误文案
- `server-rs/crates/api-server/src/app.rs`
- 若有新路由或错误码需挂接,在此统一登记
### 3. 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/styles/admin.css`
- 确认弹窗与时间窗展示样式
- `apps/admin-web/src/**` 实际管理页面组件
- 邀请码编辑表单
- 邀请码列表状态展示
- 所有写操作前的二次确认弹窗
### 4. 文档
- `docs/technical/``docs/design/`
- 补一份邀请码时间窗与后台确认交互说明
- 如现有文档已经覆盖后台管理规范,则优先补充现有文档,不重复造新说明页
---
## 分步实施计划
### Task 1: 明确数据模型与契约扩展
**Objective:** 定义邀请码开始/截止日期字段及其在响应中的展示方式。
**要点:**
- 确认字段名采用 `starts_at` / `expires_at`,避免与现有字段语义冲突。
- 确认时间类型统一用 `Timestamp` / 毫秒微秒整数转换策略。
- 明确返回给后台的字段是否需要附带派生状态(如 `status`)。
**产出:**
- 契约字段定义
- 状态枚举/派生规则
---
### Task 2: 更新 SpacetimeDB 表与迁移
**Objective:** 让邀请码表可保存有效期,并兼容旧数据。
**要点:**
- 修改 `ProfileInviteCode` 表结构。
- 更新迁移逻辑,旧记录默认无开始/截止。
- 检查是否需要补充索引或查询辅助字段。
**验证:**
- 旧数据能正常读取。
- 新数据能写入开始/截止。
---
### Task 3: 实现邀请填写时的时间窗校验
**Objective:** 在邀请码被填写时正确拒绝未生效或已过期的邀请码。
**要点:**
-`redeem_profile_referral_invite_code_record` 内增加开始/截止校验。
- 保持“自己的邀请码不能填”“邀请码不存在”等原有错误优先级清晰。
- 保留历史绑定关系不受影响。
**验证:**
- 未到开始时间时返回明确错误。
- 超过截止时间时返回明确错误。
- 正常区间可绑定成功。
---
### Task 4: 扩展后台邀请码管理接口
**Objective:** 让后台可以创建/修改邀请码时间窗,并在列表中查看状态。
**要点:**
- 扩展 `AdminUpsertProfileInviteCodeRequest`
- 扩展 `ProfileInviteCodeAdminResponse`
- `api-server` 接口负责接收新字段并转发。
- 列表接口返回可读时间字段与状态。
**验证:**
- 后台表单提交后,返回结果包含时间窗信息。
- 列表页能看到状态与时间。
---
### Task 5: 给后台所有写操作加二次确认
**Objective:** 统一拦截所有后台写动作,避免误点直接生效。
**覆盖范围建议:**
- 邀请码新增/更新
- 兑换码新增/更新/停用
- 任务配置新增/更新/停用
- 后续新增的管理写操作
**实现要求:**
- 在真正调用 API 之前弹出确认框。
- 确认框需要展示对象名、操作类型、影响范围。
- 取消后不发送请求。
- 尽量抽象出通用确认组件/通用 action 包装函数,避免每个页面重复写。
**验证:**
- 点击“保存”不会直接提交,需先确认。
- 点击“取消”不会发请求。
- 所有后台写入口行为一致。
---
### Task 6: 补充文档与交互说明
**Objective:** 把新规则写进项目文档,避免后续实现偏差。
**要点:**
- 记录邀请码时间窗语义。
- 记录后台二次确认规范。
- 说明哪些动作属于“必须确认”的写操作。
---
## 测试与验证
### 服务端
- 邀请码时间窗单测 / 集成测试
- 邀请码 redeem 流程回归测试
- 旧数据兼容测试
### API / 前端
- 管理后台列表展示正确
- 表单提交能回传新字段
- 二次确认取消后不请求接口
- 二次确认确认后正常提交
### 推荐验证命令
- 视项目现有脚本执行对应后端测试
- 前端按 admin-web 构建/测试脚本验证
- 如涉及生成绑定,先确认生成产物无漏字段
---
## 风险与权衡
1. **时间字段格式不统一**
- 风险:前后端对时间单位理解不一致。
- 处理:在契约层明确是 ISO 字符串还是微秒整数,并全链路统一。
2. **后台“所有操作”范围过大**
- 风险:遗漏某些写入口。
- 处理:先枚举现有写 API再做统一确认封装。
3. **邀请码过期后历史链接解释成本**
- 风险:用户误以为历史邀请码失效影响已绑定关系。
- 处理:文案明确“仅影响新填写,不影响已绑定记录”。
4. **契约与生成绑定联动较多**
- 风险:字段变更后生成文件数量较多。
- 处理:先改源契约与服务端,再统一重生成 bindings。
---
## 待确认问题
1. `starts_at` / `expires_at` 在接口里要返回 **ISO 字符串** 还是 **微秒整数**
2. 后台二次确认是否统一用一个全局弹窗组件,还是页面级本地实现?
3. 邀请码列表是否需要直接展示“状态标签”还是只展示时间字段由前端推导?
4. 现有后台所有写操作里,是否还要覆盖调试类接口,还是仅覆盖业务管理接口?
---
## 建议执行顺序
1. 先确认时间字段格式与确认弹窗范围。
2. 再改服务端契约与迁移。
3. 再改 redeem 校验与后台接口。
4. 最后统一改 admin-web 的二次确认与表单展示。
---
**结论:** 这是一个适合分阶段落地的改动,建议先做“邀请码时间窗 + 后台统一二次确认”的基础能力,再补交互细节。