feat(gateway): 重构 usage 数据层、迁移系统与系统导入

数据库迁移:
- 引入 baseline v2 bootstrap,空库首次启动自动初始化
- 服务启动不再自动执行迁移,需显式 `--migrate` 运行
- 新增 pending migration 检测,schema 落后时拒绝启动

Usage 数据层:
- usage body 存储外部化为独立 blob 表
- 新增 HTTP audit 表拆分存储请求/响应头与 body ref
- 后台清理任务支持 legacy body ref 元数据迁移
- usage runtime 写入迁移到专用 tokio runtime(独立线程池, 8MB 栈)

系统导入/导出:
- 支持用户、API Keys、钱包数据的完整导入
- 兼容 legacy 与 v1.3+ 两种导出格式

其他改进:
- executor outcome 增加 runtime miss 诊断上下文
- 主 tokio runtime 栈大小调整为 8MB
- 前端 provider 管理支持 base URL 配置
- dev.sh 支持 --migrate 参数
This commit is contained in:
fawney19
2026-04-13 14:01:22 +08:00
parent 3698e5a833
commit 5bb08e6aa4
106 changed files with 21730 additions and 1523 deletions
+226 -114
View File
@@ -1,17 +1,22 @@
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use aether_contracts::{ExecutionPlan, ExecutionTelemetry};
use aether_contracts::ExecutionTelemetry;
use aether_data::redis::RedisStreamRunner;
use aether_data_contracts::repository::usage::UpsertUsageRecord;
use aether_data_contracts::DataLayerError;
use async_trait::async_trait;
use tracing::warn;
use crate::executor::spawn_on_usage_background_runtime;
use crate::{
build_pending_usage_record, build_stream_terminal_usage_outcome, build_streaming_usage_record,
build_sync_terminal_usage_outcome, build_terminal_usage_event_from_outcome,
build_upsert_usage_record_from_event, build_usage_queue_worker, settle_usage_if_needed,
GatewayStreamReportRequest, GatewaySyncReportRequest, UsageEvent, UsageQueue,
UsageRecordWriter, UsageRuntimeConfig, UsageSettlementWriter, UsageTerminalState,
build_pending_usage_record_from_seed, build_stream_terminal_usage_seed,
build_streaming_usage_record_from_seed, build_sync_terminal_usage_seed,
build_terminal_usage_event_from_seed, build_upsert_usage_record_from_event,
build_usage_queue_worker, settle_usage_if_needed, LifecycleUsageSeed,
StreamTerminalUsagePayloadSeed, SyncTerminalUsagePayloadSeed, TerminalUsageContextSeed,
UsageEvent, UsageQueue, UsageRecordWriter, UsageRuntimeConfig, UsageSettlementWriter,
};
#[async_trait]
@@ -32,6 +37,17 @@ pub struct UsageRuntime {
config: UsageRuntimeConfig,
}
struct SyncTerminalUsageTaskInput {
context_seed: TerminalUsageContextSeed,
payload_seed: SyncTerminalUsagePayloadSeed,
}
struct StreamTerminalUsageTaskInput {
context_seed: TerminalUsageContextSeed,
payload_seed: StreamTerminalUsagePayloadSeed,
cancelled: bool,
}
impl Default for UsageRuntime {
fn default() -> Self {
Self::disabled()
@@ -73,169 +89,197 @@ impl UsageRuntime {
Some(worker.spawn())
}
pub async fn record_pending<T>(
&self,
data: &T,
plan: &ExecutionPlan,
report_context: Option<&serde_json::Value>,
) where
T: UsageRuntimeAccess,
pub fn record_pending<T>(&self, data: &T, seed: &LifecycleUsageSeed)
where
T: UsageRuntimeAccess + Clone + 'static,
{
if !self.is_enabled() {
return;
}
let now_unix_secs = now_unix_secs();
match build_pending_usage_record(plan, report_context, now_unix_secs) {
Ok(record) => {
if let Err(err) = data.upsert_usage_record(record).await {
let data = T::clone(data);
let seed = seed.clone();
let request_id = seed.request_id.clone();
spawn_on_usage_background_runtime(boxed_usage_task(async move {
let now_unix_secs = now_unix_secs();
match build_pending_usage_record_offthread(&seed, now_unix_secs).await {
Ok(record) => {
if let Err(err) = data.upsert_usage_record(record).await {
warn!(
event_name = "usage_pending_record_failed",
log_type = "event",
request_id = %request_id,
error = %err,
"usage runtime failed to record sync pending usage"
);
}
}
Err(err) => {
warn!(
event_name = "usage_pending_record_failed",
event_name = "usage_pending_build_failed",
log_type = "event",
request_id = %plan.request_id,
request_id = %request_id,
error = %err,
"usage runtime failed to record sync pending usage"
);
"usage runtime failed to build sync pending usage"
)
}
}
Err(err) => {
warn!(
event_name = "usage_pending_build_failed",
log_type = "event",
request_id = %plan.request_id,
error = %err,
"usage runtime failed to build sync pending usage"
)
}
}
}));
}
pub async fn record_stream_started<T>(
pub fn record_stream_started<T>(
&self,
data: &T,
plan: &ExecutionPlan,
report_context: Option<&serde_json::Value>,
seed: &LifecycleUsageSeed,
status_code: u16,
headers: &std::collections::BTreeMap<String, String>,
telemetry: Option<&ExecutionTelemetry>,
) where
T: UsageRuntimeAccess,
T: UsageRuntimeAccess + Clone + 'static,
{
if !self.is_enabled() {
return;
}
let now_unix_secs = now_unix_secs();
match build_streaming_usage_record(
plan,
report_context,
status_code,
headers,
telemetry,
now_unix_secs,
) {
Ok(record) => {
if let Err(err) = data.upsert_usage_record(record).await {
let data = T::clone(data);
let seed = seed.clone();
let telemetry = telemetry.cloned();
let request_id = seed.request_id.clone();
spawn_on_usage_background_runtime(boxed_usage_task(async move {
let now_unix_secs = now_unix_secs();
match build_streaming_usage_record_offthread(
&seed,
status_code,
telemetry.as_ref(),
now_unix_secs,
)
.await
{
Ok(record) => {
if let Err(err) = data.upsert_usage_record(record).await {
warn!(
event_name = "usage_stream_record_failed",
log_type = "event",
request_id = %request_id,
error = %err,
"usage runtime failed to record stream usage"
);
}
}
Err(err) => {
warn!(
event_name = "usage_stream_record_failed",
event_name = "usage_stream_build_failed",
log_type = "event",
request_id = %plan.request_id,
request_id = %request_id,
error = %err,
"usage runtime failed to record stream usage"
);
"usage runtime failed to build stream usage"
)
}
}
Err(err) => {
warn!(
event_name = "usage_stream_build_failed",
log_type = "event",
request_id = %plan.request_id,
error = %err,
"usage runtime failed to build stream usage"
)
}
}
}));
}
pub async fn record_sync_terminal<T>(
pub fn record_sync_terminal<T>(
&self,
data: &T,
plan: &ExecutionPlan,
report_context: Option<&serde_json::Value>,
payload: &GatewaySyncReportRequest,
context_seed: &TerminalUsageContextSeed,
payload_seed: &SyncTerminalUsagePayloadSeed,
) where
T: UsageRuntimeAccess,
T: UsageRuntimeAccess + Clone + 'static,
{
if !self.is_enabled() {
return;
}
match build_terminal_usage_event_from_outcome(build_sync_terminal_usage_outcome(
plan,
report_context,
payload,
)) {
Ok(mut event) => {
if let Err(err) = data.enrich_usage_event(&mut event).await {
let runtime = self.clone();
let data = T::clone(data);
let request_id = context_seed.request_id.clone();
let input = Box::new(SyncTerminalUsageTaskInput {
context_seed: context_seed.clone(),
payload_seed: payload_seed.clone(),
});
spawn_on_usage_background_runtime(boxed_usage_task(async move {
match build_sync_terminal_usage_event_offthread(input).await {
Ok(mut event) => {
if let Err(err) = data.enrich_usage_event(&mut event).await {
warn!(
event_name = "usage_sync_terminal_billing_enrichment_failed",
log_type = "event",
request_id = %request_id,
error = %err,
"usage runtime failed to enrich sync usage event with billing"
);
}
runtime.enqueue_or_write_terminal(&data, event).await
}
Err(err) => {
warn!(
event_name = "usage_sync_terminal_billing_enrichment_failed",
event_name = "usage_sync_terminal_build_failed",
log_type = "event",
request_id = %plan.request_id,
request_id = %request_id,
error = %err,
"usage runtime failed to enrich sync usage event with billing"
);
"usage runtime failed to build sync terminal usage event"
)
}
self.enqueue_or_write_terminal(data, event).await
}
Err(err) => {
warn!(
event_name = "usage_sync_terminal_build_failed",
log_type = "event",
request_id = %plan.request_id,
error = %err,
"usage runtime failed to build sync terminal usage event"
)
}
}
}));
}
pub async fn record_stream_terminal<T>(
pub fn record_stream_terminal<T>(
&self,
data: &T,
plan: &ExecutionPlan,
report_context: Option<&serde_json::Value>,
payload: &GatewayStreamReportRequest,
context_seed: &TerminalUsageContextSeed,
payload_seed: &StreamTerminalUsagePayloadSeed,
cancelled: bool,
) where
T: UsageRuntimeAccess,
T: UsageRuntimeAccess + Clone + 'static,
{
if !self.is_enabled() {
return;
}
let mut outcome = build_stream_terminal_usage_outcome(plan, report_context, payload);
if cancelled {
outcome.terminal_state = UsageTerminalState::Cancelled;
}
match build_terminal_usage_event_from_outcome(outcome) {
Ok(mut event) => {
if let Err(err) = data.enrich_usage_event(&mut event).await {
let runtime = self.clone();
let data = T::clone(data);
let request_id = context_seed.request_id.clone();
let input = Box::new(StreamTerminalUsageTaskInput {
context_seed: context_seed.clone(),
payload_seed: payload_seed.clone(),
cancelled,
});
spawn_on_usage_background_runtime(boxed_usage_task(async move {
match build_stream_terminal_usage_event_offthread(input).await {
Ok(mut event) => {
if let Err(err) = data.enrich_usage_event(&mut event).await {
warn!(
event_name = "usage_stream_terminal_billing_enrichment_failed",
log_type = "event",
request_id = %request_id,
error = %err,
"usage runtime failed to enrich stream usage event with billing"
);
}
runtime.enqueue_or_write_terminal(&data, event).await
}
Err(err) => {
warn!(
event_name = "usage_stream_terminal_billing_enrichment_failed",
event_name = "usage_stream_terminal_build_failed",
log_type = "event",
request_id = %plan.request_id,
request_id = %request_id,
error = %err,
"usage runtime failed to enrich stream usage event with billing"
);
"usage runtime failed to build stream terminal usage event"
)
}
self.enqueue_or_write_terminal(data, event).await
}
Err(err) => {
warn!(
event_name = "usage_stream_terminal_build_failed",
log_type = "event",
request_id = %plan.request_id,
error = %err,
"usage runtime failed to build stream terminal usage event"
)
}
}));
}
pub fn submit_terminal_event<T>(&self, data: &T, event: UsageEvent)
where
T: UsageRuntimeAccess + Clone + 'static,
{
if !self.is_enabled() {
return;
}
let runtime = self.clone();
let data = T::clone(data);
spawn_on_usage_background_runtime(boxed_usage_task(async move {
runtime.record_terminal_event(&data, event).await;
}));
}
pub async fn record_terminal_event<T>(&self, data: &T, mut event: UsageEvent)
@@ -326,6 +370,74 @@ impl UsageRuntime {
}
}
async fn build_pending_usage_record_offthread(
seed: &LifecycleUsageSeed,
now_unix_secs: u64,
) -> Result<UpsertUsageRecord, DataLayerError> {
let seed = seed.clone();
tokio::task::spawn_blocking(move || build_pending_usage_record_from_seed(&seed, now_unix_secs))
.await
.map_err(join_error_to_data_layer)?
}
async fn build_streaming_usage_record_offthread(
seed: &LifecycleUsageSeed,
status_code: u16,
telemetry: Option<&ExecutionTelemetry>,
now_unix_secs: u64,
) -> Result<UpsertUsageRecord, DataLayerError> {
let seed = seed.clone();
let telemetry = telemetry.cloned();
tokio::task::spawn_blocking(move || {
build_streaming_usage_record_from_seed(
&seed,
status_code,
telemetry.as_ref(),
now_unix_secs,
)
})
.await
.map_err(join_error_to_data_layer)?
}
async fn build_sync_terminal_usage_event_offthread(
input: Box<SyncTerminalUsageTaskInput>,
) -> Result<UsageEvent, DataLayerError> {
tokio::task::spawn_blocking(move || {
build_terminal_usage_event_from_seed(build_sync_terminal_usage_seed(
input.context_seed,
input.payload_seed,
))
})
.await
.map_err(join_error_to_data_layer)?
}
async fn build_stream_terminal_usage_event_offthread(
input: Box<StreamTerminalUsageTaskInput>,
) -> Result<UsageEvent, DataLayerError> {
tokio::task::spawn_blocking(move || {
build_terminal_usage_event_from_seed(build_stream_terminal_usage_seed(
input.context_seed,
input.payload_seed,
input.cancelled,
))
})
.await
.map_err(join_error_to_data_layer)?
}
fn join_error_to_data_layer(err: tokio::task::JoinError) -> DataLayerError {
DataLayerError::UnexpectedValue(format!("usage builder task join failed: {err}"))
}
fn boxed_usage_task<F>(task: F) -> Pin<Box<dyn Future<Output = ()> + Send>>
where
F: Future<Output = ()> + Send + 'static,
{
Box::pin(task)
}
fn now_unix_secs() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)