feat(auth): 添加沙箱测试用 mock_login 接口 (GET /api/mock-login)
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
use actix_web::{web, HttpResponse, Responder, post};
|
use actix_web::{web, HttpResponse, Responder, post, get};
|
||||||
use chrono::{Utc, Duration};
|
use chrono::{Utc, Duration};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use sqlx::postgres::PgPool;
|
use sqlx::postgres::PgPool;
|
||||||
use tracing::{debug, error, info, warn};
|
use tracing::{debug, error, info, warn};
|
||||||
@@ -190,3 +191,129 @@ pub async fn refresh_token(
|
|||||||
};
|
};
|
||||||
HttpResponse::Ok().json(response)
|
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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ pub static TEMPLATES_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/templates");
|
|||||||
pub use admin::admin_get_user;
|
pub use admin::admin_get_user;
|
||||||
pub use admin::admin_update_user_payment;
|
pub use admin::admin_update_user_payment;
|
||||||
pub use auth::login;
|
pub use auth::login;
|
||||||
|
pub use auth::mock_login;
|
||||||
pub use auth::refresh_token;
|
pub use auth::refresh_token;
|
||||||
pub use favorites::{add_favorite, get_favorites, remove_favorite};
|
pub use favorites::{add_favorite, get_favorites, remove_favorite};
|
||||||
pub use health::health_check;
|
pub use health::health_check;
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ use handlers::{
|
|||||||
admin_get_user, admin_update_user_payment, add_favorite, alipay_notify, alipay_pay_page,
|
admin_get_user, admin_update_user_payment, add_favorite, alipay_notify, alipay_pay_page,
|
||||||
create_order, delete_weather, generate_temp_token_handler, get_current_user_profile,
|
create_order, delete_weather, generate_temp_token_handler, get_current_user_profile,
|
||||||
get_favorites, get_user_quota, get_weather_brief, get_weather_details,
|
get_favorites, get_user_quota, get_weather_brief, get_weather_details,
|
||||||
health_check, login, mock_confirm, payment_index, payment_page, payment_success,
|
health_check, login, mock_login, mock_confirm, payment_index, payment_page, payment_success,
|
||||||
post_weather_data,
|
post_weather_data,
|
||||||
refresh_token, remove_favorite, root, save_user_profile, serve_static_files,
|
refresh_token, remove_favorite, root, save_user_profile, serve_static_files,
|
||||||
};
|
};
|
||||||
@@ -72,6 +72,7 @@ fn create_server_config(
|
|||||||
.service(web::resource("/static/{tail:.*}").route(web::get().to(serve_static_files)))
|
.service(web::resource("/static/{tail:.*}").route(web::get().to(serve_static_files)))
|
||||||
// API 接口
|
// API 接口
|
||||||
.service(login) // #[post("/api/login")]
|
.service(login) // #[post("/api/login")]
|
||||||
|
.service(mock_login) // #[get("/api/mock-login")](沙箱测试用)
|
||||||
.service(refresh_token) // #[post("/api/refresh-token")](公开接口,无需认证)
|
.service(refresh_token) // #[post("/api/refresh-token")](公开接口,无需认证)
|
||||||
.service(get_weather_details) // #[get("/weather/details")](支持 JWT 或 temp_token,公开接口)
|
.service(get_weather_details) // #[get("/weather/details")](支持 JWT 或 temp_token,公开接口)
|
||||||
.service(health_check) // #[get("/health")](公开接口,无需认证)
|
.service(health_check) // #[get("/health")](公开接口,无需认证)
|
||||||
|
|||||||
Reference in New Issue
Block a user