"""小程序鉴权:微信登录 / 绑定 openid。""" from __future__ import annotations from datetime import datetime from fastapi import APIRouter, Depends, HTTPException, Request from sqlalchemy.orm import Session from app.core.auth import get_current_user_by_token from app.core.database import get_db from app.core.response import error_response, success_response from app.crud.user import user_crud from app.services.issue_user_tokens import issue_tokens_for_user from app.services.wechat_login_service import resolve_wechat_login_user from app.services.wechat_mini_auth import ( WechatMiniAuthError, code2session, get_phone_number, ) router = APIRouter(prefix="/mini/auth", tags=["小程序鉴权"]) async def _resolve_user_id(request: Request, db: Session) -> str: authorization = request.headers.get("Authorization", "") if not authorization.startswith("Bearer "): raise HTTPException(status_code=401, detail="未授权") token = authorization.replace("Bearer ", "") current = await get_current_user_by_token(token=token, db=db) user_id = current.get("user_id") or current.get("sub") if not user_id: raise HTTPException(status_code=401, detail="未授权") return user_id @router.post("/wechat-login", response_model=dict) async def wechat_login(body: dict, db: Session = Depends(get_db)): """微信授权登录:须授权手机号;openid 已绑定也须与账号 phone_no 一致。""" login_code = (body.get("loginCode") or "").strip() phone_code = (body.get("phoneCode") or "").strip() or None try: sess = code2session(login_code) except WechatMiniAuthError as exc: return error_response(400, str(exc)) if not phone_code: return error_response(400, "请授权手机号登录,或使用账号密码登录") try: phone = get_phone_number(phone_code) except WechatMiniAuthError as exc: return error_response(400, str(exc)) user, err_msg, err_code = resolve_wechat_login_user( db, openid=sess["openid"], phone=phone ) if err_msg: return error_response(err_code or 400, err_msg) try: data = await issue_tokens_for_user(db, user) except ValueError as exc: return error_response(403, str(exc)) return success_response(data) @router.post("/wechat-bind", response_model=dict) async def wechat_bind( request: Request, body: dict, db: Session = Depends(get_db), ): """密码登录后绑定当前微信 openid(需 Bearer)。""" try: user_id = await _resolve_user_id(request, db) except HTTPException: return error_response(401, "未授权") login_code = (body.get("loginCode") or "").strip() try: sess = code2session(login_code) except WechatMiniAuthError as exc: return error_response(400, str(exc)) openid = sess["openid"] user = user_crud.get(db, user_id) if not user or user.del_flag != 0: return error_response(401, "用户不存在") other = user_crud.get_by_wx_openid(db, openid) if other and other.id != user.id: return error_response(409, "该微信已绑定其他账号") existing_oid = (getattr(user, "wx_openid", None) or "").strip() if existing_oid == openid: return success_response({"bound": True, "message": "已绑定"}) if existing_oid and existing_oid != openid: return error_response(409, "该账号已绑定其他微信,请联系管理员") user.wx_openid = openid user.update_time = datetime.now() db.add(user) db.commit() return success_response({"bound": True, "message": "绑定成功"})