feat: 初始化考培练系统项目
- 从服务器拉取完整代码 - 按框架规范整理项目结构 - 配置 Drone CI 测试环境部署 - 包含后端(FastAPI)、前端(Vue3)、管理端 技术栈: Vue3 + TypeScript + FastAPI + MySQL
This commit is contained in:
141
backend/app/services/auth_service.py
Normal file
141
backend/app/services/auth_service.py
Normal file
@@ -0,0 +1,141 @@
|
||||
"""
|
||||
认证服务
|
||||
"""
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.exceptions import UnauthorizedError
|
||||
from app.core.logger import logger
|
||||
from app.core.security import create_access_token, create_refresh_token, decode_token
|
||||
from app.models.user import User
|
||||
from app.schemas.auth import Token
|
||||
from app.services.user_service import UserService
|
||||
|
||||
|
||||
class AuthService:
|
||||
"""认证服务"""
|
||||
|
||||
def __init__(self, db: AsyncSession):
|
||||
self.db = db
|
||||
self.user_service = UserService(db)
|
||||
|
||||
async def login(self, username: str, password: str) -> tuple[User, Token]:
|
||||
"""
|
||||
用户登录
|
||||
|
||||
Args:
|
||||
username: 用户名/邮箱/手机号
|
||||
password: 密码
|
||||
|
||||
Returns:
|
||||
用户对象和令牌
|
||||
"""
|
||||
# 验证用户
|
||||
user = await self.user_service.authenticate(
|
||||
username=username, password=password
|
||||
)
|
||||
|
||||
if not user:
|
||||
logger.warning(
|
||||
"登录失败:用户名或密码错误",
|
||||
username=username,
|
||||
)
|
||||
raise UnauthorizedError("用户名或密码错误")
|
||||
|
||||
if not user.is_active:
|
||||
logger.warning(
|
||||
"登录失败:用户已被禁用",
|
||||
user_id=user.id,
|
||||
username=user.username,
|
||||
)
|
||||
raise UnauthorizedError("用户已被禁用")
|
||||
|
||||
# 生成令牌
|
||||
access_token = create_access_token(subject=user.id)
|
||||
refresh_token = create_refresh_token(subject=user.id)
|
||||
|
||||
# 更新最后登录时间
|
||||
await self.user_service.update_last_login(user.id)
|
||||
|
||||
# 记录日志
|
||||
logger.info(
|
||||
"用户登录成功",
|
||||
user_id=user.id,
|
||||
username=user.username,
|
||||
role=user.role,
|
||||
)
|
||||
|
||||
return user, Token(
|
||||
access_token=access_token,
|
||||
refresh_token=refresh_token,
|
||||
)
|
||||
|
||||
async def refresh_token(self, refresh_token: str) -> Token:
|
||||
"""
|
||||
刷新访问令牌
|
||||
|
||||
Args:
|
||||
refresh_token: 刷新令牌
|
||||
|
||||
Returns:
|
||||
新的令牌
|
||||
"""
|
||||
try:
|
||||
# 解码刷新令牌
|
||||
payload = decode_token(refresh_token)
|
||||
|
||||
# 验证令牌类型
|
||||
if payload.get("type") != "refresh":
|
||||
raise UnauthorizedError("无效的刷新令牌")
|
||||
|
||||
# 获取用户ID
|
||||
user_id = int(payload.get("sub"))
|
||||
|
||||
# 获取用户
|
||||
user = await self.user_service.get_by_id(user_id)
|
||||
if not user:
|
||||
raise UnauthorizedError("用户不存在")
|
||||
|
||||
if not user.is_active:
|
||||
raise UnauthorizedError("用户已被禁用")
|
||||
|
||||
# 生成新的访问令牌
|
||||
access_token = create_access_token(subject=user.id)
|
||||
|
||||
logger.info(
|
||||
"令牌刷新成功",
|
||||
user_id=user.id,
|
||||
username=user.username,
|
||||
)
|
||||
|
||||
return Token(
|
||||
access_token=access_token,
|
||||
refresh_token=refresh_token, # 保持原刷新令牌
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"令牌刷新失败",
|
||||
error=str(e),
|
||||
)
|
||||
raise UnauthorizedError("无效的刷新令牌")
|
||||
|
||||
async def logout(self, user_id: int) -> None:
|
||||
"""
|
||||
用户登出
|
||||
|
||||
注意:JWT是无状态的,实际的登出需要在客户端删除令牌
|
||||
这里只是记录日志,如果需要可以将令牌加入黑名单
|
||||
|
||||
Args:
|
||||
user_id: 用户ID
|
||||
"""
|
||||
user = await self.user_service.get_by_id(user_id)
|
||||
if user:
|
||||
logger.info(
|
||||
"用户登出",
|
||||
user_id=user.id,
|
||||
username=user.username,
|
||||
)
|
||||
Reference in New Issue
Block a user