refactor: 移除独立 hub/proxy/executor/gateway crate,统一为 gateway tunnel 架构

- 删除 aether-hub、aether-proxy 独立项目及其 Dockerfile/配置
- 删除 crates/aether-executor 和 crates/aether-gateway 全部模块
- 新增 apps/ 目录作为应用入口
- 将 hub 概念重构为 gateway tunnel transport
- 将 executor 重构为 execution runtime
- 新增 tunnel.rs 合约定义和 testkit tunnel/execution_runtime 模块
- 更新 Python 服务层和测试适配新架构命名
This commit is contained in:
fawney19
2026-04-03 14:59:58 +08:00
parent ddf18fed9a
commit 8f26e1a31f
983 changed files with 103097 additions and 105836 deletions
@@ -0,0 +1,272 @@
use super::*;
#[derive(Debug, serde::Deserialize)]
pub(super) struct AdminCreateUserApiKeyRequest {
#[serde(default)]
pub(super) name: Option<String>,
#[serde(default)]
pub(super) allowed_providers: Option<Vec<String>>,
#[serde(default)]
pub(super) allowed_api_formats: Option<Vec<String>>,
#[serde(default)]
pub(super) allowed_models: Option<Vec<String>>,
#[serde(default)]
pub(super) rate_limit: Option<i32>,
#[serde(default)]
pub(super) expire_days: Option<i32>,
#[serde(default)]
pub(super) expires_at: Option<String>,
#[serde(default)]
pub(super) initial_balance_usd: Option<f64>,
#[serde(default)]
pub(super) unlimited_balance: Option<bool>,
#[serde(default)]
pub(super) is_standalone: Option<bool>,
#[serde(default)]
pub(super) auto_delete_on_expiry: Option<bool>,
}
#[derive(Debug, serde::Deserialize)]
pub(super) struct AdminUpdateUserApiKeyRequest {
#[serde(default)]
pub(super) name: Option<String>,
#[serde(default)]
pub(super) rate_limit: Option<i32>,
}
#[derive(Debug, serde::Deserialize)]
pub(super) struct AdminToggleUserApiKeyLockRequest {
#[serde(default)]
pub(super) locked: Option<bool>,
}
#[derive(Debug, serde::Deserialize)]
pub(super) struct AdminCreateUserRequest {
pub(super) username: String,
pub(super) password: String,
#[serde(default)]
pub(super) email: Option<String>,
#[serde(default)]
pub(super) role: Option<String>,
#[serde(default)]
pub(super) initial_gift_usd: Option<f64>,
#[serde(default)]
pub(super) unlimited: bool,
#[serde(default)]
pub(super) allowed_providers: Option<Vec<String>>,
#[serde(default)]
pub(super) allowed_api_formats: Option<Vec<String>>,
#[serde(default)]
pub(super) allowed_models: Option<Vec<String>>,
#[serde(default)]
pub(super) rate_limit: Option<i32>,
}
#[derive(Debug, serde::Deserialize)]
pub(super) struct AdminUpdateUserRequest {
#[serde(default)]
pub(super) email: Option<String>,
#[serde(default)]
pub(super) username: Option<String>,
#[serde(default)]
pub(super) password: Option<String>,
#[serde(default)]
pub(super) role: Option<String>,
#[serde(default)]
pub(super) unlimited: Option<bool>,
#[serde(default)]
pub(super) allowed_providers: Option<Vec<String>>,
#[serde(default)]
pub(super) allowed_api_formats: Option<Vec<String>>,
#[serde(default)]
pub(super) allowed_models: Option<Vec<String>>,
#[serde(default)]
pub(super) rate_limit: Option<i32>,
#[serde(default)]
pub(super) is_active: Option<bool>,
}
#[derive(Debug, Default)]
pub(super) struct AdminUpdateUserFieldPresence {
pub(super) allowed_providers: bool,
pub(super) allowed_api_formats: bool,
pub(super) allowed_models: bool,
}
pub(super) fn build_admin_users_maintenance_response() -> Response<Body> {
(
http::StatusCode::SERVICE_UNAVAILABLE,
Json(json!({ "detail": ADMIN_USERS_RUST_BACKEND_DETAIL })),
)
.into_response()
}
pub(super) fn build_admin_users_read_only_response(detail: &'static str) -> Response<Body> {
(
http::StatusCode::CONFLICT,
Json(json!({
"detail": detail,
"error_code": "read_only_mode",
})),
)
.into_response()
}
pub(super) fn build_admin_users_bad_request_response(detail: &'static str) -> Response<Body> {
(
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": detail })),
)
.into_response()
}
pub(super) fn normalize_admin_optional_user_email(
value: Option<&str>,
) -> Result<Option<String>, String> {
let Some(value) = value else {
return Ok(None);
};
let value = value.trim();
if value.is_empty() {
return Ok(None);
}
let normalized = value.to_ascii_lowercase();
let pattern = Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")
.expect("email regex should compile");
if !pattern.is_match(&normalized) {
return Err("邮箱格式无效".to_string());
}
Ok(Some(normalized))
}
pub(super) fn normalize_admin_username(value: &str) -> Result<String, String> {
let value = value.trim();
if value.is_empty() {
return Err("用户名不能为空".to_string());
}
if value.len() < 3 {
return Err("用户名长度至少为3个字符".to_string());
}
if value.len() > 30 {
return Err("用户名长度不能超过30个字符".to_string());
}
let pattern = Regex::new(r"^[a-zA-Z0-9_.-]+$").expect("username regex should compile");
if !pattern.is_match(value) {
return Err("用户名只能包含字母、数字、下划线、连字符和点号".to_string());
}
Ok(value.to_string())
}
pub(super) fn validate_admin_user_password(password: &str, policy: &str) -> Result<(), String> {
if password.is_empty() {
return Err("密码不能为空".to_string());
}
if password.as_bytes().len() > 72 {
return Err("密码长度不能超过72字节".to_string());
}
let min_len = if matches!(policy, "medium" | "strong") {
8
} else {
6
};
if password.chars().count() < min_len {
return Err(format!("密码长度至少为{min_len}个字符"));
}
if policy == "medium" {
if !password.chars().any(|ch| ch.is_ascii_alphabetic()) {
return Err("密码必须包含至少一个字母".to_string());
}
if !password.chars().any(|ch| ch.is_ascii_digit()) {
return Err("密码必须包含至少一个数字".to_string());
}
} else if policy == "strong" {
if !password.chars().any(|ch| ch.is_ascii_uppercase()) {
return Err("密码必须包含至少一个大写字母".to_string());
}
if !password.chars().any(|ch| ch.is_ascii_lowercase()) {
return Err("密码必须包含至少一个小写字母".to_string());
}
if !password.chars().any(|ch| ch.is_ascii_digit()) {
return Err("密码必须包含至少一个数字".to_string());
}
if !password.chars().any(|ch| !ch.is_ascii_alphanumeric()) {
return Err("密码必须包含至少一个特殊字符".to_string());
}
}
Ok(())
}
pub(super) fn normalize_admin_user_role(value: Option<&str>) -> Result<String, String> {
match value
.map(str::trim)
.filter(|value| !value.is_empty())
.unwrap_or("user")
.to_ascii_lowercase()
.as_str()
{
"user" => Ok("user".to_string()),
"admin" => Ok("admin".to_string()),
_ => Err("角色参数不合法".to_string()),
}
}
pub(crate) fn normalize_admin_user_string_list(
value: Option<Vec<String>>,
field_name: &str,
) -> Result<Option<Vec<String>>, String> {
let Some(values) = value else {
return Ok(None);
};
let mut normalized = Vec::new();
let mut seen = std::collections::BTreeSet::new();
for item in values {
let item = item.trim();
if item.is_empty() {
return Err(format!("{field_name} 不能为空"));
}
if seen.insert(item.to_string()) {
normalized.push(item.to_string());
}
}
Ok(Some(normalized))
}
pub(crate) fn normalize_admin_user_api_formats(
value: Option<Vec<String>>,
) -> Result<Option<Vec<String>>, String> {
let Some(values) = value else {
return Ok(None);
};
let mut normalized = Vec::new();
let mut seen = std::collections::BTreeSet::new();
let pattern =
Regex::new(r"^[A-Za-z0-9_.-]+:[A-Za-z0-9_.-]+$").expect("api format regex should compile");
for item in values {
let item = item.trim();
if item.is_empty() {
return Err("allowed_api_formats 不能为空".to_string());
}
if !pattern.is_match(item) {
return Err(format!("allowed_api_formats 格式无效: {item}"));
}
let normalized_item = item.to_ascii_lowercase();
if seen.insert(normalized_item.clone()) {
normalized.push(normalized_item);
}
}
Ok(Some(normalized))
}
pub(super) fn admin_default_user_initial_gift(value: Option<&serde_json::Value>) -> f64 {
match value {
Some(serde_json::Value::Number(number)) => number.as_f64().unwrap_or(10.0),
Some(serde_json::Value::String(value)) => value.parse::<f64>().unwrap_or(10.0),
_ => 10.0,
}
}
pub(super) fn format_optional_datetime_iso8601(
value: Option<chrono::DateTime<chrono::Utc>>,
) -> Option<String> {
value.map(|value| value.to_rfc3339())
}