diff --git a/docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md b/docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md index eeb525c85..3010bab32 100644 --- a/docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md +++ b/docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md @@ -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` diff --git a/server-rs/crates/module-runtime/src/domain.rs b/server-rs/crates/module-runtime/src/domain.rs index 29e195f25..b94aa6476 100644 --- a/server-rs/crates/module-runtime/src/domain.rs +++ b/server-rs/crates/module-runtime/src/domain.rs @@ -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` 决定(见 diff --git a/server-rs/crates/spacetime-client/src/module_bindings.rs b/server-rs/crates/spacetime-client/src/module_bindings.rs index 2bfd173fe..1465fdd4b 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings.rs @@ -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; diff --git a/server-rs/crates/spacetime-client/src/module_bindings/profile_membership_table.rs b/server-rs/crates/spacetime-client/src/module_bindings/profile_membership_table.rs index b1b70a4db..b2b9ff777 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/profile_membership_table.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/profile_membership_table.rs @@ -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`. diff --git a/server-rs/crates/spacetime-client/src/module_bindings/profile_membership_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/profile_membership_type.rs index 01c8e27d6..451462434 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/profile_membership_type.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/profile_membership_type.rs @@ -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, pub status: __sdk::__query_builder::Col, + pub tier: __sdk::__query_builder::Col, pub started_at: __sdk::__query_builder::Col, pub expires_at: __sdk::__query_builder::Col, pub updated_at: __sdk::__query_builder::Col, @@ -43,6 +47,7 @@ pub struct ProfileMembershipCols { pub cycle_resets_at: __sdk::__query_builder::Col>, pub cycle_granted_points: __sdk::__query_builder::Col, pub cycle_remaining_points: __sdk::__query_builder::Col, + pub cycle_period_days: __sdk::__query_builder::Col, pub plan: __sdk::__query_builder::Col, pub cycle_index: __sdk::__query_builder::Col, pub cycle_count: __sdk::__query_builder::Col, @@ -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"), diff --git a/server-rs/crates/spacetime-client/src/module_bindings/profile_recharge_product_config_table.rs b/server-rs/crates/spacetime-client/src/module_bindings/profile_recharge_product_config_table.rs index d5a19ee07..cf0d11d37 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/profile_recharge_product_config_table.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/profile_recharge_product_config_table.rs @@ -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}; diff --git a/server-rs/crates/spacetime-client/src/module_bindings/profile_recharge_product_config_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/profile_recharge_product_config_type.rs index e3c80e159..e25730490 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/profile_recharge_product_config_type.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/profile_recharge_product_config_type.rs @@ -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, pub badge_label: __sdk::__query_builder::Col, pub description: __sdk::__query_builder::Col, + pub tier: + __sdk::__query_builder::Col, pub enabled: __sdk::__query_builder::Col, pub sort_order: __sdk::__query_builder::Col, pub created_by: __sdk::__query_builder::Col, pub created_at: __sdk::__query_builder::Col, pub updated_by: __sdk::__query_builder::Col, pub updated_at: __sdk::__query_builder::Col, + pub membership_period_points: __sdk::__query_builder::Col, + pub membership_period_days: __sdk::__query_builder::Col, + pub membership_queue_limit: __sdk::__query_builder::Col, + pub membership_discount_bps: __sdk::__query_builder::Col, } 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", + ), } } } diff --git a/server-rs/crates/spacetime-client/src/module_bindings/runtime_profile_membership_tier_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/runtime_profile_membership_tier_type.rs new file mode 100644 index 000000000..f8c9e0748 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/runtime_profile_membership_tier_type.rs @@ -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; +} diff --git a/server-rs/crates/spacetime-module/src/runtime/active/profile.rs b/server-rs/crates/spacetime-module/src/runtime/active/profile.rs index 869fc7af6..708d61f7c 100644 --- a/server-rs/crates/spacetime-module/src/runtime/active/profile.rs +++ b/server-rs/crates/spacetime-module/src/runtime/active/profile.rs @@ -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(),