初次提交
This commit is contained in:
0
backend/app/api/v1/__init__.py
Normal file
0
backend/app/api/v1/__init__.py
Normal file
0
backend/app/api/v1/endpoints/__init__.py
Normal file
0
backend/app/api/v1/endpoints/__init__.py
Normal file
217
backend/app/api/v1/endpoints/auth.py
Normal file
217
backend/app/api/v1/endpoints/auth.py
Normal file
@@ -0,0 +1,217 @@
|
||||
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={}
|
||||
)
|
||||
382
backend/app/api/v1/endpoints/files.py
Normal file
382
backend/app/api/v1/endpoints/files.py
Normal file
@@ -0,0 +1,382 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File, Form, Query, Request
|
||||
from fastapi.responses import FileResponse
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Optional, List
|
||||
from datetime import datetime
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.services.file_service import FileService
|
||||
from app.schemas.file import (
|
||||
FileUploadRequest, FileUpdateRequest, FileSearchRequest,
|
||||
FileResponse, FileListResponse, FileInfo, StorageInfo, ApiResponse,
|
||||
UploadResponse, DeleteResponse, FileListRequest, FileIdRequest, StorageInfoRequest
|
||||
)
|
||||
from app.models.user import User
|
||||
from app.dependencies.auth import get_current_user_from_headers
|
||||
from app.exceptions.file import (
|
||||
FileTooLargeException, StorageQuotaExceededException,
|
||||
FileAlreadyExistsException, FileNotFoundException,
|
||||
InvalidFileTypeException, FileUploadException, FileDeleteException
|
||||
)
|
||||
|
||||
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] INFO: [MODULE] Files module loaded successfully")
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.post("/upload", response_model=ApiResponse, status_code=status.HTTP_201_CREATED)
|
||||
async def upload_file(
|
||||
file: UploadFile = File(...),
|
||||
description: Optional[str] = Form(None),
|
||||
tags: Optional[str] = Form(None),
|
||||
is_public: bool = Form(False),
|
||||
current_user: User = Depends(get_current_user_from_headers),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""上传文件"""
|
||||
try:
|
||||
file_service = FileService(db)
|
||||
|
||||
# 创建上传请求对象
|
||||
upload_request = FileUploadRequest(
|
||||
description=description,
|
||||
tags=tags,
|
||||
is_public=is_public
|
||||
)
|
||||
|
||||
# 上传文件
|
||||
db_file = file_service.upload_file(file, current_user, upload_request)
|
||||
|
||||
# 转换为响应格式
|
||||
file_response = FileResponse(
|
||||
id=db_file.id,
|
||||
user_id=db_file.user_id,
|
||||
filename=db_file.filename,
|
||||
original_filename=db_file.original_filename,
|
||||
file_size=db_file.file_size,
|
||||
mime_type=db_file.mime_type,
|
||||
file_hash=db_file.file_hash,
|
||||
is_public=db_file.is_public,
|
||||
download_count=db_file.download_count,
|
||||
description=db_file.description,
|
||||
tags=db_file.tags,
|
||||
created_at=db_file.created_at,
|
||||
updated_at=db_file.updated_at,
|
||||
last_accessed_at=db_file.last_accessed_at
|
||||
)
|
||||
|
||||
return ApiResponse(
|
||||
success=True,
|
||||
message="文件上传成功",
|
||||
data={
|
||||
"file": file_response.model_dump()
|
||||
}
|
||||
)
|
||||
|
||||
except (FileTooLargeException, StorageQuotaExceededException,
|
||||
FileAlreadyExistsException, InvalidFileTypeException) as e:
|
||||
raise e
|
||||
except Exception as e:
|
||||
raise FileUploadException(str(e))
|
||||
|
||||
@router.get("/list", response_model=ApiResponse)
|
||||
def get_user_files(
|
||||
page: int = Query(1, ge=1),
|
||||
size: int = Query(10, ge=1, le=100),
|
||||
current_user: User = Depends(get_current_user_from_headers),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取用户文件列表"""
|
||||
try:
|
||||
file_service = FileService(db)
|
||||
file_list = file_service.get_user_files(current_user.id, page, size)
|
||||
|
||||
return ApiResponse(
|
||||
success=True,
|
||||
message="获取文件列表成功",
|
||||
data={
|
||||
"files": [FileResponse(
|
||||
id=file.id,
|
||||
user_id=file.user_id,
|
||||
filename=file.filename,
|
||||
original_filename=file.original_filename,
|
||||
file_size=file.file_size,
|
||||
mime_type=file.mime_type,
|
||||
file_hash=file.file_hash,
|
||||
is_public=file.is_public,
|
||||
download_count=file.download_count,
|
||||
description=file.description,
|
||||
tags=file.tags,
|
||||
created_at=file.created_at,
|
||||
updated_at=file.updated_at,
|
||||
last_accessed_at=file.last_accessed_at
|
||||
).model_dump() for file in file_list.files],
|
||||
"pagination": {
|
||||
"total": file_list.total,
|
||||
"page": file_list.page,
|
||||
"size": file_list.size,
|
||||
"pages": file_list.pages
|
||||
}
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail={
|
||||
"code": "GET_FILES_FAILED",
|
||||
"message": "获取文件列表失败"
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@router.post("/info", response_model=ApiResponse)
|
||||
def get_file_info(
|
||||
request: FileIdRequest,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取文件详细信息"""
|
||||
try:
|
||||
file_service = FileService(db)
|
||||
file_info = file_service.get_file_info(request.file_id, request.user_id)
|
||||
|
||||
return ApiResponse(
|
||||
success=True,
|
||||
message="获取文件信息成功",
|
||||
data=file_info.model_dump()
|
||||
)
|
||||
except FileNotFoundException as e:
|
||||
raise e
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail={
|
||||
"code": "GET_FILE_INFO_FAILED",
|
||||
"message": "获取文件信息失败"
|
||||
}
|
||||
)
|
||||
|
||||
@router.post("/update", response_model=ApiResponse)
|
||||
def update_file(
|
||||
file_id_request: FileIdRequest,
|
||||
update_request: FileUpdateRequest,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""更新文件信息"""
|
||||
try:
|
||||
file_service = FileService(db)
|
||||
db_file = file_service.update_file(file_id_request.file_id, file_id_request.user_id, update_request)
|
||||
|
||||
file_response = FileResponse(
|
||||
id=db_file.id,
|
||||
user_id=db_file.user_id,
|
||||
filename=db_file.filename,
|
||||
original_filename=db_file.original_filename,
|
||||
file_size=db_file.file_size,
|
||||
mime_type=db_file.mime_type,
|
||||
file_hash=db_file.file_hash,
|
||||
is_public=db_file.is_public,
|
||||
download_count=db_file.download_count,
|
||||
description=db_file.description,
|
||||
tags=db_file.tags,
|
||||
created_at=db_file.created_at,
|
||||
updated_at=db_file.updated_at,
|
||||
last_accessed_at=db_file.last_accessed_at
|
||||
)
|
||||
|
||||
return ApiResponse(
|
||||
success=True,
|
||||
message="文件信息更新成功",
|
||||
data={
|
||||
"file": file_response.model_dump()
|
||||
}
|
||||
)
|
||||
except FileNotFoundException as e:
|
||||
raise e
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail={
|
||||
"code": "UPDATE_FILE_FAILED",
|
||||
"message": "更新文件信息失败"
|
||||
}
|
||||
)
|
||||
|
||||
@router.post("/delete", response_model=ApiResponse)
|
||||
def delete_file(
|
||||
request: FileIdRequest,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""删除文件"""
|
||||
try:
|
||||
file_service = FileService(db)
|
||||
success = file_service.delete_file(request.file_id, request.user_id)
|
||||
|
||||
if success:
|
||||
return ApiResponse(
|
||||
success=True,
|
||||
message="文件删除成功",
|
||||
data={}
|
||||
)
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail={
|
||||
"code": "DELETE_FILE_FAILED",
|
||||
"message": "文件删除失败"
|
||||
}
|
||||
)
|
||||
except FileNotFoundException as e:
|
||||
raise e
|
||||
except Exception as e:
|
||||
raise FileDeleteException(str(e))
|
||||
|
||||
@router.post("/download")
|
||||
def download_file(
|
||||
request: FileIdRequest,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""下载文件"""
|
||||
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] INFO: [IN] Processing download request: file_id={request.file_id}, user_id={request.user_id}")
|
||||
|
||||
try:
|
||||
file_service = FileService(db)
|
||||
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] DEBUG: File service created")
|
||||
|
||||
db_file = file_service.get_file_by_id(request.file_id, request.user_id)
|
||||
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] DEBUG: File found in database: {db_file is not None}")
|
||||
|
||||
if not db_file:
|
||||
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] ERROR: File not found in database")
|
||||
raise FileNotFoundException()
|
||||
|
||||
# 增加下载次数
|
||||
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] DEBUG: Incrementing download count")
|
||||
file_service.increment_download_count(request.file_id)
|
||||
|
||||
# 确保使用绝对路径
|
||||
import os
|
||||
absolute_path = os.path.abspath(db_file.file_path)
|
||||
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] INFO: [PATH] File path: {absolute_path}")
|
||||
|
||||
# 验证文件存在
|
||||
if not os.path.exists(absolute_path):
|
||||
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] ERROR: File not found on disk: {absolute_path}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail={
|
||||
"code": "FILE_NOT_FOUND_ON_DISK",
|
||||
"message": "upload文件夹与数据库不匹配"
|
||||
}
|
||||
)
|
||||
|
||||
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] INFO: [OK] File exists on disk")
|
||||
|
||||
# 对于文本文件,直接读取内容返回
|
||||
if db_file.mime_type and db_file.mime_type.startswith('text/'):
|
||||
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] INFO: [TEXT] Processing text file")
|
||||
|
||||
with open(absolute_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] INFO: [READ] Read {len(content)} characters from file")
|
||||
|
||||
from fastapi.responses import Response
|
||||
import urllib.parse
|
||||
# 对文件名进行URL编码以支持中文
|
||||
encoded_filename = urllib.parse.quote(db_file.original_filename.encode('utf-8'))
|
||||
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] INFO: [SEND] Sending file: {db_file.original_filename}")
|
||||
|
||||
return Response(
|
||||
content=content,
|
||||
media_type=db_file.mime_type,
|
||||
headers={
|
||||
"Content-Disposition": f"attachment; filename*=UTF-8''{encoded_filename}"
|
||||
}
|
||||
)
|
||||
else:
|
||||
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] INFO: [BINARY] Processing binary file with FileResponse")
|
||||
|
||||
# 对于二进制文件,使用FileResponse
|
||||
return FileResponse(
|
||||
path=absolute_path,
|
||||
filename=db_file.original_filename,
|
||||
media_type=db_file.mime_type
|
||||
)
|
||||
|
||||
except FileNotFoundException as e:
|
||||
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] ERROR: FileNotFoundException: {e}")
|
||||
raise e
|
||||
except Exception as e:
|
||||
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] ERROR: Download error: {e}")
|
||||
import traceback
|
||||
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] ERROR: {traceback.format_exc()}")
|
||||
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail={
|
||||
"code": "DOWNLOAD_FILE_FAILED",
|
||||
"message": "文件下载失败"
|
||||
}
|
||||
)
|
||||
|
||||
@router.post("/test-download")
|
||||
def test_download_file():
|
||||
"""测试下载功能 - 直接返回5KB文件内容"""
|
||||
print("[TEST_DOWNLOAD] Test endpoint called", flush=True)
|
||||
return {"message": "Test endpoint is working"}
|
||||
|
||||
@router.post("/simple-download")
|
||||
def simple_download_file():
|
||||
"""简单下载测试"""
|
||||
try:
|
||||
print("[SIMPLE_DOWNLOAD] Simple download endpoint called", flush=True)
|
||||
|
||||
# 直接读取我们创建的文件
|
||||
file_path = "uploads/verified_5kb_download_test.txt"
|
||||
|
||||
import os
|
||||
if not os.path.exists(file_path):
|
||||
print(f"[SIMPLE_DOWNLOAD] File not found: {file_path}", flush=True)
|
||||
return {"error": "File not found"}
|
||||
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
print(f"[SIMPLE_DOWNLOAD] Read {len(content)} characters", flush=True)
|
||||
|
||||
from fastapi.responses import Response
|
||||
return Response(
|
||||
content=content,
|
||||
media_type="text/plain",
|
||||
headers={"Content-Disposition": "attachment; filename=verified_5kb.txt"}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[SIMPLE_DOWNLOAD] Exception: {e}", flush=True)
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return {"error": str(e)}
|
||||
|
||||
@router.post("/storage/info", response_model=ApiResponse)
|
||||
def get_storage_info(
|
||||
request: StorageInfoRequest,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取用户存储信息"""
|
||||
try:
|
||||
file_service = FileService(db)
|
||||
storage_info = file_service.get_storage_info(request.user_id)
|
||||
|
||||
return ApiResponse(
|
||||
success=True,
|
||||
message="获取存储信息成功",
|
||||
data=storage_info.model_dump()
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail={
|
||||
"code": "GET_STORAGE_INFO_FAILED",
|
||||
"message": "获取存储信息失败"
|
||||
}
|
||||
)
|
||||
|
||||
81
backend/app/api/v1/endpoints/health.py
Normal file
81
backend/app/api/v1/endpoints/health.py
Normal file
@@ -0,0 +1,81 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from app.core.database import get_db
|
||||
from app.core.config import settings
|
||||
import redis
|
||||
import time
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/health")
|
||||
async def health_check():
|
||||
"""基础健康检查"""
|
||||
return {
|
||||
"success": True,
|
||||
"data": {
|
||||
"status": "healthy",
|
||||
"service": "cloud-drive-api",
|
||||
"environment": settings.ENVIRONMENT,
|
||||
"timestamp": int(time.time())
|
||||
},
|
||||
"message": "API服务运行正常"
|
||||
}
|
||||
|
||||
@router.get("/ready")
|
||||
async def readiness_check(db: Session = Depends(get_db)):
|
||||
"""就绪检查 - 检查数据库和Redis连接"""
|
||||
checks = {}
|
||||
|
||||
# 检查数据库连接
|
||||
try:
|
||||
db.execute("SELECT 1")
|
||||
checks["database"] = {
|
||||
"status": "healthy",
|
||||
"message": "数据库连接正常"
|
||||
}
|
||||
except Exception as e:
|
||||
checks["database"] = {
|
||||
"status": "unhealthy",
|
||||
"message": f"数据库连接失败: {str(e)}"
|
||||
}
|
||||
raise HTTPException(status_code=503, detail="数据库连接失败")
|
||||
|
||||
# 检查Redis连接
|
||||
try:
|
||||
r = redis.from_url(settings.REDIS_URL)
|
||||
r.ping()
|
||||
checks["redis"] = {
|
||||
"status": "healthy",
|
||||
"message": "Redis连接正常"
|
||||
}
|
||||
except Exception as e:
|
||||
checks["redis"] = {
|
||||
"status": "unhealthy",
|
||||
"message": f"Redis连接失败: {str(e)}"
|
||||
}
|
||||
raise HTTPException(status_code=503, detail="Redis连接失败")
|
||||
|
||||
# 检查文件存储
|
||||
try:
|
||||
import os
|
||||
os.makedirs(settings.UPLOAD_DIR, exist_ok=True)
|
||||
checks["storage"] = {
|
||||
"status": "healthy",
|
||||
"message": "文件存储正常"
|
||||
}
|
||||
except Exception as e:
|
||||
checks["storage"] = {
|
||||
"status": "unhealthy",
|
||||
"message": f"文件存储失败: {str(e)}"
|
||||
}
|
||||
raise HTTPException(status_code=503, detail="文件存储失败")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"data": {
|
||||
"status": "ready",
|
||||
"checks": checks,
|
||||
"timestamp": int(time.time())
|
||||
},
|
||||
"message": "所有服务已就绪"
|
||||
}
|
||||
Reference in New Issue
Block a user