fix(会员): 退役列保留不删,发布改回追加式兼容

- domain.rs 恢复已退役的 RuntimeProfileMembershipTier 枚举,仅供存量 tier 列兼容。

- profile.rs 将 profile_membership 的 tier / cycle_period_days 还原到原始位置,新字段 plan / cycle_index / cycle_count / cycle_kind 末位追加。

- profile.rs 恢复 profile_recharge_product_config 的 tier 与 membership_* 兼容列,运行时不读不写、写入统一用固定默认值。

- 重新生成 module_bindings;schema guard 无需 allow-breaking 即通过,发布不再触发导出 / 清库 / 回灌。

- 数据契约文档同步标注退役列与六档泥点商品。
This commit is contained in:
2026-10-03 17:59:31 +08:00
parent 5157ec1784
commit ba451bfc0c
9 changed files with 137 additions and 4 deletions
@@ -727,7 +727,8 @@ Responses 的终态载荷既是工具调用的恢复源,也是正文的恢复
- Rust 结构体:`ProfileMembership`
- 源码:`server-rs/crates/spacetime-module/src/runtime/active/profile.rs`
- 作用:会员有效期和当前周期限时泥点事实源。`started_at/expires_at` 表示会员有效期,`cycle_started_at/cycle_resets_at/cycle_period_days` 表示当前周期,`cycle_granted_points/cycle_remaining_points` 表示当前周期已发放和剩余限时泥点。
- 作用:会员有效期和当前周期限时泥点事实源。`started_at/expires_at` 表示会员有效期,`cycle_started_at/cycle_resets_at` 表示当前周期,`cycle_granted_points/cycle_remaining_points` 表示当前周期已发放和剩余限时泥点;`plan/cycle_index/cycle_count/cycle_kind` 是档位身份与自然月期数的权威字段,末位追加。
- 兼容列:旧 `tier`(`RuntimeProfileMembershipTier`)与固定 30 天的 `cycle_period_days` 已退役,仅为兼容存量 schema 保留在原始位置,运行时不读不写。
### `profile_membership_plan`
@@ -742,8 +743,8 @@ Responses 的终态载荷既是工具调用的恢复源,也是正文的恢复
- Rust 结构体:`ProfileRechargeProductConfig`
- 源码:`server-rs/crates/spacetime-module/src/runtime/active/profile.rs`
- 作用:泥点和会员充值商品配置真相源,供充值中心展示、下单校验、支付确认和后台“充值商品”页维护;当前公开充值中心只展示四档泥点商品,会员商品配置留存但不公开购买或升级入口。
- 字段补充:会员商品追加 `membership_period_points`、`membership_period_days`、`membership_queue_limit`、`membership_discount_bps`;泥点商品这些字段必须为 `0`。
- 作用:泥点充值商品配置真相源,供充值中心展示、下单校验、支付确认和后台“充值商品”页维护;当前公开充值中心只展示六档泥点商品(`points_60` ~ `points_3280`),会员购买已下线。
- 兼容列:旧 `tier`(`RuntimeProfileMembershipTier`)与会员商品权益列 `membership_period_points`、`membership_period_days`、`membership_queue_limit`、`membership_discount_bps` 已退役,仅为兼容存量 schema 保留在原始位置,运行时不读不写。
### `profile_played_world`
@@ -1231,6 +1231,23 @@ impl RuntimeProfileMembershipStatus {
}
}
/// 已退役:旧会员档位身份。
///
/// 仅为兼容存量 `profile_membership.tier` 与 `profile_recharge_product_config.tier`
/// 列保留,运行时不读不写;档位身份统一由 [`RuntimeProfileMembershipPlan`] 承载。
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum RuntimeProfileMembershipTier {
Normal,
Month,
Season,
Year,
Starter,
Basic,
Pro,
Ultimate,
}
/// 会员档位身份。取代旧的 `RuntimeProfileMembershipTier`,作为唯一权威档位标识。
///
/// 档位高低由目录表的 `rank` 决定(见
@@ -666,6 +666,7 @@ pub mod runtime_profile_membership_plan_type;
pub mod runtime_profile_membership_snapshot_procedure_result_type;
pub mod runtime_profile_membership_snapshot_type;
pub mod runtime_profile_membership_status_type;
pub mod runtime_profile_membership_tier_type;
pub mod runtime_profile_membership_upgrade_quote_get_input_type;
pub mod runtime_profile_membership_upgrade_quote_procedure_result_type;
pub mod runtime_profile_membership_upgrade_quote_snapshot_type;
@@ -1486,6 +1487,7 @@ pub use runtime_profile_membership_plan_type::RuntimeProfileMembershipPlan;
pub use runtime_profile_membership_snapshot_procedure_result_type::RuntimeProfileMembershipSnapshotProcedureResult;
pub use runtime_profile_membership_snapshot_type::RuntimeProfileMembershipSnapshot;
pub use runtime_profile_membership_status_type::RuntimeProfileMembershipStatus;
pub use runtime_profile_membership_tier_type::RuntimeProfileMembershipTier;
pub use runtime_profile_membership_upgrade_quote_get_input_type::RuntimeProfileMembershipUpgradeQuoteGetInput;
pub use runtime_profile_membership_upgrade_quote_procedure_result_type::RuntimeProfileMembershipUpgradeQuoteProcedureResult;
pub use runtime_profile_membership_upgrade_quote_snapshot_type::RuntimeProfileMembershipUpgradeQuoteSnapshot;
@@ -6,6 +6,7 @@ use super::profile_membership_type::ProfileMembership;
use super::runtime_profile_membership_cycle_kind_type::RuntimeProfileMembershipCycleKind;
use super::runtime_profile_membership_plan_type::RuntimeProfileMembershipPlan;
use super::runtime_profile_membership_status_type::RuntimeProfileMembershipStatus;
use super::runtime_profile_membership_tier_type::RuntimeProfileMembershipTier;
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
/// Table handle for the table `profile_membership`.
@@ -7,12 +7,14 @@ use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
use super::runtime_profile_membership_cycle_kind_type::RuntimeProfileMembershipCycleKind;
use super::runtime_profile_membership_plan_type::RuntimeProfileMembershipPlan;
use super::runtime_profile_membership_status_type::RuntimeProfileMembershipStatus;
use super::runtime_profile_membership_tier_type::RuntimeProfileMembershipTier;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct ProfileMembership {
pub user_id: String,
pub status: RuntimeProfileMembershipStatus,
pub tier: RuntimeProfileMembershipTier,
pub started_at: __sdk::Timestamp,
pub expires_at: __sdk::Timestamp,
pub updated_at: __sdk::Timestamp,
@@ -20,6 +22,7 @@ pub struct ProfileMembership {
pub cycle_resets_at: Option<__sdk::Timestamp>,
pub cycle_granted_points: u64,
pub cycle_remaining_points: u64,
pub cycle_period_days: u32,
pub plan: RuntimeProfileMembershipPlan,
pub cycle_index: u32,
pub cycle_count: u32,
@@ -36,6 +39,7 @@ impl __sdk::InModule for ProfileMembership {
pub struct ProfileMembershipCols {
pub user_id: __sdk::__query_builder::Col<ProfileMembership, String>,
pub status: __sdk::__query_builder::Col<ProfileMembership, RuntimeProfileMembershipStatus>,
pub tier: __sdk::__query_builder::Col<ProfileMembership, RuntimeProfileMembershipTier>,
pub started_at: __sdk::__query_builder::Col<ProfileMembership, __sdk::Timestamp>,
pub expires_at: __sdk::__query_builder::Col<ProfileMembership, __sdk::Timestamp>,
pub updated_at: __sdk::__query_builder::Col<ProfileMembership, __sdk::Timestamp>,
@@ -43,6 +47,7 @@ pub struct ProfileMembershipCols {
pub cycle_resets_at: __sdk::__query_builder::Col<ProfileMembership, Option<__sdk::Timestamp>>,
pub cycle_granted_points: __sdk::__query_builder::Col<ProfileMembership, u64>,
pub cycle_remaining_points: __sdk::__query_builder::Col<ProfileMembership, u64>,
pub cycle_period_days: __sdk::__query_builder::Col<ProfileMembership, u32>,
pub plan: __sdk::__query_builder::Col<ProfileMembership, RuntimeProfileMembershipPlan>,
pub cycle_index: __sdk::__query_builder::Col<ProfileMembership, u32>,
pub cycle_count: __sdk::__query_builder::Col<ProfileMembership, u32>,
@@ -56,6 +61,7 @@ impl __sdk::__query_builder::HasCols for ProfileMembership {
ProfileMembershipCols {
user_id: __sdk::__query_builder::Col::new(table_name, "user_id"),
status: __sdk::__query_builder::Col::new(table_name, "status"),
tier: __sdk::__query_builder::Col::new(table_name, "tier"),
started_at: __sdk::__query_builder::Col::new(table_name, "started_at"),
expires_at: __sdk::__query_builder::Col::new(table_name, "expires_at"),
updated_at: __sdk::__query_builder::Col::new(table_name, "updated_at"),
@@ -69,6 +75,7 @@ impl __sdk::__query_builder::HasCols for ProfileMembership {
table_name,
"cycle_remaining_points",
),
cycle_period_days: __sdk::__query_builder::Col::new(table_name, "cycle_period_days"),
plan: __sdk::__query_builder::Col::new(table_name, "plan"),
cycle_index: __sdk::__query_builder::Col::new(table_name, "cycle_index"),
cycle_count: __sdk::__query_builder::Col::new(table_name, "cycle_count"),
@@ -3,6 +3,7 @@
#![allow(unused, clippy::all)]
use super::profile_recharge_product_config_type::ProfileRechargeProductConfig;
use super::runtime_profile_membership_tier_type::RuntimeProfileMembershipTier;
use super::runtime_profile_recharge_product_kind_type::RuntimeProfileRechargeProductKind;
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
@@ -4,6 +4,7 @@
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
use super::runtime_profile_membership_tier_type::RuntimeProfileMembershipTier;
use super::runtime_profile_recharge_product_kind_type::RuntimeProfileRechargeProductKind;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
@@ -18,12 +19,17 @@ pub struct ProfileRechargeProductConfig {
pub duration_days: u32,
pub badge_label: String,
pub description: String,
pub tier: RuntimeProfileMembershipTier,
pub enabled: bool,
pub sort_order: i32,
pub created_by: String,
pub created_at: __sdk::Timestamp,
pub updated_by: String,
pub updated_at: __sdk::Timestamp,
pub membership_period_points: u64,
pub membership_period_days: u32,
pub membership_queue_limit: u32,
pub membership_discount_bps: u32,
}
impl __sdk::InModule for ProfileRechargeProductConfig {
@@ -46,12 +52,18 @@ pub struct ProfileRechargeProductConfigCols {
pub duration_days: __sdk::__query_builder::Col<ProfileRechargeProductConfig, u32>,
pub badge_label: __sdk::__query_builder::Col<ProfileRechargeProductConfig, String>,
pub description: __sdk::__query_builder::Col<ProfileRechargeProductConfig, String>,
pub tier:
__sdk::__query_builder::Col<ProfileRechargeProductConfig, RuntimeProfileMembershipTier>,
pub enabled: __sdk::__query_builder::Col<ProfileRechargeProductConfig, bool>,
pub sort_order: __sdk::__query_builder::Col<ProfileRechargeProductConfig, i32>,
pub created_by: __sdk::__query_builder::Col<ProfileRechargeProductConfig, String>,
pub created_at: __sdk::__query_builder::Col<ProfileRechargeProductConfig, __sdk::Timestamp>,
pub updated_by: __sdk::__query_builder::Col<ProfileRechargeProductConfig, String>,
pub updated_at: __sdk::__query_builder::Col<ProfileRechargeProductConfig, __sdk::Timestamp>,
pub membership_period_points: __sdk::__query_builder::Col<ProfileRechargeProductConfig, u64>,
pub membership_period_days: __sdk::__query_builder::Col<ProfileRechargeProductConfig, u32>,
pub membership_queue_limit: __sdk::__query_builder::Col<ProfileRechargeProductConfig, u32>,
pub membership_discount_bps: __sdk::__query_builder::Col<ProfileRechargeProductConfig, u32>,
}
impl __sdk::__query_builder::HasCols for ProfileRechargeProductConfig {
@@ -67,12 +79,29 @@ impl __sdk::__query_builder::HasCols for ProfileRechargeProductConfig {
duration_days: __sdk::__query_builder::Col::new(table_name, "duration_days"),
badge_label: __sdk::__query_builder::Col::new(table_name, "badge_label"),
description: __sdk::__query_builder::Col::new(table_name, "description"),
tier: __sdk::__query_builder::Col::new(table_name, "tier"),
enabled: __sdk::__query_builder::Col::new(table_name, "enabled"),
sort_order: __sdk::__query_builder::Col::new(table_name, "sort_order"),
created_by: __sdk::__query_builder::Col::new(table_name, "created_by"),
created_at: __sdk::__query_builder::Col::new(table_name, "created_at"),
updated_by: __sdk::__query_builder::Col::new(table_name, "updated_by"),
updated_at: __sdk::__query_builder::Col::new(table_name, "updated_at"),
membership_period_points: __sdk::__query_builder::Col::new(
table_name,
"membership_period_points",
),
membership_period_days: __sdk::__query_builder::Col::new(
table_name,
"membership_period_days",
),
membership_queue_limit: __sdk::__query_builder::Col::new(
table_name,
"membership_queue_limit",
),
membership_discount_bps: __sdk::__query_builder::Col::new(
table_name,
"membership_discount_bps",
),
}
}
}
@@ -0,0 +1,30 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
#[derive(Copy, Eq, Hash)]
pub enum RuntimeProfileMembershipTier {
Normal,
Month,
Season,
Year,
Starter,
Basic,
Pro,
Ultimate,
}
impl __sdk::InModule for RuntimeProfileMembershipTier {
type Module = super::RemoteModule;
}
@@ -440,6 +440,8 @@ pub struct ProfileMembership {
#[primary_key]
pub(crate) user_id: String,
pub(crate) status: RuntimeProfileMembershipStatus,
// 已退役:旧档位身份列,仅为兼容存量 schema 保留,运行时不读不写。
pub(crate) tier: RuntimeProfileMembershipTier,
pub(crate) started_at: Timestamp,
pub(crate) expires_at: Timestamp,
pub(crate) updated_at: Timestamp,
@@ -451,7 +453,10 @@ pub struct ProfileMembership {
pub(crate) cycle_granted_points: u64,
#[default(0u64)]
pub(crate) cycle_remaining_points: u64,
// 末位追加:档位身份与自然月期数。旧 `tier` 与固定 30 天的 `cycle_period_days` 已退役。
// 已退役:旧固定 30 天周期列,仅为兼容存量 schema 保留,运行时不读不写。
#[default(30u32)]
pub(crate) cycle_period_days: u32,
// 末位追加:档位身份与自然月期数。
#[default(RuntimeProfileMembershipPlan::Normal)]
pub(crate) plan: RuntimeProfileMembershipPlan,
#[default(0u32)]
@@ -475,12 +480,23 @@ pub struct ProfileRechargeProductConfig {
pub(crate) duration_days: u32,
pub(crate) badge_label: String,
pub(crate) description: String,
// 已退役:旧会员档位列,仅为兼容存量 schema 保留,运行时不读不写。
pub(crate) tier: RuntimeProfileMembershipTier,
pub(crate) enabled: bool,
pub(crate) sort_order: i32,
pub(crate) created_by: String,
pub(crate) created_at: Timestamp,
pub(crate) updated_by: String,
pub(crate) updated_at: Timestamp,
// 已退役:旧会员商品权益列,仅为兼容存量 schema 保留,运行时不读不写。
#[default(0u64)]
pub(crate) membership_period_points: u64,
#[default(0u32)]
pub(crate) membership_period_days: u32,
#[default(0u32)]
pub(crate) membership_queue_limit: u32,
#[default(0u32)]
pub(crate) membership_discount_bps: u32,
}
#[spacetimedb::table(accessor = profile_membership_plan)]
@@ -3804,12 +3820,19 @@ mod tests {
duration_days: 0,
badge_label: "首充双倍".to_string(),
description: format!("首充送{points_amount}泥点"),
// 已退役列:不参与运行态,写固定默认值即可。
tier: RuntimeProfileMembershipTier::Normal,
enabled: true,
sort_order: 0,
created_by: PROFILE_RECHARGE_PRODUCT_SYSTEM_USER_ID.to_string(),
created_at: timestamp,
updated_by: PROFILE_RECHARGE_PRODUCT_SYSTEM_USER_ID.to_string(),
updated_at: timestamp,
// 已退役列:不参与运行态,写固定默认值即可。
membership_period_points: 0,
membership_period_days: 0,
membership_queue_limit: 0,
membership_discount_bps: 0,
};
let migration =
@@ -4143,6 +4166,8 @@ mod tests {
let row = || ProfileMembership {
user_id: "user-1".to_string(),
status: RuntimeProfileMembershipStatus::Active,
// 已退役列:不参与运行态,写固定默认值即可。
tier: RuntimeProfileMembershipTier::Normal,
started_at: Timestamp::from_micros_since_unix_epoch(anchor),
expires_at: Timestamp::from_micros_since_unix_epoch(anchor + 400 * day),
updated_at: Timestamp::from_micros_since_unix_epoch(anchor),
@@ -4150,6 +4175,8 @@ mod tests {
cycle_resets_at: Some(Timestamp::from_micros_since_unix_epoch(anchor + 28 * day)),
cycle_granted_points: 400,
cycle_remaining_points: 120,
// 已退役列:不参与运行态,写固定默认值即可。
cycle_period_days: 30,
plan: RuntimeProfileMembershipPlan::Starter,
cycle_index: 1,
cycle_count: 12,
@@ -8172,6 +8199,8 @@ fn upsert_profile_recharge_product_config_record(
duration_days: validated_input.duration_days,
badge_label: validated_input.badge_label,
description: validated_input.description,
// 已退役列:不参与运行态,写固定默认值即可。
tier: RuntimeProfileMembershipTier::Normal,
enabled: validated_input.enabled,
sort_order: validated_input.sort_order,
created_by: existing
@@ -8184,6 +8213,11 @@ fn upsert_profile_recharge_product_config_record(
.unwrap_or(updated_at),
updated_by: validated_input.admin_user_id,
updated_at,
// 已退役列:不参与运行态,写固定默认值即可。
membership_period_points: 0,
membership_period_days: 0,
membership_queue_limit: 0,
membership_discount_bps: 0,
});
Ok(build_profile_recharge_product_config_snapshot_from_row(
&inserted,
@@ -8875,12 +8909,19 @@ fn ensure_default_profile_recharge_product_config(ctx: &ReducerContext) {
duration_days: product.duration_days,
badge_label: product.badge_label,
description: product.description,
// 已退役列:不参与运行态,写固定默认值即可。
tier: RuntimeProfileMembershipTier::Normal,
enabled: true,
sort_order: sort_order as i32,
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,
// 已退役列:不参与运行态,写固定默认值即可。
membership_period_points: 0,
membership_period_days: 0,
membership_queue_limit: 0,
membership_discount_bps: 0,
});
}
}
@@ -10110,6 +10151,8 @@ fn apply_profile_membership_fresh_purchase(
let next_row = ProfileMembership {
user_id: user_id.to_string(),
status: RuntimeProfileMembershipStatus::Active,
// 已退役列:不参与运行态,写固定默认值即可。
tier: RuntimeProfileMembershipTier::Normal,
started_at: purchased_at,
expires_at,
updated_at: purchased_at,
@@ -10119,6 +10162,8 @@ fn apply_profile_membership_fresh_purchase(
cycle_resets_at: Some(cycle_resets_at),
cycle_granted_points: settlement.period_points,
cycle_remaining_points: settlement.period_points,
// 已退役列:不参与运行态,写固定默认值即可。
cycle_period_days: 30,
plan,
cycle_index: 1,
cycle_count: cycle_kind.cycle_count(),