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
+201
View File
@@ -0,0 +1,201 @@
use std::collections::BTreeMap;
use aether_data::repository::video_tasks::{
StoredVideoTask, VideoTaskModelCount, VideoTaskQueryFilter, VideoTaskStatusCount,
};
use serde::Serialize;
use crate::gateway::{AppState, GatewayError};
#[derive(Debug, Clone, Serialize)]
pub(crate) struct VideoTaskPageResponse {
pub(crate) items: Vec<StoredVideoTask>,
pub(crate) total: u64,
pub(crate) page: usize,
pub(crate) page_size: usize,
pub(crate) pages: usize,
}
#[derive(Debug, Clone, Serialize)]
pub(crate) struct VideoTaskStatsResponse {
pub(crate) total: u64,
pub(crate) by_status: BTreeMap<String, u64>,
pub(crate) by_model: BTreeMap<String, u64>,
pub(crate) today_count: u64,
pub(crate) processing_count: u64,
}
#[derive(Debug, Clone)]
pub(crate) enum VideoTaskVideoSource {
Redirect {
url: String,
},
Proxy {
url: String,
header_name: String,
header_value: String,
filename: String,
},
}
pub(crate) async fn read_video_task_page(
state: &AppState,
filter: &VideoTaskQueryFilter,
page: usize,
page_size: usize,
) -> Result<VideoTaskPageResponse, GatewayError> {
let page = page.max(1);
let page_size = page_size.clamp(1, 100);
let total = state.count_video_tasks(filter).await?;
let offset = page_size.saturating_mul(page.saturating_sub(1));
let items = state
.list_video_task_page(filter, offset, page_size)
.await?;
let pages = if total == 0 {
0
} else {
((total as usize) + page_size - 1) / page_size
};
Ok(VideoTaskPageResponse {
items,
total,
page,
page_size,
pages,
})
}
pub(crate) async fn read_video_task_detail(
state: &AppState,
task_id: &str,
) -> Result<Option<StoredVideoTask>, GatewayError> {
state.find_video_task_by_id(task_id).await
}
pub(crate) async fn read_video_task_video_source(
state: &AppState,
task_id: &str,
) -> Result<Option<VideoTaskVideoSource>, GatewayError> {
let Some(task) = read_video_task_detail(state, task_id).await? else {
return Ok(None);
};
let Some(video_url) = task
.video_url
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned)
else {
return Ok(None);
};
if !video_url.contains("generativelanguage.googleapis.com") {
return Ok(Some(VideoTaskVideoSource::Redirect { url: video_url }));
}
let Some(provider_id) = task.provider_id.as_deref() else {
return Err(GatewayError::Internal(
"video task is missing provider_id for proxied video".to_string(),
));
};
let Some(endpoint_id) = task.endpoint_id.as_deref() else {
return Err(GatewayError::Internal(
"video task is missing endpoint_id for proxied video".to_string(),
));
};
let Some(key_id) = task.key_id.as_deref() else {
return Err(GatewayError::Internal(
"video task is missing key_id for proxied video".to_string(),
));
};
let Some(transport) = state
.read_provider_transport_snapshot(provider_id, endpoint_id, key_id)
.await?
else {
return Err(GatewayError::Internal(
"provider transport snapshot is unavailable for proxied video".to_string(),
));
};
let api_key = transport.key.decrypted_api_key.trim();
if api_key.is_empty() {
return Err(GatewayError::Internal(
"provider transport key is unavailable for proxied video".to_string(),
));
}
Ok(Some(VideoTaskVideoSource::Proxy {
url: video_url,
header_name: "x-goog-api-key".to_string(),
header_value: api_key.to_string(),
filename: format!("video_{task_id}.mp4"),
}))
}
pub(crate) async fn read_video_task_stats(
state: &AppState,
filter: &VideoTaskQueryFilter,
now_unix_secs: u64,
) -> Result<VideoTaskStatsResponse, GatewayError> {
let total = state.count_video_tasks(filter).await?;
let by_status = state.count_video_tasks_by_status(filter).await?;
let by_model = state.top_video_task_models(filter, 10).await?;
let today_count = state
.count_video_tasks_created_since(filter, start_of_utc_day(now_unix_secs))
.await?;
let processing_count = by_status
.iter()
.filter(|entry| {
matches!(
entry.status,
aether_data::repository::video_tasks::VideoTaskStatus::Submitted
| aether_data::repository::video_tasks::VideoTaskStatus::Queued
| aether_data::repository::video_tasks::VideoTaskStatus::Processing
)
})
.map(|entry| entry.count)
.sum();
Ok(VideoTaskStatsResponse {
total,
by_status: map_status_counts(by_status),
by_model: map_model_counts(by_model),
today_count,
processing_count,
})
}
fn map_status_counts(counts: Vec<VideoTaskStatusCount>) -> BTreeMap<String, u64> {
counts
.into_iter()
.map(|entry| (status_key(entry.status), entry.count))
.collect()
}
fn map_model_counts(counts: Vec<VideoTaskModelCount>) -> BTreeMap<String, u64> {
counts
.into_iter()
.map(|entry| (entry.model, entry.count))
.collect()
}
fn status_key(status: aether_data::repository::video_tasks::VideoTaskStatus) -> String {
match status {
aether_data::repository::video_tasks::VideoTaskStatus::Pending => "pending",
aether_data::repository::video_tasks::VideoTaskStatus::Submitted => "submitted",
aether_data::repository::video_tasks::VideoTaskStatus::Queued => "queued",
aether_data::repository::video_tasks::VideoTaskStatus::Processing => "processing",
aether_data::repository::video_tasks::VideoTaskStatus::Completed => "completed",
aether_data::repository::video_tasks::VideoTaskStatus::Failed => "failed",
aether_data::repository::video_tasks::VideoTaskStatus::Cancelled => "cancelled",
aether_data::repository::video_tasks::VideoTaskStatus::Expired => "expired",
aether_data::repository::video_tasks::VideoTaskStatus::Deleted => "deleted",
}
.to_string()
}
fn start_of_utc_day(now_unix_secs: u64) -> u64 {
now_unix_secs - (now_unix_secs % 86_400)
}