枚举化(会员后台):档位与模型权限请求字段改用 typed enum

- AdminUpsertProfileMembershipPlanRequest.plan/model_access 由 String 改为 token 枚举
- 未登记的档位 / 权限 token 在反序列化阶段直接拒绝,后台不能再持久化任意字符串
- admin handler 去掉字符串解析分支,改为 From 转换
- 删除仅用于手写解析的 parse_runtime_profile_membership_* 辅助函数
This commit is contained in:
2026-10-04 11:46:08 +08:00
parent c660ca41fe
commit d7dec9b475
2 changed files with 5 additions and 31 deletions
@@ -1188,19 +1188,8 @@ pub async fn admin_upsert_profile_membership_plan(
Extension(admin): Extension<AuthenticatedAdmin>,
Json(payload): Json<AdminUpsertProfileMembershipPlanRequest>,
) -> Result<Json<Value>, Response> {
let plan = parse_runtime_profile_membership_plan(&payload.plan).map_err(|error| {
runtime_profile_error_response(
&request_context,
AppError::from_status(StatusCode::BAD_REQUEST).with_message(error),
)
})?;
let model_access = parse_runtime_profile_membership_model_access(&payload.model_access)
.map_err(|error| {
runtime_profile_error_response(
&request_context,
AppError::from_status(StatusCode::BAD_REQUEST).with_message(error),
)
})?;
let plan = payload.plan.into();
let model_access = payload.model_access.into();
let updated_at_micros = OffsetDateTime::now_utc().unix_timestamp_nanos() / 1_000;
let record = state
.spacetime_client()
@@ -2434,22 +2423,7 @@ fn parse_profile_recharge_product_kind(
}
}
/// 会员档位 token → 枚举。JSON 没有枚举,但解析必须严格:未知 token 一律 400。
fn parse_runtime_profile_membership_plan(
raw: &str,
) -> Result<module_runtime::RuntimeProfileMembershipPlan, String> {
module_runtime::RuntimeProfileMembershipPlan::parse(raw)
.ok_or_else(|| "会员档位无效".to_string())
}
/// 模型权限 token → 枚举;`basic` = 仅基础模型,`full` = 基础及高性能模型。
fn parse_runtime_profile_membership_model_access(
raw: &str,
) -> Result<module_runtime::RuntimeProfileMembershipModelAccess, String> {
module_runtime::RuntimeProfileMembershipModelAccess::parse(raw)
.ok_or_else(|| "模型权限无效".to_string())
}
/// 会员档位 / 模型权限 / 周期改由 typed enum 在反序列化阶段校验,未知 token 直接 4xx。
fn parse_tracking_scope_kind(raw: &str) -> Result<RuntimeTrackingScopeKind, String> {
match raw.trim().to_ascii_lowercase().as_str() {
TRACKING_SCOPE_KIND_SITE => Ok(RuntimeTrackingScopeKind::Site),
@@ -801,13 +801,13 @@ pub struct ProfileMembershipPlanAdminListResponse {
#[serde(rename_all = "camelCase")]
pub struct AdminUpsertProfileMembershipPlanRequest {
/// 档位 token,作为主键即档位身份。
pub plan: String,
pub plan: ProfileMembershipPlanToken,
pub title: String,
pub month_price_cents: u64,
pub year_price_cents: u64,
pub period_points: u64,
/// 模型权限 token:`basic` / `full`;本期只落字段、不做服务端拦截。
pub model_access: String,
pub model_access: ProfileMembershipModelAccessToken,
/// 并发上限;`128` 表示不设上限。
pub concurrent_job_limit: u32,
#[serde(default = "default_true")]