提供基本前后端骨架

This commit is contained in:
hisatri
2026-01-06 23:49:23 +08:00
parent 84d4ccc226
commit 06f8176e23
89 changed files with 19293 additions and 2 deletions

View 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,
)