mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-10 03:09:50 +08:00
feat(admin): 实现系统数据导入导出功能,支持提供商和模型批量配置
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
use crate::handlers::admin::provider::shared::payloads::AdminProviderKeyUpdateRequest;
|
||||
use crate::handlers::admin::provider::shared::payloads::AdminProviderKeyUpdatePatch;
|
||||
use crate::handlers::admin::provider::write::normalize::{
|
||||
normalize_auth_type, validate_vertex_api_formats,
|
||||
};
|
||||
@@ -17,11 +17,11 @@ pub(crate) async fn build_admin_update_provider_key_record(
|
||||
state: &AdminAppState<'_>,
|
||||
provider: &StoredProviderCatalogProvider,
|
||||
existing: &StoredProviderCatalogKey,
|
||||
raw_payload: &serde_json::Map<String, serde_json::Value>,
|
||||
payload: AdminProviderKeyUpdateRequest,
|
||||
patch: AdminProviderKeyUpdatePatch,
|
||||
) -> Result<StoredProviderCatalogKey, String> {
|
||||
let state = state.as_ref();
|
||||
let mut updated = existing.clone();
|
||||
let (fields, payload) = patch.into_parts();
|
||||
let current_auth_type = normalize_auth_type(Some(&existing.auth_type))?;
|
||||
let target_auth_type = payload
|
||||
.auth_type
|
||||
@@ -34,7 +34,7 @@ pub(crate) async fn build_admin_update_provider_key_record(
|
||||
.as_deref()
|
||||
.is_some_and(|_| target_auth_type != current_auth_type);
|
||||
|
||||
let api_key_present = raw_payload.contains_key("api_key");
|
||||
let api_key_present = fields.contains("api_key");
|
||||
let api_key_value = payload
|
||||
.api_key
|
||||
.as_deref()
|
||||
@@ -44,7 +44,7 @@ pub(crate) async fn build_admin_update_provider_key_record(
|
||||
return Err("api_key 不能为空".to_string());
|
||||
}
|
||||
|
||||
let auth_config_present = raw_payload.contains_key("auth_config");
|
||||
let auth_config_present = fields.contains("auth_config");
|
||||
let auth_config = normalize_json_object(payload.auth_config, "auth_config")?;
|
||||
let auth_config_object = auth_config
|
||||
.as_ref()
|
||||
@@ -182,7 +182,7 @@ pub(crate) async fn build_admin_update_provider_key_record(
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if raw_payload.contains_key("api_formats") {
|
||||
if fields.contains("api_formats") {
|
||||
let api_formats = normalize_string_list(payload.api_formats)
|
||||
.ok_or_else(|| "api_formats 为必填字段".to_string())?;
|
||||
validate_vertex_api_formats(&provider.provider_type, &target_auth_type, &api_formats)?;
|
||||
@@ -201,30 +201,30 @@ pub(crate) async fn build_admin_update_provider_key_record(
|
||||
}
|
||||
updated.name = trimmed.to_string();
|
||||
}
|
||||
if raw_payload.contains_key("rate_multipliers") {
|
||||
if fields.contains("rate_multipliers") {
|
||||
updated.rate_multipliers =
|
||||
normalize_json_object(payload.rate_multipliers, "rate_multipliers")?;
|
||||
}
|
||||
if let Some(internal_priority) = payload.internal_priority {
|
||||
updated.internal_priority = internal_priority;
|
||||
}
|
||||
if raw_payload.contains_key("global_priority_by_format") {
|
||||
if fields.contains("global_priority_by_format") {
|
||||
updated.global_priority_by_format = normalize_json_object(
|
||||
payload.global_priority_by_format,
|
||||
"global_priority_by_format",
|
||||
)?;
|
||||
}
|
||||
if raw_payload.contains_key("rpm_limit") {
|
||||
if fields.contains("rpm_limit") {
|
||||
updated.rpm_limit = payload.rpm_limit;
|
||||
if payload.rpm_limit.is_none() {
|
||||
updated.learned_rpm_limit = None;
|
||||
}
|
||||
}
|
||||
if raw_payload.contains_key("allowed_models") {
|
||||
if fields.contains("allowed_models") {
|
||||
updated.allowed_models =
|
||||
normalize_string_list(payload.allowed_models).map(|value| json!(value));
|
||||
}
|
||||
if raw_payload.contains_key("capabilities") {
|
||||
if fields.contains("capabilities") {
|
||||
updated.capabilities = normalize_json_object(payload.capabilities, "capabilities")?;
|
||||
}
|
||||
if let Some(cache_ttl_minutes) = payload.cache_ttl_minutes {
|
||||
@@ -236,7 +236,7 @@ pub(crate) async fn build_admin_update_provider_key_record(
|
||||
if let Some(is_active) = payload.is_active {
|
||||
updated.is_active = is_active;
|
||||
}
|
||||
if raw_payload.contains_key("note") {
|
||||
if fields.contains("note") {
|
||||
updated.note = payload
|
||||
.note
|
||||
.map(|value| value.trim().to_string())
|
||||
@@ -245,22 +245,22 @@ pub(crate) async fn build_admin_update_provider_key_record(
|
||||
if let Some(auto_fetch_models) = payload.auto_fetch_models {
|
||||
updated.auto_fetch_models = auto_fetch_models;
|
||||
}
|
||||
if raw_payload.contains_key("locked_models") {
|
||||
if fields.contains("locked_models") {
|
||||
updated.locked_models =
|
||||
normalize_string_list(payload.locked_models).map(|value| json!(value));
|
||||
}
|
||||
if raw_payload.contains_key("model_include_patterns") {
|
||||
if fields.contains("model_include_patterns") {
|
||||
updated.model_include_patterns =
|
||||
normalize_string_list(payload.model_include_patterns).map(|value| json!(value));
|
||||
}
|
||||
if raw_payload.contains_key("model_exclude_patterns") {
|
||||
if fields.contains("model_exclude_patterns") {
|
||||
updated.model_exclude_patterns =
|
||||
normalize_string_list(payload.model_exclude_patterns).map(|value| json!(value));
|
||||
}
|
||||
if raw_payload.contains_key("proxy") {
|
||||
if fields.contains("proxy") {
|
||||
updated.proxy = normalize_json_object(payload.proxy, "proxy")?;
|
||||
}
|
||||
if raw_payload.contains_key("fingerprint") {
|
||||
if fields.contains("fingerprint") {
|
||||
updated.fingerprint = normalize_json_object(payload.fingerprint, "fingerprint")?;
|
||||
}
|
||||
if auth_config_present && !auth_type_switch && updated.auth_type != "api_key" {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::handlers::admin::provider::shared::payloads::AdminProviderUpdateRequest;
|
||||
use crate::handlers::admin::provider::shared::payloads::AdminProviderUpdatePatch;
|
||||
use crate::handlers::admin::provider::shared::support::{
|
||||
normalize_provider_billing_type, parse_optional_rfc3339_unix_secs,
|
||||
};
|
||||
@@ -11,15 +11,15 @@ use std::time::{SystemTime, UNIX_EPOCH};
|
||||
pub(crate) async fn build_admin_update_provider_record(
|
||||
state: &AdminAppState<'_>,
|
||||
existing: &StoredProviderCatalogProvider,
|
||||
raw_payload: &serde_json::Map<String, serde_json::Value>,
|
||||
payload: AdminProviderUpdateRequest,
|
||||
patch: AdminProviderUpdatePatch,
|
||||
) -> Result<StoredProviderCatalogProvider, String> {
|
||||
let state = state.as_ref();
|
||||
let mut updated = existing.clone();
|
||||
let (fields, payload) = patch.into_parts();
|
||||
|
||||
if let Some(value) = raw_payload.get("name") {
|
||||
if fields.contains("name") {
|
||||
let Some(name) = payload.name.as_deref() else {
|
||||
return Err(if value.is_null() {
|
||||
return Err(if fields.is_null("name") {
|
||||
"name 不能为空".to_string()
|
||||
} else {
|
||||
"name 必须是字符串".to_string()
|
||||
@@ -41,9 +41,9 @@ pub(crate) async fn build_admin_update_provider_record(
|
||||
updated.name = trimmed.to_string();
|
||||
}
|
||||
|
||||
let target_provider_type = if let Some(value) = raw_payload.get("provider_type") {
|
||||
let target_provider_type = if fields.contains("provider_type") {
|
||||
let Some(provider_type) = payload.provider_type.as_deref() else {
|
||||
return Err(if value.is_null() {
|
||||
return Err(if fields.is_null("provider_type") {
|
||||
"provider_type 不能为空".to_string()
|
||||
} else {
|
||||
"provider_type 必须是字符串".to_string()
|
||||
@@ -56,17 +56,17 @@ pub(crate) async fn build_admin_update_provider_record(
|
||||
updated.provider_type.clone()
|
||||
};
|
||||
|
||||
if raw_payload.contains_key("description") {
|
||||
if fields.contains("description") {
|
||||
updated.description = payload
|
||||
.description
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty());
|
||||
}
|
||||
|
||||
if let Some(value) = raw_payload.get("website") {
|
||||
if fields.contains("website") {
|
||||
updated.website = match payload.website {
|
||||
None => {
|
||||
if value.is_null() {
|
||||
if fields.is_null("website") {
|
||||
None
|
||||
} else {
|
||||
return Err("website 必须是字符串".to_string());
|
||||
@@ -85,9 +85,9 @@ pub(crate) async fn build_admin_update_provider_record(
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(value) = raw_payload.get("billing_type") {
|
||||
if fields.contains("billing_type") {
|
||||
let Some(billing_type) = payload.billing_type.as_deref() else {
|
||||
return Err(if value.is_null() {
|
||||
return Err(if fields.is_null("billing_type") {
|
||||
"billing_type 不能为空".to_string()
|
||||
} else {
|
||||
"billing_type 必须是字符串".to_string()
|
||||
@@ -96,8 +96,8 @@ pub(crate) async fn build_admin_update_provider_record(
|
||||
updated.billing_type = Some(normalize_provider_billing_type(billing_type)?);
|
||||
}
|
||||
|
||||
if let Some(value) = raw_payload.get("monthly_quota_usd") {
|
||||
if value.is_null() {
|
||||
if fields.contains("monthly_quota_usd") {
|
||||
if fields.is_null("monthly_quota_usd") {
|
||||
updated.monthly_quota_usd = None;
|
||||
} else {
|
||||
let Some(monthly_quota_usd) = payload.monthly_quota_usd else {
|
||||
@@ -110,8 +110,8 @@ pub(crate) async fn build_admin_update_provider_record(
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(value) = raw_payload.get("quota_reset_day") {
|
||||
if value.is_null() {
|
||||
if fields.contains("quota_reset_day") {
|
||||
if fields.is_null("quota_reset_day") {
|
||||
updated.quota_reset_day = None;
|
||||
} else {
|
||||
let Some(quota_reset_day) = payload.quota_reset_day else {
|
||||
@@ -124,8 +124,8 @@ pub(crate) async fn build_admin_update_provider_record(
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(value) = raw_payload.get("quota_last_reset_at") {
|
||||
if value.is_null() {
|
||||
if fields.contains("quota_last_reset_at") {
|
||||
if fields.is_null("quota_last_reset_at") {
|
||||
updated.quota_last_reset_at_unix_secs = None;
|
||||
} else {
|
||||
let Some(raw) = payload.quota_last_reset_at.as_deref() else {
|
||||
@@ -138,8 +138,8 @@ pub(crate) async fn build_admin_update_provider_record(
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(value) = raw_payload.get("quota_expires_at") {
|
||||
if value.is_null() {
|
||||
if fields.contains("quota_expires_at") {
|
||||
if fields.is_null("quota_expires_at") {
|
||||
updated.quota_expires_at_unix_secs = None;
|
||||
} else {
|
||||
let Some(raw) = payload.quota_expires_at.as_deref() else {
|
||||
@@ -150,9 +150,9 @@ pub(crate) async fn build_admin_update_provider_record(
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(value) = raw_payload.get("provider_priority") {
|
||||
if fields.contains("provider_priority") {
|
||||
let Some(provider_priority) = payload.provider_priority else {
|
||||
return Err(if value.is_null() {
|
||||
return Err(if fields.is_null("provider_priority") {
|
||||
"provider_priority 不能为空".to_string()
|
||||
} else {
|
||||
"provider_priority 必须是整数".to_string()
|
||||
@@ -164,21 +164,21 @@ pub(crate) async fn build_admin_update_provider_record(
|
||||
updated.provider_priority = provider_priority;
|
||||
}
|
||||
|
||||
if let Some(_value) = raw_payload.get("keep_priority_on_conversion") {
|
||||
if fields.contains("keep_priority_on_conversion") {
|
||||
let Some(keep_priority_on_conversion) = payload.keep_priority_on_conversion else {
|
||||
return Err("keep_priority_on_conversion 必须是布尔值".to_string());
|
||||
};
|
||||
updated.keep_priority_on_conversion = keep_priority_on_conversion;
|
||||
}
|
||||
|
||||
if let Some(_value) = raw_payload.get("is_active") {
|
||||
if fields.contains("is_active") {
|
||||
let Some(is_active) = payload.is_active else {
|
||||
return Err("is_active 必须是布尔值".to_string());
|
||||
};
|
||||
updated.is_active = is_active;
|
||||
}
|
||||
|
||||
if raw_payload.contains_key("concurrent_limit") {
|
||||
if fields.contains("concurrent_limit") {
|
||||
updated.concurrent_limit = match payload.concurrent_limit {
|
||||
Some(value) if value >= 0 => Some(value),
|
||||
Some(_) => return Err("concurrent_limit 必须是非负整数".to_string()),
|
||||
@@ -186,7 +186,7 @@ pub(crate) async fn build_admin_update_provider_record(
|
||||
};
|
||||
}
|
||||
|
||||
if raw_payload.contains_key("max_retries") {
|
||||
if fields.contains("max_retries") {
|
||||
updated.max_retries = match payload.max_retries {
|
||||
Some(value) if (0..=999).contains(&value) => Some(value),
|
||||
Some(_) => return Err("max_retries 必须是 0 到 999 之间的整数".to_string()),
|
||||
@@ -194,11 +194,11 @@ pub(crate) async fn build_admin_update_provider_record(
|
||||
};
|
||||
}
|
||||
|
||||
if raw_payload.contains_key("proxy") {
|
||||
if fields.contains("proxy") {
|
||||
updated.proxy = normalize_json_object(payload.proxy, "proxy")?;
|
||||
}
|
||||
|
||||
if raw_payload.contains_key("stream_first_byte_timeout") {
|
||||
if fields.contains("stream_first_byte_timeout") {
|
||||
updated.stream_first_byte_timeout_secs = match payload.stream_first_byte_timeout {
|
||||
Some(value) if (1.0..=300.0).contains(&value) => Some(value),
|
||||
Some(_) => {
|
||||
@@ -208,7 +208,7 @@ pub(crate) async fn build_admin_update_provider_record(
|
||||
};
|
||||
}
|
||||
|
||||
if raw_payload.contains_key("request_timeout") {
|
||||
if fields.contains("request_timeout") {
|
||||
updated.request_timeout_secs = match payload.request_timeout {
|
||||
Some(value) if (1.0..=600.0).contains(&value) => Some(value),
|
||||
Some(_) => return Err("request_timeout 必须是 1 到 600 之间的数字".to_string()),
|
||||
@@ -216,14 +216,14 @@ pub(crate) async fn build_admin_update_provider_record(
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(_value) = raw_payload.get("enable_format_conversion") {
|
||||
if fields.contains("enable_format_conversion") {
|
||||
let Some(enable_format_conversion) = payload.enable_format_conversion else {
|
||||
return Err("enable_format_conversion 必须是布尔值".to_string());
|
||||
};
|
||||
updated.enable_format_conversion = enable_format_conversion;
|
||||
}
|
||||
|
||||
let config_seed = if raw_payload.contains_key("config") {
|
||||
let config_seed = if fields.contains("config") {
|
||||
normalize_json_object(payload.config, "config")?
|
||||
} else {
|
||||
updated.config.clone()
|
||||
@@ -232,11 +232,8 @@ pub(crate) async fn build_admin_update_provider_record(
|
||||
.and_then(|value| value.as_object().cloned())
|
||||
.unwrap_or_default();
|
||||
|
||||
if raw_payload.contains_key("claude_code_advanced") {
|
||||
if raw_payload
|
||||
.get("claude_code_advanced")
|
||||
.is_some_and(serde_json::Value::is_null)
|
||||
{
|
||||
if fields.contains("claude_code_advanced") {
|
||||
if fields.is_null("claude_code_advanced") {
|
||||
config_map.remove("claude_code_advanced");
|
||||
} else {
|
||||
if target_provider_type != "claude_code" {
|
||||
@@ -251,11 +248,8 @@ pub(crate) async fn build_admin_update_provider_record(
|
||||
config_map.remove("claude_code_advanced");
|
||||
}
|
||||
|
||||
if raw_payload.contains_key("pool_advanced") {
|
||||
if raw_payload
|
||||
.get("pool_advanced")
|
||||
.is_some_and(serde_json::Value::is_null)
|
||||
{
|
||||
if fields.contains("pool_advanced") {
|
||||
if fields.is_null("pool_advanced") {
|
||||
config_map.remove("pool_advanced");
|
||||
} else {
|
||||
let value = normalize_json_object(payload.pool_advanced, "pool_advanced")?
|
||||
@@ -264,11 +258,8 @@ pub(crate) async fn build_admin_update_provider_record(
|
||||
}
|
||||
}
|
||||
|
||||
if raw_payload.contains_key("failover_rules") {
|
||||
if raw_payload
|
||||
.get("failover_rules")
|
||||
.is_some_and(serde_json::Value::is_null)
|
||||
{
|
||||
if fields.contains("failover_rules") {
|
||||
if fields.is_null("failover_rules") {
|
||||
config_map.remove("failover_rules");
|
||||
} else {
|
||||
let value = normalize_json_object(payload.failover_rules, "failover_rules")?
|
||||
|
||||
Reference in New Issue
Block a user