毕业设计论文系统从零搭建:新手开发者避坑指南与技术选型实战
摘要:很多计算机专业的同学把“论文管理系统”当成“能跑就行”的小网站,结果答辩现场被问到“如果 200 人同时上传 30 MB 的 PDF,你的接口会不会崩?”时直接沉默。本文用“能抄、能改、能吹”为目标,带你从 0 到 1 搭一套结构清晰、可扩展、能部署的毕设骨架,顺带把常见坑一次性填平。
1. 背景痛点:为什么你的“能跑”在老师眼里是“跑不了”
- 前后端混写:JSP 里直接
<% %>写 SQL,一学期后自己都看不懂。 - 无数据库设计:一张表
thesis_info存了 20 个字段,状态用varchar(10)存中文,后期加索引直接崩溃。 - 忽略幂等:刷新页面重复提交,同一份论文在表里插了 5 条记录,答辩时老师问“哪个是正版?”
- 文件裸奔:上传路径写死
D:/upload,服务器是 Linux,部署当天 404。 - 权限硬编码:
if (user.name.equals("admin"))——加一个新角色就要改 30 个if。
一句话总结:“能跑”≠“能演示”≠“能部署”≠“能答辩”。
2. 技术选型:3 条主流路线 5 分钟看懂
| 技术栈 | 开发效率 | 学习曲线 | 部署复杂度 | 适合场景 |
|---|---|---|---|---|
| Django(Python) | 高,自带 admin、ORM、用户系统 | 低,官方文档保姆级 | 低,一条gunicorn命令 | 想 3 周交差,答辩优先 |
| Spring Boot + Vue | 中,需懂 Spring 生态 | 中,注解 + 前端工程化 | 中,jar + nginx 反向代理 | 打算吹“企业级”,后续找 Java 岗 |
| Flask + React | 高,但“裸奔”需自己补轮子 | 高,要配蓝图、JWT、CORS | 高,静态构建 + 反向代理 | 想炫技,简历写“全栈” |
结论:
- 时间紧、人手少、Python 课刚及格——选 Django。
- 实验室师兄有 Spring 基础,想冲大厂——选 Spring Boot + Vue。
- 想炫技且肯熬夜——Flask + React,但别哭。
下文以Django 4.2 + PostgreSQL + Nginx为例,其他栈思路同源码见 GitHub 同名仓库。
3. 核心实现:把“上传-审核-通过”拆成 4 个微服务?不,拆 4 个包就够
3.1 项目结构(Django 原生 MTV 再切一刀)
thesis/ ├── apps/ │ ├── users/ # 认证 & 角色 │ ├── thesis/ # 论文业务 │ ├── review/ # 审核流程 │ └── common/ # 工具、枚举、异常 ├── uploads/ # 开发期媒体文件 ├── settings/ │ ├── base.py │ ├── prod.py │ └── dev.py └── manage.py每个 app 只暴露
urls.py与serializers.py,别的 app 不允许import *,天然解耦。
3.2 用户认证:JWT + SimpleUI 后台
Django 自带User模型,但毕设需要三角色:学生、导师、教务。采用proxy 继承不折腾原表:
# users/models.py class Role(models.TextChoices): STUDENT = "S", "Student" TEACHER = "T", "Teacher" ADMIN = "A", "Admin" class Profile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) role = models.CharField(max_length=1, choices=Role.choices) # 扩展字段登录返回 JWT,前端 axios 统一拦截:
axios.interceptors.request.use(cfg => { cfg.headers.Authorization = `Bearer ${localStorage.token}`; return cfg; });3.3 论文上传:用 Django-Filer 不如自己写 50 行
需求:只允许 PDF、≤30 MB、落盘文件名 UUID 重命名、入库记录。
# thesis/models.py def upload_to(instance, filename): ext = filename.split('.')[-1] if ext.lower() != 'pdf': raise ValidationError("Only PDF accepted.") return f"thesis/{uuid4().hex}.{ext}" class Thesis(models.Model): owner = models.ForeignKey(User, on_delete=models.CASCADE) title = models.CharField(max_length=120) file = models.FileField(upload_to=upload_to, max_length=200) status = models.CharField(max_length=10, choices=Status.choices, default=Status.PENDING) created = models.DateTimeField(auto_now_add=True)视图层加Form+Clean:
# thesis/views.py class ThesisUploadAPI(GenericAPIView): parser_classes = (MultiPartParser,) serializer_class = ThesisSerializer def post(self, request): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) serializer.save(owner=request.user) return Response(serializer.data, status=201)坑点:
upload_to要在settings.MEDIA_ROOT下,Linux 记得chmod 755。- Nginx 开
client_max_body_size 50M;,否则 413。
3.4 状态流转:把“审核”做成有限状态机
class Status(models.TextChoices): PENDING = "pending" # 待审核 APPROVED = "approved" # 通过 REJECTED = "rejected" # 驳回使用django-fsm一行注解即可:
from django_fsm import FSMField, transition class Thesis(models.Model): status = FSMField(default=Status.PENDING) @transition(field=status, source=Status.PENDING, target=Status.APPROVED, permission=lambda u: u.profile.role == Role.TEACHER) def approve(self, by): # 可发邮件、写日志 pass前端拿到新状态立即变色,答辩演示时老师点“通过”按钮,页面唰地变绿,观感极佳。
4. 代码片段:Clean Code 不是多写注释,而是少写废话
需求:导师批量下载学生论文,打包成 zip 返回。
# review/utils.py import zipfile, os from django.conf import settings from io import BytesIO from pathlib import Path def zip_thesis_by_ids(ids: list[int]) -> BytesIO: """Return in-memory zip buffer.""" buffer = BytesIO() with zipfile.ZipFile(buffer, 'w', zipfile.ZIP_DEFLATED) as zf: for thesis in Thesis.objects.filter(id__in=ids).select_related('owner'): file_path = Path(thesis.file.path) if not file_path.exists(): continue # 日志记录略 arcname = f"{thesis.owner.username}_{file_path.name}" zf.write(file_path, arcname) buffer.seek(0) return buffer视图层直接FileResponse:
from review.utils import zip_thesis_by_ids class BatchDownloadAPI(APIView): def post(self, request): ids = request.data.get("ids") buffer = zip_thesis_by_ids(ids) return FileResponse(buffer, as_attachment=True, filename=f"thesis_{now().strftime('%Y%m%d')}.zip")亮点:
- 内存级 zip,不落地临时文件,Heroku 1×CPU 小水管也能跑。
select_related预取owner,N+1 不存在。- 路径用
pathlib,Windows 与 Linux 双兼容。
5. 安全 & 性能:把“老师上传木马”扼杀在摇篮
- 文件类型白名单:后端再校验一次 MIME,
file.content_type == 'application/pdf'。 - CSRF:Django 默认开箱开启,前后端分离把
CSRF_USE_SESSIONS = False,JWT 无 Cookie 可省。 - 并发竞争:状态修改走单行原子
select_for_update:
with transaction.atomic(): thesis = (Thesis.objects.select_for_update() .get(pk=pk, status=Status.PENDING)) thesis.approve(by=request.user)- 接口限流:
django-ratelimit装饰器,单 IP 每分钟 10 次上传,防脚本刷洞。 - 敏感字段脱敏:日志里打印
user.id而非username,GDPR 就算国内答辩也加分。
6. 生产环境避坑:Windows 跑得好好的,一上服务器全 502
- 路径大小写:Windows 不区分,Linux 区分,
import别写错。 - 静态文件:
DEBUG=False后 Django 不管静态,用python manage.py collectstatic收集到/var/www/static,Nginx 配location /static/。 - 数据库迁移:第一次上线前
makemigrations+migrate别忘了,CI 里加检测:
python manage.py showmigrations --plan | grep '\[ \]' && exit 1- 媒体文件持久化:容器化时挂
volume,或者直接用 OSS/S3,省得重启 Pod 文件蒸发。 - 时区:服务器 UTC,数据库存
timestamptz,展示层dayjs.utc()转本地,避免“老师看到的时间差 8 小时”。
7. 可扩展方向:从“单人交论文”到“多人协作”
- 增加角色:在
Profile.role加REVIEWER外审专家,走同一套django-fsm。 - 细粒度权限:用
django-guardian对象级权限,导师只能改自己学生论文。 - 消息系统:审核后自动发 WebSocket 通知,前端
vue-socket-io实时弹 Toastr。 - 版本管理:论文传新版本不改原文件,新增
ThesisRevision模型,老文件走冷存储,答辩时可追溯“第 3 版为何被驳回”。 - 微服务(吹水用):把“文件转换”“PDF 加水印”抽出去做独立服务,简历写“基于领域驱动设计拆分微服务”。
写在最后:
整套代码含测试 1 200 行左右,Docker 一键起,答辩演示 5 分钟跑完。
如果你把本文的骨架抄过去,再按业务加 2–3 个“智能”噱头(比如 TF-IDF 自动关键词提取),基本就能从“老师我看网上教程”升级为“我参考了工业级实践”。
下一步,不妨想想:当教务处说要“查重对接知网”时,你的状态机该怎么加节点?