feat(会员): 会员档位目录随充值商品配置播种
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m23s
Project CI / Backend tests (pull_request) Failing after 28s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m56s
Project CI / Frontend tests (pull_request) Successful in 2m59s
Project CI / Repository checks (pull_request) Failing after 15s
Project CI / AI game creator shell web tests (pull_request) Successful in 2m0s
Project CI / Native shell tests (pull_request) Successful in 7m6s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 11m24s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 13m27s

- 新增 ensure_default_profile_membership_plan,表为空时按会员目录种子整表播种。
- 播种入口挂在 ensure_default_profile_recharge_product_config,表非空时保留后台改价。
- 设计文档 §10 改为「已确认输入 + 实施状态」,记录三项拍板结论。
- 设计文档补充实施约束:module-runtime 与 spacetime-module 相互依赖,领域层清退必须与 schema 变更同批提交。
- 设计文档列出待落地清单(会员遗留清退、三池 DTO、报价接口、充点 6 档、前端同步)。
This commit is contained in:
2026-10-02 15:02:15 +08:00
parent 1790ddff6a
commit 71c778ccee
2 changed files with 59 additions and 4 deletions
@@ -442,8 +442,33 @@ flowchart TD
| 14 | 下单接口 | 复用 `POST /api/profile/recharge/orders` |
| 15 | `walletBalance` | 用户面不暴露 |
## 10. 仍需你确认的输入
## 10. 评审输入与实施清单
- **并发与模型权限本期不执行**:目录字段与投影照常暴露,但服务端拦截留到后续里程碑——如需本期就拦,请明确。
- **`profile_recharge_product_config` 原地删列**:若 SpacetimeDB 本地发布拒绝,回退为「保留列 + 默认值 + 不读」,是否接受该回退。
- **旧副本 `runtime/profile.rs`**:确认删除。
### 10.1 已确认输入(本轮拍板)
- **并发与模型权限**:只落字段、只用于展示,服务端本期不做拦截。
- **`profile_recharge_product_config` 原地删列**:若 SpacetimeDB 本地发布拒绝删列,接受回退为「保留列 + 写死默认值 + 运行时不读」,并在生成绑定与表目录里标注为废弃残留。
- **旧副本 `server-rs/crates/spacetime-module/src/runtime/profile.rs`**:确认删除(已删除)。
- **档位命名**:列名与 wire 字段统一为 `plan`,不再使用 `plan_id` / `tier`。
### 10.2 实施状态
已落地(各自独立可编译):
- 会员领域模块 `module-runtime/src/membership/{catalog,cycle,limits,upgrade}.rs`,公历基元抽为 `civil_calendar`。
- 账本来源枚举末尾追加 `MembershipUpgradeGrant`,并同步契约常量、后台索引映射、前端来源标签与生成绑定。
- 新增表 `profile_membership_plan`(主键为档位枚举 `RuntimeProfileMembershipPlan`),同步 `migration.rs`、表目录与生成绑定。
- 会员档位目录播种 `ensure_default_profile_membership_plan`:表为空时按代码内目录整表播种,表非空时保留后台改价。
- 删除未被引用的旧副本 `runtime/profile.rs`。
待落地——**第 1、2 项必须作为同一个提交落地**,否则工作区不可编译:
1. `module-runtime` 领域层清退:删 `RuntimeProfileMembershipTier`、会员权益快照/记录、`cycle_period_days`、`tier` 与充点商品上的四个 `membership_*` 字段;会员快照/记录改用 `plan` + `cycle_index` / `cycle_count` / `cycle_kind`;充值中心改用 `membership_plans`。
2. `spacetime-module`:两张表的列增删、充值中心装配、后台商品 upsert 收敛为泥点商品、会员目录价格与自然月周期逻辑改走 `plan` + 目录表。
3. `spacetime-client` mapper / facade 与 `api-server` DTO / 后台接口同步;重新生成绑定。
4. 三池余额投影 `ProfileMudPointBalanceResponse`、`ProfileMembershipResponse.plan`、`POST /api/profile/membership/upgrade-quote` 与下单重算。
5. 充点 6 档落库与 `resolve_default_point_product_migration` 中 `points_1280` / `points_3280` 的历史反例修正。
6. `packages/shared` 契约与共享组件同步。
7. 门禁:`SPACETIME_SCHEMA_GUARD_ALLOW_BREAKING=1 npm run check:spacetime-schema`、`npm run check:generated-bindings`、`npm run check:encoding`、`git diff --check`。
> 约束来源:`scripts/check-spacetime-schema-guard.mjs` 只做源码静态比对,破坏性变更必须显式带 `SPACETIME_SCHEMA_GUARD_ALLOW_BREAKING=1` 才会放行;而 `module-runtime` 与 `spacetime-module` 相互依赖,任一侧单独清退都会让另一侧无法编译,所以领域层清退必须与 schema 变更同批提交。
@@ -8436,7 +8436,37 @@ fn ensure_default_profile_task_config(ctx: &ReducerContext) -> ProfileTaskConfig
})
}
/// 中文注释:会员档位目录以代码内目录为种子真相源,表为空时整表播种一次;后台改价后不再覆盖。
fn ensure_default_profile_membership_plan(ctx: &ReducerContext) {
if ctx.db.profile_membership_plan().count() > 0 {
return;
}
let now = ctx.timestamp;
for row in runtime_profile_membership_plan_catalog() {
ctx.db
.profile_membership_plan()
.insert(ProfileMembershipPlan {
plan: row.plan,
title: row.title,
rank: row.rank,
month_price_cents: row.month_price_cents,
year_price_cents: row.year_price_cents,
period_points: row.period_points,
model_access: row.model_access,
concurrent_job_limit: row.concurrent_job_limit,
sort_order: row.sort_order,
enabled: row.enabled,
created_by: PROFILE_RECHARGE_PRODUCT_SYSTEM_USER_ID.to_string(),
created_at: now,
updated_by: PROFILE_RECHARGE_PRODUCT_SYSTEM_USER_ID.to_string(),
updated_at: now,
});
}
}
fn ensure_default_profile_recharge_product_config(ctx: &ReducerContext) {
ensure_default_profile_membership_plan(ctx);
if ctx.db.profile_recharge_product_config().count() > 0 {
migrate_legacy_default_profile_recharge_product_config(ctx);
return;