endpoints.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. # app/api/endpoints.py
  2. from fastapi import APIRouter, UploadFile, File, Form, HTTPException, Query
  3. from fastapi.responses import StreamingResponse
  4. from typing import List, Optional
  5. import urllib.parse
  6. from app.models.schemas import FileItem, CreateFolderRequest
  7. from app.services.minio_service import minio_service
  8. from app.services.img_minio_service import img_minio_service
  9. from minio.error import S3Error
  10. router = APIRouter()
  11. @router.get("/files", response_model=List[FileItem])
  12. async def list_files(path: str = "/"):
  13. try:
  14. return minio_service.list_files(path)
  15. except S3Error as e:
  16. raise HTTPException(status_code=500, detail=str(e))
  17. # 在 app/api/endpoints.py 中修改 upload_file 接口
  18. @router.post("/upload")
  19. async def upload_file(
  20. file: UploadFile = File(...),
  21. path: str = Form(...)
  22. ):
  23. """
  24. path: 完整的文件路径 (例如: folder/subfolder/image.png)
  25. """
  26. try:
  27. # 调试信息
  28. print(f"上传文件: {file.filename}, 目标路径: {path}")
  29. # 确保路径不以 / 开头(MinIO 规范)
  30. if path.startswith('/'):
  31. path = path[1:]
  32. # 获取文件大小
  33. # 获取文件大小 (UploadFile 的 spool_max_size 后会存入磁盘或内存)
  34. # 为了稳妥起见,移动指针到最后获取大小,再移回
  35. file.file.seek(0, 2)
  36. size = file.file.tell()
  37. file.file.seek(0)
  38. minio_service.upload_file(
  39. file.file,
  40. path,
  41. file.content_type or "application/octet-stream",
  42. size
  43. )
  44. return {"message": "Upload successful", "filename": file.filename, "path": path}
  45. except Exception as e:
  46. print(f"上传错误: {str(e)}")
  47. raise HTTPException(status_code=500, detail=str(e))
  48. @router.post("/folder")
  49. async def create_folder(req: CreateFolderRequest):
  50. try:
  51. minio_service.create_folder(req.path)
  52. return {"message": "Folder created"}
  53. except S3Error as e:
  54. raise HTTPException(status_code=500, detail=str(e))
  55. @router.get("/download")
  56. async def download_file(path: str):
  57. try:
  58. data_stream = minio_service.get_file_stream(path)
  59. filename = path.split("/")[-1]
  60. quoted_filename = urllib.parse.quote(filename)
  61. return StreamingResponse(
  62. data_stream,
  63. media_type="application/octet-stream",
  64. headers={
  65. "Content-Disposition": f"attachment; filename*=UTF-8''{quoted_filename}"
  66. }
  67. )
  68. except Exception as e:
  69. raise HTTPException(status_code=404, detail="File not found or error")
  70. @router.delete("/delete")
  71. async def delete_item(path: str):
  72. try:
  73. is_dir = path.endswith("/")
  74. minio_service.delete_file(path, is_dir)
  75. return {"message": "Deleted successfully"}
  76. except S3Error as e:
  77. raise HTTPException(status_code=500, detail=str(e))
  78. @router.get("/preview")
  79. async def preview_file(path: str):
  80. try:
  81. url = minio_service.get_presigned_url(path)
  82. return {"url": url}
  83. except Exception as e:
  84. raise HTTPException(status_code=500, detail=str(e))
  85. @router.get("/storage-info")
  86. async def get_storage_info():
  87. try:
  88. return minio_service.get_storage_info()
  89. except S3Error as e:
  90. raise HTTPException(status_code=500, detail=str(e))
  91. @router.get("/img")
  92. async def preview_file(path: str):
  93. try:
  94. url = img_minio_service.get_presigned_url(path)
  95. return {"url": url}
  96. except Exception as e:
  97. raise HTTPException(status_code=500, detail=str(e))
  98. @router.post("/img/upload")
  99. async def upload_image(
  100. file: UploadFile = File(..., description="上传的图片文件"),
  101. path: Optional[str] = Form(..., description="指定存储路径,不指定则使用随机文件名")
  102. ):
  103. """
  104. 上传图片到MinIO存储
  105. """
  106. try:
  107. # 读取文件内容
  108. file_content = await file.read()
  109. # 生成对象名称:路径 + 文件名
  110. if path:
  111. # 清理路径格式,确保以/结尾
  112. if not path.endswith('/'):
  113. path = path + '/'
  114. object_name = f"{path}{file.filename}"
  115. else:
  116. # 直接使用文件名
  117. object_name = file.filename
  118. # 上传到MinIO
  119. stored_name = img_minio_service.upload_image_data(
  120. image_data=file_content,
  121. object_name=object_name,
  122. content_type=file.content_type
  123. )
  124. return {
  125. "message": "图片上传成功",
  126. "object_name": stored_name,
  127. "file_size": len(file_content),
  128. "content_type": file.content_type
  129. }
  130. except Exception as e:
  131. print(e)
  132. raise HTTPException(status_code=500, detail=f"上传失败: {str(e)}")
  133. @router.put("/img/rename")
  134. async def rename_image(
  135. old_path: str = Form(..., description="原文件路径"),
  136. new_path: str = Form(..., description="新文件路径"),
  137. overwrite: bool = Form(False, description="是否覆盖已存在的新路径文件")
  138. ):
  139. """
  140. 重命名或移动图片文件
  141. """
  142. try:
  143. # 检查原文件是否存在
  144. if not img_minio_service.image_exists(old_path):
  145. raise HTTPException(status_code=404, detail="原文件不存在")
  146. # 检查新文件是否已存在
  147. if not overwrite and img_minio_service.image_exists(new_path):
  148. raise HTTPException(
  149. status_code=400,
  150. detail="目标文件已存在,如需覆盖请设置 overwrite=true"
  151. )
  152. # 执行重命名操作
  153. success = img_minio_service.rename_image(old_path, new_path)
  154. if success:
  155. return {
  156. "message": "文件重命名成功",
  157. "old_path": old_path,
  158. "new_path": new_path
  159. }
  160. else:
  161. raise HTTPException(status_code=500, detail="文件重命名失败")
  162. except HTTPException:
  163. raise
  164. except Exception as e:
  165. print(e)
  166. raise HTTPException(status_code=500, detail=f"重命名失败: {str(e)}")