| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192 |
- """文件上传接口"""
- import os
- from typing import Optional
- from fastapi import APIRouter, Depends, UploadFile, File, HTTPException, Form
- from sqlalchemy.orm import Session
- from app.core.database import get_db
- from app.core.auth import get_current_user
- from app.core.response import success_response, error_response
- from app.core.storage import storage
- router = APIRouter(prefix="/api/upload", tags=["文件上传"])
- # 允许的文件类型
- ALLOWED_IMAGE_TYPES = {"image/jpeg", "image/png", "image/gif", "image/webp", "image/heic", "image/heif"}
- ALLOWED_DOCUMENT_TYPES = {
- "application/pdf",
- "application/msword",
- "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
- }
- # 文件大小限制 (字节)
- MAX_IMAGE_SIZE = 10 * 1024 * 1024 # 10MB
- MAX_DOCUMENT_SIZE = 20 * 1024 * 1024 # 20MB
- async def validate_file(file: UploadFile, max_size: int, allowed_types: set) -> bytes:
- """验证文件"""
- if file.content_type not in allowed_types:
- raise HTTPException(
- status_code=400,
- detail=f"不支持的文件类型: {file.content_type}"
- )
-
- content = b""
- chunk_size = 1024 * 1024
- while True:
- chunk = await file.read(chunk_size)
- if not chunk:
- break
- content += chunk
- if len(content) > max_size:
- raise HTTPException(status_code=400, detail="文件大小超过限制")
-
- await file.seek(0)
- return content
- @router.post("/image", response_model=dict)
- async def upload_image(
- file: UploadFile = File(...),
- folder: str = Form("images"),
- current_user: dict = Depends(get_current_user)
- ):
- """上传图片"""
- try:
- content = await validate_file(file, MAX_IMAGE_SIZE, ALLOWED_IMAGE_TYPES)
- original_filename = file.filename or "image.jpg"
- url = storage.upload_file(content, original_filename, folder)
- return success_response({
- "url": url,
- "filename": original_filename,
- "contentType": file.content_type,
- "size": len(content)
- })
- except HTTPException:
- raise
- except Exception as e:
- return error_response(500, f"上传失败: {str(e)}")
- @router.post("/images", response_model=dict)
- async def upload_images(
- files: list[UploadFile] = File(...),
- folder: str = Form("images"),
- current_user: dict = Depends(get_current_user)
- ):
- """批量上传图片"""
- if len(files) > 9:
- return error_response(400, "最多支持同时上传9张图片")
-
- results = []
- errors = []
-
- for i, file in enumerate(files):
- try:
- content = await validate_file(file, MAX_IMAGE_SIZE, ALLOWED_IMAGE_TYPES)
- original_filename = file.filename or f"image_{i}.jpg"
- url = storage.upload_file(content, original_filename, folder)
- results.append({
- "url": url,
- "filename": original_filename,
- "contentType": file.content_type,
- "size": len(content),
- "index": i
- })
- except Exception as e:
- errors.append({"index": i, "filename": file.filename, "error": str(e)})
-
- return success_response({
- "success": results,
- "errors": errors,
- "totalSuccess": len(results),
- "totalError": len(errors)
- })
- @router.post("/document", response_model=dict)
- async def upload_document(
- file: UploadFile = File(...),
- folder: str = Form("documents"),
- current_user: dict = Depends(get_current_user)
- ):
- """上传文档"""
- try:
- content = await validate_file(file, MAX_DOCUMENT_SIZE, ALLOWED_DOCUMENT_TYPES)
- original_filename = file.filename or "document.pdf"
- url = storage.upload_file(content, original_filename, folder)
- return success_response({
- "url": url,
- "filename": original_filename,
- "contentType": file.content_type,
- "size": len(content)
- })
- except HTTPException:
- raise
- except Exception as e:
- return error_response(500, f"上传失败: {str(e)}")
- @router.post("/base64", response_model=dict)
- async def upload_base64(
- data: dict,
- current_user: dict = Depends(get_current_user)
- ):
- """上传 Base64 编码的图片"""
- try:
- base64_data = data.get("base64")
- filename = data.get("filename", "image.png")
- folder = data.get("folder", "images")
-
- if not base64_data:
- return error_response(400, "base64数据不能为空")
-
- url = storage.upload_base64(base64_data, filename, folder)
- return success_response({"url": url, "filename": filename})
- except Exception as e:
- return error_response(500, f"上传失败: {str(e)}")
- @router.delete("/file", response_model=dict)
- async def delete_file(
- url: str,
- current_user: dict = Depends(get_current_user)
- ):
- """删除文件"""
- try:
- success = storage.delete_file(url)
- if success:
- return success_response({"message": "删除成功"})
- return error_response(400, "文件不存在或删除失败")
- except Exception as e:
- return error_response(500, f"删除失败: {str(e)}")
- @router.get("/info", response_model=dict)
- async def get_storage_info(
- current_user: dict = Depends(get_current_user)
- ):
- """获取存储配置信息"""
- from app.core.config import settings
-
- info = {
- "storageType": settings.STORAGE_TYPE,
- "maxImageSize": MAX_IMAGE_SIZE,
- "maxDocumentSize": MAX_DOCUMENT_SIZE,
- "allowedImageTypes": list(ALLOWED_IMAGE_TYPES),
- "allowedDocumentTypes": list(ALLOWED_DOCUMENT_TYPES)
- }
-
- if settings.STORAGE_TYPE == "local":
- info["baseUrl"] = settings.LOCAL_STORAGE_URL
- elif settings.STORAGE_TYPE == "qiniu":
- info["domain"] = settings.QINIU_DOMAIN
- elif settings.STORAGE_TYPE == "oss":
- info["domain"] = settings.OSS_DOMAIN
- elif settings.STORAGE_TYPE == "minio":
- scheme = "https" if settings.MINIO_SECURE else "http"
- info["domain"] = settings.MINIO_BASE_URL or f"{scheme}://{settings.MINIO_ENDPOINT}/{settings.MINIO_BUCKET}"
-
- return success_response(info)
|