mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 17:30:23 +08:00
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:
@@ -385,15 +385,15 @@ impl AuthApiKeyWriteRepository for InMemoryAuthApiKeySnapshotRepository {
|
||||
StoredAuthApiKeySnapshot {
|
||||
api_key_id: record.api_key_id.clone(),
|
||||
api_key_name: record.name.clone(),
|
||||
api_key_is_active: true,
|
||||
api_key_is_active: record.is_active,
|
||||
api_key_is_locked: false,
|
||||
api_key_is_standalone: false,
|
||||
api_key_rate_limit: Some(record.rate_limit),
|
||||
api_key_concurrent_limit: Some(record.concurrent_limit),
|
||||
api_key_expires_at_unix_secs: None,
|
||||
api_key_allowed_providers: None,
|
||||
api_key_allowed_api_formats: None,
|
||||
api_key_allowed_models: None,
|
||||
api_key_expires_at_unix_secs: record.expires_at_unix_secs,
|
||||
api_key_allowed_providers: record.allowed_providers.clone(),
|
||||
api_key_allowed_api_formats: record.allowed_api_formats.clone(),
|
||||
api_key_allowed_models: record.allowed_models.clone(),
|
||||
..template
|
||||
}
|
||||
} else {
|
||||
@@ -413,15 +413,24 @@ impl AuthApiKeyWriteRepository for InMemoryAuthApiKeySnapshotRepository {
|
||||
None,
|
||||
record.api_key_id.clone(),
|
||||
record.name.clone(),
|
||||
true,
|
||||
record.is_active,
|
||||
false,
|
||||
false,
|
||||
Some(record.rate_limit),
|
||||
Some(record.concurrent_limit),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
record.expires_at_unix_secs.map(|value| value as i64),
|
||||
record
|
||||
.allowed_providers
|
||||
.as_ref()
|
||||
.map(|value| serde_json::json!(value)),
|
||||
record
|
||||
.allowed_api_formats
|
||||
.as_ref()
|
||||
.map(|value| serde_json::json!(value)),
|
||||
record
|
||||
.allowed_models
|
||||
.as_ref()
|
||||
.map(|value| serde_json::json!(value)),
|
||||
)?
|
||||
};
|
||||
|
||||
@@ -431,17 +440,26 @@ impl AuthApiKeyWriteRepository for InMemoryAuthApiKeySnapshotRepository {
|
||||
record.key_hash.clone(),
|
||||
record.key_encrypted,
|
||||
record.name,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
record
|
||||
.allowed_providers
|
||||
.as_ref()
|
||||
.map(|value| serde_json::json!(value)),
|
||||
record
|
||||
.allowed_api_formats
|
||||
.as_ref()
|
||||
.map(|value| serde_json::json!(value)),
|
||||
record
|
||||
.allowed_models
|
||||
.as_ref()
|
||||
.map(|value| serde_json::json!(value)),
|
||||
Some(record.rate_limit),
|
||||
Some(record.concurrent_limit),
|
||||
None,
|
||||
true,
|
||||
None,
|
||||
false,
|
||||
0,
|
||||
0.0,
|
||||
record.force_capabilities,
|
||||
record.is_active,
|
||||
record.expires_at_unix_secs.map(|value| value as i64),
|
||||
record.auto_delete_on_expiry,
|
||||
record.total_requests as i64,
|
||||
record.total_cost_usd,
|
||||
false,
|
||||
)?;
|
||||
|
||||
@@ -487,12 +505,12 @@ impl AuthApiKeyWriteRepository for InMemoryAuthApiKeySnapshotRepository {
|
||||
StoredAuthApiKeySnapshot {
|
||||
api_key_id: record.api_key_id.clone(),
|
||||
api_key_name: record.name.clone(),
|
||||
api_key_is_active: true,
|
||||
api_key_is_active: record.is_active,
|
||||
api_key_is_locked: false,
|
||||
api_key_is_standalone: true,
|
||||
api_key_rate_limit: Some(record.rate_limit),
|
||||
api_key_concurrent_limit: Some(record.concurrent_limit),
|
||||
api_key_expires_at_unix_secs: None,
|
||||
api_key_expires_at_unix_secs: record.expires_at_unix_secs,
|
||||
api_key_allowed_providers: record.allowed_providers.clone(),
|
||||
api_key_allowed_api_formats: record.allowed_api_formats.clone(),
|
||||
api_key_allowed_models: record.allowed_models.clone(),
|
||||
@@ -515,12 +533,12 @@ impl AuthApiKeyWriteRepository for InMemoryAuthApiKeySnapshotRepository {
|
||||
None,
|
||||
record.api_key_id.clone(),
|
||||
record.name.clone(),
|
||||
true,
|
||||
record.is_active,
|
||||
false,
|
||||
true,
|
||||
Some(record.rate_limit),
|
||||
Some(record.concurrent_limit),
|
||||
None,
|
||||
record.expires_at_unix_secs.map(|value| value as i64),
|
||||
record
|
||||
.allowed_providers
|
||||
.as_ref()
|
||||
@@ -556,12 +574,12 @@ impl AuthApiKeyWriteRepository for InMemoryAuthApiKeySnapshotRepository {
|
||||
.map(|value| serde_json::json!(value)),
|
||||
Some(record.rate_limit),
|
||||
Some(record.concurrent_limit),
|
||||
None,
|
||||
true,
|
||||
None,
|
||||
false,
|
||||
0,
|
||||
0.0,
|
||||
record.force_capabilities,
|
||||
record.is_active,
|
||||
record.expires_at_unix_secs.map(|value| value as i64),
|
||||
record.auto_delete_on_expiry,
|
||||
record.total_requests as i64,
|
||||
record.total_cost_usd,
|
||||
true,
|
||||
)?;
|
||||
|
||||
|
||||
@@ -311,10 +311,14 @@ INSERT INTO api_keys (
|
||||
key_hash,
|
||||
key_encrypted,
|
||||
name,
|
||||
allowed_providers,
|
||||
allowed_api_formats,
|
||||
allowed_models,
|
||||
rate_limit,
|
||||
concurrent_limit,
|
||||
force_capabilities,
|
||||
is_active,
|
||||
expires_at,
|
||||
is_locked,
|
||||
is_standalone,
|
||||
auto_delete_on_expiry,
|
||||
@@ -331,13 +335,17 @@ VALUES (
|
||||
$5,
|
||||
$6,
|
||||
$7,
|
||||
NULL,
|
||||
TRUE,
|
||||
$8,
|
||||
$9,
|
||||
$10,
|
||||
$11,
|
||||
$12,
|
||||
$13,
|
||||
FALSE,
|
||||
FALSE,
|
||||
FALSE,
|
||||
0,
|
||||
0,
|
||||
$14,
|
||||
$15,
|
||||
$16,
|
||||
NOW(),
|
||||
NOW()
|
||||
)
|
||||
@@ -375,6 +383,7 @@ INSERT INTO api_keys (
|
||||
concurrent_limit,
|
||||
force_capabilities,
|
||||
is_active,
|
||||
expires_at,
|
||||
is_locked,
|
||||
is_standalone,
|
||||
auto_delete_on_expiry,
|
||||
@@ -394,13 +403,14 @@ VALUES (
|
||||
$8,
|
||||
$9,
|
||||
$10,
|
||||
NULL,
|
||||
TRUE,
|
||||
$11,
|
||||
$12,
|
||||
$13,
|
||||
FALSE,
|
||||
TRUE,
|
||||
FALSE,
|
||||
0,
|
||||
0,
|
||||
$14,
|
||||
$15,
|
||||
$16,
|
||||
NOW(),
|
||||
NOW()
|
||||
)
|
||||
@@ -920,14 +930,46 @@ impl AuthApiKeyWriteRepository for SqlxAuthApiKeySnapshotReadRepository {
|
||||
&self,
|
||||
record: CreateUserApiKeyRecord,
|
||||
) -> Result<Option<StoredAuthApiKeyExportRecord>, DataLayerError> {
|
||||
let allowed_providers = record
|
||||
.allowed_providers
|
||||
.map(serde_json::to_value)
|
||||
.transpose()
|
||||
.map_err(|err| DataLayerError::UnexpectedValue(err.to_string()))?;
|
||||
let allowed_api_formats = record
|
||||
.allowed_api_formats
|
||||
.map(serde_json::to_value)
|
||||
.transpose()
|
||||
.map_err(|err| DataLayerError::UnexpectedValue(err.to_string()))?;
|
||||
let allowed_models = record
|
||||
.allowed_models
|
||||
.map(serde_json::to_value)
|
||||
.transpose()
|
||||
.map_err(|err| DataLayerError::UnexpectedValue(err.to_string()))?;
|
||||
let expires_at = record
|
||||
.expires_at_unix_secs
|
||||
.map(|value| {
|
||||
chrono::DateTime::<chrono::Utc>::from_timestamp(value as i64, 0).ok_or_else(|| {
|
||||
DataLayerError::UnexpectedValue(format!("invalid api_keys.expires_at: {value}"))
|
||||
})
|
||||
})
|
||||
.transpose()?;
|
||||
let row = sqlx::query(CREATE_USER_API_KEY_SQL)
|
||||
.bind(record.api_key_id)
|
||||
.bind(record.user_id)
|
||||
.bind(record.key_hash)
|
||||
.bind(record.key_encrypted)
|
||||
.bind(record.name)
|
||||
.bind(allowed_providers)
|
||||
.bind(allowed_api_formats)
|
||||
.bind(allowed_models)
|
||||
.bind(record.rate_limit)
|
||||
.bind(record.concurrent_limit)
|
||||
.bind(record.force_capabilities)
|
||||
.bind(record.is_active)
|
||||
.bind(expires_at)
|
||||
.bind(record.auto_delete_on_expiry)
|
||||
.bind(record.total_requests as i64)
|
||||
.bind(record.total_cost_usd)
|
||||
.fetch_optional(&self.pool)
|
||||
.await
|
||||
.map_postgres_err()?;
|
||||
@@ -953,6 +995,14 @@ impl AuthApiKeyWriteRepository for SqlxAuthApiKeySnapshotReadRepository {
|
||||
.map(serde_json::to_value)
|
||||
.transpose()
|
||||
.map_err(|err| DataLayerError::UnexpectedValue(err.to_string()))?;
|
||||
let expires_at = record
|
||||
.expires_at_unix_secs
|
||||
.map(|value| {
|
||||
chrono::DateTime::<chrono::Utc>::from_timestamp(value as i64, 0).ok_or_else(|| {
|
||||
DataLayerError::UnexpectedValue(format!("invalid api_keys.expires_at: {value}"))
|
||||
})
|
||||
})
|
||||
.transpose()?;
|
||||
let row = sqlx::query(CREATE_STANDALONE_API_KEY_SQL)
|
||||
.bind(record.api_key_id)
|
||||
.bind(record.user_id)
|
||||
@@ -964,6 +1014,12 @@ impl AuthApiKeyWriteRepository for SqlxAuthApiKeySnapshotReadRepository {
|
||||
.bind(allowed_models)
|
||||
.bind(record.rate_limit)
|
||||
.bind(record.concurrent_limit)
|
||||
.bind(record.force_capabilities)
|
||||
.bind(record.is_active)
|
||||
.bind(expires_at)
|
||||
.bind(record.auto_delete_on_expiry)
|
||||
.bind(record.total_requests as i64)
|
||||
.bind(record.total_cost_usd)
|
||||
.fetch_optional(&self.pool)
|
||||
.await
|
||||
.map_postgres_err()?;
|
||||
|
||||
@@ -352,15 +352,24 @@ pub struct StandaloneApiKeyExportListQuery {
|
||||
pub is_active: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct CreateUserApiKeyRecord {
|
||||
pub user_id: String,
|
||||
pub api_key_id: String,
|
||||
pub key_hash: String,
|
||||
pub key_encrypted: Option<String>,
|
||||
pub name: Option<String>,
|
||||
pub allowed_providers: Option<Vec<String>>,
|
||||
pub allowed_api_formats: Option<Vec<String>>,
|
||||
pub allowed_models: Option<Vec<String>>,
|
||||
pub rate_limit: i32,
|
||||
pub concurrent_limit: i32,
|
||||
pub force_capabilities: Option<serde_json::Value>,
|
||||
pub is_active: bool,
|
||||
pub expires_at_unix_secs: Option<u64>,
|
||||
pub auto_delete_on_expiry: bool,
|
||||
pub total_requests: u64,
|
||||
pub total_cost_usd: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -371,7 +380,7 @@ pub struct UpdateUserApiKeyBasicRecord {
|
||||
pub rate_limit: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct CreateStandaloneApiKeyRecord {
|
||||
pub user_id: String,
|
||||
pub api_key_id: String,
|
||||
@@ -383,6 +392,12 @@ pub struct CreateStandaloneApiKeyRecord {
|
||||
pub allowed_models: Option<Vec<String>>,
|
||||
pub rate_limit: i32,
|
||||
pub concurrent_limit: i32,
|
||||
pub force_capabilities: Option<serde_json::Value>,
|
||||
pub is_active: bool,
|
||||
pub expires_at_unix_secs: Option<u64>,
|
||||
pub auto_delete_on_expiry: bool,
|
||||
pub total_requests: u64,
|
||||
pub total_cost_usd: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
|
||||
Reference in New Issue
Block a user