217 lines
7.2 KiB
Python
217 lines
7.2 KiB
Python
from fastapi import APIRouter, Depends, HTTPException, status, Request
|
|
from sqlalchemy.orm import Session
|
|
from datetime import datetime, timedelta
|
|
|
|
from app.core.database import get_db
|
|
from app.core.security import verify_token
|
|
from app.core.token_blacklist import token_blacklist
|
|
from app.services.user_service import UserService
|
|
from app.schemas.auth import (
|
|
UserRegister, UserLogin, UserResponse, LoginResponse,
|
|
TokenResponse, TokenRefresh, ApiResponse
|
|
)
|
|
from app.dependencies.auth import get_current_user_response
|
|
from app.models.user import User
|
|
from app.exceptions.auth import UsernameAlreadyExistsException
|
|
|
|
router = APIRouter()
|
|
|
|
@router.post("/register", status_code=status.HTTP_201_CREATED)
|
|
async def register(request: Request, user_data: UserRegister, db: Session = Depends(get_db)):
|
|
"""用户注册"""
|
|
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
print(f"[{timestamp}] INFO: === 注册接口 ===")
|
|
print(f"[{timestamp}] INFO: 用户名: {user_data.username}")
|
|
print(f"[{timestamp}] INFO: 邮箱: {user_data.email}")
|
|
print(f"[{timestamp}] INFO: 密码长度: {len(user_data.password)}字符")
|
|
print(f"[{timestamp}] INFO: 确认密码长度: {len(user_data.confirm_password)}字符")
|
|
print(f"[{timestamp}] DEBUG: Starting registration process...")
|
|
|
|
try:
|
|
user_service = UserService(db)
|
|
|
|
# 创建用户
|
|
print("[DEBUG] Creating user...")
|
|
user = user_service.create_user(user_data)
|
|
print(f"[DEBUG] User created successfully with ID: {user.id}")
|
|
|
|
# 创建令牌
|
|
print("[DEBUG] Creating tokens...")
|
|
tokens = user_service.create_user_tokens(user)
|
|
print("[DEBUG] Tokens created successfully")
|
|
|
|
# 转换为响应格式
|
|
print("[DEBUG] Converting to response format...")
|
|
user_response = user_service.to_user_response(user)
|
|
print("[DEBUG] Response conversion successful")
|
|
|
|
response_data = {
|
|
"user": user_response.dict(),
|
|
"tokens": tokens
|
|
}
|
|
print("[DEBUG] Response data created successfully")
|
|
|
|
return ApiResponse(
|
|
success=True,
|
|
message="注册成功",
|
|
data=response_data
|
|
)
|
|
|
|
except UsernameAlreadyExistsException as e:
|
|
print(f"[DEBUG] UsernameAlreadyExistsException caught: {e}")
|
|
raise e
|
|
except HTTPException as e:
|
|
print(f"[DEBUG] HTTPException caught: {e}")
|
|
raise e
|
|
except Exception as e:
|
|
# 打印异常信息以便调试
|
|
import traceback
|
|
print(f"[ERROR] Unexpected error in register: {e}")
|
|
print(f"[ERROR] Exception type: {type(e)}")
|
|
traceback.print_exc()
|
|
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail={
|
|
"code": "REGISTRATION_FAILED",
|
|
"message": f"注册过程中发生错误: {str(e)}"
|
|
}
|
|
)
|
|
|
|
@router.post("/login", response_model=ApiResponse)
|
|
async def login(request: Request, login_data: UserLogin, db: Session = Depends(get_db)):
|
|
"""用户登录"""
|
|
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
print(f"[{timestamp}] INFO: === 登录接口 ===")
|
|
print(f"[{timestamp}] INFO: 用户名: {login_data.username}")
|
|
print(f"[{timestamp}] INFO: 密码长度: {len(login_data.password)}字符")
|
|
print(f"[{timestamp}] DEBUG: Starting authentication process...")
|
|
|
|
try:
|
|
user_service = UserService(db)
|
|
# 验证用户
|
|
user = user_service.authenticate_user(login_data)
|
|
if not user:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail={
|
|
"code": "INVALID_CREDENTIALS",
|
|
"message": "用户名或密码错误"
|
|
}
|
|
)
|
|
|
|
# 创建令牌
|
|
tokens = user_service.create_user_tokens(user)
|
|
|
|
# 转换为响应格式
|
|
user_response = user_service.to_user_response(user)
|
|
|
|
return ApiResponse(
|
|
success=True,
|
|
message="登录成功",
|
|
data={
|
|
"user": user_response.dict(),
|
|
"tokens": tokens
|
|
}
|
|
)
|
|
|
|
except HTTPException as e:
|
|
raise e
|
|
except Exception as e:
|
|
print("用户登录的异常:",e)
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail={
|
|
"code": "LOGIN_FAILED",
|
|
"message": "登录过程中发生错误"
|
|
}
|
|
)
|
|
|
|
@router.post("/refresh", response_model=ApiResponse)
|
|
async def refresh_token(token_data: TokenRefresh, db: Session = Depends(get_db)):
|
|
"""刷新访问令牌"""
|
|
try:
|
|
# 验证刷新令牌
|
|
payload = verify_token(token_data.refresh_token, "refresh")
|
|
if not payload:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail={
|
|
"code": "INVALID_REFRESH_TOKEN",
|
|
"message": "无效的刷新令牌"
|
|
}
|
|
)
|
|
|
|
# 获取用户ID
|
|
user_id = int(payload.get("sub"))
|
|
user_service = UserService(db)
|
|
user = user_service.get_user_by_id(user_id)
|
|
|
|
if not user:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail={
|
|
"code": "USER_NOT_FOUND",
|
|
"message": "用户不存在"
|
|
}
|
|
)
|
|
|
|
# 创建新的访问令牌
|
|
tokens = user_service.create_user_tokens(user)
|
|
|
|
return ApiResponse(
|
|
success=True,
|
|
message="令牌刷新成功",
|
|
data={
|
|
"tokens": tokens
|
|
}
|
|
)
|
|
|
|
except HTTPException as e:
|
|
raise e
|
|
except Exception as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail={
|
|
"code": "TOKEN_REFRESH_FAILED",
|
|
"message": "令牌刷新过程中发生错误"
|
|
}
|
|
)
|
|
|
|
@router.get("/me", response_model=ApiResponse)
|
|
async def get_current_user_info(current_user: UserResponse = Depends(get_current_user_response)):
|
|
"""获取当前用户信息"""
|
|
return ApiResponse(
|
|
success=True,
|
|
message="获取用户信息成功",
|
|
data={
|
|
"user": current_user.dict()
|
|
}
|
|
)
|
|
|
|
@router.post("/logout", response_model=ApiResponse)
|
|
async def logout(
|
|
request: Request,
|
|
current_user: UserResponse = Depends(get_current_user_response)
|
|
):
|
|
"""用户登出"""
|
|
try:
|
|
# 从请求头中获取Authorization令牌
|
|
authorization = request.headers.get("Authorization")
|
|
if authorization and authorization.startswith("Bearer "):
|
|
token = authorization.split(" ")[1]
|
|
# 将令牌加入黑名单
|
|
token_blacklist.add_token(token)
|
|
|
|
return ApiResponse(
|
|
success=True,
|
|
message="登出成功",
|
|
data={}
|
|
)
|
|
except Exception as e:
|
|
# 即使添加令牌到黑名单失败,也返回成功,因为登出操作主要目的是让客户端删除令牌
|
|
return ApiResponse(
|
|
success=True,
|
|
message="登出成功",
|
|
data={}
|
|
) |