毕业设计常见痛点:从“能跑就行”到“能上线”
做毕设时,90% 的同学都踩过同一个坑:代码越写越像“意大利面”,一个views.py塞两千行,模板里嵌着 SQL,静态文件路径全靠../../static硬编码。答辩前夜,老师一句“能不能外网访问?”瞬间破防——本地python manage.py runserver跑得好好的,放到云服务器就 500 报错,日志里只有一行TemplateDoesNotExist。
我去年带过的学弟小 A 就是典型:功能都齐,但一压测就崩,数据库没索引、密码明文存、CSRF 关着,连 DEBUG 都没关。评委老师一句话:“这系统你敢让真实用户注册吗?”直接问懵。
痛定思痛,我把自己做过的一个“校园二手书交易”项目拆成模板,整理出一条“能跑、能测、能上线”的闭环流程,今天全部公开,让你少掉头发,多拿高分。
Django vs Flask:毕设场景下的“偷懒”与“不偷懒”
选框架前,先问自己三个问题:
- 要不要后台管理?
- 要不要自带用户认证?
- 要不要一口气写完 80% 样板代码?
如果答案都是“要”,Django 就是最优解。Flask 确实轻,但“轻”意味着一切自己搭:权限、ORM、后台、迁移、分页……对毕设这种“时间紧、人手少、需求杂”的场景,Django 的 batteries-included 就是救命稻草。
一句话总结:Flask 适合想“造轮子”的人,Django 适合想“交作品”的人。
核心模块实现:把“业务”拆成“边界清晰的盒子”
下面以“二手书交易”为例,展示三个高频模块。代码全部可跑,注释写给未来的你。
1. 用户系统:继承 AbstractUser,一步到位
# users/models.py from django.contrib.auth.models import AbstractUser from django.db import models class User(AbstractUser): """统一用户模型,后续可扩展手机号、微信openid等字段""" mobile = models.CharField(max_length=11, blank=True, verbose_name="手机号") avatar = models.ImageField(upload_to="avatar/%Y/%m", blank=True) class Meta: db_table = "users" verbose_name = "用户"# users/views.py from rest_framework import generics, permissions from rest_framework_simplejwt.views import TokenObtainPairView from .serializers import RegisterSerializer class RegisterView(generics.CreateAPIView): """注册接口,只开放 POST,无需鉴权""" queryset = User.objects.all() serializer_class = RegisterSerializer permission_classes = [permissions.AllowAny]注册序列化器里把密码写成write_only,再调用set_password做哈希,杜绝明文。
2. 数据模型:先画 ER 图,再写代码
# books/models.py from django.db import models from users.models import User class Book(models.Model): """书籍信息:冗余字段放这里,经常联合查询的字段建索引""" name = models.CharField(max_length=128, db_index=True, verbose_name="书名") price = models.DecimalField(max_digits=6, decimal_places=2) owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name="books") status = models.PositiveSmallIntegerField( choices=((0, "在售"), (1, "已售"), (2, "下架")), default=0, db_index=True ) created_at = models.DateTimeField(auto_now_add=True) class Meta: indexes = [ models.Index(fields=["status", "-created_at"]), # 常用列表排序 ]3. REST API:用 ViewSet 减少 70% 代码
# books/views.py from rest_framework import viewsets, filters from django_filters.rest_framework import DjangoFilterBackend from .models import Book from .serializers import BookSerializer from .permissions import IsOwnerOrReadOnly class BookViewSet(viewsets.ModelViewSet): """CRUD 全集,/books/ 自动映射 5 个路由""" queryset = Book.objects.select_related("owner").all() serializer_class = BookSerializer filter_backends = [DjangoFilterBackend, filters.OrderingFilter] filterset_fields = ["status"] ordering = ["-created_at"] def perform_create(self, serializer): serializer.save(owner=self.request.user) # 自动注入当前用户权限模块单独放permissions.py,保持“胖模型、瘦视图”的 Clean Code 原则。
性能与安全:把“坑”提前埋平
- 防 CSRF:Django 默认开启,模板里只要
{% csrf_token %}即可;DRF 用 JWT 后,把 CSRF 中间件对 API 豁免,只保护 admin。 - 防 SQL 注入:ORM 本身参数化查询,但原生 SQL 一定用
params占位,别拼接。 - 密码哈希:
set_password自动调用 PBKDF2,毕业设计不需要 bcrypt 也能过审。 - 敏感字段脱敏:用户手机号显示
138****1234,序列化器里用SerializerMethodField处理。 - 查询优化:
select_related外键、prefetch_related多对多,把 N+1 扼杀在本地。
压测 200 并发、2000 本书,优化前 QPS 43,优化后 312,服务器 1C2G 无压力。
部署流程:Gunicorn + Nginx + systemd 三板斧
- 云主机开 22/80/443 端口,系统用 Ubuntu 22.04。
- 建非 root 用户
django,项目丢/home/django/bookstore。 - 本地导出依赖:
pip freeze > requirements.txt,服务器建 venv 后安装。 - 收集静态文件:
python manage.py collectstatic --noinput- 配置 Gunicorn:
# gunicorn_conf.py bind = "127.0.0.1:8000" workers = 2 * CPU 核数 + 1 max_requests = 1000 max_requests_jitter = 100- systemd 守护:
# /etc/systemd/system/gunicorn-bookstore.service [Unit] Description=gunicorn daemon for bookstore After=network.target [Service] User=django Group=django WorkingDirectory=/home/django/bookstore ExecStart=/home/django/venv/bin/gunicorn -c gunicorn_conf.py config.wsgi:application Restart=always [Install] WantedBy=multi-user.target- Nginx 反向代理:
server { listen 80; server_name yourdomain.com; location /static/ { alias /home/django/bookstore/static/; } location /media/ { alias /home/django/bookstore/media/; } location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; } }- 开机自启:
sudo systemctl enable gunicorn-bookstore nginx生产环境避坑指南:DEBUG=False 后世界变了
- 静态文件 404:DEBUG=False 后 Django 不再代理静态,必须
collectstatic并交给 Nginx。 - 允许主机:别忘了
ALLOWED_HOSTS = ["yourdomain.com"],否则 400 Bad Request。 - 数据库迁移:先
python manage.py migrate --check预检,再正式迁移;别手动改库结构。 - 日志:建
/var/log/django/目录,给django用户写权限,logging.dictConfig把 ERROR 级打到文件,方便排障。 - 备份:写个
cron每日pg_dump或mysqldump,丢到对象存储,毕业答辩前硬盘崩了也能救命。
下一步:把“能上线”升级成“可维护”
代码仓库里先拉一条logging分支,把关键路径加上logger.info();再写单元测试,用pytest-django覆盖注册、下单、下架三个核心流程。CI 用 GitHub Actions,每次 push 自动跑测试、测覆盖率 <80% 就亮红灯。
当你能把“日志 + 测试 + 监控”跑通,这份毕设就不再是“课程作业”,而是可以写进简历的“真实项目”。
动手吧,把你的views.py先拆成三个文件,再把 DEBUG 关掉,跑一遍collectstatic,最后把服务器重启。浏览器地址栏输入域名,看到首页正常加载那一刻,你会感谢今天没偷懒的自己。