From dcaab089cc5e93db67d6374afd68f1f67fdcbb4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Sun, 4 Oct 2026 20:58:21 +0800 Subject: [PATCH] =?UTF-8?q?refactor(=E5=90=8E=E5=8F=B0=E5=A5=91=E7=BA=A6):?= =?UTF-8?q?=20AdminAgcModel.access=20=E6=94=B9=20ProfileMembershipModelAcc?= =?UTF-8?q?essToken?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - shared-contracts 的 AdminAgcModel.access 由裸 String 改为 ProfileMembershipModelAccessToken,未知值在反序列化阶段被拒(评审第 18 条) - module-runtime 补 AgcModelAccess <-> ProfileMembershipModelAccessToken 双向 From - api-server 的 agc_models 去掉 AgcModelAccess::parse 兜底,改直接 From - admin-web 的 AdminAgcModelAccess 改为复用 @genarrative/shared 生成类型 - 扩展 catalog_dto 测试断言缺省 Basic 与未知 access 反序列化失败 --- apps/admin-web/src/api/adminApiTypes.ts | 3 ++- server-rs/crates/api-server/src/agc_models.rs | 22 ++++++++++++++----- .../module-runtime/src/agc_model_access.rs | 18 +++++++++++++++ .../crates/shared-contracts/src/admin.rs | 10 ++++----- 4 files changed, 42 insertions(+), 11 deletions(-) diff --git a/apps/admin-web/src/api/adminApiTypes.ts b/apps/admin-web/src/api/adminApiTypes.ts index 65fe26188..48dac87fe 100644 --- a/apps/admin-web/src/api/adminApiTypes.ts +++ b/apps/admin-web/src/api/adminApiTypes.ts @@ -1174,7 +1174,8 @@ export interface AdminWalletRestrictionResponse { wallet: AdminProfileWalletPayload; } export type AdminAgcAgentMode = 'codex' | 'cc'; -export type AdminAgcModelAccess = 'basic' | 'full'; +/** 复用 `@genarrative/shared` 的生成类型,与后端 `ProfileMembershipModelAccessToken` 同源。 */ +export type AdminAgcModelAccess = ProfileMembershipModelAccess; export type AdminAgcModelProtocol = | 'openai_responses' | 'openai_chat' diff --git a/server-rs/crates/api-server/src/agc_models.rs b/server-rs/crates/api-server/src/agc_models.rs index 6f265632f..957dc9605 100644 --- a/server-rs/crates/api-server/src/agc_models.rs +++ b/server-rs/crates/api-server/src/agc_models.rs @@ -244,10 +244,7 @@ pub async fn admin_save_agc_models( .map_err(|message| { AppError::from_status(StatusCode::BAD_REQUEST).with_message(message) })?; - let access = AgcModelAccess::parse(&m.access).ok_or_else(|| { - AppError::from_status(StatusCode::BAD_REQUEST) - .with_message("权限档只允许 basic 或 full") - })?; + let access = AgcModelAccess::from(m.access); Ok(module_runtime::AgcModel { id: m.id, alias: m.alias, @@ -302,7 +299,7 @@ fn catalog_dto(catalog: AgcModelCatalog) -> AdminAgcModelCatalog { enabled: m.enabled, agent_mode: m.agent_mode.as_str().to_string(), protocol: Some(protocol), - access: m.access.as_str().to_string(), + access: m.access.into(), } }) .collect(), @@ -330,6 +327,21 @@ mod tests { })) .unwrap(); assert_eq!(legacy.agent_mode, "codex"); + assert_eq!( + legacy.access, + shared_contracts::runtime::ProfileMembershipModelAccessToken::Basic + ); + // 权限档 token 在反序列化阶段就收口,未知值不再推迟到 `AgcModelAccess::parse`。 + assert!( + serde_json::from_value::(json!({ + "id": "model-a", + "alias": "model-a", + "modelId": "model-a", + "enabled": true, + "access": "premium" + })) + .is_err() + ); } #[test] diff --git a/server-rs/crates/module-runtime/src/agc_model_access.rs b/server-rs/crates/module-runtime/src/agc_model_access.rs index 4d4285aa7..bf74ee07c 100644 --- a/server-rs/crates/module-runtime/src/agc_model_access.rs +++ b/server-rs/crates/module-runtime/src/agc_model_access.rs @@ -66,6 +66,24 @@ impl From for AgcModelAccess { } } +impl From for AgcModelAccess { + fn from(value: shared_contracts::runtime::ProfileMembershipModelAccessToken) -> Self { + match value { + shared_contracts::runtime::ProfileMembershipModelAccessToken::Basic => Self::Basic, + shared_contracts::runtime::ProfileMembershipModelAccessToken::Full => Self::Full, + } + } +} + +impl From for shared_contracts::runtime::ProfileMembershipModelAccessToken { + fn from(value: AgcModelAccess) -> Self { + match value { + AgcModelAccess::Basic => Self::Basic, + AgcModelAccess::Full => Self::Full, + } + } +} + /// 模型解析失败原因;调用方据此选择错误码与 HTTP 状态,不靠字符串匹配。 #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum AgcModelResolveError { diff --git a/server-rs/crates/shared-contracts/src/admin.rs b/server-rs/crates/shared-contracts/src/admin.rs index adf1c2b46..96c1eebe6 100644 --- a/server-rs/crates/shared-contracts/src/admin.rs +++ b/server-rs/crates/shared-contracts/src/admin.rs @@ -4,8 +4,8 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use crate::runtime::{ - ProfileMembershipChangeKindToken, ProfileMembershipCycleKindToken, ProfileMembershipPlanToken, - ProfileMembershipStatusToken, + ProfileMembershipChangeKindToken, ProfileMembershipCycleKindToken, + ProfileMembershipModelAccessToken, ProfileMembershipPlanToken, ProfileMembershipStatusToken, }; #[cfg(any())] @@ -1706,8 +1706,8 @@ fn default_codex_agent_mode() -> String { } /// 存量后台请求缺省为 `basic`(失败开放),只有人工标注才收紧到 `full`。 -fn default_basic_agc_model_access() -> String { - "basic".to_string() +fn default_basic_agc_model_access() -> ProfileMembershipModelAccessToken { + ProfileMembershipModelAccessToken::Basic } #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] @@ -1726,7 +1726,7 @@ pub struct AdminAgcModel { pub protocol: Option, /// 该模型要求的会员档位:`basic`(缺省,对全部档位开放)/ `full`(后台人工标注)。 #[serde(default = "default_basic_agc_model_access")] - pub access: String, + pub access: ProfileMembershipModelAccessToken, } #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]