mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 09:50:21 +08:00
refactor: 拆分 gateway 单体为独立 crate,新增 systemd 部署方案
将 gateway 内部的 model-fetch、provider-transport、scheduler-core、 usage-runtime、video-tasks-core 模块提取为独立 crate;重构 gateway 内部模块结构(state/router/cache/data/query 等);移除大量遗留模块 文件;新增 systemd 二进制部署骨架及相关文档;更新前端 usage 相关 API 和组件。
This commit is contained in:
229
crates/aether-provider-transport/src/snapshot_mapping.rs
Normal file
229
crates/aether-provider-transport/src/snapshot_mapping.rs
Normal file
@@ -0,0 +1,229 @@
|
||||
use aether_crypto::{decrypt_python_fernet_ciphertext, looks_like_python_fernet_ciphertext};
|
||||
use aether_data::repository::provider_catalog::{
|
||||
StoredProviderCatalogEndpoint, StoredProviderCatalogKey, StoredProviderCatalogProvider,
|
||||
};
|
||||
use aether_data::DataLayerError;
|
||||
|
||||
use super::{
|
||||
GatewayProviderTransportEndpoint, GatewayProviderTransportKey, GatewayProviderTransportProvider,
|
||||
};
|
||||
|
||||
pub(super) fn map_provider(
|
||||
provider: StoredProviderCatalogProvider,
|
||||
) -> GatewayProviderTransportProvider {
|
||||
GatewayProviderTransportProvider {
|
||||
id: provider.id,
|
||||
name: provider.name,
|
||||
provider_type: provider.provider_type,
|
||||
website: provider.website,
|
||||
is_active: provider.is_active,
|
||||
keep_priority_on_conversion: provider.keep_priority_on_conversion,
|
||||
enable_format_conversion: provider.enable_format_conversion,
|
||||
concurrent_limit: provider.concurrent_limit,
|
||||
max_retries: provider.max_retries,
|
||||
proxy: normalize_optional_json(provider.proxy),
|
||||
request_timeout_secs: provider.request_timeout_secs,
|
||||
stream_first_byte_timeout_secs: provider.stream_first_byte_timeout_secs,
|
||||
config: normalize_optional_json(provider.config),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn map_endpoint(
|
||||
endpoint: StoredProviderCatalogEndpoint,
|
||||
) -> GatewayProviderTransportEndpoint {
|
||||
GatewayProviderTransportEndpoint {
|
||||
id: endpoint.id,
|
||||
provider_id: endpoint.provider_id,
|
||||
api_format: endpoint.api_format,
|
||||
api_family: endpoint.api_family,
|
||||
endpoint_kind: endpoint.endpoint_kind,
|
||||
is_active: endpoint.is_active,
|
||||
base_url: endpoint.base_url,
|
||||
header_rules: normalize_optional_json(endpoint.header_rules),
|
||||
body_rules: normalize_optional_json(endpoint.body_rules),
|
||||
max_retries: endpoint.max_retries,
|
||||
custom_path: endpoint.custom_path,
|
||||
config: normalize_optional_json(endpoint.config),
|
||||
format_acceptance_config: normalize_optional_json(endpoint.format_acceptance_config),
|
||||
proxy: normalize_optional_json(endpoint.proxy),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn map_key(
|
||||
key: StoredProviderCatalogKey,
|
||||
encryption_key: &str,
|
||||
fallback_encryption_keys: &[String],
|
||||
) -> Result<GatewayProviderTransportKey, DataLayerError> {
|
||||
let decrypted_api_key = decrypt_secret(
|
||||
encryption_key,
|
||||
fallback_encryption_keys,
|
||||
&key.encrypted_api_key,
|
||||
"provider_api_keys.api_key",
|
||||
)?;
|
||||
let decrypted_auth_config = key
|
||||
.encrypted_auth_config
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(|ciphertext| {
|
||||
decrypt_secret(
|
||||
encryption_key,
|
||||
fallback_encryption_keys,
|
||||
ciphertext,
|
||||
"provider_api_keys.auth_config",
|
||||
)
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
Ok(GatewayProviderTransportKey {
|
||||
id: key.id,
|
||||
provider_id: key.provider_id,
|
||||
name: key.name,
|
||||
auth_type: key.auth_type,
|
||||
is_active: key.is_active,
|
||||
api_formats: normalize_string_list(
|
||||
normalize_optional_json(key.api_formats),
|
||||
"provider_api_keys.api_formats",
|
||||
)?,
|
||||
allowed_models: normalize_string_list(
|
||||
normalize_optional_json(key.allowed_models),
|
||||
"provider_api_keys.allowed_models",
|
||||
)?,
|
||||
capabilities: normalize_optional_json(key.capabilities),
|
||||
rate_multipliers: normalize_optional_json(key.rate_multipliers),
|
||||
global_priority_by_format: normalize_optional_json(key.global_priority_by_format),
|
||||
expires_at_unix_secs: key.expires_at_unix_secs,
|
||||
proxy: normalize_optional_json(key.proxy),
|
||||
fingerprint: normalize_optional_json(key.fingerprint),
|
||||
decrypted_api_key,
|
||||
decrypted_auth_config,
|
||||
})
|
||||
}
|
||||
|
||||
fn normalize_optional_json(value: Option<serde_json::Value>) -> Option<serde_json::Value> {
|
||||
match value {
|
||||
Some(serde_json::Value::Null) | None => None,
|
||||
Some(value) => Some(value),
|
||||
}
|
||||
}
|
||||
|
||||
fn decrypt_secret(
|
||||
encryption_key: &str,
|
||||
fallback_encryption_keys: &[String],
|
||||
ciphertext: &str,
|
||||
field_name: &str,
|
||||
) -> Result<String, DataLayerError> {
|
||||
match decrypt_python_fernet_ciphertext(encryption_key, ciphertext) {
|
||||
Ok(value) => Ok(value),
|
||||
Err(_error) if should_use_plaintext_secret(ciphertext, field_name) => {
|
||||
Ok(ciphertext.trim().to_string())
|
||||
}
|
||||
Err(error) => {
|
||||
for fallback_encryption_key in fallback_encryption_keys {
|
||||
if let Ok(value) =
|
||||
decrypt_python_fernet_ciphertext(fallback_encryption_key, ciphertext)
|
||||
{
|
||||
return Ok(value);
|
||||
}
|
||||
}
|
||||
Err(DataLayerError::UnexpectedValue(format!(
|
||||
"failed to decrypt {field_name}: {error}"
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn fallback_encryption_keys(primary_encryption_key: &str) -> Vec<String> {
|
||||
let mut keys = Vec::new();
|
||||
for env_key in ["AETHER_GATEWAY_DATA_ENCRYPTION_KEY", "ENCRYPTION_KEY"] {
|
||||
let Ok(value) = std::env::var(env_key) else {
|
||||
continue;
|
||||
};
|
||||
let value = value.trim();
|
||||
if value.is_empty()
|
||||
|| value == primary_encryption_key
|
||||
|| keys.iter().any(|existing| existing == value)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
keys.push(value.to_string());
|
||||
}
|
||||
keys
|
||||
}
|
||||
|
||||
fn should_use_plaintext_secret(ciphertext: &str, field_name: &str) -> bool {
|
||||
let ciphertext = ciphertext.trim();
|
||||
if ciphertext.is_empty() {
|
||||
return false;
|
||||
}
|
||||
if looks_like_python_fernet_ciphertext(ciphertext) {
|
||||
return false;
|
||||
}
|
||||
|
||||
match field_name {
|
||||
"provider_api_keys.api_key" => !ciphertext.starts_with('{') && !ciphertext.starts_with('['),
|
||||
"provider_api_keys.auth_config" => {
|
||||
ciphertext.starts_with('{') || ciphertext.starts_with('[')
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_string_list(
|
||||
raw: Option<serde_json::Value>,
|
||||
field_name: &str,
|
||||
) -> Result<Option<Vec<String>>, DataLayerError> {
|
||||
let Some(raw) = raw else {
|
||||
return Ok(None);
|
||||
};
|
||||
normalize_string_list_value(&raw, field_name)
|
||||
}
|
||||
|
||||
fn normalize_string_list_value(
|
||||
raw: &serde_json::Value,
|
||||
field_name: &str,
|
||||
) -> Result<Option<Vec<String>>, DataLayerError> {
|
||||
match raw {
|
||||
serde_json::Value::Null => Ok(None),
|
||||
serde_json::Value::Array(items) => normalize_string_list_array(items, field_name).map(Some),
|
||||
serde_json::Value::String(raw) => normalize_embedded_string_list(raw, field_name),
|
||||
_ => Err(DataLayerError::UnexpectedValue(format!(
|
||||
"{field_name} is not a JSON array"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_embedded_string_list(
|
||||
raw: &str,
|
||||
field_name: &str,
|
||||
) -> Result<Option<Vec<String>>, DataLayerError> {
|
||||
let raw = raw.trim();
|
||||
if raw.is_empty() || raw.eq_ignore_ascii_case("null") {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if let Ok(decoded) = serde_json::from_str::<serde_json::Value>(raw) {
|
||||
return normalize_string_list_value(&decoded, field_name);
|
||||
}
|
||||
|
||||
Ok(Some(vec![raw.to_string()]))
|
||||
}
|
||||
|
||||
fn normalize_string_list_array(
|
||||
items: &[serde_json::Value],
|
||||
field_name: &str,
|
||||
) -> Result<Vec<String>, DataLayerError> {
|
||||
let mut values = Vec::with_capacity(items.len());
|
||||
for item in items {
|
||||
let Some(value) = item.as_str() else {
|
||||
return Err(DataLayerError::UnexpectedValue(format!(
|
||||
"{field_name} contains a non-string item"
|
||||
)));
|
||||
};
|
||||
let value = value.trim();
|
||||
if !value.is_empty() {
|
||||
values.push(value.to_string());
|
||||
}
|
||||
}
|
||||
Ok(values)
|
||||
}
|
||||
Reference in New Issue
Block a user