from datetime import datetime from typing import Optional from fastapi import Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.core.database import get_db from app.core.security import create_access_token, hash_password, verify_password, decode_access_token from app.models.user import User, UserStatus from app.schemas.user import UserCreate, UserLogin, Token oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login") class AuthService: """认证服务""" @staticmethod async def register_user( db: AsyncSession, user_data: UserCreate, ) -> User: """ 注册用户 Args: db: 数据库会话 user_data: 用户数据 Returns: 创建的用户对象 """ # 检查邮箱是否已存在 result = await db.execute(select(User).where(User.email == user_data.email)) if result.scalar_one_or_none(): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="邮箱已被注册" ) # 创建用户 hashed_password = hash_password(user_data.password) user = User( company_id=user_data.company_id, email=user_data.email, hashed_password=hashed_password, full_name=user_data.full_name, role=user_data.role, ) db.add(user) await db.commit() await db.refresh(user) return user @staticmethod async def authenticate_user( db: AsyncSession, login_data: UserLogin, ) -> tuple[User, str]: """ 认证用户并生成 Token Args: db: 数据库会话 login_data: 登录数据 Returns: 用户对象和访问令牌 Raises: HTTPException: 认证失败 """ # 查找用户 result = await db.execute(select(User).where(User.email == login_data.email)) user = result.scalar_one_or_none() if not user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="邮箱或密码错误" ) # 验证密码 if not verify_password(login_data.password, user.hashed_password): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="邮箱或密码错误" ) # 检查用户状态 if user.status != UserStatus.ACTIVE.value: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=f"用户已被{user.status}" ) # 更新最后登录时间 user.last_login_at = datetime.utcnow() await db.commit() await db.refresh(user) # 生成 Token access_token = create_access_token(data={"sub": str(user.id), "email": user.email}) return user, access_token @staticmethod async def get_current_user( token: str = Depends(oauth2_scheme), db: AsyncSession = Depends(get_db), ) -> User: """ 获取当前用户 Args: token: JWT token db: 数据库会话 Returns: 当前用户对象 Raises: HTTPException: Token 无效或用户不存在 """ credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="无法验证凭据", headers={"WWW-Authenticate": "Bearer"}, ) payload = decode_access_token(token) if not payload: raise credentials_exception user_id: str = payload.get("sub") if not user_id: raise credentials_exception result = await db.execute(select(User).where(User.id == int(user_id))) user = result.scalar_one_or_none() if not user: raise credentials_exception if user.status != UserStatus.ACTIVE.value: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="用户已被禁用" ) return user # 单例实例 auth_service = AuthService()