feat: 初始化考培练系统项目

- 从服务器拉取完整代码
- 按框架规范整理项目结构
- 配置 Drone CI 测试环境部署
- 包含后端(FastAPI)、前端(Vue3)、管理端

技术栈: Vue3 + TypeScript + FastAPI + MySQL
This commit is contained in:
111
2026-01-24 19:33:28 +08:00
commit 998211c483
1197 changed files with 228429 additions and 0 deletions

156
backend/app/api/v1/auth.py Normal file
View File

@@ -0,0 +1,156 @@
"""
认证 API
"""
from fastapi import APIRouter, Depends, status, Request
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.deps import get_current_active_user, get_db
from app.core.logger import logger
from app.models.user import User
from app.schemas.auth import LoginRequest, RefreshTokenRequest, Token
from app.schemas.base import ResponseModel
from app.schemas.user import User as UserSchema
from app.services.auth_service import AuthService
from app.services.system_log_service import system_log_service
from app.schemas.system_log import SystemLogCreate
from app.core.exceptions import UnauthorizedError
router = APIRouter()
@router.post("/login", response_model=ResponseModel)
async def login(
login_data: LoginRequest,
request: Request,
db: AsyncSession = Depends(get_db),
) -> ResponseModel:
"""
用户登录
支持使用用户名、邮箱或手机号登录
"""
auth_service = AuthService(db)
try:
user, token = await auth_service.login(
username=login_data.username,
password=login_data.password,
)
# 记录登录成功日志
await system_log_service.create_log(
db,
SystemLogCreate(
level="INFO",
type="security",
message=f"用户 {user.username} 登录成功",
user_id=user.id,
user=user.username,
ip=request.client.host if request.client else None,
path="/api/v1/auth/login",
method="POST",
user_agent=request.headers.get("user-agent")
)
)
return ResponseModel(
message="登录成功",
data={
"user": UserSchema.model_validate(user).model_dump(),
"token": token.model_dump(),
},
)
except UnauthorizedError as e:
# 记录登录失败日志
await system_log_service.create_log(
db,
SystemLogCreate(
level="WARNING",
type="security",
message=f"用户 {login_data.username} 登录失败:密码错误",
user=login_data.username,
ip=request.client.host if request.client else None,
path="/api/v1/auth/login",
method="POST",
user_agent=request.headers.get("user-agent")
)
)
# 不返回 401统一返回 HTTP 200 + 业务失败码,便于前端友好提示
logger.warning("login_failed_wrong_credentials", username=login_data.username)
return ResponseModel(
code=400,
message=str(e) or "用户名或密码错误",
data=None,
)
except Exception as e:
logger.error("login_failed_unexpected", error=str(e))
return ResponseModel(
code=500,
message="登录失败,请稍后重试",
data=None,
)
@router.post("/refresh", response_model=ResponseModel)
async def refresh_token(
refresh_data: RefreshTokenRequest,
db: AsyncSession = Depends(get_db),
) -> ResponseModel:
"""
刷新访问令牌
使用刷新令牌获取新的访问令牌
"""
auth_service = AuthService(db)
token = await auth_service.refresh_token(refresh_data.refresh_token)
return ResponseModel(message="令牌刷新成功", data=token.model_dump())
@router.post("/logout", response_model=ResponseModel)
async def logout(
request: Request,
current_user: User = Depends(get_current_active_user),
db: AsyncSession = Depends(get_db),
) -> ResponseModel:
"""
用户登出
注意:客户端需要删除本地存储的令牌
"""
auth_service = AuthService(db)
await auth_service.logout(current_user.id)
# 记录登出日志
await system_log_service.create_log(
db,
SystemLogCreate(
level="INFO",
type="security",
message=f"用户 {current_user.username} 登出",
user_id=current_user.id,
user=current_user.username,
ip=request.client.host if request.client else None,
path="/api/v1/auth/logout",
method="POST",
user_agent=request.headers.get("user-agent")
)
)
return ResponseModel(message="登出成功")
@router.get("/verify", response_model=ResponseModel)
async def verify_token(
current_user: User = Depends(get_current_active_user),
) -> ResponseModel:
"""
验证令牌
用于检查当前令牌是否有效
"""
return ResponseModel(
message="令牌有效",
data={
"user": UserSchema.model_validate(current_user).model_dump(),
},
)