feat(auth): 添加沙箱测试用 mock_login 接口 (GET /api/mock-login)

This commit is contained in:
2026-04-23 13:11:10 +08:00
parent 502ae8babb
commit ea705f0917
3 changed files with 131 additions and 2 deletions

View File

@@ -1,5 +1,6 @@
use actix_web::{web, HttpResponse, Responder, post};
use actix_web::{web, HttpResponse, Responder, post, get};
use chrono::{Utc, Duration};
use serde::{Deserialize, Serialize};
use reqwest::Client;
use sqlx::postgres::PgPool;
use tracing::{debug, error, info, warn};
@@ -190,3 +191,129 @@ pub async fn refresh_token(
};
HttpResponse::Ok().json(response)
}
/// 模拟登录请求(仅供测试/沙箱环境使用)
#[derive(Debug, Deserialize)]
pub struct MockLoginRequest {
/// 指定用户 ID如不指定则自动创建测试用户
pub user_id: Option<i32>,
}
/// 模拟登录响应
#[derive(Debug, Serialize)]
pub struct MockLoginResponse {
pub success: bool,
pub token: String,
pub refresh_token: String,
pub user_id: i32,
}
/// GET /api/mock-login
/// 沙箱测试用:无需微信 code直接返回 JWT
/// 通过环境变量 MOCK_LOGIN_ENABLED 控制是否启用
#[get("/api/mock-login")]
pub async fn mock_login(
pool: web::Data<PgPool>,
query: web::Query<MockLoginRequest>,
app_state: web::Data<AppState>,
) -> impl Responder {
// 检查是否启用
if std::env::var("MOCK_LOGIN_ENABLED").ok() != Some("true".to_string()) {
return HttpResponse::NotFound().json(ErrorResponse::<()>::error("模拟登录未启用"));
}
let user_id = match query.user_id {
Some(id) => {
// 验证用户存在
match sqlx::query_as::<_, (i32,)>(("SELECT id FROM users WHERE id = $1"))
.bind(id)
.fetch_optional(pool.get_ref())
.await
{
Ok(Some((uid,))) => uid,
Ok(None) => {
return HttpResponse::BadRequest()
.json(ErrorResponse::<()>::error("用户不存在"));
}
Err(e) => {
error!("查询用户失败: {}", e);
return HttpResponse::InternalServerError()
.json(ErrorResponse::<()>::error("数据库错误"));
}
}
}
None => {
// 创建或获取测试用户
let test_openid = format!("mock_test_{}", Utc::now().timestamp_millis());
match sqlx::query_as::<_, (i32,)>(
r#"INSERT INTO users (openid, name, type) VALUES ($1, $2, 2)
ON CONFLICT (openid) DO UPDATE SET id = users.id RETURNING id"#,
)
.bind(&test_openid)
.bind(&test_openid[..8.min(test_openid.len())])
.fetch_one(pool.get_ref())
.await
{
Ok((id,)) => id,
Err(e) => {
error!("创建测试用户失败: {}", e);
return HttpResponse::InternalServerError()
.json(ErrorResponse::<()>::error("创建测试用户失败"));
}
}
}
};
// 获取 openid
let openid: String = match sqlx::query_as::<_, (String,)>("SELECT openid FROM users WHERE id = $1")
.bind(user_id)
.fetch_optional(pool.get_ref())
.await
{
Ok(Some((o,))) => o,
Ok(None) => {
return HttpResponse::InternalServerError()
.json(ErrorResponse::<()>::error("用户无 openid"));
}
Err(e) => {
error!("查询 openid 失败: {}", e);
return HttpResponse::InternalServerError()
.json(ErrorResponse::<()>::error("数据库错误"));
}
};
// 生成 token
let token = match generate_token(user_id, &openid, 2, &app_state.jwt_secret) {
Ok(t) => t,
Err(e) => {
error!("JWT 生成失败: {}", e);
return HttpResponse::InternalServerError()
.json(ErrorResponse::<()>::error("生成令牌失败"));
}
};
// 生成 refresh_token
let refresh_token_str = match generate_refresh_token(user_id, &app_state.jwt_secret) {
Ok(t) => t,
Err(e) => {
error!("refresh_token 生成失败: {}", e);
return HttpResponse::InternalServerError()
.json(ErrorResponse::<()>::error("生成刷新令牌失败"));
}
};
let refresh_expires_at = Utc::now() + Duration::days(7);
if let Err(e) =
db::create_refresh_token(pool.get_ref(), user_id, &refresh_token_str, refresh_expires_at).await
{
warn!("保存 refresh_token 失败(继续): {}", e);
}
info!("[MOCK LOGIN] user_id={} 登录成功", user_id);
HttpResponse::Ok().json(MockLoginResponse {
success: true,
token,
refresh_token: refresh_token_str,
user_id,
})
}