资讯动态

Django私有云盘实战:从文件存储到权限模型的完整实现

发布时间:2026/9/13 16:36:42 来源:尧图企业网站定制
简介基于Django框架的私有云盘项目是一份面向计算机专业学生毕业设计或课程设计的完整Web应用源码用于解决个人文件云端存储与管理需求。压缩包共包含十八个文件以十二个Python源代码文件为主配合三个HTML模板、两个XML配置和一个项目管理文件整体体积约为八KB便于快速下载与部署。项目中可学习到Django的MVT架构、用户认证、数据库建模、文件上传下载及前端模板渲染等核心知识点覆盖了从后端逻辑到页面交互的完整开发流程。压缩包内部目录结构清晰包含Django标准的入口脚本、项目配置、URL路由、数据模型与视图处理模块以及登录、注册、首页等前端模板适合按模块逐步拆解学习。截至目前该资源已有四百一十四人进行学习与浏览能够帮助初学者将Django理论应用于实际项目提升Web开发实战能力。1. Django私有云盘存储易做权限模型的坑才是大头自建一个私有云盘绝大多数人第一步想的是“文件存哪”我见过不少项目连Django的MEDIA_ROOT都没想清楚就开始写Model。真正让私有云盘区别于普通文件上传功能的是文件归属、访问控制和可追溯的下载行为。标题里的“私有”二字落点不在存储介质而在权限模型谁能传、谁能看、谁能删。这篇文章按照“存储设计 → 上传下载 → 鉴权删除 → 页面与部署 → 秒传优化”的顺序把一套可复现的Django私有云盘铺开。适合两类人一是要给团队内部搭一个轻量文件共享服务、但不想引入全套对象存储的开发者二是准备Django相关岗位面试、需要把“文件处理”讲成体系的人。2. Django私有云盘存储层MEDIA_ROOT、File模型与上传接口2.1 先搭项目骨架创建Django项目和drive应用常见做法是先建虚拟环境再创建项目和应用命令不长但顺序别乱。python3 -m venv venv source venv/bin/activate pip install django django-admin startproject cloud_drive cd cloud_drive python manage.py startapp drive把drive加进settings.py的INSTALLED_APPS再在项目根urls.py里用include把drive的URL挂载进来这样后续的文件列表、上传下载都有了入口。漏掉INSTALLED_APPS会导致迁移表建不出来漏掉include会直接报NoReverseMatch。做完这两步建议立刻执行python manage.py check确认配置没有语法问题。参数说明venv是Python虚拟环境把Django装进项目自己的环境里不污染系统Pythonstartproject生成项目配置骨架settings.py和根urls.py都在这里startapp生成业务模块私有云盘的文件表、视图、URL都放drive这个app下后面加权限、任务队列也不会让项目变成单文件泥潭。2.2 MEDIA_ROOT与MEDIA_URL私有云盘的文件存放边界settings.py里加这两行MEDIA_URL /media/ MEDIA_ROOT BASE_DIR / mediaMEDIA_URL是浏览器访问文件时的URL前缀MEDIA_ROOT是物理磁盘上的真实目录。Django不会自动把上传文件塞进MEDIA_ROOT上传逻辑的落盘动作要自己写开发环境要用django.views.static.serve做临时路由生产环境交给nginx。有个常见误用有人把MEDIA_ROOT直接设成项目根目录静态文件、上传文件混在一起nginx部署时目录权限很难收紧。私有云盘建议按用户隔离目录在model里生成相对路径时带上用户IDdef user_dir(instance, filename): return fusers/{instance.owner_id}/{filename}这样同一用户上传的文件落在同一目录后续做配额统计、打包下载都省事。注意用户ID不要直接拼到可公开访问的URL上除非配合后面的权限查询逻辑否则任何人猜到路径都能访问别人的文件。2.3 File模型设计元数据与物理文件分离核心表是DriveFile字段设计要覆盖归属、文件句柄、大小和摘要。FileField可以复用Django的Storage体系将来想把本地存储换成对象存储改动集中在配置层而不是业务层。class DriveFile(models.Model): owner models.ForeignKey(auth.User, on_deletemodels.CASCADE, related_namedrive_files) name models.CharField(max_length255) file models.FileField(upload_touser_dir, max_length512) size models.BigIntegerField(default0) md5 models.CharField(max_length32, blankTrue) created_at models.DateTimeField(auto_now_addTrue) class Meta: ordering [-created_at]字段作用备注owner文件归属用户外键级联删除用户注销时文件记录一并清理file物理文件句柄upload_to指向user_dir按用户分目录size文件字节数BigIntegerField避免超出int上限md5文件摘要后续做秒传和文件去重created_at上传时间倒序排列列表数据量大后建议加索引md5字段现在先留空第5章的秒传功能会用它做查重。created_at要加db_index私有云盘文件列表默认按时间倒序文件一多没索引会明显变慢。2.4 上传视图request.FILES与文件校验上传视图除了保存文件还要做三件事限制文件大小、计算文件摘要、防重复提交。import hashlib from django.shortcuts import render, redirect from .models import DriveFile MAX_SIZE 100 * 1024 * 1024 # 100MB def upload_file(request): if request.method POST and request.FILES.get(file): f request.FILES[file] if f.size MAX_SIZE: return render(request, upload.html, {error: 文件超过100MB限额}) md5 hashlib.md5() for chunk in f.chunks(): md5.update(chunk) f.seek(0) exists DriveFile.objects.filter(ownerrequest.user, md5md5.hexdigest()).exists() if exists: return redirect(drive:file_list) DriveFile.objects.create( ownerrequest.user, namef.name, filef, sizef.size, md5md5.hexdigest() ) return redirect(drive:file_list) return render(request, upload.html)说明用chunks()逐块读取而不是read()大文件不会一次性打满内存md5计算在保存前完成为秒传铺路f.seek(0)必须加chunks()遍历后指针停在文件末尾不回到开头直接传给FileField落盘的文件会是空文件。已存在相同md5且归属当前用户就直接返回避免同一用户重复上传相同内容。提示项目要接MySQL时宝塔或Windows环境经常报django install mysqlclient失败可以用pip install pymysql然后在项目包的__init__.py里执行pymysql.install_as_MySQLdb()或者编译安装mysqlclient依赖包。驱动层不过后面migrate会在连接数据库时卡住。3. Django私有云盘下载与鉴权要流式响应更要按用户过滤3.1 下载接口FileResponse与StreamingHttpResponse的参数差异私有云盘下载不是简单return一个文件要给浏览器正确的Content-Type和下载名。两个常用切入点from urllib.parse import quote from django.http import FileResponse, HttpResponseNotFound from .models import DriveFile def download_file(request, pk): f DriveFile.objects.filter(ownerrequest.user, pkpk).first() if not f: return HttpResponseNotFound(文件不存在) resp FileResponse(f.file.open(rb), as_attachmentTrue) resp[Content-Disposition] fattachment; filename*UTF-8{quote(f.name)} return resp如果用StreamingHttpResponse差别在于要手动管文件句柄和响应头from django.http import StreamingHttpResponse resp StreamingHttpResponse( streaming_contentf.file.open(rb), content_typeapplication/octet-stream, headers{Content-Disposition: fattachment; filename*UTF-8{quote(f.name)}} )两段代码效果接近但FileResponse内部做了更多处理自动关闭文件句柄、支持单次读取大小控制、部署时能配合nginx的sendfile优化StreamingHttpResponse适合手动控制生成过程的场景比如把多个文件拼成一个压缩包往外推。私有云盘单文件下载用FileResponse就够了。参数FileResponseStreamingHttpResponse适用场景单文件下载动态生成内容、拼包推送content_type自动推断可覆盖需要显式传入Content-Disposition推荐设置必须手动设置到headers文件句柄关闭自动关闭需要手动管理底层优化支持sendfile逐块迭代传输参数说明as_attachmentTrue告诉浏览器这是下载而不是内联打开filename*UTF-8是RFC 5987编码直接拼原始中文名在Chrome下会乱码quote(f.name)来自urllib.parse。没加这一行用户下载中文名文件会得到一串下划线。3.2 私有云盘的私有归属校验必须在查询层完成前面下载接口里最关键的是filter(ownerrequest.user, pkpk)。图省事的人会写成DriveFile.objects.get(pkpk)然后在视图里if判断owner隐患在于一旦将来有接口复用这段代码、有人传了不带owner的查询条件权限就漏了。把owner条件写进QuerySet无论查询条件怎么组合都漏不过过滤。这与Django官方FAQ里强调的“用查询条件做权限而不是在视图里if”一致。第2章的user_dir同样不起鉴权作用nginx可以直接按路径访问media下的文件所以下载必须走视图接口media目录不要对外裸奔。生产环境我会在nginx里把/media/设为internal浏览器直连media路径返回404文件只能通过Django下载视图流出这样下载行为才能被记录和审计。私有云盘要能追溯“谁在什么时候下载了什么”可以加一张操作日志表class AccessLog(models.Model): file models.ForeignKey(DriveFile, on_deletemodels.CASCADE) user models.ForeignKey(auth.User, on_deletemodels.CASCADE) action models.CharField(max_length16) created_at models.DateTimeField(auto_now_addTrue)下载时写一条actiondownload的日志后续排查问题直接SQL查这张表就行。3.3 删除对象用QuerySet.delete()与FileField.delete()配合删除文件要同时删物理文件和数据库记录。如果先os.remove物理文件再删DB记录物理文件删除失败会导致记录残留反过来的次序会让物理文件变成孤儿文件。常见做法是def delete_file(request, pk): f DriveFile.objects.filter(ownerrequest.user, pkpk).first() if not f: return HttpResponseNotFound(文件不存在) f.file.delete(saveFalse) # 删除物理文件 f.delete() # 删除数据库记录 return redirect(drive:file_list)f.file.delete(saveFalse)是FileField自带方法它删除物理文件但不保存模型然后f.delete()删除记录。两步都可能失败讲究一点就try/except包起来删除失败也把记录删掉把物理路径交给后台定时任务清理。Django面试里常问的“执行查询-删除对象”区别就在这QuerySet.delete()返回删除条数且不走Model的delete()方法Model.delete()删除单个实例云盘场景必须把两个删除动作拆开控制直接DriveFile.objects.filter(pkpk).delete()只会删记录物理文件会一直留在磁盘上。第5章秒传功能上线后这段删除逻辑还要再加一层引用计数。4. Django私有云盘前端与部署预览、后台与宝塔uwsgi配置4.1 文件列表页模板渲染与URL路由私有云盘的首页就是“我的文件”列表。不引入Vue前端来增加部署体积直接用Django模板渲染就够了。URL和视图from django.urls import path from django.contrib.auth.decorators import login_required from . import views urlpatterns [ path(, login_required(views.file_list), namefile_list), path(upload/, login_required(views.upload_file), nameupload), path(download/int:pk/, login_required(views.download_file), namedownload), path(preview/int:pk/, login_required(views.preview_file), namepreview), path(delete/int:pk/, login_required(views.delete_file), namedelete), ]模板里用{% url drive:download file.pk %}生成下载链接。需要调试URL时可以用reverse和resolve两个函数反向验证python manage.py shell里跑from django.urls import reverse, resolve; print(reverse(drive:download, args[1]))就能看到完整路径resolve则从路径反解出视图函数这两个工具排查路由错位非常好用。列表查询要分页别在模板里用Python切片。用Django的Paginator每页50条文件一多页面加载也不会卡。4.2 在线预览Content-Type映射与浏览器的能力边界用户点一个PDF不想下载而是直接预览。常见做法是先判断扩展名允许预览的扩展名才内联打开from pathlib import Path from django.http import FileResponse, HttpResponseNotFound from urllib.parse import quote PREVIEW_TYPES { .pdf: application/pdf, .jpg: image/jpeg, .png: image/png, .txt: text/plain; charsetutf-8, .mp4: video/mp4, } def preview_file(request, pk): f DriveFile.objects.filter(ownerrequest.user, pkpk).first() if not f: return HttpResponseNotFound() ext Path(f.name).suffix.lower() content_type PREVIEW_TYPES.get(ext, application/octet-stream) resp FileResponse(f.file.open(rb), content_typecontent_type) resp[Content-Disposition] finline; filename*UTF-8{quote(f.name)} return respinline会让浏览器直接渲染PDF和图片attachment强制下载不认识的扩展名一律octet-stream避免浏览器把html等恶意内容直接执行。视频预览对大文件不友好mp4要配合nginx的Range支持才能拖动进度条宝塔的nginx默认支持但如果你用Django自带的runserver预览视频拖动会卡那是开发服务器不支持Range请求不是代码问题。4.3 Django重定向传递数据用messages反馈上传删除结果上传完成后直接重定向模板里怎么提示“上传成功”Django的messages框架是官方做法from django.contrib import messages messages.success(request, f{name} 上传成功) return redirect(drive:file_list)file_list模板里循环{% for message in messages %}{{ message }}{% endfor %}即可。常见误用是把提示塞在session里手动get、手动delDjango的messages在请求-响应周期内自动清理省事而且不会污染session结构。上传失败、删除成功、超出限额这些场景都能用同一个机制反馈。4.4 admin后台也能管文件ModelAdmin的最小美化私有云盘的普通用户走前端管理员还需要一个后台查文件、查日志。Django自带admin加几行配置就够用from django.contrib import admin from .models import DriveFile, AccessLog admin.register(DriveFile) class DriveFileAdmin(admin.ModelAdmin): list_display (name, owner, size, md5, created_at) list_filter (created_at,) search_fields (name, owner__username) admin.register(AccessLog) class AccessLogAdmin(admin.ModelAdmin): list_display (file, user, action, created_at) list_filter (action, created_at)list_display让表格显示关键列search_fields支持按文件名和用户名搜索list_filter按时间筛选。管理员处理“用户说文件丢了”的工单时后台能直接看到操作记录。admin界面美化不止list_display还有django-jet、django-admin-interface这类皮肤包但私有云盘场景优先保功能换肤会在升级Django版本时增加兼容成本。4.5 宝塔部署Django私有云盘uwsgi、nginx与media目录保护本地开发跑runserver没问题私有云盘部署给同事用就得放到服务器。宝塔部署Django的常见做法是Python项目管理器建环境uwsgi启动项目站点配置nginx转发。一个能跑起来的uwsgi.ini[uwsgi] chdir /www/wwwroot/cloud_drive module cloud_drive.wsgi:application master true processes 4 threads 2 socket 127.0.0.1:8001 chmod-socket 664 vacuum true die-on-term truenginx关键location配置location /media/ { internal; alias /www/wwwroot/cloud_drive/media/; } location /static/ { alias /www/wwwroot/cloud_drive/static/; } location / { include uwsgi_params; uwsgi_pass 127.0.0.1:8001; uwsgi_read_timeout 300; }/media/的internal表示浏览器直连https://域名/media/某个文件会返回404文件下载请求先进Django做权限校验Django用FileResponse把内容返回给nginx再转发给用户物理文件不裸奔在公网路径下。这个细节漏掉私有云盘就变成了公开云盘。配置项值说明socket127.0.0.1:8001只监听本机不要暴露公网端口processes / threads4 / 2按CPU核数调整uwsgi_read_timeout300大文件下载需要放宽admin后台能正常显示样式先执行python manage.py collectstatic --noinput再在settings.py里配好STATIC_ROOT。每次改完uwsgi配置用touch /www/wwwroot/cloud_drive/uwsgi.ini触发优雅重载不要直接kill进程。5. Django私有云盘的秒传用MD5把重复上传挡在门外这类私有云盘项目里最容易被面试官追问、也最影响存储成本的功能是秒传。文件一旦多起来团队里同一份设计稿被上传四五次很常见。秒传的思路是先比较摘要再决定要不要传内容。提示下面的方案是逻辑秒传。真正的大文件秒级体验需要前端先把文件读完算出MD5POST到后端查重通过后后端只创建记录不传输文件内容。前端算MD5用FileReader加SparkMD5100MB以内体验尚可更大文件建议用Web Worker并行分片计算。后端查重接口from django.http import JsonResponse def check_exist(request): if request.method POST: md5 request.POST.get(md5, ) if not md5: return JsonResponse({exists: False}) existed DriveFile.objects.filter(md5md5).first() if existed: DriveFile.objects.create( ownerrequest.user, namerequest.POST.get(name, existed.name), fileexisted.file, sizeexisted.size, md5md5, ) return JsonResponse({exists: True, reused: True}) return JsonResponse({exists: False})这里给当前用户创建一条自己的DriveFile记录file直接指向已存在记录的物理文件。前端拿到exists: true后不再走上传接口用户体感就是瞬间完成。但删除逻辑必须跟着改直接调f.file.delete(saveFalse)会把另一个用户共享的文件也删掉。删除前先数引用def delete_file(request, pk): f DriveFile.objects.filter(ownerrequest.user, pkpk).first() if not f: return HttpResponseNotFound() refs DriveFile.objects.filter(md5f.md5).exclude(pkf.pk).exists() path f.file.path f.delete() if not refs and os.path.exists(path): os.remove(path) return redirect(drive:file_list)提示这段逻辑比第3章的版本多了一步引用计数。只有没有任何记录再指向该物理文件时才允许删文件否则只删记录。秒传功能上了之后第3章的删除代码必须同步替换不然就会出现“某用户删自己的文件把别人文件也删了”的生产事故。更保险的做法是引入内容寻址存储文件路径直接用md5的前两级做blob目录DriveFile表不再依赖FileField的原始文件名。内容寻址存储一上重复文件从根上消失这个设计在Django面试题里能展开讲五分钟。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价