- 从服务器拉取完整代码 - 按框架规范整理项目结构 - 配置 Drone CI 测试环境部署 - 包含后端(FastAPI)、前端(Vue3)、管理端 技术栈: Vue3 + TypeScript + FastAPI + MySQL
202 lines
5.0 KiB
Python
202 lines
5.0 KiB
Python
"""
|
||
认证API路由示例代码
|
||
"""
|
||
from fastapi import APIRouter, Depends, HTTPException, status
|
||
from fastapi.security import OAuth2PasswordRequestForm
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
from app.api.deps import get_db
|
||
from app.core.exceptions import UnauthorizedError, ConflictError
|
||
from app.schemas.auth import UserRegister, Token, PasswordReset
|
||
from app.schemas.base import ResponseModel
|
||
from app.services.auth_service import AuthService
|
||
|
||
router = APIRouter(prefix="/auth", tags=["认证"])
|
||
|
||
|
||
@router.post("/login", response_model=ResponseModel[Token], summary="用户登录")
|
||
async def login(
|
||
form_data: OAuth2PasswordRequestForm = Depends(),
|
||
db: AsyncSession = Depends(get_db)
|
||
):
|
||
"""
|
||
用户登录接口
|
||
|
||
- **username**: 用户名或邮箱
|
||
- **password**: 密码
|
||
|
||
返回访问令牌和刷新令牌
|
||
"""
|
||
auth_service = AuthService(db)
|
||
|
||
# 验证用户
|
||
user = await auth_service.authenticate_user(
|
||
username=form_data.username,
|
||
password=form_data.password
|
||
)
|
||
|
||
if not user:
|
||
raise UnauthorizedError("用户名或密码错误")
|
||
|
||
# 创建Token
|
||
token = await auth_service.create_tokens(user)
|
||
|
||
return ResponseModel(
|
||
code=200,
|
||
message="登录成功",
|
||
data=token
|
||
)
|
||
|
||
|
||
@router.post("/register", response_model=ResponseModel[Token], status_code=status.HTTP_201_CREATED)
|
||
async def register(
|
||
user_data: UserRegister,
|
||
db: AsyncSession = Depends(get_db)
|
||
):
|
||
"""
|
||
用户注册接口
|
||
|
||
注册成功后自动登录,返回Token
|
||
"""
|
||
# 验证密码一致性
|
||
if user_data.password != user_data.confirm_password:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_400_BAD_REQUEST,
|
||
detail="两次输入的密码不一致"
|
||
)
|
||
|
||
auth_service = AuthService(db)
|
||
|
||
try:
|
||
# 创建用户
|
||
user = await auth_service.create_user(user_data)
|
||
|
||
# 自动登录
|
||
token = await auth_service.create_tokens(user)
|
||
|
||
return ResponseModel(
|
||
code=201,
|
||
message="注册成功",
|
||
data=token
|
||
)
|
||
except ConflictError as e:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_409_CONFLICT,
|
||
detail=str(e)
|
||
)
|
||
|
||
|
||
@router.post("/logout", response_model=ResponseModel)
|
||
async def logout(
|
||
token: str = Depends(get_current_token),
|
||
db: AsyncSession = Depends(get_db)
|
||
):
|
||
"""
|
||
用户登出接口
|
||
|
||
将当前Token加入黑名单
|
||
"""
|
||
auth_service = AuthService(db)
|
||
await auth_service.logout(token)
|
||
|
||
return ResponseModel(
|
||
code=200,
|
||
message="登出成功"
|
||
)
|
||
|
||
|
||
@router.post("/refresh", response_model=ResponseModel[Token])
|
||
async def refresh_token(
|
||
refresh_token: str,
|
||
db: AsyncSession = Depends(get_db)
|
||
):
|
||
"""
|
||
刷新访问令牌
|
||
|
||
使用刷新令牌获取新的访问令牌
|
||
"""
|
||
auth_service = AuthService(db)
|
||
|
||
try:
|
||
token = await auth_service.refresh_access_token(refresh_token)
|
||
return ResponseModel(
|
||
code=200,
|
||
message="刷新成功",
|
||
data=token
|
||
)
|
||
except UnauthorizedError:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||
detail="刷新令牌无效或已过期"
|
||
)
|
||
|
||
|
||
@router.post("/reset-password/request", response_model=ResponseModel)
|
||
async def request_password_reset(
|
||
email: str,
|
||
db: AsyncSession = Depends(get_db)
|
||
):
|
||
"""
|
||
请求重置密码
|
||
|
||
向用户邮箱发送重置链接
|
||
"""
|
||
auth_service = AuthService(db)
|
||
|
||
# 查找用户
|
||
user = await auth_service.get_user_by_email(email)
|
||
if not user:
|
||
# 为了安全,即使用户不存在也返回成功
|
||
return ResponseModel(
|
||
code=200,
|
||
message="如果邮箱存在,重置链接已发送"
|
||
)
|
||
|
||
# 生成重置令牌
|
||
reset_token = await auth_service.create_password_reset_token(user)
|
||
|
||
# TODO: 发送邮件
|
||
# await send_reset_email(email, reset_token)
|
||
|
||
return ResponseModel(
|
||
code=200,
|
||
message="如果邮箱存在,重置链接已发送"
|
||
)
|
||
|
||
|
||
@router.post("/reset-password/confirm", response_model=ResponseModel)
|
||
async def reset_password(
|
||
reset_data: PasswordReset,
|
||
db: AsyncSession = Depends(get_db)
|
||
):
|
||
"""
|
||
确认重置密码
|
||
|
||
使用重置令牌设置新密码
|
||
"""
|
||
auth_service = AuthService(db)
|
||
|
||
try:
|
||
await auth_service.reset_password(
|
||
token=reset_data.token,
|
||
new_password=reset_data.new_password
|
||
)
|
||
|
||
return ResponseModel(
|
||
code=200,
|
||
message="密码重置成功"
|
||
)
|
||
except UnauthorizedError:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_400_BAD_REQUEST,
|
||
detail="重置令牌无效或已过期"
|
||
)
|
||
|
||
|
||
# 辅助函数
|
||
async def get_current_token(
|
||
authorization: str = Depends(oauth2_scheme)
|
||
) -> str:
|
||
"""获取当前请求的Token"""
|
||
return authorization
|