简介:这是一套基于Django+Mysql开发的个人网盘与云盘系统源程序,面向具备Python基础、想学习Web全栈开发或搭建私有存储服务的开发者。项目采用Django框架组织后端逻辑,配合Mysql完成数据持久化,涵盖用户认证、文件管理、权限控制等网盘核心模块,适合作为课程设计、毕业设计或二次开发的基础工程。压缩包共66个文件,约1.21MB,其中21个py文件承载视图、模型、序列化与配置等后端逻辑,15个html与10个js构成前端页面与交互,另有4个sql文件提供角色、权限、文件类型等基础数据,辅以图片、样式及说明文档,结构完整。资源附有依赖安装、配置修改、数据库迁移、基础SQL导入、超级用户创建与本地启动的完整流程说明,便于快速跑通项目。目前已有1495人学习下载,读者可借此理解Django项目分层设计、REST接口组织与网盘业务建模思路,并在此基础上扩展分享、回收站等功能。
1. 从一份 Django 网盘源码说起:个人云盘系统到底该怎么落地
很多人第一次接触「Python个人网盘源码、云盘系统源程序,基于Django+Mysql」这个标题,脑子里冒出来的第一个念头是:下载下来、装个 Python、跑起来,是不是就能拥有一个自己的百度网盘了?我当年也是这么想的,结果第一次跑别人的 Django 项目,光mysqlclient编译就卡了一下午。个人网盘系统本质上是一个「文件上传 + 存储 + 权限 + 分享」的 Web 应用,Django 负责请求路由、用户体系和后台管理,Mysql 负责存用户、文件元数据和分享记录,真正的文件本体一般落在本地磁盘或对象存储上。它适合两类人:一类是想拿一个完整项目练 Django 全栈的新手,另一类是想给自己或小团队搭一个私有文件管理入口的运维、后端。这篇文章不讲空话,就按「源码结构怎么读 → 环境怎么搭 → 上传下载怎么实现 → 权限和分享怎么做 → 坑在哪」这条线,把一份 Django 云盘源码从能跑到能用讲透。
2. 读懂 Django 云盘源码的目录结构与数据模型
拿到一份 Django 网盘源码,别急着runserver,先花二十分钟把目录和数据模型看明白,后面能省掉大量「改了没反应」的玄学问题。Django 项目是强约定框架,目录结构基本固定,看懂settings.py和models.py这两个文件,整个系统的骨架就清楚了。
2.1 一个典型 Django 网盘项目的目录长什么样
常见的目录结构大致是这样,不同作者会有出入,但核心文件跑不掉:
netdisk/ ├── manage.py ├── netdisk/ # 项目配置包 │ ├── settings.py # 数据库、静态文件、中间件配置 │ ├── urls.py # 根路由 │ └── wsgi.py ├── users/ # 用户 App:注册、登录、个人中心 │ ├── models.py │ ├── views.py │ └── urls.py ├── files/ # 文件 App:上传、下载、删除、分享 │ ├── models.py │ ├── views.py │ └── urls.py ├── static/ # CSS、JS、图片 ├── media/ # 用户上传的文件实际落盘位置 └── templates/ # HTML 模板users和files两个 App 是核心。users管身份,files管内容。media目录是文件本体存放的地方,static是前端资源,这两个目录在settings.py里必须配好,否则会出现「上传成功但下载 404」或者「CSS 加载不出来」的经典问题。我一般会先grep -r "MEDIA_ROOT\|STATIC_ROOT" netdisk/settings.py确认路径,再决定要不要改。
2.2 数据模型:用户、文件、分享三张表怎么设计
Django 的 ORM 把表结构写在models.py里,网盘系统的核心模型通常就三个。下面是我常用的一个精简版本,可以直接对照源码看:
# files/models.py from django.db import models from django.contrib.auth.models import User class UserFile(models.Model): owner = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="所属用户") file_name = models.CharField(max_length=255, verbose_name="文件名") file_path = models.CharField(max_length=500, verbose_name="存储路径") file_size = models.BigIntegerField(default=0, verbose_name="文件大小(字节)") upload_time = models.DateTimeField(auto_now_add=True, verbose_name="上传时间") is_deleted = models.BooleanField(default=False, verbose_name="软删除标记") class Meta: db_table = 'user_file' indexes = [models.Index(fields=['owner', 'is_deleted'])] class ShareLink(models.Model): file = models.ForeignKey(UserFile, on_delete=models.CASCADE) token = models.CharField(max_length=64, unique=True, verbose_name="分享令牌") expire_at = models.DateTimeField(verbose_name="过期时间") extract_code = models.CharField(max_length=8, blank=True, verbose_name="提取码")这里有几个参数值得说清楚。file_size用BigIntegerField而不是IntegerField,因为IntegerField在 Mysql 里对应int,最大约 21 亿字节,也就是 2GB 左右,超过就溢出,这是很多人上传大文件失败却查不出原因的根源。is_deleted做软删除,用户点删除只是把标记置为True,文件本体和记录都保留,方便做回收站,也避免误删后没有后悔药。ShareLink里的token用unique=True保证分享链接不重复,expire_at控制有效期,extract_code就是常见的提取码。Meta里的联合索引['owner', 'is_deleted']很关键,网盘列表页几乎每次查询都带这两个条件,没有索引在文件多了以后会明显变慢。
2.3 从 models 到数据库:迁移命令与字段类型对照
模型写好后,靠 Django 的迁移系统同步到 Mysql:
python manage.py makemigrations files python manage.py migratemakemigrations根据models.py的变化生成迁移脚本,migrate把脚本执行到数据库。如果改了字段类型,比如把file_size从IntegerField改成BigIntegerField,Django 会生成AlterField,但已有数据不会自动转换,数据量大时要自己写数据迁移。字段类型和 Mysql 的对应关系建议记牢:CharField→varchar,TextField→longtext,DateTimeField→datetime,BooleanField→tinyint(1)。搞混了在navicat里看表结构会一脸懵。
3. 把 Django+Mysql 网盘系统在本地跑起来
环境搭建是新手翻车最集中的环节,尤其是 Mysql 和 Python 驱动的连接。这一章按顺序把 Python、Mysql、依赖、配置四步走完,每一步都给可复制的命令和排错方向。
3.1 Python 环境与虚拟环境的准备
先确认 Python 版本,Django 3.x 建议 Python 3.7 以上,Django 4.x 建议 3.8 以上:
python --version # 或 python3 --version然后建虚拟环境,别把依赖装到全局,否则多个项目版本冲突会让你怀疑人生:
python -m venv venv # Windows venv\Scripts\activate # Linux / macOS source venv/bin/activate激活后命令行前面会出现(venv)。这一步在vscode里也要注意,右下角解释器要选到虚拟环境里的 Python,否则vscode python环境配置没配对,终端里装的包编辑器识别不到,代码里全是黄色波浪线。
3.2 Mysql 安装与数据库初始化
Windows 用户去 Mysql 官网下载安装包,安装时记住 root 密码,字符集选utf8mb4。Linux 用户一条命令:
sudo apt install mysql-server sudo mysql_secure_installation装好后登录建库:
CREATE DATABASE netdisk DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; CREATE USER 'netdisk'@'localhost' IDENTIFIED BY '你的密码'; GRANT ALL PRIVILEGES ON netdisk.* TO 'netdisk'@'localhost'; FLUSH PRIVILEGES;字符集一定要用utf8mb4,不要用utf8。Mysql 的utf8是残缺的三字节实现,存不了 emoji 和部分生僻字,用户上传一个带 emoji 的文件名就会报错,这是血泪经验。建库时如果报ERROR 2002 (HY000): Can't connect to local MySQL server through socket,说明 Mysql 服务没起来,Linux 下sudo service mysql start即可。
3.3 依赖安装与 settings.py 数据库配置
网盘项目常见依赖:
pip install django mysqlclient pillowmysqlclient是 Django 连 Mysql 的驱动,Windows 上如果编译失败,可以改用pymysql,然后在项目__init__.py里加:
import pymysql pymysql.install_as_MySQLdb()接着改settings.py:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'netdisk', 'USER': 'netdisk', 'PASSWORD': '你的密码', 'HOST': '127.0.0.1', 'PORT': '3306', 'OPTIONS': {'charset': 'utf8mb4'}, } }HOST写127.0.0.1而不是localhost,能避开一部分 socket 连接问题。OPTIONS里的charset和建库时保持一致。如果报mysql ssl连接错误,在OPTIONS里加'ssl': {'ssl_mode': 'DISABLED'}先绕过,本地开发不需要 SSL。
3.4 迁移、建超级用户与启动
python manage.py migrate python manage.py createsuperuser python manage.py runservermigrate会把 Django 内置的 auth、admin 等表和你的业务表一起建好。createsuperuser建后台管理员,之后访问/admin能直接管理用户和文件记录。runserver默认跑在127.0.0.1:8000。如果页面能开但样式全丢,检查settings.py里STATIC_URL和DEBUG,开发阶段DEBUG=True时 Django 才会自动托管静态文件,vscode写img标签在django的static文件中显示不了多半就是DEBUG关了或者路径没写{% static %}。
4. 文件上传、下载与分享的核心实现
环境跑通只是第一步,网盘真正有价值的是文件流转。这一章讲上传落盘、下载响应、分享链接三个核心功能的实现思路和关键参数。
4.1 文件上传:落盘路径与大小限制
上传视图的核心是把request.FILES里的文件写到MEDIA_ROOT下,并记录元数据:
# files/views.py import os, uuid from django.conf import settings from django.shortcuts import render, redirect from .models import UserFile def upload(request): if request.method == 'POST' and request.FILES.get('file'): f = request.FILES['file'] # 用 uuid 重命名,避免同名覆盖和中文名乱码 ext = os.path.splitext(f.name)[1] save_name = f"{uuid.uuid4().hex}{ext}" save_path = os.path.join(settings.MEDIA_ROOT, save_name) with open(save_path, 'wb+') as dest: for chunk in f.chunks(): dest.write(chunk) UserFile.objects.create( owner=request.user, file_name=f.name, file_path=save_name, file_size=f.size, ) return redirect('file_list') return render(request, 'upload.html')关键点有三个。第一,存储文件名用uuid重命名,原始文件名只存数据库,这样能避免同名覆盖,也能绕开中文文件名在不同系统下的编码问题。第二,用f.chunks()分块写入而不是f.read()一次性读,大文件不会把内存撑爆。第三,Django 默认上传大小限制约 2.5MB,超过会报错,要在settings.py里放开:
DATA_UPLOAD_MAX_MEMORY_SIZE = 104857600 # 100MB FILE_UPLOAD_MAX_MEMORY_SIZE = 104857600注意这两个参数控制的是「内存中处理的大小」,超过阈值的部分 Django 会写到临时文件,真正限制大文件还要看 Nginx 或前端。如果部署在 Nginx 后面,client_max_body_size也要同步改,否则请求根本到不了 Django。
4.2 文件下载:流式响应与权限校验
下载不能直接把文件路径返回给前端,那样任何人都能猜到地址。正确做法是走视图做权限校验,再用FileResponse流式返回:
from django.http import FileResponse, Http404 import os from django.conf import settings def download(request, file_id): try: uf = UserFile.objects.get(id=file_id, owner=request.user, is_deleted=False) except UserFile.DoesNotExist: raise Http404("文件不存在") full_path = os.path.join(settings.MEDIA_ROOT, uf.file_path) if not os.path.exists(full_path): raise Http404("文件已丢失") response = FileResponse(open(full_path, 'rb'), as_attachment=True, filename=uf.file_name) return responseowner=request.user保证只能下自己的文件,is_deleted=False过滤回收站。as_attachment=True让浏览器弹下载框而不是直接打开,filename用数据库里的原始名,用户下载下来还是原来的名字。FileResponse是流式的,大文件不会一次性读进内存。这里有个坑:如果文件被手动从media目录删了但数据库记录还在,os.path.exists判断能兜住,返回 404 而不是 500。
4.3 分享链接:token 生成与过期控制
分享功能的核心是生成一个不可猜测的 token,并带上过期时间:
import uuid from datetime import timedelta from django.utils import timezone from .models import ShareLink def create_share(request, file_id): uf = UserFile.objects.get(id=file_id, owner=request.user) token = uuid.uuid4().hex ShareLink.objects.create( file=uf, token=token, expire_at=timezone.now() + timedelta(days=7), extract_code=request.POST.get('code', ''), ) return redirect('share_detail', token=token)uuid4().hex生成 32 位随机串,碰撞概率极低,也不像自增 ID 那样能被遍历。expire_at用timezone.now()加天数,注意要用 Django 的timezone而不是 Python 原生datetime,否则在USE_TZ=True的项目里会出现时区偏差,分享链接提前或延后失效。访问分享页时校验expire_at > timezone.now(),过期就返回提示。提取码为空表示公开分享,非空则要求输入匹配。
5. 权限、安全与性能:网盘系统绕不开的坑
功能能跑不等于能用,网盘系统一旦放到公网,权限、安全和性能问题会集中爆发。这一章把最常见的几类坑按「现象 → 原因 → 解决」列清楚。
5.1 越权访问:改一个 URL 就能看别人的文件
现象:用户 A 登录后,把下载链接里的file_id改成 B 的文件 ID,居然能下载成功。原因:视图里只用了UserFile.objects.get(id=file_id),没有加owner=request.user过滤。解决:所有涉及具体文件的操作,查询条件必须带当前用户,或者用get_object_or_404(UserFile, id=file_id, owner=request.user)。这是网盘系统最典型也最危险的漏洞,写视图时养成「查询必带 owner」的习惯。
5.2 上传目录被直接访问:media 暴露成静态目录
现象:知道文件名就能通过/media/xxx直接访问,绕过权限校验。原因:开发时为了图方便把MEDIA_URL配成静态可访问,生产环境没改。解决:生产环境media目录不要交给 Nginx 直接location /media/,所有文件访问都走 Django 视图做权限判断,或者用带签名的临时 URL。本地开发图省事可以放开,上线前必须收掉。
5.3 大文件上传超时与内存暴涨
现象:上传几百 MB 的文件,要么请求超时,要么服务器内存飙升。原因:FILE_UPLOAD_MAX_MEMORY_SIZE设得过大,或者视图里用了f.read()。解决:用f.chunks()分块写盘,FILE_UPLOAD_MAX_MEMORY_SIZE保持在合理范围(比如 5MB),超过的部分 Django 自动落临时文件。Nginx 反代时同步调大client_max_body_size和proxy_read_timeout。
5.4 数据库连接数耗尽
现象:并发一上来就报Too many connections。原因:每个请求都新建数据库连接,或者用了连接池但配置不当。解决:Django 默认每个请求结束后关闭连接,CONN_MAX_AGE设为 0 是安全的;如果要用持久连接,设CONN_MAX_AGE=60并配合 Mysql 的max_connections调整。生产环境可以考虑mysql的数据库连接池方案,但小规模个人网盘没必要,先把CONN_MAX_AGE调对更实际。
5.5 文件名与路径穿越
现象:上传文件名里带../,文件被写到media目录之外。原因:直接用用户提供的文件名拼路径。解决:存储时用uuid重命名,原始文件名只存数据库不参与路径拼接。如果一定要用原始名,用os.path.basename去掉路径部分,并过滤..。这个坑不常遇到,但一旦被利用就是服务器文件被覆盖。
6. 让网盘更好用:几个能立刻加上的进阶技巧
基础功能跑通后,有几个小改动能明显提升体验,而且实现成本不高。第一个是文件列表分页,Django 自带Paginator,文件多了不分页会一次性查出所有记录,页面卡、数据库压力大:
from django.core.paginator import Paginator def file_list(request): qs = UserFile.objects.filter(owner=request.user, is_deleted=False).order_by('-upload_time') paginator = Paginator(qs, 20) # 每页 20 条 page = paginator.get_page(request.GET.get('page')) return render(request, 'list.html', {'page': page})Paginator的第二个参数是每页条数,个人网盘 20 到 50 都合理。模板里用page.has_next、page.next_page_number做翻页控件。
第二个是上传进度和断点续传的前置准备。Django 原生不擅长处理大文件分片,常见做法是前端用File.slice()切片,每片单独 POST,后端按file_id + chunk_index存临时块,全部传完再合并。这个改动量不小,但如果你的网盘要传几个 GB 的文件,值得做。合并时注意按chunk_index排序,别依赖到达顺序。
第三个是定期清理过期分享和软删除文件。写一个管理命令,用crontab每天跑一次:
# files/management/commands/clean_expired.py from django.core.management.base import BaseCommand from django.utils import timezone from files.models import ShareLink, UserFile class Command(BaseCommand): def handle(self, *args, **options): ShareLink.objects.filter(expire_at__lt=timezone.now()).delete() UserFile.objects.filter(is_deleted=True).delete()expire_at__lt=timezone.now()是 Django ORM 的字段查询语法,__lt表示小于。这个命令配合python manage.py clean_expired就能手动执行,加到定时任务里自动跑。注意软删除文件直接delete()会连文件本体一起删,如果还想保留一段时间,改成先标记再延迟清理。
我自己维护这套东西最大的教训是:别一上来就追求功能全,先把上传、下载、权限三件事做扎实,再谈分享和回收站。很多网盘源码看着功能多,实际权限校验一塌糊涂,跑起来能用,放出去就出事。环境搭建阶段把字符集、字段类型、上传限制这三个参数定对,后面能少熬好几个夜。希望帮到你。
本文还有配套的精品资源,点击获取