feat: add web auth wx-qr create and status endpoints

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-09-18 13:53:40 +08:00
1 parent 9d4a11339b
commit 38b11cce35
2 files changed
+76

No files matched your search

+46
View File
@@ -1,4 +1,5 @@
"""公共接口(Web 登录、刷新 Token、登出;用户模型对齐 docs/sql/zkxw.sql 基线)"""
import base64
import os
from datetime import datetime
from typing import Optional
@@ -39,6 +40,8 @@ from app.services.web_auth_roles import (
from app.schemas.auth import ProfileUpdateRequest
from app.crud.user import update_user, change_password
from app.services.issue_user_tokens import build_user_info_payload
from app.services import wx_qr_session
from app.services.wechat_mini_auth import get_unlimited_wxacode, WechatMiniAuthError
router = APIRouter(prefix="", tags=["公共接口"])
@@ -155,6 +158,49 @@ async def web_login(
)
@router.post("/web/auth/wx-qr/create", response_model=dict)
async def wx_qr_create():
session_id, short = wx_qr_session.create_session()
page = (settings.WX_QR_PAGE or "pages/login/web-qr/index").strip()
try:
raw = get_unlimited_wxacode(
scene=short,
page=page,
env_version=settings.WX_QR_ENV_VERSION,
)
except WechatMiniAuthError as exc:
wx_qr_session.invalidate(session_id)
return error_response(400, str(exc))
b64 = base64.b64encode(raw).decode("ascii")
mime = "image/jpeg" if raw[:2] == b"\xff\xd8" else "image/png"
return success_response(
{
"sessionId": session_id,
"expiresIn": settings.WX_QR_SESSION_TTL_SEC,
"qrImageBase64": f"data:{mime};base64,{b64}",
}
)
@router.get("/web/auth/wx-qr/status", response_model=dict)
async def wx_qr_status(sessionId: str = ""):
sid = (sessionId or "").strip()
data = wx_qr_session.get_session(sid)
if not data:
return success_response({"status": "expired", "message": "二维码已过期,请刷新"})
status = data.get("status") or "expired"
if status == "success":
tokens = wx_qr_session.consume_success(sid)
if not tokens:
return success_response({"status": "expired", "message": "登录凭证已失效,请刷新二维码"})
return success_response({"status": "success", **tokens})
if status == "failed":
return success_response({"status": "failed", "message": data.get("message") or "登录失败"})
if status in ("expired", "consumed"):
return success_response({"status": "expired", "message": "二维码已过期,请刷新"})
return success_response({"status": status})
@router.post("/web/auth/refresh-token", response_model=dict)
async def refresh_token(body: dict, db: Session = Depends(get_db)):
"""刷新 Token"""
+30
View File
@@ -0,0 +1,30 @@
"""web wx-qr create/status API。"""
from unittest.mock import patch
from fastapi.testclient import TestClient
from app.main import app
from app.services import wx_qr_session
client = TestClient(app)
def setup_function():
wx_qr_session._memory_clear_for_tests()
def test_wx_qr_create_and_poll_pending():
with patch("app.services.wx_qr_session._redis", return_value=None), patch(
"app.api.public.get_unlimited_wxacode",
return_value=b"\x89PNG\r\n\x1a\n",
):
r = client.post("/api/web/auth/wx-qr/create")
assert r.status_code == 200
body = r.json()
assert body.get("code") == 200
data = body["data"]
sid = data["sessionId"]
assert data["qrImageBase64"].startswith("data:image/")
st = client.get("/api/web/auth/wx-qr/status", params={"sessionId": sid})
assert st.status_code == 200
assert st.json()["data"]["status"] == "pending"