refactor: 大规模模块拆分与重组,新增 aether-admin crate

- 新建独立 aether-admin crate 承载 admin 相关共享契约与纯辅助函数
- 拆分 ai_pipeline 下 kiro/private_envelope/conversion/planner 等大文件为子模块目录
- 重组 admin handlers 各业务域(billing/oauth/provider/system/users 等)为目录结构,移除 shared.rs/builders.rs 等反模式
- 移除 ai_pipeline runtime adapters 旧实现(claude/openai/gemini/kiro/vertex/antigravity 等),改由 provider transport 统一承载
- 移除 control_facade/execution_facade/auth_snapshot_facade 等冗余 facade 层
- 拆分 query/billing 与 query/monitoring 模块、state/runtime/payments 与 security 模块
- 扩展架构测试覆盖 admin_billing/admin_model/admin_users 等新模块
- 删除 docs/architecture/refactor-execution-plan.md 已完成的执行计划文档
This commit is contained in:
fawney19
2026-04-09 00:10:38 +08:00
parent 4fb9882b54
commit 4fc95adfb9
663 changed files with 48471 additions and 40232 deletions
@@ -1,5 +1,5 @@
use crate::control::GatewayPublicRequestContext;
use crate::{AppState, GatewayError};
use crate::handlers::admin::request::{AdminAppState, AdminRequestContext};
use crate::GatewayError;
use axum::body::{Body, Bytes};
use axum::http::Response;
@@ -7,8 +7,8 @@ mod routes;
mod shared;
pub(crate) async fn maybe_build_local_admin_adaptive_response(
state: &AppState,
request_context: &GatewayPublicRequestContext,
state: &AdminAppState<'_>,
request_context: &AdminRequestContext<'_>,
request_body: Option<&Bytes>,
) -> Result<Option<Response<Body>>, GatewayError> {
routes::maybe_build_local_admin_adaptive_response(state, request_context, request_body).await
@@ -1,38 +1,23 @@
use super::shared::{
admin_adaptive_adjustment_items, admin_adaptive_dispatcher_not_found_response,
admin_adaptive_effective_limit, admin_adaptive_find_key, admin_adaptive_key_id_from_path,
admin_adaptive_key_not_found_response, admin_adaptive_key_payload,
admin_adaptive_load_candidate_keys,
};
use crate::control::GatewayPublicRequestContext;
use crate::handlers::admin::shared::{
build_proxy_error_response, query_param_value, unix_secs_to_rfc3339,
};
use crate::{AppState, GatewayError};
use super::shared::admin_adaptive_key_id_from_path;
use crate::handlers::admin::request::{AdminAppState, AdminRequestContext};
use crate::handlers::admin::shared::{build_proxy_error_response, query_param_value};
use crate::GatewayError;
use axum::{
body::{Body, Bytes},
http,
response::{IntoResponse, Response},
Json,
};
use serde::Deserialize;
use serde_json::json;
const ADMIN_ADAPTIVE_DATA_UNAVAILABLE_DETAIL: &str = "Admin adaptive data unavailable";
#[derive(Debug, Deserialize)]
struct AdminAdaptiveToggleModeRequest {
enabled: bool,
#[serde(default)]
fixed_limit: Option<u32>,
}
pub(super) async fn maybe_build_local_admin_adaptive_response(
state: &AppState,
request_context: &GatewayPublicRequestContext,
state: &AdminAppState<'_>,
request_context: &AdminRequestContext<'_>,
request_body: Option<&Bytes>,
) -> Result<Option<Response<Body>>, GatewayError> {
let Some(decision) = request_context.control_decision.as_ref() else {
let Some(decision) = request_context.decision() else {
return Ok(None);
};
@@ -52,136 +37,42 @@ pub(super) async fn maybe_build_local_admin_adaptive_response(
}
if decision.route_kind.as_deref() == Some("list_keys")
&& request_context.request_method == http::Method::GET
&& request_context.method() == http::Method::GET
&& matches!(
request_context.request_path.as_str(),
request_context.path(),
"/api/admin/adaptive/keys" | "/api/admin/adaptive/keys/"
)
{
let provider_id = query_param_value(
request_context.request_query_string.as_deref(),
"provider_id",
);
let payload = admin_adaptive_load_candidate_keys(state, provider_id.as_deref())
.await?
.into_iter()
.filter(|key| key.rpm_limit.is_none())
.map(|key| admin_adaptive_key_payload(&key))
.collect::<Vec<_>>();
return Ok(Some(Json(payload).into_response()));
}
if decision.route_kind.as_deref() == Some("summary")
&& request_context.request_method == http::Method::GET
&& matches!(
request_context.request_path.as_str(),
"/api/admin/adaptive/summary" | "/api/admin/adaptive/summary/"
)
{
let keys = admin_adaptive_load_candidate_keys(state, None).await?;
let adaptive_keys = keys
.into_iter()
.filter(|key| key.rpm_limit.is_none())
.collect::<Vec<_>>();
let total_keys = adaptive_keys.len() as u64;
let total_concurrent_429 = adaptive_keys
.iter()
.map(|key| u64::from(key.concurrent_429_count.unwrap_or(0)))
.sum::<u64>();
let total_rpm_429 = adaptive_keys
.iter()
.map(|key| u64::from(key.rpm_429_count.unwrap_or(0)))
.sum::<u64>();
let mut recent_adjustments = vec![];
let mut total_adjustments = 0usize;
for key in adaptive_keys {
let history = admin_adaptive_adjustment_items(key.adjustment_history.as_ref());
total_adjustments += history.len();
for adjustment in history.into_iter().rev().take(3) {
let mut payload = adjustment;
payload.insert("key_id".to_string(), json!(key.id));
payload.insert("key_name".to_string(), json!(key.name));
recent_adjustments.push(serde_json::Value::Object(payload));
}
}
recent_adjustments.sort_by(|left, right| {
let lhs = left
.get("timestamp")
.and_then(serde_json::Value::as_str)
.unwrap_or_default();
let rhs = right
.get("timestamp")
.and_then(serde_json::Value::as_str)
.unwrap_or_default();
rhs.cmp(lhs)
});
let provider_id = query_param_value(request_context.query_string(), "provider_id");
return Ok(Some(
Json(json!({
"total_adaptive_keys": total_keys,
"total_concurrent_429_errors": total_concurrent_429,
"total_rpm_429_errors": total_rpm_429,
"total_adjustments": total_adjustments,
"recent_adjustments": recent_adjustments.into_iter().take(10).collect::<Vec<_>>(),
}))
.into_response(),
state
.build_admin_adaptive_keys_response(provider_id.as_deref())
.await?,
));
}
let key_id = admin_adaptive_key_id_from_path(&request_context.request_path);
if decision.route_kind.as_deref() == Some("summary")
&& request_context.method() == http::Method::GET
&& matches!(
request_context.path(),
"/api/admin/adaptive/summary" | "/api/admin/adaptive/summary/"
)
{
return Ok(Some(state.build_admin_adaptive_summary_response().await?));
}
let key_id = admin_adaptive_key_id_from_path(request_context.path());
if key_id.is_none() {
return Ok(Some(admin_adaptive_dispatcher_not_found_response()));
return Ok(Some(state.admin_adaptive_dispatcher_not_found_response()));
}
let key_id = key_id.expect("checked is_some above");
if decision.route_kind.as_deref() == Some("get_stats")
&& request_context.request_method == http::Method::GET
&& request_context.request_path.ends_with("/stats")
&& request_context.method() == http::Method::GET
&& request_context.path().ends_with("/stats")
{
let Some(key) = admin_adaptive_find_key(state, &key_id).await? else {
return Ok(Some(admin_adaptive_key_not_found_response(&key_id)));
};
let status_snapshot = key
.status_snapshot
.as_ref()
.and_then(serde_json::Value::as_object);
let adjustments = admin_adaptive_adjustment_items(key.adjustment_history.as_ref());
let adjustment_count = adjustments.len();
let recent_adjustments = adjustments
.into_iter()
.rev()
.take(10)
.map(serde_json::Value::Object)
.collect::<Vec<_>>();
return Ok(Some(
Json(json!({
"adaptive_mode": key.rpm_limit.is_none(),
"rpm_limit": key.rpm_limit,
"effective_limit": admin_adaptive_effective_limit(&key),
"learned_limit": key.learned_rpm_limit,
"concurrent_429_count": key.concurrent_429_count.unwrap_or(0),
"rpm_429_count": key.rpm_429_count.unwrap_or(0),
"last_429_at": key.last_429_at_unix_secs.and_then(unix_secs_to_rfc3339),
"last_429_type": key.last_429_type,
"adjustment_count": adjustment_count,
"recent_adjustments": recent_adjustments,
"learning_confidence": status_snapshot.and_then(|value| value.get("learning_confidence")).cloned(),
"enforcement_active": status_snapshot.and_then(|value| value.get("enforcement_active")).cloned(),
"observation_count": status_snapshot
.and_then(|value| value.get("observation_count"))
.and_then(serde_json::Value::as_u64)
.unwrap_or(0),
"header_observation_count": status_snapshot
.and_then(|value| value.get("header_observation_count"))
.and_then(serde_json::Value::as_u64)
.unwrap_or(0),
"latest_upstream_limit": status_snapshot
.and_then(|value| value.get("latest_upstream_limit"))
.and_then(serde_json::Value::as_u64),
}))
.into_response(),
state.build_admin_adaptive_stats_response(&key_id).await?,
));
}
@@ -197,8 +88,8 @@ pub(super) async fn maybe_build_local_admin_adaptive_response(
}
if decision.route_kind.as_deref() == Some("toggle_mode")
&& request_context.request_method == http::Method::PATCH
&& request_context.request_path.ends_with("/mode")
&& request_context.method() == http::Method::PATCH
&& request_context.path().ends_with("/mode")
{
let Some(request_body) = request_body else {
return Ok(Some(build_proxy_error_response(
@@ -208,71 +99,18 @@ pub(super) async fn maybe_build_local_admin_adaptive_response(
None,
)));
};
let body = match serde_json::from_slice::<AdminAdaptiveToggleModeRequest>(request_body) {
Ok(payload) => payload,
Err(_) => {
return Ok(Some(build_proxy_error_response(
http::StatusCode::BAD_REQUEST,
"invalid_request",
"请求数据验证失败",
None,
)));
}
};
let Some(mut key) = admin_adaptive_find_key(state, &key_id).await? else {
return Ok(Some(admin_adaptive_key_not_found_response(&key_id)));
};
let message = if body.enabled {
key.rpm_limit = None;
"已切换为自适应模式,系统将自动学习并调整 RPM 限制".to_string()
} else {
let Some(fixed_limit) = body.fixed_limit else {
return Ok(Some(
(
http::StatusCode::BAD_REQUEST,
Json(json!({
"detail": "禁用自适应模式时必须提供 fixed_limit 参数",
})),
)
.into_response(),
));
};
if !(1..=100).contains(&fixed_limit) {
return Ok(Some(
(
http::StatusCode::BAD_REQUEST,
Json(json!({
"detail": "fixed_limit 超出范围(1-100)",
})),
)
.into_response(),
));
}
key.rpm_limit = Some(fixed_limit);
format!("已切换为固定限制模式,RPM 限制设为 {fixed_limit}")
};
let Some(updated) = state.update_provider_catalog_key(&key).await? else {
return Ok(Some(admin_adaptive_key_not_found_response(&key_id)));
};
return Ok(Some(
Json(json!({
"message": message,
"key_id": updated.id,
"is_adaptive": updated.rpm_limit.is_none(),
"rpm_limit": updated.rpm_limit,
"effective_limit": admin_adaptive_effective_limit(&updated),
}))
.into_response(),
state
.toggle_admin_adaptive_mode_response(&key_id, request_body)
.await?,
));
}
if decision.route_kind.as_deref() == Some("set_limit")
&& request_context.request_method == http::Method::PATCH
&& request_context.request_path.ends_with("/limit")
&& request_context.method() == http::Method::PATCH
&& request_context.path().ends_with("/limit")
{
let Some(limit_value) =
query_param_value(request_context.request_query_string.as_deref(), "limit")
else {
let Some(limit_value) = query_param_value(request_context.query_string(), "limit") else {
return Ok(Some(
(
http::StatusCode::BAD_REQUEST,
@@ -300,50 +138,23 @@ pub(super) async fn maybe_build_local_admin_adaptive_response(
));
}
let Some(mut key) = admin_adaptive_find_key(state, &key_id).await? else {
return Ok(Some(admin_adaptive_key_not_found_response(&key_id)));
};
let was_adaptive = key.rpm_limit.is_none();
key.rpm_limit = Some(limit);
let Some(updated) = state.update_provider_catalog_key(&key).await? else {
return Ok(Some(admin_adaptive_key_not_found_response(&key_id)));
};
return Ok(Some(
Json(json!({
"message": format!("已设置为固定限制模式,RPM 限制为 {limit}"),
"key_id": updated.id,
"is_adaptive": false,
"rpm_limit": updated.rpm_limit,
"previous_mode": if was_adaptive { "adaptive" } else { "fixed" },
}))
.into_response(),
state
.set_admin_adaptive_limit_response(&key_id, limit)
.await?,
));
}
if decision.route_kind.as_deref() == Some("reset_learning")
&& request_context.request_method == http::Method::DELETE
&& request_context.request_path.ends_with("/learning")
&& request_context.method() == http::Method::DELETE
&& request_context.path().ends_with("/learning")
{
let Some(mut key) = admin_adaptive_find_key(state, &key_id).await? else {
return Ok(Some(admin_adaptive_key_not_found_response(&key_id)));
};
key.learned_rpm_limit = None;
key.concurrent_429_count = None;
key.rpm_429_count = None;
key.last_429_at_unix_secs = None;
key.last_429_type = None;
key.adjustment_history = None;
let Some(updated) = state.update_provider_catalog_key(&key).await? else {
return Ok(Some(admin_adaptive_key_not_found_response(&key_id)));
};
return Ok(Some(
Json(json!({
"message": "学习状态已重置",
"key_id": updated.id,
}))
.into_response(),
state
.reset_admin_adaptive_learning_response(&key_id)
.await?,
));
}
Ok(Some(admin_adaptive_dispatcher_not_found_response()))
Ok(Some(state.admin_adaptive_dispatcher_not_found_response()))
}
@@ -1,86 +1,36 @@
use crate::handlers::admin::shared::json_string_list;
use crate::{AppState, GatewayError};
use crate::handlers::admin::request::AdminAppState;
use crate::GatewayError;
use aether_data_contracts::repository::provider_catalog::StoredProviderCatalogKey;
use axum::{
body::Body,
http,
response::{IntoResponse, Response},
Json,
};
use serde_json::json;
use axum::{body::Body, response::Response};
pub(super) fn admin_adaptive_effective_limit(key: &StoredProviderCatalogKey) -> Option<u32> {
if key.rpm_limit.is_none() {
key.learned_rpm_limit
} else {
key.rpm_limit
}
aether_admin::system::admin_adaptive_effective_limit(key)
}
pub(super) fn admin_adaptive_adjustment_items(
value: Option<&serde_json::Value>,
) -> Vec<serde_json::Map<String, serde_json::Value>> {
value
.and_then(serde_json::Value::as_array)
.into_iter()
.flatten()
.filter_map(serde_json::Value::as_object)
.cloned()
.collect()
aether_admin::system::admin_adaptive_adjustment_items(value)
}
pub(super) fn admin_adaptive_key_payload(key: &StoredProviderCatalogKey) -> serde_json::Value {
json!({
"id": key.id,
"name": key.name,
"provider_id": key.provider_id,
"api_formats": json_string_list(key.api_formats.as_ref()),
"is_adaptive": key.rpm_limit.is_none(),
"rpm_limit": key.rpm_limit,
"effective_limit": admin_adaptive_effective_limit(key),
"learned_rpm_limit": key.learned_rpm_limit,
"concurrent_429_count": key.concurrent_429_count.unwrap_or(0),
"rpm_429_count": key.rpm_429_count.unwrap_or(0),
})
aether_admin::system::admin_adaptive_key_payload(key)
}
pub(super) fn admin_adaptive_key_not_found_response(key_id: &str) -> Response<Body> {
(
http::StatusCode::NOT_FOUND,
Json(json!({ "detail": format!("Key {key_id} 不存在") })),
)
.into_response()
aether_admin::system::admin_adaptive_key_not_found_response(key_id)
}
pub(super) fn admin_adaptive_dispatcher_not_found_response() -> Response<Body> {
(
http::StatusCode::NOT_FOUND,
Json(json!({ "detail": "Adaptive route not found" })),
)
.into_response()
aether_admin::system::admin_adaptive_dispatcher_not_found_response()
}
pub(super) fn admin_adaptive_key_id_from_path(path: &str) -> Option<String> {
let normalized = path.trim_end_matches('/');
let mut segments = normalized.split('/').filter(|segment| !segment.is_empty());
match (
segments.next(),
segments.next(),
segments.next(),
segments.next(),
segments.next(),
) {
(Some("api"), Some("admin"), Some("adaptive"), Some("keys"), Some(key_id))
if !key_id.is_empty() =>
{
Some(key_id.to_string())
}
_ => None,
}
aether_admin::system::admin_adaptive_key_id_from_path(path)
}
pub(super) async fn admin_adaptive_find_key(
state: &AppState,
state: &AdminAppState<'_>,
key_id: &str,
) -> Result<Option<StoredProviderCatalogKey>, GatewayError> {
Ok(state
@@ -91,7 +41,7 @@ pub(super) async fn admin_adaptive_find_key(
}
pub(super) async fn admin_adaptive_load_candidate_keys(
state: &AppState,
state: &AdminAppState<'_>,
provider_id: Option<&str>,
) -> Result<Vec<StoredProviderCatalogKey>, GatewayError> {
if let Some(provider_id) = provider_id.filter(|value| !value.trim().is_empty()) {