feat: 添加用户端后端 API 路由
- 新增 GET /api/user/logs(用户 API 调用历史) - 新增 GET /api/user/summary(用户使用统计摘要) - stats.rs: ProxyLogRow 改为 pub 供 user 模块复用
This commit is contained in:
BIN
Binary file not shown.
+1
-1
@@ -156,7 +156,7 @@ async fn proxy_logs(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Serialize, sqlx::FromRow)]
|
#[derive(Debug, serde::Serialize, sqlx::FromRow)]
|
||||||
struct ProxyLogRow {
|
pub struct ProxyLogRow {
|
||||||
id: i64,
|
id: i64,
|
||||||
route_id: Option<i64>,
|
route_id: Option<i64>,
|
||||||
channel_id: Option<i64>,
|
channel_id: Option<i64>,
|
||||||
|
|||||||
+71
-1
@@ -5,7 +5,7 @@ use crate::{
|
|||||||
AppState,
|
AppState,
|
||||||
};
|
};
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{Path, State},
|
extract::{Path, State, Query},
|
||||||
http::{HeaderMap, StatusCode},
|
http::{HeaderMap, StatusCode},
|
||||||
routing::{delete, get},
|
routing::{delete, get},
|
||||||
Json, Router,
|
Json, Router,
|
||||||
@@ -18,6 +18,8 @@ pub fn routes() -> Router<AppState> {
|
|||||||
.route("/v1/user/profile", get(get_profile).put(update_profile))
|
.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", get(list_api_keys).post(create_api_key))
|
||||||
.route("/v1/user/api-keys/{id}", delete(delete_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)]
|
#[derive(Debug, Deserialize)]
|
||||||
@@ -158,3 +160,71 @@ async fn delete_api_key(
|
|||||||
|
|
||||||
Ok(StatusCode::NO_CONTENT)
|
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