use super::AdminAppState; use crate::api::ai::admin_endpoint_signature_parts; use crate::handlers::admin::provider::endpoints_admin::payloads::AdminProviderEndpointUpdatePatch; use crate::handlers::admin::provider::shared::payloads::{ AdminProviderCreateRequest, AdminProviderKeyCreateRequest, AdminProviderKeyUpdatePatch, AdminProviderUpdatePatch, }; use crate::handlers::admin::shared::{ normalize_json_array, normalize_json_object, normalize_string_list, }; use crate::handlers::admin::system::shared::configs::apply_admin_system_config_update; use crate::handlers::admin::users::{ hash_admin_user_api_key, normalize_admin_user_api_formats, normalize_admin_user_string_list, }; use crate::handlers::public::normalize_admin_base_url; use crate::GatewayError; use aether_admin::provider::endpoints as admin_provider_endpoints_pure; use aether_admin::provider::models_write as admin_provider_models_write_pure; use aether_admin::system::{ normalize_admin_system_config_key, parse_admin_system_config_array, parse_admin_system_config_import_request, parse_admin_system_config_nested_array, parse_admin_system_config_optional_object, AdminImportMergeMode, AdminSystemConfigEndpoint as ImportedEndpoint, AdminSystemConfigEntry as ImportedSystemConfig, AdminSystemConfigGlobalModel as ImportedGlobalModel, AdminSystemConfigImportCounter, AdminSystemConfigImportStats, AdminSystemConfigLdap as ImportedLdapConfig, AdminSystemConfigOAuthProvider as ImportedOAuthProvider, AdminSystemConfigProvider as ImportedProvider, AdminSystemConfigProviderKey as ImportedProviderKey, AdminSystemConfigProviderModel as ImportedProviderModel, AdminSystemConfigProxyNode as ImportedProxyNode, ADMIN_SYSTEM_PROVIDER_OPS_SENSITIVE_CREDENTIAL_FIELDS, ADMIN_SYSTEM_USERS_SUPPORTED_VERSIONS, }; use aether_data::repository::auth_modules::StoredLdapModuleConfig; use aether_data::repository::oauth_providers::{ EncryptedSecretUpdate, UpsertOAuthProviderConfigRecord, }; use aether_data::repository::wallet::WalletLookupKey; use aether_data_contracts::repository::global_models::{ AdminGlobalModelListQuery, AdminProviderModelListQuery, CreateAdminGlobalModelRecord, UpdateAdminGlobalModelRecord, UpsertAdminProviderModelRecord, }; use axum::{body::Bytes, http}; use serde_json::{json, Map, Value}; use std::collections::{BTreeMap, BTreeSet}; use std::time::{SystemTime, UNIX_EPOCH}; use uuid::Uuid; const ADMIN_SYSTEM_IMPORT_MAX_SIZE_BYTES: usize = 10 * 1024 * 1024; fn invalid_request(detail: impl Into) -> (http::StatusCode, Value) { ( http::StatusCode::BAD_REQUEST, json!({ "detail": detail.into() }), ) } fn trim_required(value: &str, field_name: &str) -> Result { let trimmed = value.trim(); if trimmed.is_empty() { return Err(format!("{field_name} 不能为空")); } Ok(trimmed.to_string()) } fn normalize_optional_price(value: Option, field_name: &str) -> Result, String> { admin_provider_models_write_pure::normalize_optional_price(value, field_name) } fn normalize_supported_capabilities(value: Option>) -> Option { normalize_string_list(value).map(|items| json!(items)) } fn normalize_import_auth_config(value: Option) -> Result, String> { let Some(value) = value else { return Ok(None); }; match value { Value::Null => Ok(None), Value::String(raw) => { let trimmed = raw.trim(); if trimmed.is_empty() { return Ok(None); } let parsed = serde_json::from_str::(trimmed) .map_err(|_| "auth_config 必须是 JSON 对象或 JSON 字符串".to_string())?; normalize_json_object(Some(parsed), "auth_config") } other => normalize_json_object(Some(other), "auth_config"), } } fn encrypt_imported_provider_config( state: &AdminAppState<'_>, config: Option, ) -> Result, String> { let Some(mut config) = normalize_json_object(config, "config")? else { return Ok(None); }; let Some(credentials) = config .get_mut("provider_ops") .and_then(Value::as_object_mut) .and_then(|provider_ops| provider_ops.get_mut("connector")) .and_then(Value::as_object_mut) .and_then(|connector| connector.get_mut("credentials")) .and_then(Value::as_object_mut) else { return Ok(Some(config)); }; for field in ADMIN_SYSTEM_PROVIDER_OPS_SENSITIVE_CREDENTIAL_FIELDS { let Some(Value::String(raw)) = credentials.get_mut(*field) else { continue; }; if raw.is_empty() { continue; } let encrypted = state .encrypt_catalog_secret_with_fallbacks(raw) .ok_or_else(|| "gateway 未配置 Provider Ops 加密密钥".to_string())?; *raw = encrypted; } Ok(Some(config)) } fn remap_import_proxy( proxy: Option, node_id_map: &BTreeMap, ) -> Option { let proxy = match proxy { Some(Value::Object(map)) if map.is_empty() => return None, Some(Value::Object(map)) => map, _ => return None, }; let Some(Value::String(old_node_id)) = proxy.get("node_id") else { return Some(Value::Object(proxy)); }; let old_node_id = old_node_id.trim(); if old_node_id.is_empty() { return Some(Value::Object(proxy)); } let new_node_id = node_id_map.get(old_node_id)?; let mut remapped = proxy; remapped.insert("node_id".to_string(), json!(new_node_id)); Some(Value::Object(remapped)) } fn normalize_import_endpoint_format(value: &str) -> Result { admin_endpoint_signature_parts(value) .map(|(signature, _, _)| signature.to_string()) .ok_or_else(|| format!("无效的 api_format: {value}")) } fn normalize_import_key_formats( item: &ImportedProviderKey, provider_endpoint_formats: &BTreeSet, ) -> (Vec, Vec) { let source = item .api_formats .clone() .filter(|items| !items.is_empty()) .or_else(|| { item.supported_endpoints .clone() .filter(|items| !items.is_empty()) }) .unwrap_or_else(|| provider_endpoint_formats.iter().cloned().collect()); let mut normalized = Vec::new(); let mut missing = Vec::new(); let mut seen = BTreeSet::new(); for raw in source { let trimmed = raw.trim(); if trimmed.is_empty() { continue; } let Ok(api_format) = normalize_import_endpoint_format(trimmed) else { missing.push(trimmed.to_string()); continue; }; if !seen.insert(api_format.clone()) { continue; } if !provider_endpoint_formats.is_empty() && !provider_endpoint_formats.contains(&api_format) { missing.push(api_format); continue; } normalized.push(api_format); } (normalized, missing) } fn imported_key_auth_type(item: &ImportedProviderKey) -> String { item.auth_type .as_deref() .unwrap_or("api_key") .trim() .to_ascii_lowercase() } fn imported_service_account_email(config: Option<&Value>) -> Option { match config { Some(Value::Object(map)) => map .get("client_email") .and_then(Value::as_str) .map(str::trim) .filter(|value| !value.is_empty()) .map(ToOwned::to_owned), Some(Value::String(raw)) => serde_json::from_str::(raw) .ok() .and_then(|value| imported_service_account_email(Some(&value))), _ => None, } } fn build_import_key_match_name(item: &ImportedProviderKey) -> Option { item.name .as_deref() .map(str::trim) .filter(|value| !value.is_empty()) .map(ToOwned::to_owned) } fn normalize_import_key_raw_payload( raw_key: &Map, auth_type: &str, normalized_api_formats: &[String], normalized_auth_config: Option, ) -> Map { let mut payload = raw_key.clone(); if auth_type == "oauth" { payload.remove("api_key"); } payload.insert("api_formats".to_string(), json!(normalized_api_formats)); if let Some(auth_config) = normalized_auth_config { payload.insert("auth_config".to_string(), auth_config); } else if raw_key.contains_key("auth_config") { payload.insert("auth_config".to_string(), Value::Null); } payload } fn apply_imported_oauth_key_credentials( state: &AdminAppState<'_>, raw_key: &Map, normalized_auth_config: Option<&Value>, record: &mut aether_data_contracts::repository::provider_catalog::StoredProviderCatalogKey, ) -> Result<(), String> { if let Some(api_key_value) = raw_key.get("api_key") { let plaintext = api_key_value .as_str() .map(str::trim) .filter(|value| !value.is_empty()); record.encrypted_api_key = match plaintext { Some(plaintext) => Some( state .encrypt_catalog_secret_with_fallbacks(plaintext) .ok_or_else(|| "gateway 未配置 provider key 加密密钥".to_string())?, ), None => None, }; } if raw_key.contains_key("auth_config") { record.encrypted_auth_config = match normalized_auth_config { Some(auth_config) => { let plaintext = serde_json::to_string(auth_config).map_err(|err| err.to_string())?; Some( state .encrypt_catalog_secret_with_fallbacks(&plaintext) .ok_or_else(|| "gateway 未配置 provider key 加密密钥".to_string())?, ) } None => None, }; } // Importing OAuth credentials replaces the previous session state, so stale // expiry/invalid markers must not survive across the overwrite. record.expires_at_unix_secs = imported_oauth_expires_at_unix_secs(normalized_auth_config); record.oauth_invalid_at_unix_secs = None; record.oauth_invalid_reason = None; Ok(()) } fn imported_oauth_expires_at_unix_secs(normalized_auth_config: Option<&Value>) -> Option { let object = normalized_auth_config?.as_object()?; for field in ["expires_at", "expiresAt", "expiry", "exp"] { let Some(value) = object.get(field) else { continue; }; match value { Value::Number(number) => { if let Some(expires_at) = number.as_u64() { return Some(expires_at); } } Value::String(raw) => { if let Ok(expires_at) = raw.trim().parse::() { return Some(expires_at); } } _ => {} } } None } fn imported_oauth_has_refresh_token(normalized_auth_config: Option<&Value>) -> bool { normalized_auth_config .and_then(Value::as_object) .and_then(|object| object.get("refresh_token")) .and_then(Value::as_str) .map(str::trim) .is_some_and(|value| !value.is_empty()) } async fn refresh_imported_oauth_key_after_persist( state: &AdminAppState<'_>, provider: &aether_data_contracts::repository::provider_catalog::StoredProviderCatalogProvider, key_id: &str, ) -> Result<(), GatewayError> { let endpoints = state .list_provider_catalog_endpoints_by_provider_ids(std::slice::from_ref(&provider.id)) .await?; let Some(endpoint) = crate::handlers::admin::provider::oauth::runtime::provider_oauth_runtime_endpoint_for_provider( provider.provider_type.as_str(), &endpoints, ) else { return Ok(()); }; let Some(transport) = state .read_provider_transport_snapshot(&provider.id, &endpoint.id, key_id) .await? else { return Ok(()); }; if !crate::provider_transport::supports_local_oauth_request_auth_resolution(&transport) { return Ok(()); } if let Err(error) = state.force_local_oauth_refresh_entry(&transport).await { tracing::warn!( provider_id = %provider.id, provider_type = %provider.provider_type, key_id = %key_id, error = ?error, "admin system import oauth refresh after credential import failed" ); } Ok(()) } fn build_import_provider_model_record( provider_id: &str, existing_id: Option<&str>, global_model_id: &str, item: &ImportedProviderModel, ) -> Result { let provider_model_name = trim_required(&item.provider_model_name, "provider_model_name")?; let provider_model_mappings = normalize_json_array( item.provider_model_mappings.clone(), "provider_model_mappings", )?; let price_per_request = normalize_optional_price(item.price_per_request, "price_per_request")?; let tiered_pricing = normalize_json_object(item.tiered_pricing.clone(), "tiered_pricing")?; let config = normalize_json_object(item.config.clone(), "config")?; UpsertAdminProviderModelRecord::new( existing_id .map(ToOwned::to_owned) .unwrap_or_else(|| Uuid::new_v4().to_string()), provider_id.to_string(), global_model_id.to_string(), provider_model_name, provider_model_mappings, price_per_request, tiered_pricing, item.supports_vision, item.supports_function_calling, item.supports_streaming, item.supports_extended_thinking, item.supports_image_generation, item.is_active, true, config, ) .map_err(|err| err.to_string()) } #[derive(Debug, Clone, Default, serde::Serialize)] struct AdminSystemUsersImportStats { users: AdminSystemConfigImportCounter, api_keys: AdminSystemConfigImportCounter, standalone_keys: AdminSystemConfigImportCounter, errors: Vec, } #[derive(Debug, Clone)] struct ImportedWalletTarget { recharge_balance: f64, gift_balance: f64, limit_mode: String, currency: String, status: String, total_recharged: f64, total_consumed: f64, total_refunded: f64, total_adjusted: f64, updated_at_unix_secs: Option, } fn imported_system_export_version(version: Option<&Value>) -> Result<(u32, u32), String> { let Some(Value::String(version)) = version else { return Err("version 必须是 x.y 字符串".to_string()); }; let version = version.trim(); if version.is_empty() { return Err("version 必须是 x.y 字符串".to_string()); } let mut parts = version.split('.'); let Some(major) = parts.next().and_then(|value| value.parse::().ok()) else { return Err("version 必须是 x.y 字符串".to_string()); }; let Some(minor) = parts.next().and_then(|value| value.parse::().ok()) else { return Err("version 必须是 x.y 字符串".to_string()); }; Ok((major, minor)) } fn validate_imported_system_users_export_version(version: Option<&Value>) -> Result<(), String> { let Some(Value::String(raw_version)) = version else { return Err("version 必须是 x.y 字符串".to_string()); }; let normalized = raw_version.trim(); if normalized.is_empty() { return Err("version 必须是 x.y 字符串".to_string()); } let _ = imported_system_export_version(version)?; if !ADMIN_SYSTEM_USERS_SUPPORTED_VERSIONS.contains(&normalized) { return Err(format!( "不支持的用户数据版本: {normalized},支持的版本: {}", ADMIN_SYSTEM_USERS_SUPPORTED_VERSIONS.join(", ") )); } Ok(()) } fn imported_object_field<'a>( value: &'a Value, field_name: &str, ) -> Result<&'a Map, String> { value .as_object() .ok_or_else(|| format!("{field_name} 必须是对象")) } fn imported_optional_string(value: Option<&Value>) -> Result, String> { match value { None | Some(Value::Null) => Ok(None), Some(Value::String(raw)) => { let trimmed = raw.trim(); if trimmed.is_empty() { Ok(None) } else { Ok(Some(trimmed.to_string())) } } _ => Err("字段必须是字符串".to_string()), } } fn imported_optional_bool(value: Option<&Value>) -> Result, String> { match value { None | Some(Value::Null) => Ok(None), Some(Value::Bool(value)) => Ok(Some(*value)), _ => Err("字段必须是布尔值".to_string()), } } fn imported_optional_i32(value: Option<&Value>, field_name: &str) -> Result, String> { match value { None | Some(Value::Null) => Ok(None), Some(Value::Number(number)) => number .as_i64() .ok_or_else(|| format!("{field_name} 必须是整数")) .and_then(|value| i32::try_from(value).map_err(|_| format!("{field_name} 超出范围"))) .map(Some), _ => Err(format!("{field_name} 必须是整数")), } } fn imported_optional_u64(value: Option<&Value>, field_name: &str) -> Result, String> { match value { None | Some(Value::Null) => Ok(None), Some(Value::Number(number)) => number .as_u64() .ok_or_else(|| format!("{field_name} 必须是非负整数")) .map(Some), _ => Err(format!("{field_name} 必须是非负整数")), } } fn imported_optional_f64(value: Option<&Value>, field_name: &str) -> Result, String> { match value { None | Some(Value::Null) => Ok(None), Some(Value::Number(number)) => number .as_f64() .filter(|value| value.is_finite()) .ok_or_else(|| format!("{field_name} 必须是有限数值")) .map(Some), Some(Value::String(value)) => value .trim() .parse::() .ok() .filter(|value| value.is_finite()) .ok_or_else(|| format!("{field_name} 必须是有限数值")) .map(Some), _ => Err(format!("{field_name} 必须是有限数值")), } } fn imported_optional_json_object( value: Option<&Value>, field_name: &str, ) -> Result, String> { match value { None | Some(Value::Null) => Ok(None), Some(Value::Object(map)) => Ok(Some(Value::Object(map.clone()))), _ => Err(format!("{field_name} 必须是对象")), } } fn imported_optional_value(value: Option<&Value>) -> Option { value.cloned().filter(|value| !value.is_null()) } fn imported_rfc3339_to_unix_secs( value: Option<&Value>, field_name: &str, ) -> Result, String> { let Some(value) = imported_optional_string(value)? else { return Ok(None); }; let parsed_timestamp = chrono::DateTime::parse_from_rfc3339(&value) .map(|parsed| parsed.timestamp()) .or_else(|_| { chrono::NaiveDateTime::parse_from_str(&value, "%Y-%m-%dT%H:%M:%S%.f") .map(|parsed| parsed.and_utc().timestamp()) }) .map_err(|_| format!("{field_name} 必须是 RFC3339 时间"))?; Ok(Some(parsed_timestamp.max(0) as u64)) } fn imported_string_list_from_value( value: Option<&Value>, field_name: &str, ) -> Result>, String> { let Some(value) = value else { return Ok(None); }; match value { Value::Null => Ok(None), Value::Array(items) => Ok(Some( items .iter() .filter_map(Value::as_str) .map(str::trim) .filter(|value| !value.is_empty()) .map(ToOwned::to_owned) .collect(), )), _ => Err(format!("{field_name} 必须是字符串列表")), } } fn normalize_imported_user_string_list( object: &Map, field_name: &str, ) -> Result>, String> { normalize_admin_user_string_list( imported_string_list_from_value(object.get(field_name), field_name)?, field_name, ) } fn normalize_imported_user_api_formats( object: &Map, field_name: &str, ) -> Result>, String> { normalize_admin_user_api_formats(imported_string_list_from_value( object.get(field_name), field_name, )?) } fn normalize_imported_wallet_target( wallet: Option<&Map>, unlimited: bool, ) -> Result { let gift_balance = imported_optional_f64( wallet.and_then(|map| map.get("gift_balance")), "wallet.gift_balance", )? .unwrap_or(0.0) .max(0.0); let recharge_balance = if let Some(map) = wallet { if map.contains_key("recharge_balance") { imported_optional_f64(map.get("recharge_balance"), "wallet.recharge_balance")? .unwrap_or(0.0) } else if map.contains_key("refundable_balance") { imported_optional_f64(map.get("refundable_balance"), "wallet.refundable_balance")? .unwrap_or(0.0) } else { let total_balance = imported_optional_f64(map.get("balance"), "wallet.balance")?.unwrap_or(0.0); total_balance - gift_balance } } else { 0.0 }; let limit_mode = if let Some(map) = wallet { if let Some(mode) = imported_optional_string(map.get("limit_mode"))? { match mode.to_ascii_lowercase().as_str() { "finite" => "finite".to_string(), "unlimited" => "unlimited".to_string(), _ => return Err("wallet.limit_mode 仅支持 finite / unlimited".to_string()), } } else if imported_optional_bool(map.get("unlimited"))?.unwrap_or(unlimited) { "unlimited".to_string() } else { "finite".to_string() } } else if unlimited { "unlimited".to_string() } else { "finite".to_string() }; let currency = imported_optional_string(wallet.and_then(|map| map.get("currency")))? .unwrap_or_else(|| "USD".to_string()); let status = imported_optional_string(wallet.and_then(|map| map.get("status")))? .unwrap_or_else(|| "active".to_string()); let total_recharged = imported_optional_f64( wallet.and_then(|map| map.get("total_recharged")), "wallet.total_recharged", )? .unwrap_or(recharge_balance); let total_consumed = imported_optional_f64( wallet.and_then(|map| map.get("total_consumed")), "wallet.total_consumed", )? .unwrap_or(0.0); let total_refunded = imported_optional_f64( wallet.and_then(|map| map.get("total_refunded")), "wallet.total_refunded", )? .unwrap_or(0.0); let total_adjusted = imported_optional_f64( wallet.and_then(|map| map.get("total_adjusted")), "wallet.total_adjusted", )? .unwrap_or(gift_balance); let updated_at_unix_secs = imported_rfc3339_to_unix_secs( wallet.and_then(|map| map.get("updated_at")), "wallet.updated_at", )?; Ok(ImportedWalletTarget { recharge_balance, gift_balance, limit_mode, currency, status, total_recharged, total_consumed, total_refunded, total_adjusted, updated_at_unix_secs, }) } impl<'a> AdminAppState<'a> { pub(crate) async fn import_admin_system_config( &self, request_body: &Bytes, ) -> Result, GatewayError> { macro_rules! invalid { ($expr:expr) => { match $expr { Ok(value) => value, Err(detail) => return Ok(Err(invalid_request(detail))), } }; } macro_rules! routed { ($expr:expr) => { match $expr { Ok(value) => value, Err(err) => return Ok(Err(err)), } }; } if !self.has_global_model_data_reader() || !self.has_global_model_data_writer() || !self.has_provider_catalog_data_reader() || !self.has_provider_catalog_data_writer() { return Ok(Err(( http::StatusCode::SERVICE_UNAVAILABLE, json!({ "detail": "Admin system data unavailable" }), ))); } if request_body.len() > ADMIN_SYSTEM_IMPORT_MAX_SIZE_BYTES { return Ok(Err(invalid_request("请求体大小不能超过 10MB"))); } let parsed = routed!(parse_admin_system_config_import_request(request_body)); let root = parsed.root; let merge_mode = parsed.request.merge_mode; let imported_global_models = routed!( parse_admin_system_config_array::(&root, "global_models") ); let imported_providers = routed!(parse_admin_system_config_array::( &root, "providers" )); let imported_proxy_nodes = routed!(parse_admin_system_config_array::( &root, "proxy_nodes" )); let imported_ldap = routed!(parse_admin_system_config_optional_object::< ImportedLdapConfig, >(&root, "ldap_config")); let imported_oauth_providers = routed!(parse_admin_system_config_array::< ImportedOAuthProvider, >(&root, "oauth_providers",)); let imported_system_configs = routed!(parse_admin_system_config_array::< ImportedSystemConfig, >(&root, "system_configs",)); let mut stats = AdminSystemConfigImportStats::default(); let mut global_models_by_name = self .list_admin_global_models(&AdminGlobalModelListQuery { offset: 0, limit: 10_000, is_active: None, search: None, }) .await? .items .into_iter() .map(|model| (model.name.clone(), model)) .collect::>(); if !imported_proxy_nodes.is_empty() { let empty_proxy_node_ids = imported_proxy_nodes .iter() .filter(|node| { node.value .id .as_deref() .map(str::trim) .is_none_or(|value| value.is_empty()) }) .count(); stats.proxy_nodes.skipped = imported_proxy_nodes.len() as u64; if empty_proxy_node_ids > 0 { stats.errors.push(format!( "检测到 {empty_proxy_node_ids} 个无效 proxy_nodes 项;当前 Rust 管理后端暂不支持导入代理节点" )); } else { stats.errors.push( "当前 Rust 管理后端暂不支持导入代理节点;仅引用这些节点(node_id)的自动连接代理配置会被清除,手动 URL 代理配置会保留" .to_string(), ); } } let node_id_map = BTreeMap::::new(); for imported_model in imported_global_models { let (_, model) = imported_model.into_parts(); let name = invalid!(trim_required(&model.name, "name")); let display_name = invalid!(trim_required(&model.display_name, "display_name")); let default_price_per_request = invalid!(normalize_optional_price( model.default_price_per_request, "default_price_per_request", )); let default_tiered_pricing = invalid!(normalize_json_object( model.default_tiered_pricing, "default_tiered_pricing", )); let supported_capabilities = normalize_supported_capabilities(model.supported_capabilities); let config = invalid!(normalize_json_object(model.config, "config")); if let Some(existing) = global_models_by_name.get(&name).cloned() { match merge_mode { AdminImportMergeMode::Skip => { stats.global_models.skipped += 1; } AdminImportMergeMode::Error => { return Ok(Err(invalid_request(format!("GlobalModel '{name}' 已存在")))); } AdminImportMergeMode::Overwrite => { let record = invalid!(UpdateAdminGlobalModelRecord::new( existing.id.clone(), display_name, model.is_active, default_price_per_request, default_tiered_pricing, supported_capabilities, config, ) .map_err(|err| err.to_string())); let Some(updated) = self.update_admin_global_model(&record).await? else { return Ok(Err(invalid_request(format!( "更新 GlobalModel '{name}' 失败" )))); }; global_models_by_name.insert(name, updated); stats.global_models.updated += 1; } } continue; } let record = invalid!(CreateAdminGlobalModelRecord::new( Uuid::new_v4().to_string(), name.clone(), display_name, model.is_active, default_price_per_request, default_tiered_pricing, supported_capabilities, config, ) .map_err(|err| err.to_string())); let Some(created) = self.create_admin_global_model(&record).await? else { return Ok(Err(invalid_request(format!( "创建 GlobalModel '{name}' 失败" )))); }; global_models_by_name.insert(name, created); stats.global_models.created += 1; } let mut providers_by_name = self .list_provider_catalog_providers(false) .await? .into_iter() .map(|provider| (provider.name.clone(), provider)) .collect::>(); for imported_provider_item in imported_providers { let (raw_provider, imported_provider) = imported_provider_item.into_parts(); let provider_name = invalid!(trim_required(&imported_provider.name, "name")); let existing_provider = providers_by_name.get(&provider_name).cloned(); let provider = if let Some(existing) = existing_provider { match merge_mode { AdminImportMergeMode::Skip => { stats.providers.skipped += 1; existing } AdminImportMergeMode::Error => { return Ok(Err(invalid_request(format!( "Provider '{provider_name}' 已存在" )))); } AdminImportMergeMode::Overwrite => { let patch = match AdminProviderUpdatePatch::from_object(raw_provider.clone()) { Ok(patch) => patch, Err(_) => { return Ok(Err(invalid_request(format!( "Provider '{provider_name}' 配置格式无效" )))) } }; let mut updated = invalid!( self.build_admin_update_provider_record(&existing, patch) .await ); updated.proxy = remap_import_proxy(imported_provider.proxy.clone(), &node_id_map); updated.config = invalid!(encrypt_imported_provider_config( self, imported_provider.config.clone(), )); let Some(persisted) = self.update_provider_catalog_provider(&updated).await? else { return Ok(Err(invalid_request(format!( "更新 Provider '{provider_name}' 失败" )))); }; providers_by_name.insert(provider_name.clone(), persisted.clone()); stats.providers.updated += 1; persisted } } } else { let payload = match serde_json::from_value::( Value::Object(raw_provider.clone()), ) { Ok(payload) => payload, Err(_) => { return Ok(Err(invalid_request(format!( "Provider '{provider_name}' 配置格式无效" )))) } }; let (mut record, shift_existing_priorities_from) = invalid!(self.build_admin_create_provider_record(payload).await); if let Some(enable_format_conversion) = imported_provider.enable_format_conversion { record.enable_format_conversion = enable_format_conversion; } record.proxy = remap_import_proxy(imported_provider.proxy.clone(), &node_id_map); record.config = invalid!(encrypt_imported_provider_config( self, imported_provider.config.clone(), )); let Some(created) = self .create_provider_catalog_provider(&record, shift_existing_priorities_from) .await? else { return Ok(Err(invalid_request(format!( "创建 Provider '{provider_name}' 失败" )))); }; providers_by_name.insert(provider_name.clone(), created.clone()); stats.providers.created += 1; created }; let imported_endpoints = routed!(parse_admin_system_config_nested_array::< ImportedEndpoint, >(&raw_provider, "endpoints")); let mut existing_endpoints_by_format = self .list_provider_catalog_endpoints_by_provider_ids(std::slice::from_ref(&provider.id)) .await? .into_iter() .map(|endpoint| (endpoint.api_format.clone(), endpoint)) .collect::>(); for imported_endpoint_item in imported_endpoints { let (raw_endpoint, imported_endpoint) = imported_endpoint_item.into_parts(); let normalized_api_format = invalid!(normalize_import_endpoint_format( &imported_endpoint.api_format )); let existing_endpoint = existing_endpoints_by_format .get(&normalized_api_format) .cloned(); if let Some(existing_endpoint) = existing_endpoint { match merge_mode { AdminImportMergeMode::Skip => { stats.endpoints.skipped += 1; } AdminImportMergeMode::Error => { return Ok(Err(invalid_request(format!( "Endpoint '{normalized_api_format}' 已存在于 Provider '{provider_name}'" )))); } AdminImportMergeMode::Overwrite => { let Some((normalized_signature, api_family, endpoint_kind)) = admin_endpoint_signature_parts(&imported_endpoint.api_format) else { return Ok(Err(invalid_request(format!( "无效的 api_format: {}", imported_endpoint.api_format )))); }; let patch = match AdminProviderEndpointUpdatePatch::from_object( raw_endpoint.clone(), ) { Ok(patch) => patch, Err(_) => { return Ok(Err(invalid_request( "Provider Endpoint 配置格式无效", ))) } }; let (fields, payload) = patch.into_parts(); let normalized_base_url = match payload.base_url.as_deref() { Some(base_url) => { Some(invalid!(normalize_admin_base_url(base_url))) } None => None, }; let update_fields = admin_provider_endpoints_pure::AdminProviderEndpointUpdateFields { base_url: normalized_base_url, custom_path: payload.custom_path, header_rules: payload.header_rules, body_rules: payload.body_rules, max_retries: payload.max_retries, is_active: payload.is_active, config: payload.config, proxy: payload.proxy, format_acceptance_config: payload.format_acceptance_config, }; let mut updated = invalid!( admin_provider_endpoints_pure::apply_admin_provider_endpoint_update_fields( &existing_endpoint, |field| fields.contains(field), |field| fields.is_null(field), &update_fields, ) ); if fields.contains("proxy") { updated.proxy = remap_import_proxy( imported_endpoint.proxy.clone(), &node_id_map, ); } updated.api_format = normalized_signature.to_string(); updated.api_family = Some(api_family.to_string()); updated.endpoint_kind = Some(endpoint_kind.to_string()); updated.updated_at_unix_secs = SystemTime::now() .duration_since(UNIX_EPOCH) .ok() .map(|duration| duration.as_secs()); let Some(persisted) = self.update_provider_catalog_endpoint(&updated).await? else { return Ok(Err(invalid_request(format!( "更新 Endpoint '{normalized_api_format}' 失败" )))); }; existing_endpoints_by_format .insert(normalized_api_format.clone(), persisted); stats.endpoints.updated += 1; } } continue; } let Some((normalized_signature, api_family, endpoint_kind)) = admin_endpoint_signature_parts(&imported_endpoint.api_format) else { return Ok(Err(invalid_request(format!( "无效的 api_format: {}", imported_endpoint.api_format )))); }; let now_unix_secs = SystemTime::now() .duration_since(UNIX_EPOCH) .ok() .map(|duration| duration.as_secs()) .unwrap_or(0); let mut record = invalid!( admin_provider_endpoints_pure::build_admin_provider_endpoint_record( Uuid::new_v4().to_string(), provider.id.clone(), normalized_signature.to_string(), api_family.to_string(), endpoint_kind.to_string(), invalid!(normalize_admin_base_url(&imported_endpoint.base_url)), imported_endpoint.custom_path.clone(), imported_endpoint.header_rules.clone(), imported_endpoint.body_rules.clone(), imported_endpoint.max_retries.unwrap_or(2), imported_endpoint.config.clone(), remap_import_proxy(imported_endpoint.proxy.clone(), &node_id_map), imported_endpoint.format_acceptance_config.clone(), now_unix_secs, ) ); record = record.with_health_score(1.0); record.is_active = imported_endpoint.is_active; let Some(created) = self.create_provider_catalog_endpoint(&record).await? else { return Ok(Err(invalid_request(format!( "创建 Endpoint '{normalized_api_format}' 失败" )))); }; existing_endpoints_by_format.insert(normalized_api_format, created); stats.endpoints.created += 1; } let provider_endpoint_formats = existing_endpoints_by_format .keys() .cloned() .collect::>(); let imported_keys = routed!(parse_admin_system_config_nested_array::< ImportedProviderKey, >(&raw_provider, "api_keys")); let mut existing_keys = self .list_provider_catalog_keys_by_provider_ids(std::slice::from_ref(&provider.id)) .await?; for imported_key_item in imported_keys { let (raw_key, imported_key) = imported_key_item.into_parts(); let (normalized_api_formats, missing_formats) = normalize_import_key_formats(&imported_key, &provider_endpoint_formats); if !missing_formats.is_empty() { stats.errors.push(format!( "Key (Provider: {provider_name}) 的 api_formats 未配置对应 Endpoint,已跳过: {:?}", missing_formats )); } if normalized_api_formats.is_empty() { stats.keys.skipped += 1; continue; } let normalized_auth_config = invalid!(normalize_import_auth_config( imported_key.auth_config.clone() )); let auth_type = imported_key_auth_type(&imported_key); let normalized_raw_key = normalize_import_key_raw_payload( &raw_key, &auth_type, &normalized_api_formats, normalized_auth_config.clone(), ); let existing_key_index = if auth_type == "api_key" { let target_key = imported_key .api_key .as_deref() .map(str::trim) .filter(|value| !value.is_empty()) .map(ToOwned::to_owned); existing_keys.iter().position(|existing_key| { let decrypted_existing = existing_key .encrypted_api_key .as_deref() .and_then(|ciphertext| { self.decrypt_catalog_secret_with_fallbacks(ciphertext) }); target_key .as_deref() .zip(decrypted_existing.as_deref()) .is_some_and(|(target, decrypted)| decrypted == target) }) } else if matches!(auth_type.as_str(), "service_account" | "vertex_ai") { let target_email = imported_service_account_email(normalized_auth_config.as_ref()); existing_keys.iter().position(|existing_key| { target_email.as_deref().is_some_and(|target_email| { self.parse_catalog_auth_config_json(existing_key) .and_then(|config| { config .get("client_email") .and_then(Value::as_str) .map(str::trim) .filter(|value| !value.is_empty()) .map(ToOwned::to_owned) }) .as_deref() == Some(target_email) }) }) } else { build_import_key_match_name(&imported_key).and_then(|target_name| { existing_keys.iter().position(|existing_key| { existing_key .auth_type .trim() .eq_ignore_ascii_case(&auth_type) && existing_key.name == target_name }) }) }; if let Some(existing_index) = existing_key_index { let existing_key = existing_keys[existing_index].clone(); match merge_mode { AdminImportMergeMode::Skip => { stats.keys.skipped += 1; } AdminImportMergeMode::Error => { return Ok(Err(invalid_request(format!( "Provider '{provider_name}' 中存在重复 Key" )))); } AdminImportMergeMode::Overwrite => { let patch = match AdminProviderKeyUpdatePatch::from_object( normalized_raw_key.clone(), ) { Ok(patch) => patch, Err(_) => { return Ok(Err(invalid_request("Provider Key 配置格式无效"))) } }; let mut updated = invalid!( self.build_admin_update_provider_key_record( &provider, &existing_key, patch, ) .await ); if auth_type == "oauth" { invalid!(apply_imported_oauth_key_credentials( self, &raw_key, normalized_auth_config.as_ref(), &mut updated, )); } updated.proxy = remap_import_proxy(imported_key.proxy.clone(), &node_id_map); updated.fingerprint = invalid!(normalize_json_object( imported_key.fingerprint.clone(), "fingerprint", )); let Some(persisted) = self.update_provider_catalog_key(&updated).await? else { return Ok(Err(invalid_request(format!( "更新 Provider '{provider_name}' 的 Key 失败" )))); }; if auth_type == "oauth" && imported_oauth_has_refresh_token(normalized_auth_config.as_ref()) { refresh_imported_oauth_key_after_persist( self, &provider, &persisted.id, ) .await?; } existing_keys[existing_index] = persisted; stats.keys.updated += 1; } } continue; } let payload = match serde_json::from_value::( Value::Object(normalized_raw_key.clone()), ) { Ok(payload) => payload, Err(_) => return Ok(Err(invalid_request("Provider Key 配置格式无效"))), }; let mut record = invalid!( self.build_admin_create_provider_key_record(&provider, payload) .await ); if auth_type == "oauth" { invalid!(apply_imported_oauth_key_credentials( self, &raw_key, normalized_auth_config.as_ref(), &mut record, )); } record.is_active = imported_key.is_active; record.global_priority_by_format = invalid!(normalize_json_object( imported_key.global_priority_by_format.clone(), "global_priority_by_format", )); record.proxy = remap_import_proxy(imported_key.proxy.clone(), &node_id_map); record.fingerprint = invalid!(normalize_json_object( imported_key.fingerprint.clone(), "fingerprint", )); let Some(created) = self.create_provider_catalog_key(&record).await? else { return Ok(Err(invalid_request(format!( "创建 Provider '{provider_name}' 的 Key 失败" )))); }; if auth_type == "oauth" && imported_oauth_has_refresh_token(normalized_auth_config.as_ref()) { refresh_imported_oauth_key_after_persist(self, &provider, &created.id).await?; } existing_keys.push(created); stats.keys.created += 1; } let imported_models = routed!(parse_admin_system_config_nested_array::< ImportedProviderModel, >(&raw_provider, "models")); let mut existing_models_by_name = self .list_admin_provider_models(&AdminProviderModelListQuery { provider_id: provider.id.clone(), is_active: None, offset: 0, limit: 10_000, }) .await? .into_iter() .map(|model| (model.provider_model_name.clone(), model)) .collect::>(); for imported_model_item in imported_models { let (_, imported_model) = imported_model_item.into_parts(); let Some(global_model_name) = imported_model .global_model_name .as_deref() .map(str::trim) .filter(|value| !value.is_empty()) else { stats.errors.push(format!( "跳过无 global_model_name 的模型 (Provider: {provider_name})" )); continue; }; let Some(global_model_id) = global_models_by_name .get(global_model_name) .map(|model| model.id.clone()) else { stats.errors.push(format!( "GlobalModel '{global_model_name}' 不存在,跳过模型" )); continue; }; let provider_model_name = invalid!(trim_required( &imported_model.provider_model_name, "provider_model_name" )); let existing_model = existing_models_by_name.get(&provider_model_name).cloned(); if let Some(existing_model) = existing_model { match merge_mode { AdminImportMergeMode::Skip => { stats.models.skipped += 1; } AdminImportMergeMode::Error => { return Ok(Err(invalid_request(format!( "Model '{provider_model_name}' 已存在于 Provider '{provider_name}'" )))); } AdminImportMergeMode::Overwrite => { let record = invalid!(build_import_provider_model_record( &provider.id, Some(&existing_model.id), &global_model_id, &imported_model, )); let Some(updated) = self.update_admin_provider_model(&record).await? else { return Ok(Err(invalid_request(format!( "更新 Provider '{provider_name}' 的模型 '{provider_model_name}' 失败" )))); }; existing_models_by_name.insert(provider_model_name, updated); stats.models.updated += 1; } } continue; } let record = invalid!(build_import_provider_model_record( &provider.id, None, &global_model_id, &imported_model, )); let Some(created) = self.create_admin_provider_model(&record).await? else { return Ok(Err(invalid_request(format!( "创建 Provider '{provider_name}' 的模型 '{provider_model_name}' 失败" )))); }; existing_models_by_name.insert(provider_model_name, created); stats.models.created += 1; } } if let Some(imported_ldap_item) = imported_ldap { let (_, ldap_config) = imported_ldap_item.into_parts(); if !self.has_auth_module_writer() { stats.ldap.skipped += 1; stats .errors .push("当前运行环境不支持写入 LDAP 配置,已跳过 ldap_config".to_string()); } else { let existing = self.get_ldap_module_config().await?; let server_url = invalid!(trim_required(&ldap_config.server_url, "LDAP 服务器地址")); let bind_dn = invalid!(trim_required(&ldap_config.bind_dn, "绑定 DN")); let base_dn = invalid!(trim_required(&ldap_config.base_dn, "Base DN")); let user_search_filter = invalid!(trim_required( ldap_config .user_search_filter .as_deref() .unwrap_or("(uid={username})"), "搜索过滤器", )); let username_attr = invalid!(trim_required( ldap_config.username_attr.as_deref().unwrap_or("uid"), "用户名属性", )); let email_attr = invalid!(trim_required( ldap_config.email_attr.as_deref().unwrap_or("mail"), "邮箱属性", )); let display_name_attr = invalid!(trim_required( ldap_config.display_name_attr.as_deref().unwrap_or("cn"), "显示名称属性", )); let connect_timeout = ldap_config.connect_timeout.unwrap_or(10); if !(1..=60).contains(&connect_timeout) { return Ok(Err(invalid_request( "LDAP connect_timeout 必须在 1 到 60 秒之间", ))); } let bind_password = ldap_config .bind_password .as_deref() .map(str::trim) .map(ToOwned::to_owned); let will_have_password = bind_password .as_deref() .map(|value| !value.is_empty()) .unwrap_or_else(|| { existing .as_ref() .and_then(|config| config.bind_password_encrypted.as_deref()) .map(str::trim) .is_some_and(|value| !value.is_empty()) }); if existing.is_none() && !will_have_password { return Ok(Err(invalid_request("首次配置 LDAP 时必须设置绑定密码"))); } if ldap_config.is_exclusive && !ldap_config.is_enabled { return Ok(Err(invalid_request( "仅允许 LDAP 登录 需要先启用 LDAP 认证", ))); } if ldap_config.is_enabled && !will_have_password { return Ok(Err(invalid_request("启用 LDAP 认证 需要先设置绑定密码"))); } if ldap_config.is_enabled && ldap_config.is_exclusive { let admin_count = self .count_active_local_admin_users_with_valid_password() .await?; if admin_count < 1 { return Ok(Err(invalid_request( "启用 LDAP 独占模式前,必须至少保留 1 个有效的本地管理员账户(含有效密码)作为紧急恢复通道", ))); } } let bind_password_encrypted = match bind_password { Some(password) if password.is_empty() => None, Some(password) => Some(routed!(self .encrypt_catalog_secret_with_fallbacks(&password) .ok_or_else(|| { invalid_request("LDAP 绑定密码加密失败,请检查 Rust 数据加密配置") }))), None => existing .as_ref() .and_then(|config| config.bind_password_encrypted.clone()), }; let config = StoredLdapModuleConfig { server_url, bind_dn, bind_password_encrypted, base_dn, user_search_filter: Some(user_search_filter), username_attr: Some(username_attr), email_attr: Some(email_attr), display_name_attr: Some(display_name_attr), is_enabled: ldap_config.is_enabled, is_exclusive: ldap_config.is_exclusive, use_starttls: ldap_config.use_starttls, connect_timeout: Some(connect_timeout), }; match (existing.is_some(), merge_mode) { (true, AdminImportMergeMode::Skip) => stats.ldap.skipped += 1, (true, AdminImportMergeMode::Error) => { return Ok(Err(invalid_request("LDAP 配置已存在"))); } (true, AdminImportMergeMode::Overwrite) => { let Some(_) = self.upsert_ldap_module_config(&config).await? else { return Ok(Err(invalid_request("更新 LDAP 配置失败"))); }; stats.ldap.updated += 1; } (false, _) => { let Some(_) = self.upsert_ldap_module_config(&config).await? else { return Ok(Err(invalid_request("创建 LDAP 配置失败"))); }; stats.ldap.created += 1; } } } } if !imported_oauth_providers.is_empty() { let imported_oauth_provider_count = imported_oauth_providers.len(); let mut oauth_by_type = self .list_oauth_provider_configs() .await? .into_iter() .map(|provider| (provider.provider_type.clone(), provider)) .collect::>(); for (index, imported_oauth_item) in imported_oauth_providers.into_iter().enumerate() { let (_, oauth_provider) = imported_oauth_item.into_parts(); let provider_type = invalid!(trim_required( &oauth_provider.provider_type, "provider_type", )); let existed = oauth_by_type.contains_key(&provider_type); if existed { match merge_mode { AdminImportMergeMode::Skip => { stats.oauth.skipped += 1; continue; } AdminImportMergeMode::Error => { return Ok(Err(invalid_request(format!( "OAuth Provider '{provider_type}' 已存在" )))); } AdminImportMergeMode::Overwrite => {} } } let display_name = invalid!(trim_required(&oauth_provider.display_name, "display_name")); let client_id = invalid!(trim_required(&oauth_provider.client_id, "client_id")); let redirect_uri = invalid!(trim_required(&oauth_provider.redirect_uri, "redirect_uri")); let frontend_callback_url = invalid!(trim_required( &oauth_provider.frontend_callback_url, "frontend_callback_url", )); let client_secret_encrypted = match oauth_provider.client_secret.as_deref().map(str::trim) { Some(secret) if !secret.is_empty() => { EncryptedSecretUpdate::Set(routed!(self .encrypt_catalog_secret_with_fallbacks(secret) .ok_or_else(|| { invalid_request("gateway 未配置 OAuth provider 加密密钥") }))) } _ => EncryptedSecretUpdate::Preserve, }; let record = UpsertOAuthProviderConfigRecord { provider_type: provider_type.clone(), display_name, client_id, client_secret_encrypted, authorization_url_override: oauth_provider .authorization_url_override .map(|value| value.trim().to_string()) .filter(|value| !value.is_empty()), token_url_override: oauth_provider .token_url_override .map(|value| value.trim().to_string()) .filter(|value| !value.is_empty()), userinfo_url_override: oauth_provider .userinfo_url_override .map(|value| value.trim().to_string()) .filter(|value| !value.is_empty()), scopes: normalize_string_list(oauth_provider.scopes), redirect_uri, frontend_callback_url, attribute_mapping: invalid!(normalize_json_object( oauth_provider.attribute_mapping, "attribute_mapping", )), extra_config: invalid!(normalize_json_object( oauth_provider.extra_config, "extra_config", )), is_enabled: oauth_provider.is_enabled, }; invalid!(record.validate().map_err(|err| err.to_string())); let Some(persisted) = self.upsert_oauth_provider_config(&record).await? else { stats.oauth.skipped += (imported_oauth_provider_count - index) as u64; stats.errors.push( "当前运行环境不支持 OAuth Provider 配置读写,已跳过 oauth_providers" .to_string(), ); break; }; oauth_by_type.insert(provider_type, persisted); if existed { stats.oauth.updated += 1; } else { stats.oauth.created += 1; } } } let mut existing_system_config_keys = self .list_system_config_entries() .await? .into_iter() .map(|entry| normalize_admin_system_config_key(&entry.key)) .collect::>(); for imported_config_item in imported_system_configs { let (_, system_config) = imported_config_item.into_parts(); let normalized_key = normalize_admin_system_config_key(&system_config.key); let exists = existing_system_config_keys.contains(&normalized_key); match (exists, merge_mode) { (true, AdminImportMergeMode::Skip) => { stats.system_configs.skipped += 1; continue; } (true, AdminImportMergeMode::Error) => { return Ok(Err(invalid_request(format!( "SystemConfig '{normalized_key}' 已存在" )))); } _ => {} } let request_bytes = Bytes::from( serde_json::to_vec(&json!({ "value": system_config.value, "description": system_config.description, })) .map_err(|err| GatewayError::Internal(err.to_string()))?, ); match apply_admin_system_config_update(self, &system_config.key, &request_bytes).await? { Ok(_) => { if exists { stats.system_configs.updated += 1; } else { stats.system_configs.created += 1; existing_system_config_keys.insert(normalized_key); } } Err((status, payload)) => return Ok(Err((status, payload))), } } Ok(Ok(json!({ "message": "配置导入成功", "stats": stats, }))) } pub(crate) async fn import_admin_system_users( &self, request_body: &Bytes, operator_id: Option<&str>, ) -> Result, GatewayError> { if !self.has_auth_user_write_capability() || !self.has_auth_wallet_write_capability() || !self.has_auth_api_key_writer() { return Ok(Err(( http::StatusCode::SERVICE_UNAVAILABLE, json!({ "detail": "Admin system data unavailable" }), ))); } if request_body.len() > ADMIN_SYSTEM_IMPORT_MAX_SIZE_BYTES { return Ok(Err(invalid_request("请求体大小不能超过 10MB"))); } let root = match serde_json::from_slice::(request_body) { Ok(Value::Object(map)) => map, _ => return Ok(Err(invalid_request("请求数据验证失败"))), }; let merge_mode = match serde_json::from_value::( root.get("merge_mode").cloned().unwrap_or(Value::Null), ) { Ok(value) => value, Err(_) => { return Ok(Err(invalid_request( "merge_mode 仅支持 skip / overwrite / error", ))) } }; let empty = Vec::new(); let users = match root.get("users") { Some(Value::Array(items)) => items, Some(_) => return Ok(Err(invalid_request("users 必须是数组"))), None => &empty, }; let standalone_keys = match root.get("standalone_keys") { Some(Value::Array(items)) => items, Some(_) => return Ok(Err(invalid_request("standalone_keys 必须是数组"))), None => &empty, }; let standalone_owner_id = match operator_id { Some(candidate) => match self.find_user_auth_by_id(candidate).await? { Some(user) if user.role.eq_ignore_ascii_case("admin") => Some(user.id), _ => None, }, None => None, }; macro_rules! invalid_value { ($expr:expr) => { match $expr { Ok(value) => value, Err(detail) => return Ok(Err(invalid_request(detail))), } }; } invalid_value!(validate_imported_system_users_export_version( root.get("version") )); let mut stats = AdminSystemUsersImportStats::default(); for (index, raw_user) in users.iter().enumerate() { let user = match imported_object_field(raw_user, &format!("users[{index}]")) { Ok(value) => value, Err(detail) => return Ok(Err(invalid_request(detail))), }; let role = invalid_value!(imported_optional_string(user.get("role"))) .unwrap_or_else(|| "user".to_string()) .to_ascii_lowercase(); if role == "admin" { let skipped_email = invalid_value!(imported_optional_string(user.get("email"))); let skipped_username = invalid_value!(imported_optional_string(user.get("username"))); stats.users.skipped += 1; stats.errors.push(format!( "跳过管理员用户: {}", skipped_email .or(skipped_username) .unwrap_or_else(|| format!("users[{index}]")) )); continue; } let email = invalid_value!(imported_optional_string(user.get("email"))) .map(|value| value.to_ascii_lowercase()); let email_verified = invalid_value!(imported_optional_bool(user.get("email_verified"))).unwrap_or(true); let username = invalid_value!(imported_optional_string(user.get("username"))) .or_else(|| { email.as_ref().map(|value| { value .split('@') .next() .unwrap_or(value.as_str()) .to_string() }) }) .unwrap_or_else(|| format!("imported-user-{index}")); let password_hash = invalid_value!(imported_optional_string(user.get("password_hash"))); let allowed_providers = invalid_value!(normalize_imported_user_string_list( user, "allowed_providers" )); let allowed_api_formats = invalid_value!(normalize_imported_user_api_formats( user, "allowed_api_formats" )); let allowed_models = invalid_value!(normalize_imported_user_string_list(user, "allowed_models")); let rate_limit = invalid_value!(imported_optional_i32(user.get("rate_limit"), "rate_limit")); let is_active = invalid_value!(imported_optional_bool(user.get("is_active"))).unwrap_or(true); let model_capability_settings = invalid_value!(imported_optional_json_object( user.get("model_capability_settings"), "model_capability_settings" )); let wallet_payload = match user.get("wallet") { Some(Value::Object(map)) => Some(map), Some(Value::Null) | None => None, Some(_) => return Ok(Err(invalid_request("wallet 必须是对象"))), }; let wallet_target = invalid_value!(normalize_imported_wallet_target(wallet_payload, false)); let mut existing_user = if let Some(email) = email.as_deref() { self.find_user_auth_by_identifier(email).await? } else { None }; if existing_user.is_none() { existing_user = self.find_user_auth_by_identifier(&username).await?; } let user_id = if let Some(existing) = existing_user { if existing.role.eq_ignore_ascii_case("admin") { stats.users.skipped += 1; stats.errors.push(format!( "跳过管理员用户记录: {}", email.clone().unwrap_or(username.clone()) )); continue; } match merge_mode { AdminImportMergeMode::Skip => { stats.users.skipped += 1; continue; } AdminImportMergeMode::Error => { return Ok(Err(invalid_request(format!( "用户 '{}' 已存在", email.clone().unwrap_or(username.clone()) )))); } AdminImportMergeMode::Overwrite => { if let Some(email) = email.as_deref() { if self .is_other_user_auth_email_taken(email, &existing.id) .await? { return Ok(Err(invalid_request(format!("邮箱已存在: {email}")))); } } if self .is_other_user_auth_username_taken(&username, &existing.id) .await? { return Ok(Err(invalid_request(format!("用户名已存在: {username}")))); } let updated_profile = self .update_local_auth_user_profile( &existing.id, email.clone(), Some(username.clone()), ) .await?; if updated_profile.is_none() { return Ok(Err(( http::StatusCode::SERVICE_UNAVAILABLE, json!({ "detail": "Admin system data unavailable" }), ))); } if let Some(password_hash) = password_hash.as_deref().filter(|value| !value.is_empty()) { let updated_password = self .update_local_auth_user_password_hash( &existing.id, password_hash.to_string(), chrono::Utc::now(), ) .await?; if updated_password.is_none() { return Ok(Err(( http::StatusCode::SERVICE_UNAVAILABLE, json!({ "detail": "Admin system data unavailable" }), ))); } } let updated_admin_fields = self .update_local_auth_user_admin_fields( &existing.id, Some(role.clone()), user.contains_key("allowed_providers"), allowed_providers.clone(), user.contains_key("allowed_api_formats"), allowed_api_formats.clone(), user.contains_key("allowed_models"), allowed_models.clone(), user.contains_key("rate_limit"), rate_limit, Some(is_active), ) .await?; if updated_admin_fields.is_none() { return Ok(Err(( http::StatusCode::SERVICE_UNAVAILABLE, json!({ "detail": "Admin system data unavailable" }), ))); } if user.contains_key("email_verified") { stats.errors.push(format!( "用户 '{}' 的 email_verified 当前不会覆盖已有值", email.clone().unwrap_or(username.clone()) )); } if user.contains_key("model_capability_settings") { let _ = self .update_user_model_capability_settings( &existing.id, model_capability_settings.clone(), ) .await?; } self.sync_imported_user_wallet( &existing.id, &wallet_target, &email.clone().unwrap_or(username.clone()), ) .await?; stats.users.updated += 1; existing.id } } } else { let created = self .create_local_auth_user_with_settings( email.clone(), email_verified, username.clone(), password_hash.unwrap_or_default(), role.clone(), allowed_providers.clone(), allowed_api_formats.clone(), allowed_models.clone(), rate_limit, ) .await?; let Some(created) = created else { return Ok(Err(( http::StatusCode::SERVICE_UNAVAILABLE, json!({ "detail": "Admin system data unavailable" }), ))); }; if user.contains_key("model_capability_settings") { let _ = self .update_user_model_capability_settings( &created.id, model_capability_settings.clone(), ) .await?; } self.sync_imported_user_wallet( &created.id, &wallet_target, &email.clone().unwrap_or(username.clone()), ) .await?; stats.users.created += 1; created.id }; let existing_api_keys = self .list_auth_api_key_export_records_by_user_ids(std::slice::from_ref(&user_id)) .await? .into_iter() .filter(|record| !record.is_standalone) .collect::>(); let imported_api_keys = match user.get("api_keys") { Some(Value::Array(items)) => items, Some(_) => return Ok(Err(invalid_request("api_keys 必须是数组"))), None => &empty, }; let mut existing_api_keys_by_hash = existing_api_keys .into_iter() .map(|record| (record.key_hash.clone(), record)) .collect::>(); for (key_index, raw_key) in imported_api_keys.iter().enumerate() { let key = match imported_object_field( raw_key, &format!("users[{index}].api_keys[{key_index}]"), ) { Ok(value) => value, Err(detail) => return Ok(Err(invalid_request(detail))), }; let Some((key_hash, key_encrypted)) = invalid_value!(self.resolve_imported_system_user_api_key_material(key)) else { stats.api_keys.skipped += 1; stats.errors.push(format!( "跳过无效 API Key: 用户 '{}'", email.clone().unwrap_or(username.clone()) )); continue; }; let name = invalid_value!(imported_optional_string(key.get("name"))); let allowed_providers = invalid_value!(normalize_imported_user_string_list( key, "allowed_providers" )); let allowed_api_formats = invalid_value!(normalize_imported_user_api_formats( key, "allowed_api_formats" )); let allowed_models = invalid_value!(normalize_imported_user_string_list(key, "allowed_models")); let rate_limit = invalid_value!(imported_optional_i32(key.get("rate_limit"), "rate_limit")) .unwrap_or(0); let concurrent_limit = invalid_value!(imported_optional_i32( key.get("concurrent_limit"), "concurrent_limit" )); if concurrent_limit.is_some_and(|value| value < 0) { return Ok(Err(invalid_request("concurrent_limit 必须是非负整数"))); } let force_capabilities = imported_optional_value(key.get("force_capabilities")); let is_active = invalid_value!(imported_optional_bool(key.get("is_active"))).unwrap_or(true); let expires_at_unix_secs = invalid_value!(imported_rfc3339_to_unix_secs( key.get("expires_at"), "expires_at" )); let auto_delete_on_expiry = invalid_value!(imported_optional_bool(key.get("auto_delete_on_expiry"))) .unwrap_or(false); let total_requests = invalid_value!(imported_optional_u64( key.get("total_requests"), "total_requests" )) .unwrap_or(0); let total_tokens = invalid_value!(imported_optional_u64( key.get("total_tokens"), "total_tokens" )) .unwrap_or(0); let total_cost_usd = invalid_value!(imported_optional_f64( key.get("total_cost_usd"), "total_cost_usd" )) .unwrap_or(0.0); if let Some(existing_key) = existing_api_keys_by_hash.get(&key_hash).cloned() { match merge_mode { AdminImportMergeMode::Skip => { stats.api_keys.skipped += 1; } AdminImportMergeMode::Error => { return Ok(Err(invalid_request(format!( "用户 '{}' 的 API Key 已存在", email.clone().unwrap_or(username.clone()) )))); } AdminImportMergeMode::Overwrite => { let updated = self .update_user_api_key_basic( aether_data::repository::auth::UpdateUserApiKeyBasicRecord { user_id: user_id.clone(), api_key_id: existing_key.api_key_id.clone(), name: name.clone(), rate_limit: Some(rate_limit), concurrent_limit: if key.contains_key("concurrent_limit") { concurrent_limit } else { None }, }, ) .await?; if updated.is_none() { return Ok(Err(( http::StatusCode::SERVICE_UNAVAILABLE, json!({ "detail": "Admin system data unavailable" }), ))); } let _ = self .set_user_api_key_allowed_providers( &user_id, &existing_key.api_key_id, allowed_providers.clone(), ) .await?; let _ = self .set_user_api_key_force_capabilities( &user_id, &existing_key.api_key_id, force_capabilities.clone(), ) .await?; let _ = self .set_user_api_key_active( &user_id, &existing_key.api_key_id, is_active, ) .await?; if key.contains_key("allowed_api_formats") || key.contains_key("allowed_models") || key.contains_key("expires_at") || key.contains_key("auto_delete_on_expiry") || key.contains_key("total_requests") || key.contains_key("total_tokens") || key.contains_key("total_cost_usd") { stats.errors.push(format!( "用户 '{}' 的现有 API Key 仅覆盖基础字段;高级导入字段保持原值", email.clone().unwrap_or(username.clone()) )); } stats.api_keys.updated += 1; } } continue; } let created = self .create_user_api_key(aether_data::repository::auth::CreateUserApiKeyRecord { user_id: user_id.clone(), api_key_id: Uuid::new_v4().to_string(), key_hash: key_hash.clone(), key_encrypted, name, allowed_providers, allowed_api_formats, allowed_models, rate_limit, concurrent_limit, force_capabilities, is_active, expires_at_unix_secs, auto_delete_on_expiry, total_requests, total_tokens, total_cost_usd, }) .await?; let Some(created) = created else { return Ok(Err(( http::StatusCode::SERVICE_UNAVAILABLE, json!({ "detail": "Admin system data unavailable" }), ))); }; existing_api_keys_by_hash.insert(key_hash, created); stats.api_keys.created += 1; } } if standalone_keys.is_empty() { return Ok(Ok(json!({ "message": "用户数据导入成功", "stats": stats, }))); } let Some(standalone_owner_id) = standalone_owner_id else { stats.standalone_keys.skipped += standalone_keys.len() as u64; stats .errors .push("无法导入独立余额 Key: 当前管理员用户记录不存在".to_string()); return Ok(Ok(json!({ "message": "用户数据导入成功", "stats": stats, }))); }; let existing_standalone_keys = self .list_auth_api_key_export_standalone_records() .await? .into_iter() .collect::>(); let mut existing_standalone_by_hash = existing_standalone_keys .into_iter() .map(|record| (record.key_hash.clone(), record)) .collect::>(); for (index, raw_key) in standalone_keys.iter().enumerate() { let key = match imported_object_field(raw_key, &format!("standalone_keys[{index}]")) { Ok(value) => value, Err(detail) => return Ok(Err(invalid_request(detail))), }; let Some((key_hash, key_encrypted)) = invalid_value!(self.resolve_imported_system_user_api_key_material(key)) else { stats.standalone_keys.skipped += 1; stats .errors .push(format!("跳过无效独立余额 Key: standalone_keys[{index}]")); continue; }; let name = invalid_value!(imported_optional_string(key.get("name"))); let allowed_providers = invalid_value!(normalize_imported_user_string_list( key, "allowed_providers" )); let allowed_api_formats = invalid_value!(normalize_imported_user_api_formats( key, "allowed_api_formats" )); let allowed_models = invalid_value!(normalize_imported_user_string_list(key, "allowed_models")); let rate_limit = invalid_value!(imported_optional_i32(key.get("rate_limit"), "rate_limit")) .unwrap_or(0); let concurrent_limit = invalid_value!(imported_optional_i32( key.get("concurrent_limit"), "concurrent_limit" )); if concurrent_limit.is_some_and(|value| value < 0) { return Ok(Err(invalid_request("concurrent_limit 必须是非负整数"))); } let force_capabilities = imported_optional_value(key.get("force_capabilities")); let is_active = invalid_value!(imported_optional_bool(key.get("is_active"))).unwrap_or(true); let expires_at_unix_secs = invalid_value!(imported_rfc3339_to_unix_secs( key.get("expires_at"), "expires_at" )); let auto_delete_on_expiry = invalid_value!(imported_optional_bool(key.get("auto_delete_on_expiry"))) .unwrap_or(false); let total_requests = invalid_value!(imported_optional_u64( key.get("total_requests"), "total_requests" )) .unwrap_or(0); let total_tokens = invalid_value!(imported_optional_u64( key.get("total_tokens"), "total_tokens" )) .unwrap_or(0); let total_cost_usd = invalid_value!(imported_optional_f64( key.get("total_cost_usd"), "total_cost_usd" )) .unwrap_or(0.0); let wallet_payload = match key.get("wallet") { Some(Value::Object(map)) => Some(map), Some(Value::Null) | None => None, Some(_) => return Ok(Err(invalid_request("wallet 必须是对象"))), }; let unlimited = invalid_value!(imported_optional_bool(key.get("unlimited"))).unwrap_or(false); let wallet_target = invalid_value!(normalize_imported_wallet_target(wallet_payload, unlimited)); if let Some(existing_key) = existing_standalone_by_hash.get(&key_hash).cloned() { match merge_mode { AdminImportMergeMode::Skip => { stats.standalone_keys.skipped += 1; } AdminImportMergeMode::Error => { return Ok(Err(invalid_request("独立余额 Key 已存在"))); } AdminImportMergeMode::Overwrite => { let updated = self .update_standalone_api_key_basic( aether_data::repository::auth::UpdateStandaloneApiKeyBasicRecord { api_key_id: existing_key.api_key_id.clone(), name: name.clone(), rate_limit_present: true, rate_limit: Some(rate_limit), concurrent_limit_present: key.contains_key("concurrent_limit"), concurrent_limit, allowed_providers: Some(allowed_providers.clone()), allowed_api_formats: Some(allowed_api_formats.clone()), allowed_models: Some(allowed_models.clone()), expires_at_present: false, expires_at_unix_secs: None, auto_delete_on_expiry_present: false, auto_delete_on_expiry: false, }, ) .await?; if updated.is_none() { return Ok(Err(( http::StatusCode::SERVICE_UNAVAILABLE, json!({ "detail": "Admin system data unavailable" }), ))); } let _ = self .set_standalone_api_key_active(&existing_key.api_key_id, is_active) .await?; if key.contains_key("expires_at") || key.contains_key("auto_delete_on_expiry") || key.contains_key("force_capabilities") || key.contains_key("total_requests") || key.contains_key("total_tokens") || key.contains_key("total_cost_usd") { stats.errors.push( "现有独立余额 Key 仅覆盖基础字段;高级导入字段保持原值".to_string(), ); } self.sync_imported_api_key_wallet( &existing_key.api_key_id, &wallet_target, key.get("name") .and_then(Value::as_str) .unwrap_or("独立余额 Key"), ) .await?; stats.standalone_keys.updated += 1; } } continue; } let created = self .create_standalone_api_key( aether_data::repository::auth::CreateStandaloneApiKeyRecord { user_id: standalone_owner_id.clone(), api_key_id: Uuid::new_v4().to_string(), key_hash: key_hash.clone(), key_encrypted, name, allowed_providers, allowed_api_formats, allowed_models, rate_limit: Some(rate_limit), concurrent_limit, force_capabilities, is_active, expires_at_unix_secs, auto_delete_on_expiry, total_requests, total_tokens, total_cost_usd, }, ) .await?; let Some(created) = created else { return Ok(Err(( http::StatusCode::SERVICE_UNAVAILABLE, json!({ "detail": "Admin system data unavailable" }), ))); }; self.sync_imported_api_key_wallet( &created.api_key_id, &wallet_target, created.name.as_deref().unwrap_or("独立余额 Key"), ) .await?; existing_standalone_by_hash.insert(key_hash, created); stats.standalone_keys.created += 1; } Ok(Ok(json!({ "message": "用户数据导入成功", "stats": stats, }))) } async fn sync_imported_user_wallet( &self, user_id: &str, wallet_target: &ImportedWalletTarget, label: &str, ) -> Result<(), GatewayError> { if self .find_wallet(WalletLookupKey::UserId(user_id)) .await? .is_none() { let created = self .initialize_auth_user_wallet(user_id, 0.0, false) .await?; if created.is_none() { return Err(GatewayError::Internal(format!( "failed to initialize imported wallet for {label}" ))); } } self.sync_wallet_snapshot(WalletOwner::User(user_id), wallet_target, label) .await } async fn sync_imported_api_key_wallet( &self, api_key_id: &str, wallet_target: &ImportedWalletTarget, label: &str, ) -> Result<(), GatewayError> { if self .find_wallet(WalletLookupKey::ApiKeyId(api_key_id)) .await? .is_none() { let created = self .initialize_auth_api_key_wallet(api_key_id, 0.0, false) .await?; if created.is_none() { return Err(GatewayError::Internal(format!( "failed to initialize imported wallet for {label}" ))); } } self.sync_wallet_snapshot(WalletOwner::ApiKey(api_key_id), wallet_target, label) .await } async fn sync_wallet_snapshot( &self, owner: WalletOwner<'_>, wallet_target: &ImportedWalletTarget, label: &str, ) -> Result<(), GatewayError> { let updated = match owner { WalletOwner::User(user_id) => { self.update_auth_user_wallet_snapshot( user_id, wallet_target.recharge_balance, wallet_target.gift_balance, &wallet_target.limit_mode, &wallet_target.currency, &wallet_target.status, wallet_target.total_recharged, wallet_target.total_consumed, wallet_target.total_refunded, wallet_target.total_adjusted, wallet_target.updated_at_unix_secs, ) .await? } WalletOwner::ApiKey(api_key_id) => { self.update_auth_api_key_wallet_snapshot( api_key_id, wallet_target.recharge_balance, wallet_target.gift_balance, &wallet_target.limit_mode, &wallet_target.currency, &wallet_target.status, wallet_target.total_recharged, wallet_target.total_consumed, wallet_target.total_refunded, wallet_target.total_adjusted, wallet_target.updated_at_unix_secs, ) .await? } }; if updated.is_none() { return Err(GatewayError::Internal(format!( "failed to persist imported wallet snapshot for {label}" ))); } Ok(()) } fn resolve_imported_system_user_api_key_material( &self, key: &Map, ) -> Result)>, String> { let plaintext_key = imported_optional_string(key.get("key"))?; if let Some(plaintext_key) = plaintext_key.filter(|value| !value.is_empty()) { return Ok(Some(( hash_admin_user_api_key(&plaintext_key), self.encrypt_catalog_secret_with_fallbacks(&plaintext_key), ))); } let key_hash = imported_optional_string(key.get("key_hash"))?; let key_encrypted = imported_optional_string(key.get("key_encrypted"))?; Ok(key_hash.map(|key_hash| (key_hash, key_encrypted))) } } #[derive(Clone, Copy)] enum WalletOwner<'a> { User(&'a str), ApiKey(&'a str), } #[cfg(test)] mod tests { use serde_json::json; use super::{ imported_optional_bool, imported_optional_f64, imported_optional_i32, imported_optional_u64, imported_rfc3339_to_unix_secs, imported_string_list_from_value, normalize_imported_wallet_target, validate_imported_system_users_export_version, }; #[test] fn users_import_requires_supported_export_version() { assert!(validate_imported_system_users_export_version(Some(&json!("1.3"))).is_ok()); assert_eq!( validate_imported_system_users_export_version(Some(&json!("2.2"))).unwrap_err(), "不支持的用户数据版本: 2.2,支持的版本: 1.3" ); assert_eq!( validate_imported_system_users_export_version(Some(&json!(null))).unwrap_err(), "version 必须是 x.y 字符串" ); } #[test] fn import_handles_legacy_string_scalars() { assert_eq!( imported_optional_bool(Some(&json!("true"))).unwrap_err(), "字段必须是布尔值" ); assert_eq!( imported_optional_i32(Some(&json!("5")), "rate_limit").unwrap_err(), "rate_limit 必须是整数" ); assert_eq!( imported_optional_u64(Some(&json!("5")), "total_requests").unwrap_err(), "total_requests 必须是非负整数" ); assert_eq!( imported_optional_f64(Some(&json!("1.25000000")), "total_cost_usd").unwrap(), Some(1.25) ); assert_eq!( imported_optional_f64(Some(&json!("not-a-number")), "total_cost_usd").unwrap_err(), "total_cost_usd 必须是有限数值" ); } #[test] fn import_handles_python_isoformat_timestamps() { assert_eq!( imported_rfc3339_to_unix_secs(Some(&json!("2099-01-01T00:00:00+00:00")), "expires_at") .unwrap(), Some(4_070_908_800) ); assert_eq!( imported_rfc3339_to_unix_secs(Some(&json!("2099-01-01T00:00:00")), "expires_at") .unwrap(), Some(4_070_908_800) ); assert_eq!( imported_rfc3339_to_unix_secs(Some(&json!("invalid")), "expires_at").unwrap_err(), "expires_at 必须是 RFC3339 时间" ); } #[test] fn import_preserves_python_wallet_negative_recharge_balance() { let wallet = json!({ "balance": -4.5, "recharge_balance": -5.25, "gift_balance": 0.75, "limit_mode": "finite" }); let wallet = wallet.as_object().expect("wallet should be object"); let target = normalize_imported_wallet_target(Some(wallet), false).unwrap(); assert_eq!(target.recharge_balance, -5.25); assert_eq!(target.gift_balance, 0.75); assert_eq!(target.total_recharged, -5.25); } #[test] fn import_preserves_python_wallet_negative_balance_fallback() { let wallet = json!({ "balance": -4.5, "gift_balance": 0.75, "limit_mode": "finite" }); let wallet = wallet.as_object().expect("wallet should be object"); let target = normalize_imported_wallet_target(Some(wallet), false).unwrap(); assert_eq!(target.recharge_balance, -5.25); assert_eq!(target.gift_balance, 0.75); } #[test] fn import_rejects_legacy_string_lists() { assert_eq!( imported_string_list_from_value(Some(&json!("openai")), "allowed_providers") .unwrap_err(), "allowed_providers 必须是字符串列表" ); assert_eq!( imported_string_list_from_value( Some(&json!("[\"openai:chat\"]")), "allowed_api_formats" ) .unwrap_err(), "allowed_api_formats 必须是字符串列表" ); } }