oss_router.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. """
  2. OSS文件管理路由
  3. 提供文件上传、下载接口
  4. """
  5. from fastapi import APIRouter, UploadFile, File, Depends, HTTPException, Query, Request
  6. from fastapi.responses import StreamingResponse, Response
  7. from sqlalchemy.orm import Session
  8. from typing import Optional
  9. import io
  10. from urllib.parse import urlparse
  11. from app.database import get_db
  12. from app.services.oss_service import get_oss_service, OSSService
  13. from app.services.user_service import UserService
  14. from app.services.auth_service import AuthService
  15. from app.middleware.auth_log_middleware import get_debug_mode, get_default_admin_user
  16. from app.dependencies.auth import get_current_user
  17. from app.models.user import User
  18. router = APIRouter(prefix="/api/oss", tags=["OSS文件管理"])
  19. # 文件大小限制配置
  20. MAX_FILE_SIZE_DEFAULT = 20 * 1024 * 1024 # 20MB(默认)
  21. MAX_FILE_SIZE_TINGWU = 6 * 1024 * 1024 * 1024 # 6GB(通义听悟音视频)
  22. @router.post("/upload")
  23. async def upload_file(
  24. file: UploadFile = File(...),
  25. prefix: str = Query(default="uploads", description="存储路径前缀"),
  26. current_user: User = Depends(get_current_user),
  27. oss_service: OSSService = Depends(get_oss_service)
  28. ):
  29. """
  30. 上传文件到OSS
  31. - 需要用户认证
  32. - 文件大小限制:
  33. - 通义听悟(tingwu/):6GB
  34. - 其他路径:20MB
  35. - 返回OSS公开访问URL
  36. """
  37. content = await file.read()
  38. # 根据路径前缀选择不同的大小限制
  39. max_size = MAX_FILE_SIZE_TINGWU if prefix.startswith('tingwu') else MAX_FILE_SIZE_DEFAULT
  40. max_size_mb = max_size / (1024 * 1024)
  41. if len(content) > max_size:
  42. raise HTTPException(
  43. status_code=400,
  44. detail=f"文件大小超过{max_size_mb:.0f}MB限制"
  45. )
  46. try:
  47. url = oss_service.upload_file(content, prefix, file.filename)
  48. return {
  49. "code": 200,
  50. "message": "success",
  51. "data": {
  52. "url": url,
  53. "filename": file.filename,
  54. "size": len(content)
  55. }
  56. }
  57. except RuntimeError as e:
  58. raise HTTPException(status_code=500, detail=str(e))
  59. @router.get("/file")
  60. async def get_file(
  61. request: Request,
  62. url: Optional[str] = Query(default=None, description="OSS完整URL"),
  63. path: Optional[str] = Query(default=None, description="OSS文件路径(不含域名)"),
  64. token: Optional[str] = Query(default=None, description="可选:JWT令牌(查询参数)"),
  65. db: Session = Depends(get_db),
  66. oss_service: OSSService = Depends(get_oss_service)
  67. ):
  68. user = None
  69. if token:
  70. try:
  71. payload = AuthService.verify_token(token)
  72. user_id = payload.get("user_id")
  73. if user_id:
  74. user_service = UserService(db)
  75. user = user_service.get_user_by_id(user_id)
  76. except Exception:
  77. pass
  78. if not user and get_debug_mode():
  79. user = get_default_admin_user(db)
  80. if not user:
  81. raise HTTPException(status_code=401, detail="Authentication required")
  82. if not url and not path:
  83. raise HTTPException(status_code=400, detail="缺少参数")
  84. file_path = path
  85. if not file_path and url:
  86. parsed = urlparse(url)
  87. domain = oss_service.bucket_domain or ""
  88. expected_netloc = domain.replace("https://", "").replace("http://", "")
  89. if parsed.netloc != expected_netloc:
  90. raise HTTPException(status_code=400, detail="非法域名")
  91. file_path = parsed.path.lstrip("/")
  92. try:
  93. content, content_type = oss_service.download_file(file_path)
  94. return Response(
  95. content=content,
  96. media_type=content_type,
  97. headers={"Access-Control-Allow-Origin": "*"}
  98. )
  99. except FileNotFoundError:
  100. raise HTTPException(status_code=404, detail="文件不存在")
  101. except RuntimeError as e:
  102. raise HTTPException(status_code=500, detail=str(e))
  103. @router.get("/download")
  104. async def download_file(
  105. file_path: str = Query(..., description="OSS文件路径(不含域名)"),
  106. current_user: User = Depends(get_current_user),
  107. oss_service: OSSService = Depends(get_oss_service)
  108. ):
  109. """
  110. 下载OSS文件
  111. - 需要用户认证
  112. - 通过文件路径获取文件内容
  113. - 返回文件流
  114. """
  115. try:
  116. content, content_type = oss_service.download_file(file_path)
  117. return StreamingResponse(
  118. io.BytesIO(content),
  119. media_type=content_type,
  120. headers={"Content-Disposition": f"attachment; filename={file_path.split('/')[-1]}"}
  121. )
  122. except FileNotFoundError:
  123. raise HTTPException(status_code=404, detail="文件不存在")
  124. except RuntimeError as e:
  125. raise HTTPException(status_code=500, detail=str(e))
  126. @router.get("/signed-url")
  127. async def get_signed_url(
  128. file_path: str = Query(..., description="OSS文件路径(不含域名)"),
  129. expires: int = Query(default=3600, description="签名URL有效期(秒)"),
  130. current_user: User = Depends(get_current_user),
  131. oss_service: OSSService = Depends(get_oss_service)
  132. ):
  133. """
  134. 获取文件签名URL
  135. - 需要用户认证
  136. - 返回带签名的临时访问URL
  137. - 默认有效期1小时
  138. """
  139. try:
  140. url = oss_service.get_signed_url(file_path, expires)
  141. return {
  142. "code": 200,
  143. "message": "success",
  144. "data": {
  145. "url": url,
  146. "expires_in": expires
  147. }
  148. }
  149. except RuntimeError as e:
  150. raise HTTPException(status_code=500, detail=str(e))