# 邀请码有效期与后台二次确认实施计划 > **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` - `expires_at: Option` 校验规则: - 当前时间 `< 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 的二次确认与表单展示。 --- **结论:** 这是一个适合分阶段落地的改动,建议先做“邀请码时间窗 + 后台统一二次确认”的基础能力,再补交互细节。