Files
zkxw/backend/app/api/mini_auth.py
T

131 lines
4.4 KiB
Python

"""小程序鉴权:微信登录 / 绑定 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_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;未绑定则用手机号匹配员工并绑定。"""
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))
openid = sess["openid"]
user = user_crud.get_by_wx_openid(db, openid)
if user:
try:
data = await issue_tokens_for_user(db, user)
except ValueError as exc:
return error_response(403, str(exc))
return success_response(data)
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))
users = user_crud.list_active_by_phone_no(db, phone)
if not users:
return error_response(
401, "未找到与该手机号匹配的员工账号,请使用账号密码登录"
)
if len(users) > 1:
return error_response(400, "手机号对应多个账号,请使用账号密码登录")
user = users[0]
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 and existing_oid != openid:
return error_response(409, "该账号已绑定其他微信,请联系管理员")
user.wx_openid = openid
user.update_time = datetime.now()
db.add(user)
db.commit()
db.refresh(user)
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": "绑定成功"})