from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.ext.asyncio import AsyncSession from app.api.deps import get_current_user from app.database import get_db from app.models.user import User from app.schemas.auth import ( AccessTokenResponse, ChangePasswordRequest, ForgotPasswordRequest, RefreshTokenRequest, ResetPasswordRequest, TokenResponse, UpdateProfileRequest, UserLogin, UserRegister, UserResponse, VerifyEmailRequest, ) from app.services.auth import ( authenticate_user, change_password as change_password_service, create_access_token, create_refresh_token, register_user, reset_password as reset_password_service, send_reset_link, send_verification_code, update_profile as update_profile_service, verify_email as verify_email_service, verify_refresh_token, ) from app.services.cache import get_cache_service, TTL_USER_PROFILE router = APIRouter() @router.post("/register", response_model=UserResponse, status_code=status.HTTP_201_CREATED) async def register(user_data: UserRegister, db: AsyncSession = Depends(get_db)): try: user = await register_user(db, user_data) except ValueError: # 不泄露具体原因,防止用户枚举 raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="注册失败,请检查输入信息是否已被使用") return user @router.post("/login", response_model=TokenResponse) async def login(user_data: UserLogin, db: AsyncSession = Depends(get_db)): user = await authenticate_user(db, user_data.email, user_data.password) if not user: # 统一错误消息,防止用户枚举(不区分“用户不存在” vs “密码错误”) raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="邮箱或密码错误", headers={"WWW-Authenticate": "Bearer"}, ) access_token = create_access_token(data={"sub": str(user.id)}) refresh_token = create_refresh_token(data={"sub": str(user.id)}) return { "access_token": access_token, "token_type": "bearer", "refresh_token": refresh_token, "user": user, } @router.post("/refresh", response_model=AccessTokenResponse) async def refresh_token(req: RefreshTokenRequest): """ 刷新接口:使用 refresh_token 获取新的 access_token + refresh_token(滑动过期) """ try: payload = verify_refresh_token(req.refresh_token) except ValueError: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="刷新令牌无效或已过期", headers={"WWW-Authenticate": "Bearer"}, ) user_id = payload.get("sub") if not user_id: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="刷新令牌无效或已过期", headers={"WWW-Authenticate": "Bearer"}, ) new_access_token = create_access_token(data={"sub": user_id}) new_refresh_token = create_refresh_token(data={"sub": user_id}) # 滑动过期 return { "access_token": new_access_token, "token_type": "bearer", "refresh_token": new_refresh_token, } @router.get("/me", response_model=UserResponse) async def read_current_user( current_user: User = Depends(get_current_user), ): cache = get_cache_service() cache_key = f"user:profile:{current_user.id}" cached = await cache.get_json(cache_key) if cached is not None: return cached user_data = UserResponse.model_validate(current_user).model_dump(mode="json") await cache.set_json(cache_key, user_data, expire=TTL_USER_PROFILE) return current_user @router.post("/forgot-password") async def forgot_password(req: ForgotPasswordRequest, db: AsyncSession = Depends(get_db)): await send_reset_link(db, req.email) return {"message": "如果该邮箱已注册,重置链接已发送"} @router.post("/reset-password") async def reset_password(req: ResetPasswordRequest, db: AsyncSession = Depends(get_db)): success = await reset_password_service(db, req.token, req.new_password) if not success: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="无效的令牌或令牌已过期") return {"message": "密码重置成功"} @router.post("/verify-email") async def verify_email(req: VerifyEmailRequest, db: AsyncSession = Depends(get_db)): success = await verify_email_service(db, req.email, req.code) if not success: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="验证码无效或已过期") return {"message": "邮箱验证成功"} @router.post("/resend-verification") async def resend_verification(req: ForgotPasswordRequest, db: AsyncSession = Depends(get_db)): await send_verification_code(db, req.email) return {"message": "验证码已重新发送"} @router.put("/change-password") async def change_password( req: ChangePasswordRequest, user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): success = await change_password_service(db, user.id, req.old_password, req.new_password) if not success: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="旧密码错误") return {"message": "密码修改成功"} @router.put("/profile", response_model=UserResponse) async def update_profile( req: UpdateProfileRequest, user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): updated_user = await update_profile_service(db, user.id, req) if not updated_user: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="用户不存在") # 失效用户配置缓存 cache = get_cache_service() await cache.delete(f"user:profile:{user.id}") return updated_user