有机化学、化学信息学、生物化学、生物信息学、机器学习、深度学习、药物设计、网站建设关注我!Bilibili
对于大文件的上传,分块、断点续传功能是必须的,借助drf-tus工具可以实现
pip install drf-tus
pip install redis
pip install django-redis
pip install celery
pip install django-celery-beat
pip install django-celery-results
pip install django-cors-headers
直接把对应的redis和celery也安装上
django默认不允许请求头携带的这些内容,需要放行
把下面的内容写入到base.py内(如果没有区分生产、开发,就写在settings.py内)
from corsheaders.defaults import default_headers
# 改为True即为可跨域设置Cookie
CORS_ALLOW_CREDENTIALS = True
# 扩展允许tus上传
CORS_ALLOW_HEADERS = list(default_headers) + [
'Tus-Resumable',
'Upload-Length',
'Upload-Offset',
'Upload-Metadata',
]
CORS_EXPOSE_HEADERS = [
'Location',
'Upload-Offset',
'Upload-Length',
'Tus-Resumable',
'Content-Disposition',
'Content-Length'
]
# 配置允许的请求方式
CORS_ALLOW_METHODS = [
'DELETE',
'GET',
'OPTIONS',
'PATCH',
'POST',
'PUT',
'HEAD',
]
增加需要的包:除了自己注册的app,还需要把corsheaders、celery、rest_framework_tus等加进去,看清楚顺序,不要颠倒,否则会出现问题
INSTALLED_APPS = [
# ——— Django 内置 Apps ———
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# ——— 自定义 App(必须在 drf-tus 之前) ———
'appchemnote',
'appuser',
# ——— 第三方库 ———
'corsheaders',
'celery',
'django_celery_beat',
'django_celery_results',
'django.contrib.postgres',
# ——— DRF & JWT & tus ———
'rest_framework',
'rest_framework.authtoken',
'rest_framework_simplejwt',
'rest_framework_simplejwt.token_blacklist',
'rest_framework_tus', # <— 一定要在此之前已经加载了 appchemnote
]
其实是后续配置的,感觉tus上传应该是依赖celery和redis的,是有任务ID的,这里一起配置吧:
# 设置redi后端缓存
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
}
}
}
CELERY_BROKER_URL = 'redis://127.0.0.1:6379/0' # 消息代理地址
CELERY_RESULT_BACKEND = 'django-db' # 使用 Django 数据库作为结果后端 安装完成后记得迁移一下
CELERY_WORKER_CONCURRENCY = 2 # Worker并发数量,一般默认CPU核数,可以不设置
CELERYD_FORCE_EXECV = True # 非常重要,有些情况下可以防止死锁
CELERY_WORKER_MAX_TASKS_PER_CHILD = 100 # celery 的 worker 执行多少个任务后进行重启操作
CELERY_TASK_TRACK_STARTED = True # 设置任务更新状态:启用后,任务开始执行时,状态会从 "PENDING" 更新为 "STARTED"
# CELERY_TASK_RESULT_EXPIRES = 60 * 60 * 24 # 任务结果过期时间,秒 设置为0表示不过期
CELERY_TASK_RESULT_EXPIRES = 0 # 任务结果过期时间,秒 设置为0表示不过期
CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True # 断开重新连接
CELERY_BROKER_CONNECTION_MAX_RETRIES = 10 # 默认重新连接 100 次
CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TIMEZONE = 'Asia/Shanghai' # 可选:设置时区
详细的配置参数可以后续查询资料,先这样
drf-tus提供了配置,
from rest_framework_tus import settings
这个文件内可以查看其他的配置,记得千万不要在settings文件内导入这个设置,否则会覆盖设置,导致严重问题
GB = 1024**3
BIGFILE_UPLOAD_MAX_MEMORY_SIZE = 1 * GB # 大文件最高允许上传1G文件
REST_FRAMEWORK_TUS = {
"UPLOAD_MODEL": "appchemnote.LargeUpload", # 配置模型,写入user信息
"MAX_FILE_SIZE": BIGFILE_UPLOAD_MAX_MEMORY_SIZE
}
这里的LargeUpload只是为了定义存放的时候,把用户和其他信息单独写入数据库:
这是对应的models.py内容,这里会建立关联信息,
class LargeUpload(BaseUpload):
'重写的tus上传模型,用来关联用户信息'
user = models.ForeignKey(
User,
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="tus_uploads",
)
# 大块上传完毕后,需要把新的url存储,使用handlers返回给前端
resource_id = models.IntegerField(null=True, blank=True)
resource_new_url = models.URLField(null=True, blank=True, max_length=1024)
看数据库是使用外键,重新挂了一些其他内容进去,之前尝试过重写模型,但是由于无法修改子类的内容,所以放弃
按照位置把TusMiddleware的中间件,配置上
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', # 安全
'corsheaders.middleware.CorsMiddleware', # cors
'django.contrib.sessions.middleware.SessionMiddleware', # 会话
'rest_framework_tus.middleware.TusMiddleware', # 分块上传
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
在上传完成后,需要把最后一次的patch请求从204改为200,告诉前端,文件上传完成了,因此需要自定义
在app内新建tus_handlers.py文件:
from rest_framework_tus.views import UploadViewSet
from rest_framework.response import Response
from rest_framework import status
class CustomUploadViewSet(UploadViewSet):
http_method_names = ['post', 'patch', 'head', 'options']
# 自定义 head 方法
def head(self, request, *args, **kwargs):
try:
# 获取上传对象
upload = self.get_object()
# 创建 204 No Content 响应
response = Response(status=status.HTTP_204_NO_CONTENT)
# 设置 tus 协议所需的头部信息
response['Upload-Offset'] = upload.upload_offset
response['Upload-Length'] = upload.upload_length
response['Tus-Resumable'] = '1.0.0' # tus 协议版本
return response
except Exception:
# 如果上传对象不存在,返回 404
return Response(status=status.HTTP_404_NOT_FOUND)
def finalize_response(self, request, response, *args, **kwargs):
response = super().finalize_response(request, response, *args, **kwargs)
if request.method.upper() == "PATCH" and response.status_code == 204:
upload = self.get_object()
print(upload.resource_new_url)
if upload.upload_offset == upload.upload_length:
response.data = {
"message": "大文件上传完成!",
"result":{
"filename": upload.filename,
"media_id": upload.resource_id,
"media_url": upload.resource_new_url,
},
}
response.status_code = 200
return response
在主路由文件内。配置如下:
这里无论如何配置,都会导致patch的请求发生变化,
测试完成后,发现只能通过这种方式配置大文件上传的url路径:
from appchemnote.tus_handlers import CustomUploadViewSet
from rest_framework.routers import DefaultRouter
# 挂载注册api的router
api_router = DefaultRouter()
api_router.register("files", CustomUploadViewSet, basename="upload")
tus_urlpatterns = [
path("", include((api_router.urls, "api"), namespace="api")),
]
urlpatterns = [
path('dgapi/admin/', admin.site.urls),
# 分块上传
path("dgapi/tus/", include((tus_urlpatterns, "rest_framework_tus"), namespace="rest_framework_tus")),
# 其他路由文件
path('dgapi/chemnote/', include('appchemnote.urls')),
]
# 仅在开发环境中,才允许直接指向静态资源
if settings.DEBUG:
# 仅在 DEBUG=True(开发)时,用 Django serve /media/ 和 /static/
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += staticfiles_urlpatterns()
后面的设置是为了防止用户,直接根据路径,拿到服务器的文件资源,这里做一下判断就行
MEDIA_ROOT表示上传的资源等,不是头像、图片,这类一般放在static内,是网站的静态资源
这里其实对于后端来说,tus的接收逻辑就配置完了
在传递完成后,会把最后一次请求返回的状态更改为200,把文件路径也存储到数据库内了
在需要使用信号的app内创建signal.py文件,用来写限制逻辑
举例:
我现在的app名称为:appchemnote,我需要在这个文件夹内新建signal.py文件
然后在appchemnote/app.py内,把这个信号注册:
from django.apps import AppConfig
class AppchemnoteConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'appchemnote'
def ready(self):
# 导入 signals.py,让 @receiver 注册钩子
import appchemnote.signal
需求1:只有登录的用户才允许上传大文件,而且有最大使用空间的限制
from rest_framework_tus import signals
from rest_framework.exceptions import PermissionDenied
# 使用装饰器signals.receiving表示在接收文件的时候
@receiver(signals.receiving)
def tus_upload_receiving(sender, instance, **kwargs):
upload = instance
user = getattr(upload, "user")
print('user-----', user)
if not user:
raise PermissionDenied("获取用户信息失败")
# if not (user.is_vip or user.is_admin):
# raise PermissionDenied("只有 VIP 用户或管理员才能上传文件资源。")
# 查询还剩下多少,如果超出就不让上传了
qs_resource = NoteResource.objects.filter(
user=user,
is_visible=True,
is_temp=False,
)
# ✅ 资源总大小(单位:字节)
resource_memory = qs_resource.aggregate(
total=Coalesce(Sum('file_size'), 0)
)['total']
max_memory = user.max_memory
print('已经使用:', resource_memory)
print('一共:', max_memory)
if resource_memory > max_memory:
raise PermissionDenied("超出存储限制了!")
查看了一下源码,有这4个阶段可以控制:
from django.dispatch import Signal
receiving = Signal()
received = Signal()
saving = Signal()
saved = Signal()
finished = Signal()
可以根据需要,自行研究
比如你想在上传完成后,移动到新的文件夹内,或者把文件大小等信息写入、压缩等,就可以使用finished进行操作
没有推荐的文章...
没有对应的文章...