first commit
This commit is contained in:
92
MeetSpot/api/routers/auth.py
Normal file
92
MeetSpot/api/routers/auth.py
Normal file
@@ -0,0 +1,92 @@
|
||||
"""认证相关API路由。"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.auth.jwt import create_access_token, get_current_user
|
||||
from app.auth.sms import send_login_code, validate_code
|
||||
from app.db import crud
|
||||
from app.db.database import get_db
|
||||
from app.models.user import User
|
||||
|
||||
|
||||
router = APIRouter(prefix="/api/auth", tags=["auth"])
|
||||
|
||||
|
||||
class SendCodeRequest(BaseModel):
|
||||
phone: str = Field(..., min_length=4, max_length=20, description="手机号")
|
||||
|
||||
|
||||
class VerifyCodeRequest(BaseModel):
|
||||
phone: str = Field(..., min_length=4, max_length=20, description="手机号")
|
||||
code: str = Field(..., min_length=4, max_length=10, description="验证码")
|
||||
nickname: str | None = Field(None, description="首次登录时的昵称")
|
||||
avatar_url: str | None = Field(None, description="头像URL,可选")
|
||||
|
||||
|
||||
class AuthResponse(BaseModel):
|
||||
success: bool
|
||||
token: str
|
||||
user: dict
|
||||
|
||||
|
||||
def _mask_phone(phone: str) -> str:
|
||||
"""简单脱敏手机号。"""
|
||||
if len(phone) < 7:
|
||||
return phone
|
||||
return f"{phone[:3]}****{phone[-4:]}"
|
||||
|
||||
|
||||
def _serialize_user(user: User) -> dict:
|
||||
"""统一的用户返回结构。"""
|
||||
return {
|
||||
"id": user.id,
|
||||
"phone": _mask_phone(user.phone),
|
||||
"nickname": user.nickname,
|
||||
"avatar_url": user.avatar_url or "",
|
||||
"created_at": user.created_at,
|
||||
"last_login": user.last_login,
|
||||
}
|
||||
|
||||
|
||||
@router.post("/send_code")
|
||||
async def send_code(payload: SendCodeRequest):
|
||||
"""下发登录验证码,MVP阶段固定返回Mock值。"""
|
||||
code = await send_login_code(payload.phone)
|
||||
return {"success": True, "message": "验证码已发送", "code": code}
|
||||
|
||||
|
||||
@router.post("/verify_code", response_model=AuthResponse)
|
||||
async def verify_code(
|
||||
payload: VerifyCodeRequest, db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""验证验证码并返回JWT。"""
|
||||
if not validate_code(payload.phone, payload.code):
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="验证码错误")
|
||||
|
||||
user = await crud.get_user_by_phone(db, payload.phone)
|
||||
nickname = payload.nickname
|
||||
avatar_url = payload.avatar_url or ""
|
||||
|
||||
# 首次登录创建用户;旧用户允许更新昵称
|
||||
if not user:
|
||||
user = await crud.create_user(db, phone=payload.phone, nickname=nickname, avatar_url=avatar_url)
|
||||
else:
|
||||
if nickname:
|
||||
user.nickname = nickname
|
||||
user.avatar_url = avatar_url or user.avatar_url
|
||||
await db.commit()
|
||||
await db.refresh(user)
|
||||
|
||||
await crud.touch_last_login(db, user)
|
||||
|
||||
token = create_access_token({"sub": user.id, "phone": user.phone})
|
||||
return {"success": True, "token": token, "user": _serialize_user(user)}
|
||||
|
||||
|
||||
@router.get("/me")
|
||||
async def get_me(current_user: User = Depends(get_current_user)):
|
||||
"""获取当前登录用户信息。"""
|
||||
return {"user": _serialize_user(current_user)}
|
||||
|
||||
Reference in New Issue
Block a user