要点前置:
文件上传必须用
multipart/form-data,不能用 application/json开发环境通过
MEDIA_ROOT / MEDIA_URL托管上传文件;生产环境交给 Nginx 代理 media,不要让 Django 直接提供静态文件下载DRF 解析文件依赖
MultiPartParser下载使用 Django 原生
FileResponse,as_attachment=True触发浏览器下载弹窗Django
1. settings.py 配置(新增媒体文件配置)
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'fileserver', # 我们本次的app
]
# ========= 文件上传核心配置 =========
MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
# 可选:限制上传大小
DATA_UPLOAD_MAX_MEMORY_SIZE = 52428800 # 50MB
FILE_UPLOAD_MAX_MEMORY_SIZE = 10485760 # 10MB,超过会写到临时磁盘
在项目根目录手动新建
media文件夹
2. 创建 app + 模型 models.py
python manage.py startapp fileserver
fileserver/models.py
from django.db import models
class UploadFile(models.Model):
name = models.CharField(max_length=255, verbose_name="文件原名")
file = models.FileField(upload_to="uploads/%Y/%m/%d/", verbose_name="文件")
size = models.BigIntegerField(default=0, verbose_name="文件大小byte")
mime_type = models.CharField(max_length=128, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
upload_to="uploads/%Y/%m/%d/":自动按年月日分目录存放文件,避免单目录文件太多数据库只存文件相对路径,二进制文件保存在磁盘 media 目录
执行迁移:
python manage.py makemigrations
python manage.py migrate
3. 序列化器 serializers.py(新建)
fileserver/serializers.py
from rest_framework import serializers
from .models import UploadFile
class UploadFileSerializer(serializers.ModelSerializer):
file_url = serializers.SerializerMethodField(read_only=True)
class Meta:
model = UploadFile
fields = ["id", "name", "file", "file_url", "size", "mime_type", "created_at"]
read_only_fields = ["id", "size", "mime_type", "created_at"]
def get_file_url(self, obj):
# 返回文件完整访问链接
request = self.context.get("request")
if obj.file and request:
return request.build_absolute_uri(obj.file.url)
return None
def create(self, validated_data):
file_obj = validated_data["file"]
validated_data["size"] = file_obj.size
validated_data["mime_type"] = file_obj.content_type
return super().create(validated_data)
4. 视图 views.py(上传接口 + 单独下载接口)
fileserver/views.py
from rest_framework import viewsets, parsers, status
from rest_framework.response import Response
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from django.http import FileResponse, Http404
import os
from .models import UploadFile
from .serializers import UploadFileSerializer
class UploadFileViewSet(viewsets.ModelViewSet):
queryset = UploadFile.objects.all().order_by("-created_at")
serializer_class = UploadFileSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
# 必须开启MultiPartParser,才能接收文件
parser_classes = [parsers.MultiPartParser, parsers.FormParser]
@action(detail=True, methods=["get"], url_path="download")
def download(self, request, pk=None):
""" 下载接口:GET /api/files/{id}/download/ """
obj = self.get_object()
file_path = obj.file.path
if not os.path.exists(file_path):
raise Http404("文件不存在")
# as_attachment=True 弹出下载;False则浏览器在线预览(图片/pdf)
return FileResponse(
open(file_path, "rb"),
as_attachment=True,
filename=obj.name
)
说明:
普通 list/create/delete 由 ModelViewSet 自带(上传就是 POST)
自定义 action
download单独处理文件下载
5. 路由 urls.py
fileserver/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import UploadFileViewSet
router = DefaultRouter()
router.register(r'files', UploadFileViewSet)
urlpatterns = [
path("", include(router.urls)),
]
项目根 urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('fileserver.urls')),
path('api-auth/', include('rest_framework.urls')),
]
# 开发环境才启用media路由!生产注释掉,交给Nginx
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
6. 启动 & curl 测试
python manage.py runserver
✅ 上传文件(POST,multipart/form-data)
curl -X POST http://127.0.0.1:8000/api/files/ \
-F "name=测试文档.pdf" \
-F "file=@/Users/xxx/test.pdf"
✅ 获取文件列表
curl http://127.0.0.1:8000/api/files/
✅ 下载文件(GET)
curl http://127.0.0.1:8000/api/files/1/download/ -O
✅ 删除文件
curl -X DELETE http://127.0.0.1:8000/api/files/1/
7. 前端 Vue/axios 上传注意点
const formData = new FormData()
formData.append("name", file.name)
formData.append("file", file.raw)
// headers不要手动设置Content-Type!axios会自动生成boundary
axios.post("/api/files/", formData)
8. 生产环境重要注意事项
关闭 DEBUG,删掉 static (settings.MEDIA...),Django 不能直接对外提供大文件
Nginx 配置 media 代理示例:
location /media/ {
alias /path/to/your/project/media/;
expires 7d;
}
安全风险:不要直接信任上传文件后缀;增加后缀白名单校验(防止上传 php/exe 等恶意文件)
大文件:上面是一次性读入;超大文件建议前端分片上传 + 后端分片合并
存储扩展:如果文件量大,可以替换为对象存储(MinIO、OSS),重写存储后端