提供基本前后端骨架
This commit is contained in:
191
app/api/v1/endpoints/auth.py
Normal file
191
app/api/v1/endpoints/auth.py
Normal file
@@ -0,0 +1,191 @@
|
||||
"""
|
||||
认证相关 API
|
||||
|
||||
包括注册、登录、退出、刷新令牌、修改密码等接口。
|
||||
"""
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.api.deps import ActiveUser, DbSession, get_auth_service
|
||||
from app.core.exceptions import (
|
||||
InvalidCredentialsError,
|
||||
PasswordValidationError,
|
||||
ResourceConflictError,
|
||||
TokenError,
|
||||
UserDisabledError,
|
||||
)
|
||||
from app.schemas.auth import (
|
||||
LoginRequest,
|
||||
PasswordChangeRequest,
|
||||
RefreshTokenRequest,
|
||||
TokenResponse,
|
||||
)
|
||||
from app.schemas.base import APIResponse
|
||||
from app.schemas.user import UserCreate, UserResponse
|
||||
from app.services.auth import AuthService
|
||||
from app.services.user import UserService
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/register",
|
||||
response_model=APIResponse[UserResponse],
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
summary="用户注册",
|
||||
description="创建新用户账户",
|
||||
)
|
||||
async def register(
|
||||
user_data: UserCreate,
|
||||
session: DbSession,
|
||||
) -> APIResponse[UserResponse]:
|
||||
"""
|
||||
用户注册接口
|
||||
|
||||
- **username**: 用户名(字母开头,3-32位)
|
||||
- **email**: 邮箱(可选)
|
||||
- **password**: 密码(8位以上,需包含大小写字母和数字)
|
||||
- **nickname**: 昵称(可选)
|
||||
"""
|
||||
user_service = UserService(session)
|
||||
|
||||
try:
|
||||
user = await user_service.create_user(user_data)
|
||||
return APIResponse.ok(
|
||||
data=UserResponse.model_validate(user),
|
||||
message="注册成功",
|
||||
)
|
||||
except ResourceConflictError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail=e.message,
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/login",
|
||||
response_model=APIResponse[TokenResponse],
|
||||
summary="用户登录",
|
||||
description="使用用户名/邮箱和密码登录",
|
||||
)
|
||||
async def login(
|
||||
login_data: LoginRequest,
|
||||
auth_service: Annotated[AuthService, Depends(get_auth_service)],
|
||||
) -> APIResponse[TokenResponse]:
|
||||
"""
|
||||
用户登录接口
|
||||
|
||||
- **username**: 用户名或邮箱
|
||||
- **password**: 密码
|
||||
|
||||
返回访问令牌和刷新令牌。
|
||||
"""
|
||||
try:
|
||||
_, tokens = await auth_service.login(
|
||||
username=login_data.username,
|
||||
password=login_data.password,
|
||||
)
|
||||
return APIResponse.ok(data=tokens, message="登录成功")
|
||||
except InvalidCredentialsError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=e.message,
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
except UserDisabledError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=e.message,
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/logout",
|
||||
response_model=APIResponse[None],
|
||||
summary="用户退出",
|
||||
description="退出登录(客户端应删除本地令牌)",
|
||||
)
|
||||
async def logout(
|
||||
current_user: ActiveUser,
|
||||
) -> APIResponse[None]:
|
||||
"""
|
||||
用户退出接口
|
||||
|
||||
由于使用的是无状态 JWT,服务端不存储令牌,
|
||||
因此退出登录主要由客户端删除本地存储的令牌实现。
|
||||
|
||||
如果需要实现令牌黑名单,可以在后续版本中添加。
|
||||
"""
|
||||
# 可以在这里添加令牌黑名单逻辑
|
||||
# 或者记录退出日志
|
||||
return APIResponse.ok(message="退出成功")
|
||||
|
||||
|
||||
@router.post(
|
||||
"/refresh",
|
||||
response_model=APIResponse[TokenResponse],
|
||||
summary="刷新令牌",
|
||||
description="使用刷新令牌获取新的访问令牌",
|
||||
)
|
||||
async def refresh_token(
|
||||
token_data: RefreshTokenRequest,
|
||||
auth_service: Annotated[AuthService, Depends(get_auth_service)],
|
||||
) -> APIResponse[TokenResponse]:
|
||||
"""
|
||||
刷新令牌接口
|
||||
|
||||
使用刷新令牌获取新的访问令牌和刷新令牌。
|
||||
"""
|
||||
try:
|
||||
tokens = await auth_service.refresh_tokens(token_data.refresh_token)
|
||||
return APIResponse.ok(data=tokens, message="刷新成功")
|
||||
except TokenError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=e.message,
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
except UserDisabledError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=e.message,
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/change-password",
|
||||
response_model=APIResponse[None],
|
||||
summary="修改密码",
|
||||
description="修改当前用户密码",
|
||||
)
|
||||
async def change_password(
|
||||
password_data: PasswordChangeRequest,
|
||||
current_user: ActiveUser,
|
||||
auth_service: Annotated[AuthService, Depends(get_auth_service)],
|
||||
) -> APIResponse[None]:
|
||||
"""
|
||||
修改密码接口
|
||||
|
||||
- **current_password**: 当前密码
|
||||
- **new_password**: 新密码
|
||||
"""
|
||||
try:
|
||||
await auth_service.change_password(
|
||||
user_id=current_user.id,
|
||||
password_data=password_data,
|
||||
)
|
||||
return APIResponse.ok(message="密码修改成功")
|
||||
except InvalidCredentialsError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=e.message,
|
||||
)
|
||||
except PasswordValidationError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=e.message,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user