Revert "fix(迁移): 补齐会员重构的破坏性迁移导入兼容"

This reverts commit 21f69ef13b.
This commit is contained in:
2026-10-03 17:44:30 +08:00
parent bc5552a5ea
commit 0c93256b8c
3 changed files with 4 additions and 153 deletions
@@ -181,16 +181,6 @@ pub struct ProfileMembershipPlan {
**回退为「保留列 + 写死默认值 + 运行时不读」**,并在生成绑定与表目录里明确标注为废弃残留。
两种路径都必须先让 schema 门禁通过,不得使用 `--delete-data=always` 绕过。
**实测结论(2026-10-03,SpacetimeDB 2.8.3 本地 standalone)**:持久表删列会被发布检查直接拒绝,
报 `Removing a column ... requires a manual migration`,仅传 `--delete-data=never --yes=migrate,break-clients`
不足以放行;`--delete-data=on-conflict` 则会清空整库。保留列的回退未采用,改用仓库既有的显式迁移路径:
`scripts/deploy-rust-remote.sh` 的 `run_conflict_migration_publish`(默认 `GENARRATIVE_SPACETIME_MIGRATE_ON_CONFLICT=true`)
会 导出旧库 → 清库发布新 wasm → 回灌迁移 JSON,因此不需要新增独立迁移脚本。
旧迁移包缺少新增列的导入兼容已在 `server-rs/crates/spacetime-module/src/migration.rs` 的
`normalize_migration_row` 补齐(`profile_membership` 回填 `plan` / 期数默认值;`profile_recharge_order`
回填 14 个 `membership_*` 快照默认值),并由同名单元测试守护。删列本身仍受本分支的
`SPACETIME_SCHEMA_GUARD_ALLOW_BREAKING=1` schema 门禁确认。
### 2.8 会员侧遗留清理清单
实施时**删除**(不是废弃),并同步修掉引用它们的测试:
@@ -727,7 +727,7 @@ Responses 的终态载荷既是工具调用的恢复源,也是正文的恢复
- Rust 结构体:`ProfileMembership`
- 源码:`server-rs/crates/spacetime-module/src/runtime/active/profile.rs`
- 作用:会员有效期和当前周期限时泥点事实源。`started_at/expires_at` 表示会员有效期,`cycle_started_at/cycle_resets_at` 表示当前周期,`cycle_granted_points/cycle_remaining_points` 表示当前周期已发放和剩余限时泥点;`plan` / `cycle_index` / `cycle_count` / `cycle_kind` 表示会员档位(`RuntimeProfileMembershipPlan` 枚举)与自然月期数。旧 `tier` 与固定 30 天的 `cycle_period_days` 已随会员档位枚举化删除(会员未上线,属经用户授权的破坏性变更;迁移导入按 `migration.rs` 的 `profile_membership` 兼容分支回填默认值)。
- 作用:会员有效期和当前周期限时泥点事实源。`started_at/expires_at` 表示会员有效期,`cycle_started_at/cycle_resets_at/cycle_period_days` 表示当前周期,`cycle_granted_points/cycle_remaining_points` 表示当前周期已发放和剩余限时泥点。
### `profile_membership_plan`
@@ -742,8 +742,8 @@ Responses 的终态载荷既是工具调用的恢复源,也是正文的恢复
- Rust 结构体:`ProfileRechargeProductConfig`
- 源码:`server-rs/crates/spacetime-module/src/runtime/active/profile.rs`
- 作用:泥点充值商品配置真相源,供充值中心展示、下单校验、支付确认和后台“充值商品”页维护;当前公开充值中心展示六档泥点商品,会员档位与价格改由 `profile_membership_plan` 目录驱动。
- 字段补充:原先给会员商品用的 `tier` 与 `membership_period_points` / `membership_period_days` / `membership_queue_limit` / `membership_discount_bps` 已随会员档位枚举化删除;旧迁移包里的这些列在导入时按未知字段丢弃。
- 作用:泥点和会员充值商品配置真相源,供充值中心展示、下单校验、支付确认和后台“充值商品”页维护;当前公开充值中心只展示四档泥点商品,会员商品配置留存但不公开购买或升级入口。
- 字段补充:会员商品追加 `membership_period_points`、`membership_period_days`、`membership_queue_limit`、`membership_discount_bps`;泥点商品这些字段必须为 `0`。
### `profile_played_world`
@@ -754,7 +754,7 @@ Responses 的终态载荷既是工具调用的恢复源,也是正文的恢复
- Rust 结构体:`ProfileRechargeOrder`
- 源码:`server-rs/crates/spacetime-module/src/runtime/active/profile.rs`
- 作用:账户充值订单事实源。`status` 包含 `pending`、`paid`、`failed`、`closed`、`refunded`、`expired`;过期补偿字段 `expired_at`、`expiration_checked_at`、`expiration_provider_state`、`expiration_last_error` 用于记录本地过期和微信查单结果。末位追加的 `membership_*` 字段保存会员购买 / 升级前后的档位、周期、每期额度、到期与刷新时间快照、补点差额和报价明细,供人工复核退款;旧迁移包缺少这些列时由 `migration.rs` 的 `profile_recharge_order` 兼容分支回填为空 / `0`。
- 作用:账户充值订单事实源。`status` 包含 `pending`、`paid`、`failed`、`closed`、`refunded`、`expired`;过期补偿字段 `expired_at`、`expiration_checked_at`、`expiration_provider_state`、`expiration_last_error` 用于记录本地过期和微信查单结果。
### `profile_recharge_refund`
@@ -1284,67 +1284,6 @@ fn normalize_migration_row(table_name: &str, value: &serde_json::Value) -> serde
object
.entry("expiration_last_error".to_string())
.or_insert(serde_json::Value::Null);
// 中文注释:会员购买 / 升级变更快照晚于订单表加入,旧迁移包按普通充点订单兼容。
object
.entry("membership_change_kind".to_string())
.or_insert(serde_json::Value::Null);
object
.entry("membership_plan_before".to_string())
.or_insert(serde_json::Value::Null);
object
.entry("membership_plan_after".to_string())
.or_insert(serde_json::Value::Null);
object
.entry("membership_cycle_kind_before".to_string())
.or_insert(serde_json::Value::Null);
object
.entry("membership_cycle_kind_after".to_string())
.or_insert(serde_json::Value::Null);
object
.entry("membership_period_points_before".to_string())
.or_insert(serde_json::Value::Null);
object
.entry("membership_period_points_after".to_string())
.or_insert(serde_json::Value::Null);
object
.entry("membership_granted_points_delta".to_string())
.or_insert_with(|| serde_json::Value::from(0i64));
object
.entry("membership_cycle_index".to_string())
.or_insert(serde_json::Value::Null);
object
.entry("membership_expires_at_before".to_string())
.or_insert(serde_json::Value::Null);
object
.entry("membership_expires_at_after".to_string())
.or_insert(serde_json::Value::Null);
object
.entry("membership_cycle_resets_at_before".to_string())
.or_insert(serde_json::Value::Null);
object
.entry("membership_cycle_resets_at_after".to_string())
.or_insert(serde_json::Value::Null);
object
.entry("membership_price_breakdown_json".to_string())
.or_insert(serde_json::Value::Null);
}
}
if table_name == "profile_membership" {
if let Some(object) = next_value.as_object_mut() {
// 中文注释:会员档位改枚举、账期改自然月后新增 plan / cycle_index / cycle_count / cycle_kind。
// 会员侧未上线、无存量行;旧迁移包若带旧 `tier` / `cycle_period_days`,反序列化时会按未知字段丢弃。
object
.entry("plan".to_string())
.or_insert_with(|| serde_json::json!({ "Normal": [] }));
object
.entry("cycle_index".to_string())
.or_insert_with(|| serde_json::Value::from(0u32));
object
.entry("cycle_count".to_string())
.or_insert_with(|| serde_json::Value::from(1u32));
object
.entry("cycle_kind".to_string())
.or_insert_with(|| serde_json::json!({ "Monthly": [] }));
}
}
if table_name == "external_generation_job_summary" {
@@ -1612,84 +1551,6 @@ mod migration_bootstrap_secret_tests {
assert_eq!(normalized["asset_kind_override"], serde_json::Value::Null);
}
#[test]
fn old_profile_membership_rows_default_to_normal_plan_and_first_month() {
let normalized = normalize_migration_row(
"profile_membership",
&serde_json::json!({ "user_id": "user-1", "tier": { "Month": [] } }),
);
assert_eq!(normalized["plan"], serde_json::json!({ "Normal": [] }));
assert_eq!(normalized["cycle_index"], serde_json::json!(0));
assert_eq!(normalized["cycle_count"], serde_json::json!(1));
assert_eq!(
normalized["cycle_kind"],
serde_json::json!({ "Monthly": [] })
);
}
#[test]
fn old_profile_recharge_order_rows_default_to_plain_point_order_snapshot() {
let normalized = normalize_migration_row(
"profile_recharge_order",
&serde_json::json!({ "order_id": "order-1" }),
);
assert_eq!(
normalized["membership_change_kind"],
serde_json::Value::Null
);
assert_eq!(
normalized["membership_plan_before"],
serde_json::Value::Null
);
assert_eq!(normalized["membership_plan_after"], serde_json::Value::Null);
assert_eq!(
normalized["membership_cycle_kind_before"],
serde_json::Value::Null
);
assert_eq!(
normalized["membership_cycle_kind_after"],
serde_json::Value::Null
);
assert_eq!(
normalized["membership_period_points_before"],
serde_json::Value::Null
);
assert_eq!(
normalized["membership_period_points_after"],
serde_json::Value::Null
);
assert_eq!(
normalized["membership_granted_points_delta"],
serde_json::json!(0)
);
assert_eq!(
normalized["membership_cycle_index"],
serde_json::Value::Null
);
assert_eq!(
normalized["membership_expires_at_before"],
serde_json::Value::Null
);
assert_eq!(
normalized["membership_expires_at_after"],
serde_json::Value::Null
);
assert_eq!(
normalized["membership_cycle_resets_at_before"],
serde_json::Value::Null
);
assert_eq!(
normalized["membership_cycle_resets_at_after"],
serde_json::Value::Null
);
assert_eq!(
normalized["membership_price_breakdown_json"],
serde_json::Value::Null
);
}
#[test]
fn bootstrap_secret_sha256_is_stable_and_does_not_contain_plaintext() {
let secret = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";