feat(routers): 完善auth、card、file、user相关接口开发

This commit is contained in:
xingc
2025-02-22 11:38:56 +08:00
parent 02ddc81c51
commit 1b2f9c7539
11 changed files with 573 additions and 123 deletions

View File

@@ -5,68 +5,133 @@
# @Author : xingc
# @Desc :
import logging
from typing import Annotated
import httpx
from fastapi import APIRouter, HTTPException, Depends
import ujson
from fastapi import APIRouter, HTTPException, Depends, Query, status
import redis.asyncio as aioredis
from fastapi.security import OAuth2PasswordRequestForm
from simple_spider_tool import md5, jsonpath
from sqlalchemy.ext.asyncio import AsyncSession
from . import schema
from config import settings
from core.base import schema as base_schema
from core.base.exceptions import WechatApiError
from core.db.crud import CRUD
from core.db.engine import get_async_session, get_cache
from core.db.models import User
from core.db.crud import CRUD
from core.utils.helper import generate_keyname
from core.wechat.api import wechat_api, decrypt_sensitive_data
from . import schema
from .services import create_access_token
logger = logging.getLogger(__name__)
auth_router = APIRouter(prefix='/auth', tags=['认证模块'])
test_auth_router = APIRouter(tags=['认证模块'])
async def get_wechat_session(code: str):
url = "https://api.weixin.qq.com/sns/jscode2session"
params = {
"appid": settings.APPID,
"secret": settings.APPSECRET,
"js_code": code,
"grant_type": "authorization_code",
}
async with httpx.AsyncClient() as client:
response = await client.get(url, params=params)
if response.status_code != 200:
raise HTTPException(status_code=400, detail="Failed to fetch session from WeChat")
data = response.json()
if "errcode" in data:
logger.warning(f"WeChat session error: {data['errcode']}")
raise HTTPException(status_code=400, detail=data["errmsg"])
return data
@auth_router.get(
'/phone',
summary='获取绑定的手机号',
response_model=base_schema.BaseResponse
)
async def get_wechat_phone(
code: str = Query(pattern=r'^[a-zA-Z0-9]+'),
):
try:
result = await wechat_api.get_phone_number(code)
except WechatApiError:
raise HTTPException(status.HTTP_401_UNAUTHORIZED, detail='Invalid code')
return {'data': {'phone': jsonpath(result, '$.phone_info.phoneNumber')}}
@auth_router.post('/wechat_login')
@auth_router.post(
'/wechat_login',
summary='微信小程序授权登录',
response_model=base_schema.BaseResponse[schema.Token]
)
async def wechat_login(
payload: schema.WechatLoginIn,
db_session: AsyncSession = Depends(get_async_session),
redis_conn: aioredis.Redis = Depends(get_cache)
db_session: Annotated[AsyncSession, Depends(get_async_session)],
redis_conn: Annotated[aioredis.Redis, Depends(get_cache)]
):
# 获取微信 session
wechat_session = await get_wechat_session(payload.code)
logger.info(f"WeChat session: {wechat_session}")
openid = wechat_session.get("openid")
if openid:
raise HTTPException(status_code=400, detail="Invalid WeChat session")
db_crud = CRUD(db_session, User)
user = await db_crud.get(filters={'openid': openid})
if user is None:
# 新用户注册
if not payload.encrypted_data or not payload.iv:
raise HTTPException(400, detail="需要用户授权信息")
# user = await db_crud.create(
# data={
# 'wechat_openid': openid,
# 'wechat_union_id': union_id,
# },
# filters={'union_id': union_id}
# )
keyname = generate_keyname(f'wechat_session:{md5(payload.code)}')
# 验证code的有效性
wechat_session_str = await redis_conn.get(keyname)
if wechat_session_str is None:
try:
wechat_session = await wechat_api.code_to_session(payload.code)
except WechatApiError:
raise HTTPException(status.HTTP_401_UNAUTHORIZED, detail='Invalid code')
logger.debug(f"WeChat session: {wechat_session}")
else:
wechat_session = ujson.decode(wechat_session_str)
return {'data': {'openid': openid}}
openid = wechat_session.get('openid')
if openid is None:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid code')
# 验证用户的存在/是否位首次登录
user: User | None = await db_crud.get(filters={'wechat_openid': openid})
if user is None:
if not payload.encrypted_data or not payload.iv:
await redis_conn.set(keyname, ujson.encode(wechat_session), ex=60 * 5)
raise HTTPException(4001, detail='首次登录需要用户授权信息')
if all([
# payload.phone,
payload.encrypted_data,
payload.iv,
]) is False:
raise HTTPException(status.HTTP_400_BAD_REQUEST, detail='请重试登录操作')
# 注册用户
session_key = wechat_session.get('session_key')
union_id = wechat_session.get('union_id')
try:
profile = await decrypt_sensitive_data(session_key, payload.encrypted_data, payload.iv)
except (ValueError, TypeError) as e:
raise HTTPException(status.HTTP_400_BAD_REQUEST, detail='请重试登录操作')
logger.info(f"decrypt plaintext: {wechat_session}")
user: User | bool = await db_crud.create(
data={
'nickname': profile.get('nickName'),
'gender': profile.get('gender'),
'wechat_openid': openid,
'wechat_union_id': union_id,
'phone': payload.phone,
'avatar_url': profile.get('avatarUrl')
},
filters={'wechat_openid': openid}
)
await redis_conn.delete(keyname)
result = create_access_token({
'sub': str(user.id)
})
return {'data': result}
if settings.ENVIRONMENT == 'dev':
@test_auth_router.post(
'/token',
summary='开发',
description='点击“Authorize”在username输入“user_id”, password任意输入字符点再点击“Authorize”即可'
)
async def login_for_access_token(
form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
db_session: Annotated[AsyncSession, Depends(get_async_session)]
) -> schema.Token:
logger.info(form_data.username, form_data.password)
db_crud = CRUD(db_session, User)
user = await db_crud.get(filters={'user_id': form_data.username})
if not user:
raise HTTPException(status_code=400, detail='Incorrect user_id')
result = create_access_token({
'sub': str(user.user_id)
})
return result