feat: 添加用户端后端 API 路由

- 新增 GET /api/user/logs(用户 API 调用历史)
- 新增 GET /api/user/summary(用户使用统计摘要)
- stats.rs: ProxyLogRow 改为 pub 供 user 模块复用
This commit is contained in:
boos
2026-05-19 02:33:08 +08:00
parent bf00296a90
commit 5890a9b4ac
3 changed files with 72 additions and 2 deletions
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -156,7 +156,7 @@ async fn proxy_logs(
}
#[derive(Debug, serde::Serialize, sqlx::FromRow)]
struct ProxyLogRow {
pub struct ProxyLogRow {
id: i64,
route_id: Option<i64>,
channel_id: Option<i64>,
+71 -1
View File
@@ -5,7 +5,7 @@ use crate::{
AppState,
};
use axum::{
extract::{Path, State},
extract::{Path, State, Query},
http::{HeaderMap, StatusCode},
routing::{delete, get},
Json, Router,
@@ -18,6 +18,8 @@ pub fn routes() -> Router<AppState> {
.route("/v1/user/profile", get(get_profile).put(update_profile))
.route("/v1/user/api-keys", get(list_api_keys).post(create_api_key))
.route("/v1/user/api-keys/{id}", delete(delete_api_key))
.route("/v1/user/logs", get(get_user_logs))
.route("/v1/user/summary", get(get_user_summary))
}
#[derive(Debug, Deserialize)]
@@ -158,3 +160,71 @@ async fn delete_api_key(
Ok(StatusCode::NO_CONTENT)
}
async fn get_user_logs(
State(state): State<AppState>,
headers: HeaderMap,
Query(params): Query<std::collections::HashMap<String, String>>,
) -> AppResult<Json<Value>> {
let user_id = current_user_id(&headers, &state)?;
let limit = params.get("limit").and_then(|v| v.parse::<i64>().ok()).unwrap_or(20).clamp(1, 100);
let offset = params.get("offset").and_then(|v| v.parse::<i64>().ok()).unwrap_or(0).max(0);
let items: Vec<Value> = sqlx::query_as::<_, crate::routes::stats::ProxyLogRow>(
"SELECT l.* FROM proxy_logs l
JOIN downstream_api_keys k ON l.downstream_api_key_id = k.id
WHERE k.user_id = ?
ORDER BY l.created_at DESC
LIMIT ? OFFSET ?"
)
.bind(user_id)
.bind(limit)
.bind(offset)
.fetch_all(&*state.db)
.await?
.into_iter()
.map(|row| serde_json::to_value(row).unwrap_or(json!({})))
.collect();
let total: i64 = sqlx::query_scalar(
"SELECT COUNT(*) FROM proxy_logs l
JOIN downstream_api_keys k ON l.downstream_api_key_id = k.id
WHERE k.user_id = ?"
)
.bind(user_id)
.fetch_one(&*state.db)
.await?;
Ok(Json(json!({
"items": items,
"total": total,
"limit": limit,
"offset": offset
})))
}
async fn get_user_summary(
State(state): State<AppState>,
headers: HeaderMap,
) -> AppResult<Json<Value>> {
let user_id = current_user_id(&headers, &state)?;
let stats: (i64, i64, f64) = sqlx::query_as(
"SELECT COUNT(*),
SUM(CASE WHEN l.status = 'success' THEN 1 ELSE 0 END),
COALESCE(SUM(l.estimated_cost), 0.0)
FROM proxy_logs l
JOIN downstream_api_keys k ON l.downstream_api_key_id = k.id
WHERE k.user_id = ?"
)
.bind(user_id)
.fetch_one(&*state.db)
.await?;
Ok(Json(json!({
"totalRequests": stats.0,
"successRequests": stats.1,
"totalCost": stats.2,
})))
}