From aa84bbc4e6336d850cf5241ff6ec975f0472b715 Mon Sep 17 00:00:00 2001 From: Milky0217 Date: Mon, 27 Apr 2026 09:08:25 +0800 Subject: [PATCH] =?UTF-8?q?feat(auth):=20=E4=BF=AE=E5=A4=8D=E7=BD=91?= =?UTF-8?q?=E9=A1=B5=E7=AB=AF=E7=99=BB=E5=BD=95=E6=B5=81=E7=A8=8B=EF=BC=8C?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=20web=5Fgenerate=5Flogin=5Fcode=20=E7=9A=84?= =?UTF-8?q?=20JWT=20=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/handlers/auth.rs | 33 +++++++++++++++++++++++------ src/handlers/payment.rs | 46 +++++++++++++++++++++++++---------------- 2 files changed, 55 insertions(+), 24 deletions(-) diff --git a/src/handlers/auth.rs b/src/handlers/auth.rs index ba86b6d..dd272d2 100644 --- a/src/handlers/auth.rs +++ b/src/handlers/auth.rs @@ -368,12 +368,10 @@ pub struct WebLoginCodeResponse { pub async fn web_generate_login_code( pool: web::Data, req: web::Json, - claims: web::ReqData, http_client: web::Data, app_state: web::Data, ) -> impl Responder { let code = req.code.trim(); - let user_id = claims.user_id; // 从 JWT 获取当前用户 // 1. 用 code 换取 openid(和登录流程一样) 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") .bind(&openid) .execute(pool.get_ref()) @@ -579,11 +598,13 @@ pub async fn web_login_confirm( _ => (false, None), }; - // 删除已使用的登录码 - let _ = sqlx::query("DELETE FROM web_login_codes WHERE code = $1") + // 更新登录码记录,设置 token(而非删除,让轮询接口能查到) + sqlx::query("UPDATE web_login_codes SET token = $1 WHERE code = $2") + .bind(&token) .bind(&code) .execute(pool.get_ref()) - .await; + .await + .ok(); info!("[WEB LOGIN CONFIRM] user_id={} is_paid={}", user_id, is_paid_active); HttpResponse::Ok().json(WebLoginConfirmResponse { diff --git a/src/handlers/payment.rs b/src/handlers/payment.rs index e5c9e3b..53d481d 100644 --- a/src/handlers/payment.rs +++ b/src/handlers/payment.rs @@ -431,6 +431,25 @@ pub async fn payment_index() -> Result { let isPaidActive = false; 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() { hideError(); @@ -960,17 +979,17 @@ pub async fn payment_login_status( ) -> Result { let code = query.code.trim(); - // 查询登录码记录 - let record: Option<(String, chrono::DateTime, Option)> = + // 查询登录码记录(包含 token 字段用于判断是否已确认) + let record: Option<(String, chrono::DateTime, Option, Option)> = 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) .fetch_optional(pool.get_ref()) .await .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, None => { return Ok(HttpResponse::Ok().json(LoginStatusResponse { @@ -998,9 +1017,9 @@ pub async fn payment_login_status( })); } - // 登录码存在但还没被小程序确认(user_id 为空 = 刚生成,还没点确认) - // 这种情况返回 confirmed=false,继续轮询 - if user_id.is_none() { + // 登录码存在但还没被小程序确认(token 为空 = 刚生成,还没点确认) + // 使用 token 字段判断是否已确认(而非 user_id,因为 web_generate_login_code 会设置 user_id) + if token.is_none() { return Ok(HttpResponse::Ok().json(LoginStatusResponse { success: true, 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 (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) = match sqlx::query_as::<_, (bool, Option>)>("SELECT is_paid_active($1)") .bind(user_id)