- 从服务器拉取完整代码 - 按框架规范整理项目结构 - 配置 Drone CI 测试环境部署 - 包含后端(FastAPI)、前端(Vue3)、管理端 技术栈: Vue3 + TypeScript + FastAPI + MySQL
164 lines
5.0 KiB
Python
164 lines
5.0 KiB
Python
"""
|
||
认证服务示例代码
|
||
"""
|
||
from typing import Optional
|
||
from datetime import datetime, timedelta
|
||
|
||
from sqlalchemy import select
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
from fastapi import HTTPException, status
|
||
|
||
from app.core.security import verify_password, get_password_hash, create_access_token, create_refresh_token
|
||
from app.core.exceptions import UnauthorizedError, ConflictError, ForbiddenError
|
||
from app.core.logger import logger
|
||
from app.models.user import User
|
||
from app.schemas.auth import UserRegister, Token
|
||
|
||
|
||
class AuthService:
|
||
"""认证服务类"""
|
||
|
||
def __init__(self, db: AsyncSession):
|
||
self.db = db
|
||
|
||
async def authenticate_user(self, username: str, password: str) -> Optional[User]:
|
||
"""
|
||
验证用户身份
|
||
|
||
Args:
|
||
username: 用户名或邮箱
|
||
password: 密码
|
||
|
||
Returns:
|
||
验证成功返回User对象,失败返回None
|
||
"""
|
||
# 查询用户(支持用户名或邮箱登录)
|
||
query = select(User).where(
|
||
(User.username == username) | (User.email == username)
|
||
)
|
||
result = await self.db.execute(query)
|
||
user = result.scalar_one_or_none()
|
||
|
||
if not user:
|
||
logger.warning("登录失败:用户不存在", username=username)
|
||
return None
|
||
|
||
# 验证密码
|
||
if not verify_password(password, user.password_hash):
|
||
logger.warning("登录失败:密码错误", user_id=user.id, username=username)
|
||
# TODO: 记录失败次数,实现账号锁定
|
||
return None
|
||
|
||
# 检查账号状态
|
||
if not user.is_active:
|
||
logger.warning("登录失败:账号已禁用", user_id=user.id, username=username)
|
||
raise ForbiddenError("账号已被禁用")
|
||
|
||
logger.info("用户登录成功", user_id=user.id, username=user.username)
|
||
return user
|
||
|
||
async def create_user(self, user_data: UserRegister) -> User:
|
||
"""
|
||
创建新用户
|
||
|
||
Args:
|
||
user_data: 用户注册数据
|
||
|
||
Returns:
|
||
创建的用户对象
|
||
|
||
Raises:
|
||
ConflictError: 用户名或邮箱已存在
|
||
"""
|
||
# 检查用户名是否存在
|
||
query = select(User).where(User.username == user_data.username)
|
||
result = await self.db.execute(query)
|
||
if result.scalar_one_or_none():
|
||
raise ConflictError("用户名已存在")
|
||
|
||
# 检查邮箱是否存在
|
||
query = select(User).where(User.email == user_data.email)
|
||
result = await self.db.execute(query)
|
||
if result.scalar_one_or_none():
|
||
raise ConflictError("邮箱已存在")
|
||
|
||
# 创建用户
|
||
user = User(
|
||
username=user_data.username,
|
||
email=user_data.email,
|
||
password_hash=get_password_hash(user_data.password),
|
||
is_active=True,
|
||
is_superuser=False,
|
||
role="trainee" # 默认角色为学员
|
||
)
|
||
|
||
self.db.add(user)
|
||
await self.db.commit()
|
||
await self.db.refresh(user)
|
||
|
||
logger.info("用户注册成功", user_id=user.id, username=user.username)
|
||
return user
|
||
|
||
async def create_tokens(self, user: User) -> Token:
|
||
"""
|
||
为用户创建访问令牌和刷新令牌
|
||
|
||
Args:
|
||
user: 用户对象
|
||
|
||
Returns:
|
||
Token对象
|
||
"""
|
||
# 创建访问令牌
|
||
access_token = create_access_token(
|
||
subject=user.id,
|
||
role=user.role,
|
||
username=user.username
|
||
)
|
||
|
||
# 创建刷新令牌
|
||
refresh_token = create_refresh_token(subject=user.id)
|
||
|
||
return Token(
|
||
access_token=access_token,
|
||
refresh_token=refresh_token,
|
||
token_type="bearer",
|
||
expires_in=1800, # 30分钟
|
||
user={
|
||
"id": user.id,
|
||
"username": user.username,
|
||
"email": user.email,
|
||
"role": user.role
|
||
}
|
||
)
|
||
|
||
async def logout(self, token: str) -> None:
|
||
"""
|
||
用户登出,将token加入黑名单
|
||
|
||
Args:
|
||
token: 要失效的token
|
||
"""
|
||
# TODO: 将token加入Redis黑名单
|
||
# redis_key = f"blacklist:{token}"
|
||
# await redis.setex(redis_key, 3600, "1") # 设置1小时过期
|
||
|
||
logger.info("用户登出成功")
|
||
|
||
async def refresh_access_token(self, refresh_token: str) -> Token:
|
||
"""
|
||
使用刷新令牌获取新的访问令牌
|
||
|
||
Args:
|
||
refresh_token: 刷新令牌
|
||
|
||
Returns:
|
||
新的Token对象
|
||
"""
|
||
# TODO: 验证刷新令牌
|
||
# TODO: 检查是否在黑名单
|
||
# TODO: 生成新的访问令牌
|
||
# TODO: 可选 - 轮换刷新令牌
|
||
|
||
pass
|