feat: 添加用户端后端 API 路由
- 新增 GET /api/user/logs(用户 API 调用历史) - 新增 GET /api/user/summary(用户使用统计摘要) - stats.rs: ProxyLogRow 改为 pub 供 user 模块复用
This commit is contained in:
+1
-1
@@ -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
@@ -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,
|
||||
})))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user