refactor: 大规模模块拆分与代码精简,新增 ai-pipeline/data-contracts 独立 crate

- 新增 aether-ai-pipeline 和 aether-data-contracts crate,将 pipeline 逻辑与数据契约从 gateway 中解耦
- 重构 admin handlers:拆分单体模块为 auth/billing/endpoint/features/model/observability/provider/system 等独立子模块
- 合并 chat/cli 重复代码路径:精简 conversion、finalize、planner 中的 sync/chat/cli 分支
- 重构 scheduler/executor/data 层,引入 facade 模式降低模块间耦合
- 移除冗余的 intent 模块,将 plan_fallback/policy/stream_path/sync_path 迁移至 executor
- 前端适配:调整 admin API 调用和 provider 模型测试对话框
This commit is contained in:
fawney19
2026-04-07 02:50:19 +08:00
parent 763ff03a7b
commit 5d96d6673b
732 changed files with 28589 additions and 20662 deletions
File diff suppressed because one or more lines are too long
@@ -0,0 +1,83 @@
use crate::control::GatewayPublicRequestContext;
use crate::handlers::admin::provider::shared::{
admin_provider_ops_architecture_id_from_path, is_admin_provider_ops_architectures_root,
};
use crate::GatewayError;
use axum::{
body::Body,
http,
response::{IntoResponse, Response},
Json,
};
use serde_json::{json, Value};
static ADMIN_PROVIDER_OPS_ARCHITECTURES_ALL: std::sync::LazyLock<Vec<Value>> =
std::sync::LazyLock::new(|| {
serde_json::from_str(include_str!("architectures.all.json"))
.expect("admin provider ops architectures fixture should parse")
});
fn admin_provider_ops_architectures_list_payload() -> Vec<Value> {
ADMIN_PROVIDER_OPS_ARCHITECTURES_ALL
.iter()
.filter(|item| item.get("architecture_id").and_then(Value::as_str) != Some("generic_api"))
.cloned()
.collect()
}
fn admin_provider_ops_architecture_payload(architecture_id: &str) -> Option<Value> {
ADMIN_PROVIDER_OPS_ARCHITECTURES_ALL
.iter()
.find_map(|item| {
(item.get("architecture_id").and_then(Value::as_str) == Some(architecture_id))
.then(|| item.clone())
})
}
pub(super) async fn maybe_build_local_admin_provider_ops_architectures_response(
request_context: &GatewayPublicRequestContext,
) -> Result<Option<Response<Body>>, GatewayError> {
let Some(decision) = request_context.control_decision.as_ref() else {
return Ok(None);
};
if decision.route_family.as_deref() == Some("provider_ops_manage")
&& decision.route_kind.as_deref() == Some("list_architectures")
&& request_context.request_method == http::Method::GET
&& is_admin_provider_ops_architectures_root(&request_context.request_path)
{
return Ok(Some(
Json(admin_provider_ops_architectures_list_payload()).into_response(),
));
}
if decision.route_family.as_deref() == Some("provider_ops_manage")
&& decision.route_kind.as_deref() == Some("get_architecture")
&& request_context.request_method == http::Method::GET
{
let Some(architecture_id) =
admin_provider_ops_architecture_id_from_path(&request_context.request_path)
else {
return Ok(Some(
(
http::StatusCode::NOT_FOUND,
Json(json!({ "detail": "架构不存在" })),
)
.into_response(),
));
};
return Ok(Some(
match admin_provider_ops_architecture_payload(&architecture_id) {
Some(payload) => Json(payload).into_response(),
None => (
http::StatusCode::NOT_FOUND,
Json(json!({ "detail": format!("架构 {architecture_id} 不存在") })),
)
.into_response(),
},
));
}
Ok(None)
}
@@ -0,0 +1,34 @@
use crate::control::GatewayPublicRequestContext;
use crate::{AppState, GatewayError};
use axum::body::{Body, Bytes};
use axum::http::Response;
mod architectures;
mod providers;
pub(crate) use self::providers::admin_provider_ops_local_action_response;
pub(crate) async fn maybe_build_local_admin_provider_ops_response(
state: &AppState,
request_context: &GatewayPublicRequestContext,
request_body: Option<&Bytes>,
) -> Result<Option<Response<Body>>, GatewayError> {
if let Some(response) =
architectures::maybe_build_local_admin_provider_ops_architectures_response(request_context)
.await?
{
return Ok(Some(response));
}
if let Some(response) = providers::maybe_build_local_admin_provider_ops_providers_response(
state,
request_context,
request_body,
)
.await?
{
return Ok(Some(response));
}
Ok(None)
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,384 @@
use super::{AdminProviderOpsSaveConfigRequest, ADMIN_PROVIDER_OPS_SENSITIVE_FIELDS};
use crate::handlers::admin::shared::{
decrypt_catalog_secret_with_fallbacks, encrypt_catalog_secret_with_fallbacks,
};
use crate::AppState;
use aether_data_contracts::repository::provider_catalog::{
StoredProviderCatalogEndpoint, StoredProviderCatalogProvider,
};
use serde_json::json;
pub(super) fn admin_provider_ops_config_object(
provider: &StoredProviderCatalogProvider,
) -> Option<&serde_json::Map<String, serde_json::Value>> {
provider
.config
.as_ref()
.and_then(serde_json::Value::as_object)
.and_then(|config| config.get("provider_ops"))
.and_then(serde_json::Value::as_object)
}
pub(super) fn admin_provider_ops_connector_object(
provider_ops_config: &serde_json::Map<String, serde_json::Value>,
) -> Option<&serde_json::Map<String, serde_json::Value>> {
provider_ops_config
.get("connector")
.and_then(serde_json::Value::as_object)
}
fn admin_provider_ops_masked_secret(
state: &AppState,
field: &str,
ciphertext: &str,
) -> serde_json::Value {
let plaintext = decrypt_catalog_secret_with_fallbacks(state.encryption_key(), ciphertext)
.unwrap_or_else(|| ciphertext.to_string());
if plaintext.is_empty() {
return serde_json::Value::String(String::new());
}
let masked = if field == "password" {
"********".to_string()
} else if plaintext.len() > 12 {
format!(
"{}****{}",
&plaintext[..4],
&plaintext[plaintext.len().saturating_sub(4)..]
)
} else if plaintext.len() > 8 {
format!(
"{}****{}",
&plaintext[..2],
&plaintext[plaintext.len().saturating_sub(2)..]
)
} else {
"*".repeat(plaintext.len())
};
serde_json::Value::String(masked)
}
fn admin_provider_ops_masked_credentials(
state: &AppState,
raw_credentials: Option<&serde_json::Value>,
) -> serde_json::Value {
let Some(credentials) = raw_credentials.and_then(serde_json::Value::as_object) else {
return json!({});
};
let mut masked = serde_json::Map::new();
for (key, value) in credentials {
if ADMIN_PROVIDER_OPS_SENSITIVE_FIELDS.contains(&key.as_str()) {
if let Some(ciphertext) = value.as_str().filter(|value| !value.is_empty()) {
masked.insert(
key.clone(),
admin_provider_ops_masked_secret(state, key, ciphertext),
);
continue;
}
}
masked.insert(key.clone(), value.clone());
}
serde_json::Value::Object(masked)
}
fn admin_provider_ops_is_supported_auth_type(auth_type: &str) -> bool {
matches!(
auth_type,
"api_key" | "session_login" | "oauth" | "cookie" | "none"
)
}
pub(super) fn admin_provider_ops_uses_python_verify_fallback(
architecture_id: &str,
config: &serde_json::Map<String, serde_json::Value>,
) -> bool {
let _ = architecture_id;
config
.get("proxy_enabled")
.and_then(serde_json::Value::as_bool)
.unwrap_or(false)
|| config
.get("proxy_node_id")
.and_then(serde_json::Value::as_str)
.map(str::trim)
.is_some_and(|value| !value.is_empty())
}
pub(super) fn admin_provider_ops_decrypted_credentials(
state: &AppState,
raw_credentials: Option<&serde_json::Value>,
) -> serde_json::Map<String, serde_json::Value> {
let Some(credentials) = raw_credentials.and_then(serde_json::Value::as_object) else {
return serde_json::Map::new();
};
let mut decrypted = serde_json::Map::new();
for (key, value) in credentials {
if ADMIN_PROVIDER_OPS_SENSITIVE_FIELDS.contains(&key.as_str()) {
if let Some(ciphertext) = value.as_str() {
let plaintext =
decrypt_catalog_secret_with_fallbacks(state.encryption_key(), ciphertext)
.unwrap_or_else(|| ciphertext.to_string());
decrypted.insert(key.clone(), serde_json::Value::String(plaintext));
continue;
}
}
decrypted.insert(key.clone(), value.clone());
}
decrypted
}
fn admin_provider_ops_sensitive_placeholder_or_empty(value: Option<&serde_json::Value>) -> bool {
match value {
None | Some(serde_json::Value::Null) => true,
Some(serde_json::Value::String(raw)) => raw.is_empty() || raw.chars().all(|ch| ch == '*'),
Some(serde_json::Value::Array(items)) => items.is_empty(),
Some(serde_json::Value::Object(map)) => map.is_empty(),
_ => false,
}
}
pub(super) fn admin_provider_ops_merge_credentials(
state: &AppState,
provider: &StoredProviderCatalogProvider,
mut request_credentials: serde_json::Map<String, serde_json::Value>,
) -> serde_json::Map<String, serde_json::Value> {
let saved_credentials = admin_provider_ops_decrypted_credentials(
state,
admin_provider_ops_config_object(provider)
.and_then(admin_provider_ops_connector_object)
.and_then(|connector| connector.get("credentials")),
);
for field in ADMIN_PROVIDER_OPS_SENSITIVE_FIELDS {
if admin_provider_ops_sensitive_placeholder_or_empty(request_credentials.get(*field))
&& saved_credentials.contains_key(*field)
{
if let Some(saved_value) = saved_credentials.get(*field) {
request_credentials.insert((*field).to_string(), saved_value.clone());
}
}
}
for (key, value) in saved_credentials {
if key.starts_with('_') && !request_credentials.contains_key(&key) {
request_credentials.insert(key, value);
}
}
request_credentials
}
fn admin_provider_ops_encrypt_credentials(
state: &AppState,
credentials: serde_json::Map<String, serde_json::Value>,
) -> Result<serde_json::Map<String, serde_json::Value>, String> {
let mut encrypted = serde_json::Map::new();
for (key, value) in credentials {
if ADMIN_PROVIDER_OPS_SENSITIVE_FIELDS.contains(&key.as_str()) {
if let Some(plaintext) = value.as_str() {
if plaintext.is_empty() {
encrypted.insert(key, value);
} else {
let ciphertext = encrypt_catalog_secret_with_fallbacks(state, plaintext)
.ok_or_else(|| "gateway 未配置 Provider Ops 加密密钥".to_string())?;
encrypted.insert(key, serde_json::Value::String(ciphertext));
}
continue;
}
}
encrypted.insert(key, value);
}
Ok(encrypted)
}
pub(super) fn build_admin_provider_ops_saved_config_value(
state: &AppState,
provider: &StoredProviderCatalogProvider,
payload: AdminProviderOpsSaveConfigRequest,
) -> Result<serde_json::Value, String> {
let auth_type = payload.connector.auth_type.trim().to_string();
if auth_type.is_empty() || !admin_provider_ops_is_supported_auth_type(auth_type.as_str()) {
return Err("connector.auth_type 必须是合法的认证类型".to_string());
}
let merged_credentials =
admin_provider_ops_merge_credentials(state, provider, payload.connector.credentials);
let encrypted_credentials = admin_provider_ops_encrypt_credentials(state, merged_credentials)?;
let actions = payload
.actions
.into_iter()
.map(|(action_type, config)| {
(
action_type,
json!({
"enabled": config.enabled,
"config": config.config,
}),
)
})
.collect::<serde_json::Map<String, serde_json::Value>>();
Ok(json!({
"architecture_id": payload.architecture_id,
"base_url": payload.base_url,
"connector": {
"auth_type": auth_type,
"config": payload.connector.config,
"credentials": encrypted_credentials,
},
"actions": actions,
"schedule": payload.schedule,
}))
}
pub(super) fn resolve_admin_provider_ops_base_url(
provider: &StoredProviderCatalogProvider,
endpoints: &[StoredProviderCatalogEndpoint],
provider_ops_config: Option<&serde_json::Map<String, serde_json::Value>>,
) -> Option<String> {
let from_saved_config = provider_ops_config
.and_then(|config| config.get("base_url"))
.and_then(serde_json::Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned);
if from_saved_config.is_some() {
return from_saved_config;
}
if let Some(base_url) = endpoints.iter().find_map(|endpoint| {
let value = endpoint.base_url.trim();
(!value.is_empty()).then(|| value.to_string())
}) {
return Some(base_url);
}
let from_provider_config = provider
.config
.as_ref()
.and_then(serde_json::Value::as_object)
.and_then(|config| config.get("base_url"))
.and_then(serde_json::Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned);
if from_provider_config.is_some() {
return from_provider_config;
}
provider
.website
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned)
}
pub(super) fn build_admin_provider_ops_status_payload(
provider_id: &str,
provider: Option<&StoredProviderCatalogProvider>,
) -> serde_json::Value {
let provider_ops_config = provider.and_then(admin_provider_ops_config_object);
let auth_type = provider_ops_config
.and_then(admin_provider_ops_connector_object)
.and_then(|connector| connector.get("auth_type"))
.and_then(serde_json::Value::as_str)
.unwrap_or_else(|| {
if provider_ops_config.is_some() {
"api_key"
} else {
"none"
}
});
let mut enabled_actions = provider_ops_config
.and_then(|config| config.get("actions"))
.and_then(serde_json::Value::as_object)
.map(|actions| {
actions
.iter()
.filter_map(|(action_type, config)| {
let enabled = config
.as_object()
.and_then(|config| config.get("enabled"))
.and_then(serde_json::Value::as_bool)
.unwrap_or(true);
enabled.then(|| serde_json::Value::String(action_type.clone()))
})
.collect::<Vec<_>>()
})
.unwrap_or_default();
enabled_actions.sort_by(|left, right| left.as_str().cmp(&right.as_str()));
json!({
"provider_id": provider_id,
"is_configured": provider_ops_config.is_some(),
"architecture_id": provider_ops_config.map(|config| {
config
.get("architecture_id")
.and_then(serde_json::Value::as_str)
.unwrap_or("generic_api")
}),
"connection_status": {
"status": "disconnected",
"auth_type": auth_type,
"connected_at": serde_json::Value::Null,
"expires_at": serde_json::Value::Null,
"last_error": serde_json::Value::Null,
},
"enabled_actions": enabled_actions,
})
}
pub(super) fn build_admin_provider_ops_config_payload(
state: &AppState,
provider_id: &str,
provider: Option<&StoredProviderCatalogProvider>,
endpoints: &[StoredProviderCatalogEndpoint],
) -> serde_json::Value {
let Some(provider) = provider else {
return json!({
"provider_id": provider_id,
"is_configured": false,
});
};
let Some(provider_ops_config) = admin_provider_ops_config_object(provider) else {
return json!({
"provider_id": provider_id,
"is_configured": false,
});
};
let connector = admin_provider_ops_connector_object(provider_ops_config);
json!({
"provider_id": provider_id,
"is_configured": true,
"architecture_id": provider_ops_config
.get("architecture_id")
.and_then(serde_json::Value::as_str)
.unwrap_or("generic_api"),
"base_url": resolve_admin_provider_ops_base_url(
provider,
endpoints,
Some(provider_ops_config),
),
"connector": {
"auth_type": connector
.and_then(|connector| connector.get("auth_type"))
.and_then(serde_json::Value::as_str)
.unwrap_or("api_key"),
"config": connector
.and_then(|connector| connector.get("config"))
.filter(|value| value.is_object())
.cloned()
.unwrap_or_else(|| json!({})),
"credentials": admin_provider_ops_masked_credentials(
state,
connector.and_then(|connector| connector.get("credentials")),
),
},
})
}
@@ -0,0 +1,117 @@
use crate::control::GatewayPublicRequestContext;
use crate::handlers::admin::provider::shared::{
admin_provider_id_for_provider_ops_balance, admin_provider_id_for_provider_ops_checkin,
admin_provider_id_for_provider_ops_config, admin_provider_id_for_provider_ops_connect,
admin_provider_id_for_provider_ops_disconnect, admin_provider_id_for_provider_ops_status,
admin_provider_id_for_provider_ops_verify, admin_provider_ops_action_route_parts,
};
use crate::handlers::admin::shared::{
decrypt_catalog_secret_with_fallbacks, encrypt_catalog_secret_with_fallbacks,
};
use crate::{AppState, GatewayError};
use aether_data_contracts::repository::provider_catalog::{
StoredProviderCatalogEndpoint, StoredProviderCatalogProvider,
};
use axum::{
body::{Body, Bytes},
http,
response::{IntoResponse, Response},
Json,
};
use serde::Deserialize;
use std::collections::BTreeMap;
use std::time::{SystemTime, UNIX_EPOCH};
mod actions;
mod config;
mod routes;
mod verify;
use self::actions::admin_provider_ops_is_valid_action_type;
pub(crate) use self::actions::admin_provider_ops_local_action_response;
use self::config::{
admin_provider_ops_config_object, admin_provider_ops_connector_object,
admin_provider_ops_decrypted_credentials, admin_provider_ops_merge_credentials,
admin_provider_ops_uses_python_verify_fallback, build_admin_provider_ops_config_payload,
build_admin_provider_ops_saved_config_value, build_admin_provider_ops_status_payload,
resolve_admin_provider_ops_base_url,
};
pub(super) use self::routes::maybe_build_local_admin_provider_ops_providers_response;
use self::verify::{
admin_provider_ops_local_verify_response, admin_provider_ops_normalized_verify_architecture_id,
admin_provider_ops_value_as_f64, admin_provider_ops_verify_failure,
admin_provider_ops_verify_headers,
};
const ADMIN_PROVIDER_OPS_SENSITIVE_FIELDS: &[&str] = &[
"api_key",
"password",
"refresh_token",
"session_token",
"session_cookie",
"token_cookie",
"auth_cookie",
"cookie_string",
"cookie",
];
const ADMIN_PROVIDER_OPS_CONNECT_RUST_ONLY_MESSAGE: &str =
"Provider 连接仅支持 Rust execution runtime";
const ADMIN_PROVIDER_OPS_ACTION_RUST_ONLY_MESSAGE: &str =
"Provider 操作仅支持 Rust execution runtime";
const ADMIN_PROVIDER_OPS_VERIFY_RUST_ONLY_MESSAGE: &str = "认证验证仅支持 Rust execution runtime";
#[derive(Debug, Deserialize)]
struct AdminProviderOpsSaveConfigRequest {
#[serde(default = "default_admin_provider_ops_architecture_id")]
architecture_id: String,
#[serde(default)]
base_url: Option<String>,
connector: AdminProviderOpsConnectorConfigRequest,
#[serde(default)]
actions: BTreeMap<String, AdminProviderOpsActionConfigRequest>,
#[serde(default)]
schedule: BTreeMap<String, String>,
}
#[derive(Debug, Deserialize)]
struct AdminProviderOpsConnectorConfigRequest {
auth_type: String,
#[serde(default)]
config: serde_json::Map<String, serde_json::Value>,
#[serde(default)]
credentials: serde_json::Map<String, serde_json::Value>,
}
#[derive(Debug, Deserialize)]
struct AdminProviderOpsActionConfigRequest {
#[serde(default = "default_admin_provider_ops_action_enabled")]
enabled: bool,
#[serde(default)]
config: serde_json::Map<String, serde_json::Value>,
}
#[derive(Debug, Deserialize)]
struct AdminProviderOpsConnectRequest {
#[serde(default)]
credentials: Option<serde_json::Map<String, serde_json::Value>>,
}
#[derive(Debug, Deserialize)]
struct AdminProviderOpsExecuteActionRequest {
#[serde(default)]
config: Option<serde_json::Map<String, serde_json::Value>>,
}
#[derive(Debug, Clone)]
struct AdminProviderOpsCheckinOutcome {
success: Option<bool>,
message: String,
cookie_expired: bool,
}
fn default_admin_provider_ops_architecture_id() -> String {
"generic_api".to_string()
}
fn default_admin_provider_ops_action_enabled() -> bool {
true
}
@@ -0,0 +1,697 @@
use super::{
admin_provider_ops_config_object, admin_provider_ops_connector_object,
admin_provider_ops_decrypted_credentials, admin_provider_ops_is_valid_action_type,
admin_provider_ops_local_action_response, admin_provider_ops_local_verify_response,
admin_provider_ops_merge_credentials, admin_provider_ops_normalized_verify_architecture_id,
admin_provider_ops_verify_failure, build_admin_provider_ops_config_payload,
build_admin_provider_ops_saved_config_value, build_admin_provider_ops_status_payload,
resolve_admin_provider_ops_base_url, AdminProviderOpsConnectRequest,
AdminProviderOpsExecuteActionRequest, AdminProviderOpsSaveConfigRequest,
ADMIN_PROVIDER_OPS_CONNECT_RUST_ONLY_MESSAGE,
};
use crate::control::GatewayPublicRequestContext;
use crate::handlers::admin::provider::shared::{
admin_provider_id_for_provider_ops_balance, admin_provider_id_for_provider_ops_checkin,
admin_provider_id_for_provider_ops_config, admin_provider_id_for_provider_ops_connect,
admin_provider_id_for_provider_ops_disconnect, admin_provider_id_for_provider_ops_status,
admin_provider_id_for_provider_ops_verify, admin_provider_ops_action_route_parts,
};
use crate::handlers::admin::shared::attach_admin_audit_response;
use crate::{AppState, GatewayError};
use axum::{
body::{Body, Bytes},
http,
response::{IntoResponse, Response},
Json,
};
use serde_json::json;
use std::time::{SystemTime, UNIX_EPOCH};
pub(crate) async fn maybe_build_local_admin_provider_ops_providers_response(
state: &AppState,
request_context: &GatewayPublicRequestContext,
request_body: Option<&Bytes>,
) -> Result<Option<Response<Body>>, GatewayError> {
let Some(decision) = request_context.control_decision.as_ref() else {
return Ok(None);
};
if decision.route_family.as_deref() != Some("provider_ops_manage") {
return Ok(None);
}
let route_kind = decision.route_kind.as_deref().unwrap_or_default();
if !state.has_provider_catalog_data_reader() && route_kind != "disconnect_provider" {
return Ok(None);
}
if route_kind == "batch_balance" {
let requested_provider_ids = match request_body {
Some(body) if !body.is_empty() => {
let raw_value = match serde_json::from_slice::<serde_json::Value>(body) {
Ok(value) => value,
Err(_) => {
return Ok(Some(
(
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": "请求体必须是 provider_id 数组" })),
)
.into_response(),
));
}
};
let ids = if let Some(items) = raw_value.as_array() {
items
.iter()
.filter_map(serde_json::Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned)
.collect::<Vec<_>>()
} else if let Some(items) = raw_value
.get("provider_ids")
.and_then(serde_json::Value::as_array)
{
items
.iter()
.filter_map(serde_json::Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned)
.collect::<Vec<_>>()
} else {
return Ok(Some(
(
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": "请求体必须是 provider_id 数组" })),
)
.into_response(),
));
};
Some(ids)
}
_ => None,
};
let provider_ids = if let Some(provider_ids) = requested_provider_ids {
provider_ids
} else {
state
.list_provider_catalog_providers(true)
.await?
.into_iter()
.filter(|provider| {
provider
.config
.as_ref()
.and_then(serde_json::Value::as_object)
.is_some_and(|config| config.contains_key("provider_ops"))
})
.map(|provider| provider.id)
.collect::<Vec<_>>()
};
if provider_ids.is_empty() {
return Ok(Some(Json(json!({})).into_response()));
}
let providers = state
.read_provider_catalog_providers_by_ids(&provider_ids)
.await?;
let endpoints = state
.list_provider_catalog_endpoints_by_provider_ids(&provider_ids)
.await?;
let mut payload = serde_json::Map::new();
for provider_id in &provider_ids {
let provider = providers
.iter()
.find(|provider| provider.id == *provider_id);
let provider_endpoints = endpoints
.iter()
.filter(|endpoint| endpoint.provider_id == *provider_id)
.cloned()
.collect::<Vec<_>>();
let result = admin_provider_ops_local_action_response(
state,
provider_id,
provider,
&provider_endpoints,
"query_balance",
None,
)
.await;
payload.insert(provider_id.clone(), result);
}
return Ok(Some(
Json(serde_json::Value::Object(payload)).into_response(),
));
}
let action_route = if route_kind == "execute_provider_action" {
admin_provider_ops_action_route_parts(&request_context.request_path)
} else {
None
};
let provider_id = if matches!(
route_kind,
"get_provider_status"
| "get_provider_config"
| "save_provider_config"
| "delete_provider_config"
| "verify_provider"
| "connect_provider"
| "disconnect_provider"
| "get_provider_balance"
| "refresh_provider_balance"
| "provider_checkin"
| "execute_provider_action"
) {
admin_provider_id_for_provider_ops_config(&request_context.request_path)
.or_else(|| admin_provider_id_for_provider_ops_status(&request_context.request_path))
.or_else(|| admin_provider_id_for_provider_ops_verify(&request_context.request_path))
.or_else(|| admin_provider_id_for_provider_ops_connect(&request_context.request_path))
.or_else(|| admin_provider_id_for_provider_ops_balance(&request_context.request_path))
.or_else(|| admin_provider_id_for_provider_ops_checkin(&request_context.request_path))
.or_else(|| {
action_route
.as_ref()
.map(|(provider_id, _)| provider_id.clone())
})
.or_else(|| {
admin_provider_id_for_provider_ops_disconnect(&request_context.request_path)
})
} else {
None
};
let Some(provider_id) = provider_id else {
return Ok(None);
};
if decision.route_kind.as_deref() != Some(route_kind) {
return Ok(None);
}
if route_kind == "save_provider_config" {
let Some(request_body) = request_body else {
return Ok(Some(
(
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": "请求体不能为空" })),
)
.into_response(),
));
};
let raw_value = match serde_json::from_slice::<serde_json::Value>(request_body) {
Ok(value) => value,
Err(_) => {
return Ok(Some(
(
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": "请求体必须是合法的 JSON 对象" })),
)
.into_response(),
));
}
};
if !raw_value.is_object() {
return Ok(Some(
(
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": "请求体必须是合法的 JSON 对象" })),
)
.into_response(),
));
}
let payload = match serde_json::from_value::<AdminProviderOpsSaveConfigRequest>(raw_value) {
Ok(payload) => payload,
Err(_) => {
return Ok(Some(
(
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": "请求体必须是合法的 JSON 对象" })),
)
.into_response(),
));
}
};
let Some(existing_provider) = state
.read_provider_catalog_providers_by_ids(std::slice::from_ref(&provider_id))
.await?
.into_iter()
.next()
else {
return Ok(Some(
(
http::StatusCode::NOT_FOUND,
Json(json!({ "detail": "Provider 不存在" })),
)
.into_response(),
));
};
let provider_ops_config =
match build_admin_provider_ops_saved_config_value(state, &existing_provider, payload) {
Ok(config) => config,
Err(detail) => {
return Ok(Some(
(
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": detail })),
)
.into_response(),
));
}
};
let mut updated_provider = existing_provider.clone();
let mut provider_config = updated_provider
.config
.as_ref()
.and_then(serde_json::Value::as_object)
.cloned()
.unwrap_or_default();
provider_config.insert("provider_ops".to_string(), provider_ops_config);
updated_provider.config = Some(serde_json::Value::Object(provider_config));
updated_provider.updated_at_unix_secs = SystemTime::now()
.duration_since(UNIX_EPOCH)
.ok()
.map(|duration| duration.as_secs());
let Some(_updated) = state
.update_provider_catalog_provider(&updated_provider)
.await?
else {
return Ok(None);
};
return Ok(Some(
Json(json!({
"success": true,
"message": "配置保存成功",
}))
.into_response(),
));
}
if route_kind == "verify_provider" {
let Some(request_body) = request_body else {
return Ok(Some(
(
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": "请求体不能为空" })),
)
.into_response(),
));
};
let raw_value = match serde_json::from_slice::<serde_json::Value>(request_body) {
Ok(value) => value,
Err(_) => {
return Ok(Some(
(
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": "请求体必须是合法的 JSON 对象" })),
)
.into_response(),
));
}
};
if !raw_value.is_object() {
return Ok(Some(
(
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": "请求体必须是合法的 JSON 对象" })),
)
.into_response(),
));
}
let payload = match serde_json::from_value::<AdminProviderOpsSaveConfigRequest>(raw_value) {
Ok(payload) => payload,
Err(_) => {
return Ok(Some(
(
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": "请求体必须是合法的 JSON 对象" })),
)
.into_response(),
));
}
};
let existing_provider = state
.read_provider_catalog_providers_by_ids(std::slice::from_ref(&provider_id))
.await?
.into_iter()
.next();
let endpoints = if existing_provider.is_some() {
state
.list_provider_catalog_endpoints_by_provider_ids(std::slice::from_ref(&provider_id))
.await?
} else {
Vec::new()
};
let base_url = payload
.base_url
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned)
.or_else(|| {
existing_provider.as_ref().and_then(|provider| {
resolve_admin_provider_ops_base_url(
provider,
&endpoints,
admin_provider_ops_config_object(provider),
)
})
});
let Some(base_url) = base_url else {
return Ok(Some(
Json(admin_provider_ops_verify_failure("请提供 API 地址")).into_response(),
));
};
let architecture_id =
admin_provider_ops_normalized_verify_architecture_id(&payload.architecture_id);
let credentials = existing_provider.as_ref().map_or_else(
|| payload.connector.credentials.clone(),
|provider| {
admin_provider_ops_merge_credentials(
state,
provider,
payload.connector.credentials.clone(),
)
},
);
let payload = admin_provider_ops_local_verify_response(
state,
&base_url,
architecture_id,
&payload.connector.config,
&credentials,
)
.await;
return Ok(Some(attach_admin_audit_response(
Json(payload).into_response(),
"admin_provider_ops_config_verified",
"verify_provider_ops_config",
"provider",
&provider_id,
)));
}
if route_kind == "connect_provider" {
let Some(request_body) = request_body else {
return Ok(Some(
(
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": "请求体不能为空" })),
)
.into_response(),
));
};
let raw_value = match serde_json::from_slice::<serde_json::Value>(request_body) {
Ok(value) => value,
Err(_) => {
return Ok(Some(
(
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": "请求体必须是合法的 JSON 对象" })),
)
.into_response(),
));
}
};
if !raw_value.is_object() {
return Ok(Some(
(
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": "请求体必须是合法的 JSON 对象" })),
)
.into_response(),
));
}
let payload = match serde_json::from_value::<AdminProviderOpsConnectRequest>(raw_value) {
Ok(payload) => payload,
Err(_) => {
return Ok(Some(
(
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": "请求体必须是合法的 JSON 对象" })),
)
.into_response(),
));
}
};
let Some(existing_provider) = state
.read_provider_catalog_providers_by_ids(std::slice::from_ref(&provider_id))
.await?
.into_iter()
.next()
else {
return Ok(Some(
(
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": "Provider 不存在" })),
)
.into_response(),
));
};
let Some(provider_ops_config) = admin_provider_ops_config_object(&existing_provider) else {
return Ok(Some(
(
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": "未配置操作设置" })),
)
.into_response(),
));
};
let endpoints = state
.list_provider_catalog_endpoints_by_provider_ids(std::slice::from_ref(&provider_id))
.await?;
if resolve_admin_provider_ops_base_url(
&existing_provider,
&endpoints,
Some(provider_ops_config),
)
.is_none()
{
return Ok(Some(
(
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": "Provider 未配置 base_url" })),
)
.into_response(),
));
}
let actual_credentials = payload
.credentials
.filter(|value| !value.is_empty())
.unwrap_or_else(|| {
admin_provider_ops_decrypted_credentials(
state,
admin_provider_ops_config_object(&existing_provider)
.and_then(admin_provider_ops_connector_object)
.and_then(|connector| connector.get("credentials")),
)
});
if actual_credentials.is_empty() {
return Ok(Some(
(
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": "未提供凭据" })),
)
.into_response(),
));
}
return Ok(Some(
(
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": ADMIN_PROVIDER_OPS_CONNECT_RUST_ONLY_MESSAGE })),
)
.into_response(),
));
}
if matches!(
route_kind,
"get_provider_balance"
| "refresh_provider_balance"
| "provider_checkin"
| "execute_provider_action"
) {
let action_type = if route_kind == "provider_checkin" {
"checkin".to_string()
} else if matches!(
route_kind,
"get_provider_balance" | "refresh_provider_balance"
) {
"query_balance".to_string()
} else {
let Some((_, action_type)) = action_route else {
return Ok(None);
};
if !admin_provider_ops_is_valid_action_type(&action_type) {
return Ok(Some(
(
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": format!("无效的操作类型: {action_type}") })),
)
.into_response(),
));
}
action_type
};
let request_config = if route_kind == "execute_provider_action" {
match request_body {
Some(body) if !body.is_empty() => {
let raw_value = match serde_json::from_slice::<serde_json::Value>(body) {
Ok(value) => value,
Err(_) => {
return Ok(Some(
(
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": "请求体必须是合法的 JSON 对象" })),
)
.into_response(),
));
}
};
let payload = match serde_json::from_value::<AdminProviderOpsExecuteActionRequest>(
raw_value,
) {
Ok(payload) => payload,
Err(_) => {
return Ok(Some(
(
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": "请求体必须是合法的 JSON 对象" })),
)
.into_response(),
));
}
};
payload.config
}
_ => None,
}
} else {
None
};
let providers = state
.read_provider_catalog_providers_by_ids(std::slice::from_ref(&provider_id))
.await?;
let provider = providers.first();
let endpoints = if provider.is_some() {
state
.list_provider_catalog_endpoints_by_provider_ids(std::slice::from_ref(&provider_id))
.await?
} else {
Vec::new()
};
let payload = admin_provider_ops_local_action_response(
state,
&provider_id,
provider,
&endpoints,
&action_type,
request_config.as_ref(),
)
.await;
return Ok(Some(Json(payload).into_response()));
}
if route_kind == "delete_provider_config" {
let Some(existing_provider) = state
.read_provider_catalog_providers_by_ids(std::slice::from_ref(&provider_id))
.await?
.into_iter()
.next()
else {
return Ok(Some(
(
http::StatusCode::NOT_FOUND,
Json(json!({ "detail": "Provider 不存在" })),
)
.into_response(),
));
};
let mut updated_provider = existing_provider.clone();
let mut provider_config = updated_provider
.config
.as_ref()
.and_then(serde_json::Value::as_object)
.cloned()
.unwrap_or_default();
if provider_config.remove("provider_ops").is_some() {
updated_provider.config = Some(serde_json::Value::Object(provider_config));
updated_provider.updated_at_unix_secs = SystemTime::now()
.duration_since(UNIX_EPOCH)
.ok()
.map(|duration| duration.as_secs());
let Some(_updated) = state
.update_provider_catalog_provider(&updated_provider)
.await?
else {
return Ok(None);
};
}
return Ok(Some(
Json(json!({
"success": true,
"message": "配置已删除",
}))
.into_response(),
));
}
if route_kind == "disconnect_provider" {
return Ok(Some(
Json(json!({
"success": true,
"message": "已断开连接",
}))
.into_response(),
));
}
let providers = state
.read_provider_catalog_providers_by_ids(std::slice::from_ref(&provider_id))
.await?;
let provider = providers.first();
let endpoints = if route_kind == "get_provider_config" && provider.is_some() {
state
.list_provider_catalog_endpoints_by_provider_ids(std::slice::from_ref(&provider_id))
.await?
} else {
Vec::new()
};
let payload = if route_kind == "get_provider_status" {
build_admin_provider_ops_status_payload(&provider_id, provider)
} else {
build_admin_provider_ops_config_payload(state, &provider_id, provider, &endpoints)
};
let response = Json(payload).into_response();
let response = if route_kind == "get_provider_config" {
attach_admin_audit_response(
response,
"admin_provider_ops_config_viewed",
"view_provider_ops_config",
"provider",
&provider_id,
)
} else if route_kind == "get_provider_status" {
attach_admin_audit_response(
response,
"admin_provider_ops_status_viewed",
"view_provider_ops_status",
"provider",
&provider_id,
)
} else {
response
};
Ok(Some(response))
}
@@ -0,0 +1,910 @@
use super::ADMIN_PROVIDER_OPS_VERIFY_RUST_ONLY_MESSAGE;
use crate::AppState;
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
use regex::Regex;
use serde_json::json;
pub(super) fn admin_provider_ops_normalized_verify_architecture_id(architecture_id: &str) -> &str {
match architecture_id.trim() {
"" => "generic_api",
"generic_api" | "new_api" | "cubence" | "yescode" | "nekocode" | "anyrouter"
| "sub2api" => architecture_id.trim(),
_ => "generic_api",
}
}
fn admin_provider_ops_extract_cookie_value(cookie_input: &str, key: &str) -> String {
if cookie_input.contains(&format!("{key}=")) {
for part in cookie_input.split(';') {
let trimmed = part.trim();
if let Some(value) = trimmed.strip_prefix(&format!("{key}=")) {
return value.trim().to_string();
}
}
}
cookie_input.trim().to_string()
}
fn admin_provider_ops_yescode_cookie_header(cookie_input: &str) -> String {
if cookie_input.contains("yescode_auth=") {
let mut parts = Vec::new();
for part in cookie_input.split(';') {
let trimmed = part.trim();
if let Some(value) = trimmed.strip_prefix("yescode_auth=") {
parts.push(format!("yescode_auth={}", value.trim()));
} else if let Some(value) = trimmed.strip_prefix("yescode_csrf=") {
parts.push(format!("yescode_csrf={}", value.trim()));
}
}
return parts.join("; ");
}
format!("yescode_auth={}", cookie_input.trim())
}
const ADMIN_PROVIDER_OPS_ANYROUTER_XOR_KEY: &str = "3000176000856006061501533003690027800375";
const ADMIN_PROVIDER_OPS_ANYROUTER_UNSBOX_TABLE: [usize; 40] = [
0xF, 0x23, 0x1D, 0x18, 0x21, 0x10, 0x1, 0x26, 0xA, 0x9, 0x13, 0x1F, 0x28, 0x1B, 0x16, 0x17,
0x19, 0xD, 0x6, 0xB, 0x27, 0x12, 0x14, 0x8, 0xE, 0x15, 0x20, 0x1A, 0x2, 0x1E, 0x7, 0x4, 0x11,
0x5, 0x3, 0x1C, 0x22, 0x25, 0xC, 0x24,
];
fn admin_provider_ops_anyrouter_compute_acw_sc_v2(arg1: &str) -> Option<String> {
if arg1.len() != 40 || !arg1.chars().all(|ch| ch.is_ascii_hexdigit()) {
return None;
}
let chars = arg1.chars().collect::<Vec<_>>();
let unsboxed = ADMIN_PROVIDER_OPS_ANYROUTER_UNSBOX_TABLE
.iter()
.map(|index| chars.get(index.saturating_sub(1)).copied())
.collect::<Option<String>>()?;
let mut result = String::with_capacity(40);
for i in (0..40).step_by(2) {
let a = u8::from_str_radix(&unsboxed[i..i + 2], 16).ok()?;
let b = u8::from_str_radix(&ADMIN_PROVIDER_OPS_ANYROUTER_XOR_KEY[i..i + 2], 16).ok()?;
result.push_str(&format!("{:02x}", a ^ b));
}
Some(result)
}
fn admin_provider_ops_anyrouter_parse_session_user_id(cookie_input: &str) -> Option<String> {
let session_cookie = admin_provider_ops_extract_cookie_value(cookie_input, "session");
let decoded = URL_SAFE_NO_PAD.decode(session_cookie.as_bytes()).ok()?;
let text = String::from_utf8_lossy(&decoded);
let mut parts = text.split('|');
let _timestamp = parts.next()?;
let gob_b64 = parts.next()?;
let gob_data = URL_SAFE_NO_PAD.decode(gob_b64.as_bytes()).ok()?;
let id_pattern = b"\x02id\x03int";
let id_idx = gob_data
.windows(id_pattern.len())
.position(|window| window == id_pattern)?;
let value_start = id_idx + id_pattern.len() + 2;
let first_byte = *gob_data.get(value_start)?;
if first_byte != 0 {
return None;
}
let marker = *gob_data.get(value_start + 1)?;
if marker < 0x80 {
return None;
}
let length = 256usize.saturating_sub(marker as usize);
let end = value_start + 2 + length;
let bytes = gob_data.get(value_start + 2..end)?;
let val = bytes
.iter()
.fold(0u64, |acc, byte| (acc << 8) | (*byte as u64));
Some((val >> 1).to_string())
}
async fn admin_provider_ops_anyrouter_acw_cookie(
state: &AppState,
base_url: &str,
) -> Option<String> {
let response = state
.client
.get(base_url.trim_end_matches('/'))
.header(
reqwest::header::USER_AGENT,
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
)
.send()
.await
.ok()?;
let body = response.text().await.ok()?;
let compiled = Regex::new(r"var\s+arg1\s*=\s*'([0-9a-fA-F]{40})'").ok()?;
let captures = compiled.captures(&body)?;
let arg1 = captures.get(1)?.as_str();
admin_provider_ops_anyrouter_compute_acw_sc_v2(arg1).map(|value| format!("acw_sc__v2={value}"))
}
pub(super) fn admin_provider_ops_verify_failure(message: impl Into<String>) -> serde_json::Value {
json!({
"success": false,
"message": message.into(),
})
}
fn admin_provider_ops_verify_success(
data: serde_json::Value,
updated_credentials: Option<serde_json::Map<String, serde_json::Value>>,
) -> serde_json::Value {
let mut payload = serde_json::Map::from_iter([
("success".to_string(), serde_json::Value::Bool(true)),
("data".to_string(), data),
]);
if let Some(credentials) = updated_credentials.filter(|value| !value.is_empty()) {
payload.insert(
"updated_credentials".to_string(),
serde_json::Value::Object(credentials),
);
}
serde_json::Value::Object(payload)
}
fn admin_provider_ops_verify_user_payload(
username: Option<String>,
display_name: Option<String>,
email: Option<String>,
quota: Option<f64>,
extra: Option<serde_json::Map<String, serde_json::Value>>,
) -> serde_json::Value {
let resolved_username = username.filter(|value| !value.trim().is_empty());
let resolved_display_name = display_name
.filter(|value| !value.trim().is_empty())
.or_else(|| resolved_username.clone());
let mut payload = serde_json::Map::new();
payload.insert(
"username".to_string(),
resolved_username
.map(serde_json::Value::String)
.unwrap_or(serde_json::Value::Null),
);
payload.insert(
"display_name".to_string(),
resolved_display_name
.map(serde_json::Value::String)
.unwrap_or(serde_json::Value::Null),
);
payload.insert(
"email".to_string(),
email
.map(serde_json::Value::String)
.unwrap_or(serde_json::Value::Null),
);
payload.insert(
"quota".to_string(),
quota
.and_then(serde_json::Number::from_f64)
.map(serde_json::Value::Number)
.unwrap_or(serde_json::Value::Null),
);
if let Some(extra) = extra.filter(|value| !value.is_empty()) {
payload.insert("extra".to_string(), serde_json::Value::Object(extra));
}
serde_json::Value::Object(payload)
}
pub(super) fn admin_provider_ops_value_as_f64(value: Option<&serde_json::Value>) -> Option<f64> {
match value {
Some(serde_json::Value::Number(number)) => number.as_f64(),
Some(serde_json::Value::String(raw)) => raw.trim().parse::<f64>().ok(),
_ => None,
}
}
fn admin_provider_ops_json_object(
value: &serde_json::Value,
) -> Option<&serde_json::Map<String, serde_json::Value>> {
value.as_object()
}
fn admin_provider_ops_frontend_updated_credentials(
credentials: serde_json::Map<String, serde_json::Value>,
) -> Option<serde_json::Map<String, serde_json::Value>> {
let filtered = credentials
.into_iter()
.filter(|(key, value)| {
!key.starts_with('_')
&& !matches!(value, serde_json::Value::Null)
&& !value.as_str().is_some_and(|raw| raw.trim().is_empty())
})
.collect::<serde_json::Map<String, serde_json::Value>>();
(!filtered.is_empty()).then_some(filtered)
}
fn admin_provider_ops_generic_verify_payload(
status: http::StatusCode,
response_json: &serde_json::Value,
) -> serde_json::Value {
if status == http::StatusCode::UNAUTHORIZED {
return admin_provider_ops_verify_failure("认证失败:无效的凭据");
}
if status == http::StatusCode::FORBIDDEN {
return admin_provider_ops_verify_failure("认证失败:权限不足");
}
if status != http::StatusCode::OK {
return admin_provider_ops_verify_failure(format!("验证失败:HTTP {}", status.as_u16()));
}
let user_data = if response_json
.get("success")
.and_then(serde_json::Value::as_bool)
== Some(true)
&& response_json
.get("data")
.is_some_and(serde_json::Value::is_object)
{
response_json.get("data")
} else if response_json
.get("success")
.and_then(serde_json::Value::as_bool)
== Some(false)
{
return admin_provider_ops_verify_failure(
response_json
.get("message")
.and_then(serde_json::Value::as_str)
.unwrap_or("验证失败"),
);
} else {
Some(response_json)
};
let Some(user_data) = user_data.and_then(admin_provider_ops_json_object) else {
return admin_provider_ops_verify_failure("响应格式无效");
};
let mut extra = serde_json::Map::new();
for (key, value) in user_data {
if matches!(
key.as_str(),
"username" | "display_name" | "email" | "quota" | "used_quota" | "request_count"
) {
continue;
}
extra.insert(key.clone(), value.clone());
}
admin_provider_ops_verify_success(
admin_provider_ops_verify_user_payload(
user_data
.get("username")
.and_then(serde_json::Value::as_str)
.map(ToOwned::to_owned),
user_data
.get("display_name")
.and_then(serde_json::Value::as_str)
.map(ToOwned::to_owned),
user_data
.get("email")
.and_then(serde_json::Value::as_str)
.map(ToOwned::to_owned),
admin_provider_ops_value_as_f64(user_data.get("quota")),
Some(extra),
),
None,
)
}
fn admin_provider_ops_cubence_verify_payload(
status: http::StatusCode,
response_json: &serde_json::Value,
) -> serde_json::Value {
if status == http::StatusCode::UNAUTHORIZED {
return admin_provider_ops_verify_failure("Cookie 已失效,请重新配置");
}
if status == http::StatusCode::FORBIDDEN {
return admin_provider_ops_verify_failure("Cookie 已失效或无权限");
}
if status != http::StatusCode::OK {
return admin_provider_ops_verify_failure(format!("验证失败:HTTP {}", status.as_u16()));
}
let Some(payload) = admin_provider_ops_json_object(response_json) else {
return admin_provider_ops_verify_failure("响应格式无效");
};
let user_info = payload
.get("user")
.and_then(serde_json::Value::as_object)
.cloned()
.unwrap_or_default();
let balance_info = payload
.get("balance")
.and_then(serde_json::Value::as_object)
.cloned()
.unwrap_or_default();
let mut extra = serde_json::Map::new();
if let Some(role) = user_info.get("role") {
extra.insert("role".to_string(), role.clone());
}
if let Some(invite_code) = user_info.get("invite_code") {
extra.insert("invite_code".to_string(), invite_code.clone());
}
admin_provider_ops_verify_success(
admin_provider_ops_verify_user_payload(
user_info
.get("username")
.and_then(serde_json::Value::as_str)
.map(ToOwned::to_owned),
user_info
.get("username")
.and_then(serde_json::Value::as_str)
.map(ToOwned::to_owned),
None,
admin_provider_ops_value_as_f64(balance_info.get("total_balance_dollar")),
Some(extra),
),
None,
)
}
fn admin_provider_ops_yescode_verify_payload(
status: http::StatusCode,
response_json: &serde_json::Value,
) -> serde_json::Value {
if status == http::StatusCode::UNAUTHORIZED {
return admin_provider_ops_verify_failure("Cookie 已失效,请重新配置");
}
if status == http::StatusCode::FORBIDDEN {
return admin_provider_ops_verify_failure("Cookie 已失效或无权限");
}
if status != http::StatusCode::OK {
return admin_provider_ops_verify_failure(format!("验证失败:HTTP {}", status.as_u16()));
}
let Some(payload) = admin_provider_ops_json_object(response_json) else {
return admin_provider_ops_verify_failure("响应格式无效");
};
let Some(username) = payload
.get("username")
.and_then(serde_json::Value::as_str)
.map(ToOwned::to_owned)
else {
return admin_provider_ops_verify_failure("响应格式无效");
};
let pay_as_you_go =
admin_provider_ops_value_as_f64(payload.get("pay_as_you_go_balance")).unwrap_or(0.0);
let subscription =
admin_provider_ops_value_as_f64(payload.get("subscription_balance")).unwrap_or(0.0);
let plan = payload
.get("subscription_plan")
.and_then(serde_json::Value::as_object)
.cloned()
.unwrap_or_default();
let weekly_limit = admin_provider_ops_value_as_f64(
payload
.get("weekly_limit")
.or_else(|| plan.get("weekly_limit")),
);
let weekly_spent = admin_provider_ops_value_as_f64(
payload
.get("weekly_spent_balance")
.or_else(|| payload.get("current_week_spend")),
)
.unwrap_or(0.0);
let subscription_available = weekly_limit
.map(|limit| (limit - weekly_spent).max(0.0).min(subscription))
.unwrap_or(subscription);
admin_provider_ops_verify_success(
admin_provider_ops_verify_user_payload(
Some(username.clone()),
Some(username),
payload
.get("email")
.and_then(serde_json::Value::as_str)
.map(ToOwned::to_owned),
Some(pay_as_you_go + subscription_available),
None,
),
None,
)
}
fn admin_provider_ops_nekocode_verify_payload(
status: http::StatusCode,
response_json: &serde_json::Value,
) -> serde_json::Value {
if status == http::StatusCode::UNAUTHORIZED {
return admin_provider_ops_verify_failure("Cookie 已失效,请重新配置");
}
if status == http::StatusCode::FORBIDDEN {
return admin_provider_ops_verify_failure("Cookie 已失效或无权限");
}
if status != http::StatusCode::OK {
return admin_provider_ops_verify_failure(format!("验证失败:HTTP {}", status.as_u16()));
}
let user_data = if response_json
.get("success")
.and_then(serde_json::Value::as_bool)
== Some(true)
&& response_json
.get("data")
.is_some_and(serde_json::Value::is_object)
{
response_json.get("data")
} else {
Some(response_json)
};
let Some(user_data) = user_data.and_then(admin_provider_ops_json_object) else {
return admin_provider_ops_verify_failure("响应格式无效");
};
admin_provider_ops_verify_success(
admin_provider_ops_verify_user_payload(
user_data
.get("username")
.and_then(serde_json::Value::as_str)
.map(ToOwned::to_owned),
user_data
.get("display_name")
.and_then(serde_json::Value::as_str)
.map(ToOwned::to_owned),
user_data
.get("email")
.and_then(serde_json::Value::as_str)
.map(ToOwned::to_owned),
admin_provider_ops_value_as_f64(user_data.get("balance")),
None,
),
None,
)
}
async fn admin_provider_ops_sub2api_exchange_token(
state: &AppState,
base_url: &str,
credentials: &serde_json::Map<String, serde_json::Value>,
) -> Result<(String, Option<serde_json::Map<String, serde_json::Value>>), String> {
let email = credentials
.get("email")
.and_then(serde_json::Value::as_str)
.map(str::trim)
.unwrap_or_default();
let password = credentials
.get("password")
.and_then(serde_json::Value::as_str)
.map(str::trim)
.unwrap_or_default();
let refresh_token = credentials
.get("refresh_token")
.and_then(serde_json::Value::as_str)
.map(str::trim)
.unwrap_or_default();
let (path, body, default_error, previous_refresh_token) =
if !email.is_empty() && !password.is_empty() {
(
"/api/v1/auth/login",
json!({ "email": email, "password": password }),
"登录失败",
None,
)
} else if !refresh_token.is_empty() {
(
"/api/v1/auth/refresh",
json!({ "refresh_token": refresh_token }),
"Refresh Token 无效或已过期",
Some(refresh_token),
)
} else {
return Err("请填写账号密码或 Refresh Token".to_string());
};
let response = match state
.client
.post(format!("{}{path}", base_url.trim_end_matches('/')))
.json(&body)
.send()
.await
{
Ok(response) => response,
Err(err) if err.is_timeout() => return Err("连接超时".to_string()),
Err(err) if err.is_connect() => return Err(format!("连接失败: {err}")),
Err(err) => return Err(format!("验证失败: {err}")),
};
let status = response.status();
let response_json = match response.bytes().await {
Ok(bytes) => {
serde_json::from_slice::<serde_json::Value>(&bytes).unwrap_or_else(|_| json!({}))
}
Err(_) => json!({}),
};
let payload = response_json.as_object().cloned().unwrap_or_default();
if status != http::StatusCode::OK
|| payload
.get("code")
.and_then(serde_json::Value::as_i64)
.unwrap_or(-1)
!= 0
{
let message = payload
.get("message")
.and_then(serde_json::Value::as_str)
.unwrap_or(default_error);
return Err(message.to_string());
}
let Some(token_data) = payload.get("data").and_then(serde_json::Value::as_object) else {
return Err("响应格式无效".to_string());
};
let access_token = token_data
.get("access_token")
.and_then(serde_json::Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.ok_or_else(|| "响应格式无效".to_string())?;
let mut updated_credentials = serde_json::Map::new();
if let Some(new_refresh_token) = token_data
.get("refresh_token")
.and_then(serde_json::Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
{
if previous_refresh_token != Some(new_refresh_token) {
updated_credentials.insert(
"refresh_token".to_string(),
serde_json::Value::String(new_refresh_token.to_string()),
);
}
}
Ok((
access_token.to_string(),
admin_provider_ops_frontend_updated_credentials(updated_credentials),
))
}
fn admin_provider_ops_sub2api_verify_payload(
status: http::StatusCode,
response_json: &serde_json::Value,
updated_credentials: Option<serde_json::Map<String, serde_json::Value>>,
) -> serde_json::Value {
if status == http::StatusCode::UNAUTHORIZED {
return admin_provider_ops_verify_failure("认证失败:无效的凭据");
}
if status == http::StatusCode::FORBIDDEN {
return admin_provider_ops_verify_failure("认证失败:权限不足");
}
if status != http::StatusCode::OK {
return admin_provider_ops_verify_failure(format!("验证失败:HTTP {}", status.as_u16()));
}
let Some(payload) = admin_provider_ops_json_object(response_json) else {
return admin_provider_ops_verify_failure("响应格式无效");
};
if payload
.get("code")
.and_then(serde_json::Value::as_i64)
.unwrap_or(-1)
!= 0
{
return admin_provider_ops_verify_failure(
payload
.get("message")
.and_then(serde_json::Value::as_str)
.unwrap_or("验证失败"),
);
}
let Some(user_data) = payload.get("data").and_then(serde_json::Value::as_object) else {
return admin_provider_ops_verify_failure("响应格式无效");
};
let balance = admin_provider_ops_value_as_f64(user_data.get("balance")).unwrap_or(0.0);
let points = admin_provider_ops_value_as_f64(user_data.get("points")).unwrap_or(0.0);
let mut extra = serde_json::Map::new();
for key in ["balance", "points", "status", "concurrency"] {
if let Some(value) = user_data.get(key) {
extra.insert(key.to_string(), value.clone());
}
}
admin_provider_ops_verify_success(
admin_provider_ops_verify_user_payload(
user_data
.get("username")
.or_else(|| user_data.get("email"))
.and_then(serde_json::Value::as_str)
.map(ToOwned::to_owned),
user_data
.get("username")
.or_else(|| user_data.get("email"))
.and_then(serde_json::Value::as_str)
.map(ToOwned::to_owned),
user_data
.get("email")
.and_then(serde_json::Value::as_str)
.map(ToOwned::to_owned),
Some(balance + points),
Some(extra),
),
updated_credentials,
)
}
async fn admin_provider_ops_local_sub2api_verify_response(
state: &AppState,
base_url: &str,
credentials: &serde_json::Map<String, serde_json::Value>,
) -> serde_json::Value {
let base_url = base_url.trim().trim_end_matches('/');
if base_url.is_empty() {
return admin_provider_ops_verify_failure("请提供 API 地址");
}
let (access_token, updated_credentials) =
match admin_provider_ops_sub2api_exchange_token(state, base_url, credentials).await {
Ok(value) => value,
Err(message) => return admin_provider_ops_verify_failure(message),
};
let response = match state
.client
.get(format!("{base_url}/api/v1/auth/me?timezone=Asia/Shanghai"))
.bearer_auth(access_token)
.send()
.await
{
Ok(response) => response,
Err(err) if err.is_timeout() => return admin_provider_ops_verify_failure("连接超时"),
Err(err) if err.is_connect() => {
return admin_provider_ops_verify_failure(format!("连接失败: {err}"));
}
Err(err) => return admin_provider_ops_verify_failure(format!("验证失败: {err}")),
};
let status = response.status();
let response_json = match response.bytes().await {
Ok(bytes) => {
serde_json::from_slice::<serde_json::Value>(&bytes).unwrap_or_else(|_| json!({}))
}
Err(_) => json!({}),
};
admin_provider_ops_sub2api_verify_payload(status, &response_json, updated_credentials)
}
fn admin_provider_ops_insert_header(
headers: &mut reqwest::header::HeaderMap,
name: &str,
value: &str,
) -> Result<(), String> {
let header_name = reqwest::header::HeaderName::from_bytes(name.as_bytes())
.map_err(|_| format!("无效的请求头: {name}"))?;
let header_value = reqwest::header::HeaderValue::from_str(value)
.map_err(|_| format!("无效的请求头值: {name}"))?;
headers.insert(header_name, header_value);
Ok(())
}
pub(super) fn admin_provider_ops_verify_headers(
architecture_id: &str,
config: &serde_json::Map<String, serde_json::Value>,
credentials: &serde_json::Map<String, serde_json::Value>,
) -> Result<reqwest::header::HeaderMap, String> {
let mut headers = reqwest::header::HeaderMap::new();
match architecture_id {
"generic_api" => {
let api_key = credentials
.get("api_key")
.and_then(serde_json::Value::as_str)
.unwrap_or_default()
.trim();
if !api_key.is_empty() {
let auth_method = config
.get("auth_method")
.and_then(serde_json::Value::as_str)
.unwrap_or("bearer");
if auth_method == "header" {
let header_name = config
.get("header_name")
.and_then(serde_json::Value::as_str)
.unwrap_or("X-API-Key");
admin_provider_ops_insert_header(&mut headers, header_name, api_key)?;
} else {
admin_provider_ops_insert_header(
&mut headers,
"Authorization",
&format!("Bearer {api_key}"),
)?;
}
}
}
"new_api" => {
for (name, value) in [
(
"User-Agent",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.7339.249 Electron/38.7.0 Safari/537.36",
),
("Accept", "application/json"),
("Accept-Encoding", "gzip, deflate, br"),
("Accept-Language", "zh-CN"),
("sec-ch-ua", "\"Not=A?Brand\";v=\"24\", \"Chromium\";v=\"140\""),
("sec-ch-ua-mobile", "?0"),
("sec-ch-ua-platform", "\"macOS\""),
("Sec-Fetch-Site", "cross-site"),
("Sec-Fetch-Mode", "cors"),
("Sec-Fetch-Dest", "empty"),
] {
admin_provider_ops_insert_header(&mut headers, name, value)?;
}
if let Some(api_key) = credentials
.get("api_key")
.and_then(serde_json::Value::as_str)
{
if !api_key.trim().is_empty() {
admin_provider_ops_insert_header(
&mut headers,
"Authorization",
&format!("Bearer {}", api_key.trim()),
)?;
}
}
if let Some(user_id) = credentials
.get("user_id")
.and_then(serde_json::Value::as_str)
{
if !user_id.trim().is_empty() {
admin_provider_ops_insert_header(&mut headers, "New-Api-User", user_id.trim())?;
}
}
if let Some(cookie) = credentials
.get("cookie")
.and_then(serde_json::Value::as_str)
{
if !cookie.trim().is_empty() {
admin_provider_ops_insert_header(&mut headers, "Cookie", cookie.trim())?;
}
}
}
"cubence" => {
if let Some(token_cookie) = credentials
.get("token_cookie")
.and_then(serde_json::Value::as_str)
.filter(|value| !value.trim().is_empty())
{
let token = admin_provider_ops_extract_cookie_value(token_cookie, "token");
admin_provider_ops_insert_header(
&mut headers,
"Cookie",
&format!("token={token}"),
)?;
}
}
"yescode" => {
if let Some(auth_cookie) = credentials
.get("auth_cookie")
.and_then(serde_json::Value::as_str)
.filter(|value| !value.trim().is_empty())
{
admin_provider_ops_insert_header(
&mut headers,
"Cookie",
&admin_provider_ops_yescode_cookie_header(auth_cookie),
)?;
}
}
"nekocode" => {
if let Some(session_cookie) = credentials
.get("session_cookie")
.and_then(serde_json::Value::as_str)
.filter(|value| !value.trim().is_empty())
{
let session = admin_provider_ops_extract_cookie_value(session_cookie, "session");
admin_provider_ops_insert_header(
&mut headers,
"Cookie",
&format!("session={session}"),
)?;
}
}
"anyrouter" => {
let mut cookies = Vec::new();
if let Some(acw_cookie) = config
.get("acw_cookie")
.and_then(serde_json::Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
{
cookies.push(acw_cookie.to_string());
}
if let Some(session_cookie) = credentials
.get("session_cookie")
.and_then(serde_json::Value::as_str)
.filter(|value| !value.trim().is_empty())
{
let session = admin_provider_ops_extract_cookie_value(session_cookie, "session");
cookies.push(format!("session={session}"));
if let Some(user_id) =
admin_provider_ops_anyrouter_parse_session_user_id(session_cookie)
{
admin_provider_ops_insert_header(&mut headers, "New-Api-User", user_id.trim())?;
}
}
if !cookies.is_empty() {
admin_provider_ops_insert_header(&mut headers, "Cookie", &cookies.join("; "))?;
}
}
_ => {}
}
Ok(headers)
}
pub(super) async fn admin_provider_ops_local_verify_response(
state: &AppState,
base_url: &str,
architecture_id: &str,
config: &serde_json::Map<String, serde_json::Value>,
credentials: &serde_json::Map<String, serde_json::Value>,
) -> serde_json::Value {
if architecture_id == "sub2api" {
return admin_provider_ops_local_sub2api_verify_response(state, base_url, credentials)
.await;
}
let mut resolved_config = config.clone();
if architecture_id == "anyrouter" {
if let Some(acw_cookie) = admin_provider_ops_anyrouter_acw_cookie(state, base_url).await {
resolved_config.insert(
"acw_cookie".to_string(),
serde_json::Value::String(acw_cookie),
);
}
}
let verify_path = match architecture_id {
"anyrouter" => "/api/user/self",
"cubence" => "/api/v1/dashboard/overview",
"yescode" => "/api/v1/auth/profile",
"nekocode" => "/api/user/self",
"new_api" | "generic_api" => "/api/user/self",
_ => return admin_provider_ops_verify_failure(ADMIN_PROVIDER_OPS_VERIFY_RUST_ONLY_MESSAGE),
};
let base_url = base_url.trim().trim_end_matches('/');
if base_url.is_empty() {
return admin_provider_ops_verify_failure("请提供 API 地址");
}
let headers =
match admin_provider_ops_verify_headers(architecture_id, &resolved_config, credentials) {
Ok(headers) => headers,
Err(message) => return admin_provider_ops_verify_failure(message),
};
let response = match state
.client
.get(format!("{base_url}{verify_path}"))
.headers(headers)
.send()
.await
{
Ok(response) => response,
Err(err) if err.is_timeout() => return admin_provider_ops_verify_failure("连接超时"),
Err(err) if err.is_connect() => {
return admin_provider_ops_verify_failure(format!("连接失败: {err}"));
}
Err(err) => return admin_provider_ops_verify_failure(format!("验证失败: {err}")),
};
let status = response.status();
let response_json = match response.bytes().await {
Ok(bytes) => {
serde_json::from_slice::<serde_json::Value>(&bytes).unwrap_or_else(|_| json!({}))
}
Err(_) => json!({}),
};
match architecture_id {
"cubence" => admin_provider_ops_cubence_verify_payload(status, &response_json),
"yescode" => admin_provider_ops_yescode_verify_payload(status, &response_json),
"nekocode" => admin_provider_ops_nekocode_verify_payload(status, &response_json),
_ => admin_provider_ops_generic_verify_payload(status, &response_json),
}
}