小程序后端接入微信登录与家长头像能力

This commit is contained in:
stu2not
2026-04-16 18:19:19 +08:00
parent db888df50f
commit 2526365af1
10 changed files with 515 additions and 77 deletions

View File

@@ -1,17 +1,23 @@
import logging
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Request, status
from pydantic import BaseModel
from sqlalchemy import text
from fastapi import APIRouter, Depends, HTTPException, Request
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session
try:
from app.db import get_db
from app.security import create_access_token
from app.service.parent import ParentService
from app.service.wechat_login import (
WechatAuthError,
WechatAuthService,
get_wechat_auth_service,
)
except ModuleNotFoundError:
from db import get_db
from security import create_access_token
from service.parent import ParentService
from service.wechat_login import WechatAuthError, WechatAuthService, get_wechat_auth_service
router = APIRouter(prefix="/auth", tags=["auth"])
@@ -19,15 +25,14 @@ logger = logging.getLogger("app.auth")
class LoginRequest(BaseModel):
username: Optional[str] = None
password: Optional[str] = None
code: Optional[str] = None
nickname: Optional[str] = None
avatar_url: Optional[str] = None
code: str = Field(min_length=1, max_length=191)
nickname: str | None = Field(default=None, max_length=64)
avatar_url: str | None = Field(default=None, max_length=255)
class LoginResponse(BaseModel):
access_token: str
token_type: str = "bearer"
expires_in: int
user_id: int
@@ -37,51 +42,38 @@ def login(
payload: LoginRequest,
request: Request,
db: Session = Depends(get_db),
wechat_auth_service: WechatAuthService = Depends(get_wechat_auth_service),
) -> LoginResponse:
logger.info(f"/auth/login called with code={payload.code[:20] if payload.code else None}...")
identifier = payload.code or payload.username
if not identifier:
raise HTTPException(status_code=400, detail="username or code is required")
del request
logger.info("/auth/login called")
with db.begin():
row = (
db.execute(
text("SELECT user_id FROM parents WHERE openid = :openid"),
{"openid": identifier},
).mappings().first()
try:
wechat_session = wechat_auth_service.exchange_code(payload.code)
except WechatAuthError as exc:
logger.warning(
"wechat login failed",
extra={
"event": "wechat_login_failed",
"status_code": exc.status_code,
"errcode": exc.errcode,
},
)
raise HTTPException(status_code=exc.status_code, detail=str(exc)) from exc
if row:
user_id = int(row["user_id"])
logger.info(f"Existing user found: user_id={user_id}")
if payload.nickname or payload.avatar_url:
db.execute(
text(
"""
UPDATE parents
SET nickname = COALESCE(:nickname, nickname),
avatar_url = COALESCE(:avatar_url, avatar_url)
WHERE user_id = :user_id
"""
),
{"user_id": user_id, "nickname": payload.nickname, "avatar_url": payload.avatar_url},
)
else:
logger.info("Creating new user...")
db.execute(
text(
"INSERT INTO parents (openid, nickname, avatar_url, status) VALUES (:openid, :nickname, :avatar_url, 1)"
),
{"openid": identifier, "nickname": payload.nickname, "avatar_url": payload.avatar_url},
)
user_id = int(db.execute(text("SELECT last_insert_rowid()")).scalar_one())
logger.info(f"New user created: user_id={user_id}")
parent_service = ParentService(db)
parent = parent_service.create(
openid=wechat_session.openid,
unionid=wechat_session.unionid,
nickname=payload.nickname,
avatar_url=payload.avatar_url,
)
user_id = int(parent["user_id"])
access_token, expires_in = create_access_token(user_id=user_id)
logger.info(f"Login succeeded: user_id={user_id}, expires_in={expires_in}")
logger.info("wechat login succeeded", extra={"event": "wechat_login_succeeded", "user_id": user_id})
return LoginResponse(access_token=access_token, expires_in=expires_in, user_id=user_id)
@router.post("/logout")
def logout(request: Request):
return {"message": "logged out"}
return {"message": "logged out"}