feat(auth): 修复网页端登录流程,移除 web_generate_login_code 的 JWT 依赖
This commit is contained in:
@@ -368,12 +368,10 @@ pub struct WebLoginCodeResponse {
|
|||||||
pub async fn web_generate_login_code(
|
pub async fn web_generate_login_code(
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
req: web::Json<WebLoginCodeRequest>,
|
req: web::Json<WebLoginCodeRequest>,
|
||||||
claims: web::ReqData<Claims>,
|
|
||||||
http_client: web::Data<Client>,
|
http_client: web::Data<Client>,
|
||||||
app_state: web::Data<AppState>,
|
app_state: web::Data<AppState>,
|
||||||
) -> impl Responder {
|
) -> impl Responder {
|
||||||
let code = req.code.trim();
|
let code = req.code.trim();
|
||||||
let user_id = claims.user_id; // 从 JWT 获取当前用户
|
|
||||||
|
|
||||||
// 1. 用 code 换取 openid(和登录流程一样)
|
// 1. 用 code 换取 openid(和登录流程一样)
|
||||||
let url = format!(
|
let url = format!(
|
||||||
@@ -414,7 +412,28 @@ pub async fn web_generate_login_code(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 2. 清理该 openid 的旧登录码(避免重复使用)
|
// 2. 通过 openid 查询或创建用户(不依赖 JWT)
|
||||||
|
let user_id: i32 = match sqlx::query_as::<_, (i32,)>(
|
||||||
|
r#"
|
||||||
|
INSERT INTO users (openid, name, type)
|
||||||
|
VALUES ($1, left($1, 8), 2)
|
||||||
|
ON CONFLICT (openid) DO UPDATE SET id = users.id
|
||||||
|
RETURNING id
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(&openid)
|
||||||
|
.fetch_one(pool.get_ref())
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok((id,)) => id,
|
||||||
|
Err(e) => {
|
||||||
|
error!("用户查询/创建失败: {}", e);
|
||||||
|
return HttpResponse::InternalServerError()
|
||||||
|
.json(ErrorResponse::<()>::error("用户处理失败"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 3. 清理该 openid 的旧登录码(避免重复使用)
|
||||||
if let Err(e) = sqlx::query("DELETE FROM web_login_codes WHERE openid = $1")
|
if let Err(e) = sqlx::query("DELETE FROM web_login_codes WHERE openid = $1")
|
||||||
.bind(&openid)
|
.bind(&openid)
|
||||||
.execute(pool.get_ref())
|
.execute(pool.get_ref())
|
||||||
@@ -579,11 +598,13 @@ pub async fn web_login_confirm(
|
|||||||
_ => (false, None),
|
_ => (false, None),
|
||||||
};
|
};
|
||||||
|
|
||||||
// 删除已使用的登录码
|
// 更新登录码记录,设置 token(而非删除,让轮询接口能查到)
|
||||||
let _ = sqlx::query("DELETE FROM web_login_codes WHERE code = $1")
|
sqlx::query("UPDATE web_login_codes SET token = $1 WHERE code = $2")
|
||||||
|
.bind(&token)
|
||||||
.bind(&code)
|
.bind(&code)
|
||||||
.execute(pool.get_ref())
|
.execute(pool.get_ref())
|
||||||
.await;
|
.await
|
||||||
|
.ok();
|
||||||
|
|
||||||
info!("[WEB LOGIN CONFIRM] user_id={} is_paid={}", user_id, is_paid_active);
|
info!("[WEB LOGIN CONFIRM] user_id={} is_paid={}", user_id, is_paid_active);
|
||||||
HttpResponse::Ok().json(WebLoginConfirmResponse {
|
HttpResponse::Ok().json(WebLoginConfirmResponse {
|
||||||
|
|||||||
@@ -431,6 +431,25 @@ pub async fn payment_index() -> Result<HttpResponse, AppError> {
|
|||||||
let isPaidActive = false;
|
let isPaidActive = false;
|
||||||
let paidExpiresAt = null;
|
let paidExpiresAt = null;
|
||||||
|
|
||||||
|
// ---- 页面初始化:检查 URL 中的 code 参数 ----
|
||||||
|
(function initFromUrl() {
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const urlCode = params.get('code');
|
||||||
|
if (urlCode) {
|
||||||
|
currentShortCode = urlCode;
|
||||||
|
document.getElementById('codeValue').textContent = currentShortCode;
|
||||||
|
document.getElementById('codeDisplay').style.display = 'block';
|
||||||
|
document.getElementById('scanStatus').style.display = 'block';
|
||||||
|
document.getElementById('scanStatus').className = 'scan-status waiting';
|
||||||
|
document.getElementById('scanStatus').textContent = '等待小程序授权确认...';
|
||||||
|
document.getElementById('btnGenerate').style.display = 'none';
|
||||||
|
document.getElementById('btnRefresh').style.display = 'block';
|
||||||
|
// 启动轮询
|
||||||
|
if (pollTimer) clearInterval(pollTimer);
|
||||||
|
pollTimer = setInterval(pollLoginStatus, 2000);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
// ---- 登录码流程(正式版)----
|
// ---- 登录码流程(正式版)----
|
||||||
async function generateCode() {
|
async function generateCode() {
|
||||||
hideError();
|
hideError();
|
||||||
@@ -960,17 +979,17 @@ pub async fn payment_login_status(
|
|||||||
) -> Result<HttpResponse, AppError> {
|
) -> Result<HttpResponse, AppError> {
|
||||||
let code = query.code.trim();
|
let code = query.code.trim();
|
||||||
|
|
||||||
// 查询登录码记录
|
// 查询登录码记录(包含 token 字段用于判断是否已确认)
|
||||||
let record: Option<(String, chrono::DateTime<chrono::Utc>, Option<i32>)> =
|
let record: Option<(String, chrono::DateTime<chrono::Utc>, Option<String>, Option<i32>)> =
|
||||||
sqlx::query_as(
|
sqlx::query_as(
|
||||||
"SELECT code, expires_at, user_id FROM web_login_codes WHERE code = $1",
|
"SELECT code, expires_at, token, user_id FROM web_login_codes WHERE code = $1",
|
||||||
)
|
)
|
||||||
.bind(code)
|
.bind(code)
|
||||||
.fetch_optional(pool.get_ref())
|
.fetch_optional(pool.get_ref())
|
||||||
.await
|
.await
|
||||||
.map_err(|e| AppError::Internal(format!("数据库查询失败: {}", e)))?;
|
.map_err(|e| AppError::Internal(format!("数据库查询失败: {}", e)))?;
|
||||||
|
|
||||||
let (db_code, expires_at, user_id) = match record {
|
let (db_code, expires_at, token, user_id) = match record {
|
||||||
Some(r) => r,
|
Some(r) => r,
|
||||||
None => {
|
None => {
|
||||||
return Ok(HttpResponse::Ok().json(LoginStatusResponse {
|
return Ok(HttpResponse::Ok().json(LoginStatusResponse {
|
||||||
@@ -998,9 +1017,9 @@ pub async fn payment_login_status(
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 登录码存在但还没被小程序确认(user_id 为空 = 刚生成,还没点确认)
|
// 登录码存在但还没被小程序确认(token 为空 = 刚生成,还没点确认)
|
||||||
// 这种情况返回 confirmed=false,继续轮询
|
// 使用 token 字段判断是否已确认(而非 user_id,因为 web_generate_login_code 会设置 user_id)
|
||||||
if user_id.is_none() {
|
if token.is_none() {
|
||||||
return Ok(HttpResponse::Ok().json(LoginStatusResponse {
|
return Ok(HttpResponse::Ok().json(LoginStatusResponse {
|
||||||
success: true,
|
success: true,
|
||||||
confirmed: false,
|
confirmed: false,
|
||||||
@@ -1010,19 +1029,10 @@ pub async fn payment_login_status(
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 已确认 → 获取用户信息和 openid,生成 JWT
|
// 已确认 → 使用已生成的 token
|
||||||
|
let token = token.unwrap();
|
||||||
let user_id = user_id.unwrap();
|
let user_id = user_id.unwrap();
|
||||||
|
|
||||||
let (openid,): (String,) = sqlx::query_as("SELECT openid FROM users WHERE id = $1")
|
|
||||||
.bind(user_id)
|
|
||||||
.fetch_optional(pool.get_ref())
|
|
||||||
.await
|
|
||||||
.map_err(|e| AppError::Internal(format!("数据库查询失败: {}", e)))?
|
|
||||||
.ok_or_else(|| AppError::Internal("用户不存在".to_string()))?;
|
|
||||||
|
|
||||||
let token = crate::auth::generate_token(user_id, &openid, 2, &app_state.jwt_secret)
|
|
||||||
.map_err(|e| AppError::Internal(format!("生成令牌失败: {}", e)))?;
|
|
||||||
|
|
||||||
let (is_paid_active, paid_expires_at): (bool, Option<String>) =
|
let (is_paid_active, paid_expires_at): (bool, Option<String>) =
|
||||||
match sqlx::query_as::<_, (bool, Option<chrono::DateTime<chrono::Utc>>)>("SELECT is_paid_active($1)")
|
match sqlx::query_as::<_, (bool, Option<chrono::DateTime<chrono::Utc>>)>("SELECT is_paid_active($1)")
|
||||||
.bind(user_id)
|
.bind(user_id)
|
||||||
|
|||||||
Reference in New Issue
Block a user