fix(admin-api-keys): 独立余额 Key 统计与时间字段改为直读 api_keys 表 (#319)

- 列表/详情/更新响应中的 `total_requests`、`total_tokens`、`created_at`、`last_used_at` 统一直接读取 `api_keys` 导出记录字段
- 保持“已消费”继续走钱包字段(不改消费口径)
- 在 auth 导出记录中补充 `last_used_at_unix_secs`、`created_at_unix_secs`、`updated_at_unix_secs`
- 扩展 auth SQL 查询与 RETURNING 字段映射,确保时间字段完整回传
- 移除 admin api-keys 路由中对 usage summary token 聚合的依赖
- 更新 admin api-keys 控制层测试,校验 token/时间字段来源与格式
This commit is contained in:
AAEE86
2026-04-23 22:32:00 +08:00
committed by GitHub
parent ad58f4e852
commit ccec46eddd
7 changed files with 115 additions and 81 deletions

View File

@@ -152,6 +152,9 @@ SELECT
api_keys.total_requests,
COALESCE(api_keys.total_tokens, 0)::BIGINT AS total_tokens,
COALESCE(CAST(api_keys.total_cost_usd AS DOUBLE PRECISION), 0) AS total_cost_usd,
CAST(EXTRACT(EPOCH FROM api_keys.last_used_at) AS BIGINT) AS last_used_at_unix_secs,
CAST(EXTRACT(EPOCH FROM api_keys.created_at) AS BIGINT) AS created_at_unix_secs,
CAST(EXTRACT(EPOCH FROM api_keys.updated_at) AS BIGINT) AS updated_at_unix_secs,
api_keys.is_standalone
FROM api_keys
WHERE api_keys.user_id = ANY($1::TEXT[])
@@ -178,6 +181,9 @@ SELECT
api_keys.total_requests,
COALESCE(api_keys.total_tokens, 0)::BIGINT AS total_tokens,
COALESCE(CAST(api_keys.total_cost_usd AS DOUBLE PRECISION), 0) AS total_cost_usd,
CAST(EXTRACT(EPOCH FROM api_keys.last_used_at) AS BIGINT) AS last_used_at_unix_secs,
CAST(EXTRACT(EPOCH FROM api_keys.created_at) AS BIGINT) AS created_at_unix_secs,
CAST(EXTRACT(EPOCH FROM api_keys.updated_at) AS BIGINT) AS updated_at_unix_secs,
api_keys.is_standalone
FROM api_keys
WHERE api_keys.id = ANY($1::TEXT[])
@@ -203,6 +209,9 @@ SELECT
api_keys.total_requests,
COALESCE(api_keys.total_tokens, 0)::BIGINT AS total_tokens,
COALESCE(CAST(api_keys.total_cost_usd AS DOUBLE PRECISION), 0) AS total_cost_usd,
CAST(EXTRACT(EPOCH FROM api_keys.last_used_at) AS BIGINT) AS last_used_at_unix_secs,
CAST(EXTRACT(EPOCH FROM api_keys.created_at) AS BIGINT) AS created_at_unix_secs,
CAST(EXTRACT(EPOCH FROM api_keys.updated_at) AS BIGINT) AS updated_at_unix_secs,
api_keys.is_standalone
FROM api_keys
WHERE LOWER(COALESCE(api_keys.name, '')) LIKE $1
@@ -228,6 +237,9 @@ SELECT
api_keys.total_requests,
COALESCE(api_keys.total_tokens, 0)::BIGINT AS total_tokens,
COALESCE(CAST(api_keys.total_cost_usd AS DOUBLE PRECISION), 0) AS total_cost_usd,
CAST(EXTRACT(EPOCH FROM api_keys.last_used_at) AS BIGINT) AS last_used_at_unix_secs,
CAST(EXTRACT(EPOCH FROM api_keys.created_at) AS BIGINT) AS created_at_unix_secs,
CAST(EXTRACT(EPOCH FROM api_keys.updated_at) AS BIGINT) AS updated_at_unix_secs,
api_keys.is_standalone
FROM api_keys
WHERE api_keys.is_standalone = TRUE
@@ -253,6 +265,9 @@ SELECT
api_keys.total_requests,
COALESCE(api_keys.total_tokens, 0)::BIGINT AS total_tokens,
COALESCE(CAST(api_keys.total_cost_usd AS DOUBLE PRECISION), 0) AS total_cost_usd,
CAST(EXTRACT(EPOCH FROM api_keys.last_used_at) AS BIGINT) AS last_used_at_unix_secs,
CAST(EXTRACT(EPOCH FROM api_keys.created_at) AS BIGINT) AS created_at_unix_secs,
CAST(EXTRACT(EPOCH FROM api_keys.updated_at) AS BIGINT) AS updated_at_unix_secs,
api_keys.is_standalone
FROM api_keys
WHERE api_keys.is_standalone = TRUE
@@ -322,6 +337,9 @@ SELECT
api_keys.total_requests,
COALESCE(api_keys.total_tokens, 0)::BIGINT AS total_tokens,
COALESCE(CAST(api_keys.total_cost_usd AS DOUBLE PRECISION), 0) AS total_cost_usd,
CAST(EXTRACT(EPOCH FROM api_keys.last_used_at) AS BIGINT) AS last_used_at_unix_secs,
CAST(EXTRACT(EPOCH FROM api_keys.created_at) AS BIGINT) AS created_at_unix_secs,
CAST(EXTRACT(EPOCH FROM api_keys.updated_at) AS BIGINT) AS updated_at_unix_secs,
api_keys.is_standalone
FROM api_keys
WHERE api_keys.is_standalone = TRUE
@@ -400,6 +418,9 @@ RETURNING
total_requests,
COALESCE(total_tokens, 0)::BIGINT AS total_tokens,
COALESCE(CAST(total_cost_usd AS DOUBLE PRECISION), 0) AS total_cost_usd,
CAST(EXTRACT(EPOCH FROM last_used_at) AS BIGINT) AS last_used_at_unix_secs,
CAST(EXTRACT(EPOCH FROM created_at) AS BIGINT) AS created_at_unix_secs,
CAST(EXTRACT(EPOCH FROM updated_at) AS BIGINT) AS updated_at_unix_secs,
is_standalone
"#;
@@ -468,6 +489,9 @@ RETURNING
total_requests,
COALESCE(total_tokens, 0)::BIGINT AS total_tokens,
COALESCE(CAST(total_cost_usd AS DOUBLE PRECISION), 0) AS total_cost_usd,
CAST(EXTRACT(EPOCH FROM last_used_at) AS BIGINT) AS last_used_at_unix_secs,
CAST(EXTRACT(EPOCH FROM created_at) AS BIGINT) AS created_at_unix_secs,
CAST(EXTRACT(EPOCH FROM updated_at) AS BIGINT) AS updated_at_unix_secs,
is_standalone
"#;
@@ -499,6 +523,9 @@ RETURNING
total_requests,
COALESCE(total_tokens, 0)::BIGINT AS total_tokens,
COALESCE(CAST(total_cost_usd AS DOUBLE PRECISION), 0) AS total_cost_usd,
CAST(EXTRACT(EPOCH FROM last_used_at) AS BIGINT) AS last_used_at_unix_secs,
CAST(EXTRACT(EPOCH FROM created_at) AS BIGINT) AS created_at_unix_secs,
CAST(EXTRACT(EPOCH FROM updated_at) AS BIGINT) AS updated_at_unix_secs,
is_standalone
"#;
@@ -534,6 +561,9 @@ RETURNING
total_requests,
COALESCE(total_tokens, 0)::BIGINT AS total_tokens,
COALESCE(CAST(total_cost_usd AS DOUBLE PRECISION), 0) AS total_cost_usd,
CAST(EXTRACT(EPOCH FROM last_used_at) AS BIGINT) AS last_used_at_unix_secs,
CAST(EXTRACT(EPOCH FROM created_at) AS BIGINT) AS created_at_unix_secs,
CAST(EXTRACT(EPOCH FROM updated_at) AS BIGINT) AS updated_at_unix_secs,
is_standalone
"#;
@@ -563,6 +593,9 @@ RETURNING
total_requests,
COALESCE(total_tokens, 0)::BIGINT AS total_tokens,
COALESCE(CAST(total_cost_usd AS DOUBLE PRECISION), 0) AS total_cost_usd,
CAST(EXTRACT(EPOCH FROM last_used_at) AS BIGINT) AS last_used_at_unix_secs,
CAST(EXTRACT(EPOCH FROM created_at) AS BIGINT) AS created_at_unix_secs,
CAST(EXTRACT(EPOCH FROM updated_at) AS BIGINT) AS updated_at_unix_secs,
is_standalone
"#;
@@ -591,6 +624,9 @@ RETURNING
total_requests,
COALESCE(total_tokens, 0)::BIGINT AS total_tokens,
COALESCE(CAST(total_cost_usd AS DOUBLE PRECISION), 0) AS total_cost_usd,
CAST(EXTRACT(EPOCH FROM last_used_at) AS BIGINT) AS last_used_at_unix_secs,
CAST(EXTRACT(EPOCH FROM created_at) AS BIGINT) AS created_at_unix_secs,
CAST(EXTRACT(EPOCH FROM updated_at) AS BIGINT) AS updated_at_unix_secs,
is_standalone
"#;
@@ -630,6 +666,9 @@ RETURNING
total_requests,
COALESCE(total_tokens, 0)::BIGINT AS total_tokens,
COALESCE(CAST(total_cost_usd AS DOUBLE PRECISION), 0) AS total_cost_usd,
CAST(EXTRACT(EPOCH FROM last_used_at) AS BIGINT) AS last_used_at_unix_secs,
CAST(EXTRACT(EPOCH FROM created_at) AS BIGINT) AS created_at_unix_secs,
CAST(EXTRACT(EPOCH FROM updated_at) AS BIGINT) AS updated_at_unix_secs,
is_standalone
"#;
@@ -659,6 +698,9 @@ RETURNING
total_requests,
COALESCE(total_tokens, 0)::BIGINT AS total_tokens,
COALESCE(CAST(total_cost_usd AS DOUBLE PRECISION), 0) AS total_cost_usd,
CAST(EXTRACT(EPOCH FROM last_used_at) AS BIGINT) AS last_used_at_unix_secs,
CAST(EXTRACT(EPOCH FROM created_at) AS BIGINT) AS created_at_unix_secs,
CAST(EXTRACT(EPOCH FROM updated_at) AS BIGINT) AS updated_at_unix_secs,
is_standalone
"#;
@@ -1397,6 +1439,13 @@ fn map_auth_api_key_export_row(
row_get(row, "total_cost_usd")?,
row_get(row, "is_standalone")?,
)
.and_then(|record| {
record.with_activity_timestamps(
row_get(row, "last_used_at_unix_secs")?,
row_get(row, "created_at_unix_secs")?,
row_get(row, "updated_at_unix_secs")?,
)
})
}
#[cfg(test)]

View File

@@ -268,6 +268,9 @@ pub struct StoredAuthApiKeyExportRecord {
pub total_requests: u64,
pub total_tokens: u64,
pub total_cost_usd: f64,
pub last_used_at_unix_secs: Option<u64>,
pub created_at_unix_secs: Option<u64>,
pub updated_at_unix_secs: Option<u64>,
pub is_standalone: bool,
}
@@ -337,9 +340,30 @@ impl StoredAuthApiKeyExportRecord {
total_requests: parse_u64_i64(total_requests, "api_keys.total_requests")?,
total_tokens: parse_u64_i64(total_tokens, "api_keys.total_tokens")?,
total_cost_usd,
last_used_at_unix_secs: None,
created_at_unix_secs: None,
updated_at_unix_secs: None,
is_standalone,
})
}
pub fn with_activity_timestamps(
mut self,
last_used_at_unix_secs: Option<i64>,
created_at_unix_secs: Option<i64>,
updated_at_unix_secs: Option<i64>,
) -> Result<Self, crate::DataLayerError> {
self.last_used_at_unix_secs = last_used_at_unix_secs
.map(|value| parse_u64_i64(value, "api_keys.last_used_at_unix_secs"))
.transpose()?;
self.created_at_unix_secs = created_at_unix_secs
.map(|value| parse_u64_i64(value, "api_keys.created_at_unix_secs"))
.transpose()?;
self.updated_at_unix_secs = updated_at_unix_secs
.map(|value| parse_u64_i64(value, "api_keys.updated_at_unix_secs"))
.transpose()?;
Ok(self)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]