
我是今年做毕设的时候接到的这个题目——基于Django框架的多功能校园网站的设计与实现。刚拿到题目的时候我脑子里只有一个模糊的概念大概就是一个给学校用的信息发布网站。但真正动手之后才发现多功能这三个字才是整个项目的难点和核心价值所在。如果你现在也正在做类似的毕业设计或者第一次用Django做完整项目那这篇文章应该能帮你少走不少弯路。我先把话说在前面这个题目不是让你堆功能而是要你在一个清晰的技术框架里把用户认证、内容发布、互动模块、后台管理这些校园场景里真实存在的东西整合成一套可运行的完整系统。这篇文章我会从需求拆解、技术选型、环境搭建、核心模块代码、ORM实战到部署上线完整过一遍我做这个项目时的实操记录所有的坑和细节都会讲清楚。1. 项目整体拆解与技术选型思路1.1 这个多功能到底该装下哪些功能做毕设最容易犯的错就是一上来就写代码写到一半发现功能又乱又重复改来改去项目就废了。所以我拿到题目之后先花了两天时间对着多功能校园网站这句话做需求收敛。我当时列了一个功能清单基本原则是既要体现系统的完整性又不能让工作量失控。最终定下来的是这几个核心模块用户系统学生和教师注册、登录、个人资料编辑这是整个网站的身份基础。新闻公告模块发布校园新闻、学术讲座、教务处通知按发布时间倒序展示支持分类和置顶。课程信息模块展示课程表、选修课列表学生可以收藏感兴趣的课程这个模块能体现数据关联关系。校园生活服务失物招领和二手闲置两个子功能包含发布、浏览、留言联系这类互动功能做起来很出彩。留言论坛同学之间可以发帖、回帖是体现多功能的人气模块。后台管理管理员可以审核内容、管理用户、维护新闻分类。你可能会问为什么不做成绩查询或者在线选课我的判断是这两个功能往往涉及学生隐私和复杂的业务规则做浅了没有实际意义做深了工作量太大对毕设来说性价比不高。失物招领、二手闲置、论坛留言这些反而更容易把功能做完整论文里也有话说。模块确定之后我再把这些功能和数据库表结构一一对应起来画了大概的ER图才开始动工。这一步非常重要它直接决定了后面开发的顺畅程度。1.2 技术选型Django凭什么是最不折腾的答案技术选型这部分我自己也纠结过因为备选方案其实挺多的Flask、Spring Boot、甚至原生PHP都有人用。但最终我还是选了Django而且做完整个项目之后我可以很肯定地说这个选择对于毕设场景来说是最合理的。先说说Django和Flask的区别。Flask是微框架非常轻量你想用数据库得自己选SQLAlchemy想用后台得自己找插件想用表单校验得自己集成WTForms。这些组件本身并不难但自己组装这件事非常消耗时间而且组装出来的系统不够统一安全问题也要自己想。Django则是一个全家桶框架它把ORM、表单、认证、后台管理、模板引擎这些东西全部集成好了约定大于配置你只要按照它的规则走就能迅速搭出一个完整、安全的网站骨架。再和Spring Boot比Java系的优势是企业级生态和性能但学习成本明显高尤其是对一个以快速完成毕设且有完整论文支撑为目标的项目来说Django的上手速度是碾压级的。Python的语法简洁写业务逻辑的效率很高调试也直观这对时间紧张的学生党太重要了。具体到我这个项目Django最值钱的能力有三个。第一是自带的Admin后台我几乎没怎么写管理端代码就通过注册模型和定制列表页实现了一个功能完整的运营后台。第二是内置的认证系统User模型、session、login_required装饰器都是现成的我只需要做扩展就行。第三是ORM配合SQLite做开发数据库几乎零配置后期切MySQL也就改一个连接串的事。版本方面我建议直接用Django 4.x或5.x如果你用的Python是3.8以上4.x最稳别名因为5.x对Python版本要求更高。后面我在5.2节会详细说版本相关的坑。2. 环境搭建与项目初始化实操2.1 Python环境与Django安装这一步看着简单其实很容易在第一关就出事。我自己就在安装Django的时候遇到过一次pip install django之后python manage.py完全找不到命令的情况后来排查了半天发现是系统里同时装了多个Python版本pip装到了A版本命令行跑的是B版本。我的建议是无论你用Windows、macOS还是Linux包括国产的麒麟系统都一定要用虚拟环境。虚拟环境的核心价值就是让每个项目的依赖互相隔离不会出现你在这个项目装Django 4.x、在那个项目装Django 2.x导致的全局冲突。# 创建虚拟环境venv是Python自带的不需要额外安装 python3 -m venv venv # 激活虚拟环境Windows venv\Scripts\activate # 激活虚拟环境Linux/macOS source venv/bin/activate # 激活后确认python指向 which python # 安装Django pip install django # 验证版本 python -m django --version这里我要多说一句关于麒麟系统的经验。我们实验室的服务器装的是麒麟系统系统自带的Python是3.6但是Django新版早就不支持3.6了。我第一次没注意直接pip install django结果它给我装了一个非常老旧的2.x版本然后各种新语法都不支持。后面我把Python升级到3.8以上再重新创建虚拟环境才解决。所以安装之前一定先确认Python版本用下面这条命令python --version如果版本偏低建议用pyenv或者直接编译安装新版本Python别在旧版本上硬扛。另外国内网络环境下pip安装有时候会非常慢甚至超时可以直接换清华镜像源pip install django -i https://pypi.tuna.tsinghua.edu.cn/simple还有一次我遇到pip install django一直报编码错误后来发现是终端环境的语言编码导致的执行下面这句话就能绕过export PYTHONUTF81这些看起来都是小事但要是不知道原因真的会卡上半天。2.2 项目初始化与settings配置环境搞定之后接下来就是创建项目和应用。我先强调一个概念Django项目project和应用app的区别。一个项目可以理解成一个网站一个应用是网站里的一个功能模块。我做这个校园网站的时候就没有把全部写在一个app里面而是分了users、news、course、lost_found、board这五个app这样每个模块的model、view、url都各管各的后期维护起来很舒服。# 创建项目名字我用的是 campus_site你也可以用自己的 django-admin startproject campus_site # 进入项目目录 cd campus_site # 依次创建各个应用 python manage.py startapp users python manage.py startapp news python manage.py startapp course python manage.py startapp lost_found python manage.py startapp board创建出来之后的第一步是去settings.py里把自己创建的应用都注册到INSTALLED_APPS。这一步很多人会忘记导致后面跑迁移的时候提示A model is not declared in the app或者说模板找不到。把应用加进去之后再顺手改三个配置LANGUAGE_CODE改成zh-hans、TIME_ZONE改成Asia/Shanghai、USE_TZ保持True这样时间显示和本地一致管理后台也是中文的。数据库配置在开发阶段直接用默认的SQLite就行Django会自动生成一个db.sqlite3文件。虽然毕设答辩可能演示MySQL更让人觉得专业但你完全可以在最后部署或者写报告的时候再切到MySQL。开发阶段用SQLite的好处是零配置、随处可用跑自动化测试也不会污染这个数据。如果你确实想一开始就用MySQL就在DATABASES里改成下面这样DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: campus_site, USER: root, PASSWORD: yourpassword, HOST: 127.0.0.1, PORT: 3306, } }不过用MySQL之前要先安装mysqlclient或用pymysql做适配这又是一个容易出问题的点所以我个人建议开发期SQLite上线前再切MySQL性价比最高。接着执行一下数据库初始化和创建超级管理员python manage.py migrate python manage.py createsuperuser python manage.py runserver看到浏览器出现那只火箭并且能正常打开Admin后台整个项目的地基就算打好了。3. 核心模块实现与代码细节3.1 用户认证与权限控制用户系统是我最先做的一个模块因为其他所有功能都要依赖Login用户这个概念。Django自带了一个User模型字段包括username、password、email、first_name、last_name等但它并不包含学号、院系、身份角色这些校园场景需要的信息。我一般不会去改Django自带User模型而是创建一个独立的Profile模型和它做一对一关联。为什么要这么做因为扩展原生User模型有两种方式一种是继承AbstractUser重写User模型另一种是新建一个和User一对一关联的Profile表。前者改动起来很彻底但要项目开始之前就定义好中途改会比较麻烦后者更灵活不动认证底层逻辑整改方案就是用户注册的时候同时创建User记录和Profile记录。考虑到我的校园网站需要存的额外信息并不复杂——学号/工号、学院、专业、身份类型student/teacher——用Profile扩展就很舒服。# users/models.py from django.contrib.auth.models import User from django.db import models class Profile(models.Model): USER_ROLE_CHOICES ( (student, 学生), (teacher, 教师), ) user models.OneToOneField(User, on_deletemodels.CASCADE, related_nameprofile) student_id models.CharField(学号/工号, max_length20, blankTrue) college models.CharField(学院, max_length50, blankTrue) role models.CharField(身份, max_length10, choicesUSER_ROLE_CHOICES, defaultstudent) def __str__(self): return f{self.user.username}-{self.get_role_display()}注册和登录我直接用了Django自带的UserCreationForm和AuthenticationForm这样不用自己写密码哈希逻辑安全性也有保障。注册的时候继承UserCreationForm再加一个邮箱字段然后在注册视图里同时创建Profile这个流程申报写完大概是60行代码。权限控制这里重点说一下校园网站在功能上一定要区分普通用户和管理员。新闻文章可以有草稿、已发布、已下线等状态只有管理员可以发布正式文章。普通用户可以发帖、发失物招领但发布之后需要管理员审核。这个审核后再展示的思路在毕设答辩时是很好的加分点因为它体现了你对内容安全的考虑。视图层控制权限最方便的是Django自带的装饰器from django.contrib.auth.decorators import login_required, user_passes_test def is_admin(user): return user.is_staff or user.is_superuser login_required def publish_lost_found(request): # 只有登录用户可以发布失物招领 ... user_passes_test(is_admin) def news_review(request): # 只有管理员可以审核新闻 ...模板里也可以根据user状态做显示控制。比如对管理员显示审核按钮普通用户只显示发布按钮用的是{% if request.user.is_authenticated %}和{% if perms.news.change_news %}这类标签。模板里做权限控制不要在逻辑上依赖它它只是提升体验真正的权限校验必须在后端视图完成。3.2 新闻公告与富文本编辑新闻公告模块是校园网站的门面用户打开网站最先看到的就是这个。模型字段最开始我设计得很简单标题、正文、作者、发布时间、更新时间但后来发现只有这些根本不够。加了分类和置顶之后整个模块才真正像一个内容管理系统。# news/models.py from django.contrib.auth.models import User from django.db import models class Category(models.Model): name models.CharField(分类名, max_length20) class Meta: verbose_name 分类 verbose_name_plural verbose_name def __str__(self): return self.name class News(models.Model): STATUS_CHOICES ( (draft, 草稿), (published, 已发布), (offline, 已下线), ) title models.CharField(标题, max_length200) content models.TextField(正文) author models.ForeignKey(User, on_deletemodels.CASCADE, verbose_name作者) category models.ForeignKey(Category, on_deletemodels.SET_NULL, nullTrue, verbose_name分类) status models.CharField(状态, max_length10, choicesSTATUS_CHOICES, defaultdraft) is_top models.BooleanField(置顶, defaultFalse) created_at models.DateTimeField(创建时间, auto_now_addTrue) updated_at models.DateTimeField(更新时间, auto_nowTrue) class Meta: ordering [-is_top, -created_at] def __str__(self): return self.title这里有几个细节值得专门说。第一content用TextField而不是CharField因为新闻正文可能会有几千字CharField的max_length在MySQL下虽然可以设很大但会带来不必要的性能问题TextField更合适。第二author用了外键关联到User并且设置了on_deletemodels.CASCADE意思是用户被删了他发布的新闻也一起删掉。这个场景我后来认真想过其实真实系统里更稳妥的做法是on_deletemodels.SET_NULL因为即使作者离开历史新闻内容也应该保留。但CASCADE在答辩时容易讲清楚我就保留了你如果做真实系统建议用SET_NULL。第三is_top字段和ordering配合置顶文章自动排在最前面这个逻辑简单有效完全不用写复杂的查询。编辑新闻正文的时候普通textarea体验太差了我直接集成了wangEditor这个富文本编辑器。前端引入很简单下载对应版本的JS/CSS文件然后在模板里初始化!-- templates/news/edit_news.html 片段 -- div ideditor/div textarea namecontent idcontent styledisplay:none;/textarea script src/static/wangeditor/index.js/script script const E window.wangEditor const editor new E(#editor) editor.config.uploadImgServer /upload/image/ editor.create() // 表单提交前把编辑器的HTML同步到隐藏的textarea document.querySelector(form).addEventListener(submit, function () { document.getElementById(content).value editor.txt.html() }) /script后端视图保存的时候注意要在Django的settings里配置MEDIA_URL和MEDIA_ROOT让上传的图片能存到本地目录并且在开发模式下用django.conf.urls.static.static去托管这些图片路径。列表页分页Django有现成的Paginator我就直接在视图里封装了一层from django.core.paginator import Paginator def news_list(request, category_idNone): news_qs News.objects.filter(statuspublished) if category_id: news_qs news_qs.filter(category_idcategory_id) paginator Paginator(news_qs, 10) page_number request.GET.get(page) page_obj paginator.get_page(page_number) return render(request, news/list.html, {page_obj: page_obj})再用{% for news in page_obj %}循环渲染用page_obj.has_previous和page_obj.has_next生成上一页/下一页按钮。这套组合基本是所有内容模块的公版后面做课程列表、失物招领列表都是复制这套逻辑。3.3 生活服务类模块的状态设计失物招领和二手闲置这两个模块表面上看就是发布信息展示信息但如果你真的只做这么简单项目就没有亮点。我在这两个模块里重点设计的是合作关系状态机。先拿失物招领举例。一条失物记录有类型失物/招领、物品名称、丢失/拾取地点、描述、联系方式、状态。这个状态一定要从进行中流转到已找到/已归还不能永远挂着。在模型里我用了一个choice字段# lost_found/models.py class LostFoundItem(models.Model): TYPE_CHOICES ( (lost, 寻物), (found, 招领), ) STATUS_CHOICES ( (pending, 待审核), (active, 进行中), (resolved, 已结束), (rejected, 未通过), ) item_type models.CharField(类型, max_length10, choicesTYPE_CHOICES) title models.CharField(物品名称, max_length100) location models.CharField(地点, max_length100) description models.TextField(详细描述) contact models.CharField(联系方式, max_length100) status models.CharField(状态, max_length10, choicesSTATUS_CHOICES, defaultpending) publisher models.ForeignKey(User, on_deletemodels.CASCADE) created_at models.DateTimeField(auto_now_addTrue)列表页只展示statusactive的记录管理员后台可以审核待审核的记录发布人可以对自己的记录点击标记为已找到/已归还把status改成resolved这样整个流程就闭环了。二手闲置模块除了发布、浏览之外我还加了一个简单但实用的功能收藏。收藏这个功能在技术本质上是一个多对多关系的练习很能体现你对Django ORM的理解。# course/models.py 或者独立出一个collect app这里我用课程收藏举例 class Course(models.Model): name models.CharField(课程名, max_length100) teacher models.CharField(授课教师, max_length50) location models.CharField(上课地点, max_length50) weekday models.IntegerField(星期几, choices[(1,一),(2,二),(3,三),(4,四),(5,五)]) start_time models.TimeField(开始时间) end_time models.TimeField(结束时间) collected_by models.ManyToManyField(User, related_namefavorite_courses, blankTrue) def __str__(self): return self.name收藏与取消收藏的视图思路很直接login_required def toggle_collect(request, course_id): course get_object_or_404(Course, pkcourse_id) if request.user in course.collected_by.all(): course.collected_by.remove(request.user) else: course.collected_by.add(request.user) return redirect(course:detail, course_idcourse.id)这样在最后写论文的时候不管是设计我的收藏页面还是画多对多关系的ER图都有很实在的内容支撑。4. ORM查询、删除对象与反向解析实战4.1 模型设计与数据库迁移很多初学者第一次建好模型然后一跑migrate就各种报错不少是因为字段类型、外键关系没想清楚。我在这部分把建模的核心原则总结成三句话模型字段尽量语义化外键关系想清楚谁是一、谁是多删除行为提前定好策略。我在前面贴的News模型、LostFoundItem模型都涉及外键和外键删除策略这里补充一下Django外键的on_delete一共有哪几个常用选项CASCADE父表记录删除时子表关联记录一起删除。使用场景是明确从属关系的比如User和Profile用户没了他的资料理所当然删掉。SET_NULL父表删除时子表外键置为NULL一般用法是新闻分类删除后新闻本身保留外键变成空。使用这个选项时字段必须设置nullTrue。PROTECT有子表记录引用时禁止删除父表记录会抛ProtectedError。适合分类被引用就不允许删分类这种场景。SET_DEFAULT父表删除时子表外键设为默认值需要配合default参数。这些选项在项目一开始就要规划好因为一旦线上有真实数据再改删除策略就非常麻烦要写数据迁移脚本。模型定义完之后的流程是# 1. 生成迁移文件不执行SQL python manage.py makemigrations # 2. 查看SQL语句可选确认生成的SQL符合预期 python manage.py sqlmigrate news 0001 # 3. 执行迁移 python manage.py migrate我强烈建议你每次都makemigrations之后看一眼它生成的迁移内容确认新增了哪些表、改了哪些字段。因为有时候模型字段写错了比如外键关联错了模型本来是要关联Category结果关联成了Usermakemigrations会照样生成迁移但数据完全错位。我在做新闻模块的时候就这么翻过一次车后面养成了每个改动都检查迁移文件的习惯。4.2 查询、删除对象的方法和坑Django的ORM是项目里使用频率最高的部分所以我把它单独拿出来说。查询和删除对象的核心方法其实不多但是组合方式非常多而且性能陷阱不少。最常用的链式查询# 获取单个对象不存在或者多于一个都会抛异常 news News.objects.get(id12) # 获取多个对象返回QuerySet惰性查询 active_items LostFoundItem.objects.filter(statusactive) # filter是且关系如果要多条件且有一个条件是or就要用Q对象 from django.db.models import Q result News.objects.filter( Q(category__name校园新闻) | Q(is_topTrue), statuspublished, )第一个坑就是get和filter的区别。get返回的是单个模型实例filter返回的是QuerySet即便filter结果只有一条也是QuerySet。在模板里一个是直接用一个要再循环一遍或者用.first()取出。如果你在视图里写了item LostFoundItem.objects.filter(id1)然后模板里写{{ item.title }}那一定会报错显示不出来因为QuerySet没有title属性。这种情况应该写成.filter(id1).first()或者直接get_object_or_404(LostFoundItem, id1)。第二个坑是惰性查询。QuerySet在真正执行遍历之前不会访问数据库。如果你先filter再修改某个对象再遍历QuerySet得到的数据很可能是不一致的。我调试过很多次都是类似问题后来养成了一个习惯在循环之前用list()强制求值避免和后续的数据库写操作互相干扰。删除对象# 单个删除 news News.objects.get(id12) news.delete() # 批量删除满足条件的记录全部删除返回(删除总数, 各模型删除数量字典) deleted_count, detail News.objects.filter(statusdraft).delete()批量删除要注意两个点。第一filter返回的QuerySetdelete()会执行批量删除它会先加载所有被删对象因此对每一条都触发关联的级联删除所以数据量大时比较消耗性能正常毕设项目数据量不大不用担心。第二delete()不会自动调用每个模型重写的save()或delete()方法如果你重写了这些方法去更新缓存或者日志批量删除时它们不会被调用。这属于比较进阶的内容答辩时你说出来会加分。第三个坑是contains模糊查询。如果你用News.objects.filter(title__contains考试)在SQLite和MySQL下都能正常实现LIKE模糊匹配但如果数据库是PostgreSQL推荐用search或trigram_similar做全文检索。我建议毕设直接用__contains和__icontains后者不区分大小写更常用。4.3 reverse与resolve的使用场景很多学Django的同学一开始会把URL分发和视图的关系理解成一条单向链路用户在浏览器输入URLDjango的urls.py匹配到视图函数视图运行完返回响应。这个理解没错但在项目中我们经常会反过来用——已知视图函数的名称和参数想得到对应的URL字符串这时就要用reverse。最常见的场景是视图处理完POST请求后做重定向。比如发布失物招领成功后要跳转到该失物招领的详情页。详情页的URL是/lost_found/3/但我不能在视图里硬编码这个路径因为如果以后URL规则改了这里也要跟着改非常容易漏。正确方式是给URL规则起名字然后用reverse解析# urls.py app_name lost_found urlpatterns [ path(lost_found/, views.list_item, namelist), path(lost_found/int:pk/, views.detail_item, namedetail), ]# views.py from django.urls import reverse from django.shortcuts import redirect def create_item(request): ... item form.save() # 重定向到详情页 return redirect(reverse(lost_found:detail, args[item.pk]))这里用了app_namelost_found然后reverse(lost_found:detail)带参数就放在args里。使用namespane的命名空间之后就算多个app里都有detail这个名字也不会冲突。模板里也可以用url标签实现同样的效果a href{% url lost_found:detail item.pk %}{{ item.title }}/a这样模板代码就耦合在URL配置上改URL规则的时候不用去模板里一个一个改。我在做新闻详情跳转、商品收藏回跳、帖子回帖跳转这些场景里全部用的reverse和url命名后期改了一次URL格式只改了urls.py别的完全不用动非常舒服。resolve是和reverse相反的方向。reverse是从视图名拿URLresolve是从URL拿匹配到的视图函数信息。这个在调试的时候特别好用比如你访问一个页面报错你怀疑是URL匹配问题可以在shell里这样测试from django.urls import resolve match resolve(/lost_found/12/) print(match.func) # function detail_item at 0x... print(match.kwargs) # {pk: 12}还有一个场景是中间件或者自定义装饰器里想判断当前请求被路由到了哪个视图从而做不同的处理resolve就派上用场了。总体来讲reverse是日常开发必备技能resolve是排查疑难杂症的利器两者都建议掌握。5. 宝塔部署与毕设答辩避坑5.1 宝塔面板部署Django的全过程开发跑得好好的项目一旦上线部署就容易出幺蛾子我自己也踩了不少坑。我用的是宝塔面板部署因为毕设需要演示给导师看部署到一台公网服务器上会方便很多而且宝塔面板对新手足够友好。先说流程部署Django项目我推荐的方案是Nginx Gunicorn Django MySQL/SQLite。Nginx负责接收用户请求、托管静态文件和媒体文件Gunicorn作为Python的WSGI服务器运行Django程序。宝塔面板可以很方便地安装Nginx然后在Python项目管理器或者使用命令行方式安装Gunicorn。先在项目虚拟环境里安装Gunicornpip install gunicorn然后在项目根目录使用Gunicorn启动让它在后台运行gunicorn campus_site.wsgi:application --bind 0.0.0.0:8000 这里campus_site.wsgi:application指代你的项目wsgi.py文件里的application对象bind绑定到8000端口。测试的时候可以先直接访问http://服务器IP:8000如果能看到页面说明Gunicorn已经跑起来了。下一步是在宝塔面板里添加一个站点域名或者IP都行然后把Nginx配置成反向代理server { listen 80; server_name your_server_ip; # 静态文件直接由Nginx托管不走Django location /static/ { alias /root/campus_site/static/; } location /media/ { alias /root/campus_site/media/; } # 其他请求转发给Gunicorn location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }这个配置里最关键的是location /static/。Django在生产模式DEBUGFalse时不会处理静态文件必须由Nginx直接提供所以要先执行python manage.py collectstatic这个命令会把所有app里static目录下的文件以及Django admin自带的JS/CSS全部收集到settings.py配置的STATIC_ROOT目录中。我的settings里这样配置STATIC_URL /static/ STATIC_ROOT os.path.join(BASE_DIR, static) MEDIA_URL /media/ MEDIA_ROOT os.path.join(BASE_DIR, media)部署到生产环境时还有几个settings必须改否则网站会直接暴露错误详细信息非常危险DEBUG False # 这里要写成你的域名或者服务器IP ALLOWED_HOSTS [your_server_ip, www.yourdomain.com]我把DEBUG改成False之后遇到过一次页面CSS全丢的情况排查了好久才发现是collectstatic之后Nginx配置里的static路径和STATIC_ROOT对不上。建议你部署的时候用命令nginx -t检查配置文件再用systemctl reload nginx重新加载然后强制刷新浏览器缓存避免旧静态文件缓存导致误判。如果你需要频繁更新代码建议用宝塔的Git部署或者写一个简单的shell脚本每次git pull然后重启Gunicorn和Nginx一键完成cd /root/campus_site git pull origin main source venv/bin/activate pip install -r requirements.txt python manage.py migrate python manage.py collectstatic --noinput pkill gunicorn gunicorn campus_site.wsgi:application --bind 0.0.0.0:8000 --daemon最后宝塔面板里一定要记得放行80端口和8000端口的防火墙规则。我第一次部署完之后浏览器一直打不开跑去宝塔检查发现端口没放行白折腾了一个晚上。5.2 高频面试题与答辩要点毕设做完之后紧接着就是论文和答辩。我在准备答辩的时候总结了一些高频考察点这些内容同时也是Django面试的常见题目一起列出来给你做个参考。第一个问题是讲一下Django的请求生命周期。这是一个很标准的问法也是必须拿下的基础题。完整流程是浏览器发请求 → Nginx接收 → 如果匹配到静态文件就直接返回否则转发给Gunicorn → Gunicorn将请求交给Django的WSGI接口 → 请求经过中间件 → URLconf路由解析 → 调用视图函数 → 视图函数通过ORM操作数据库、渲染模板或者返回JSON → 响应再经过中间件 → 返回给浏览器。答辩的时候能画出这个流程图且讲清楚每一步基本就证明你对框架有整体理解了。第二个问题是ORM是什么和原生SQL有什么优缺点。ORM就是把Python类映射成数据库表把对象操作翻译成SQL语句所以你不必直接写SQL。优点很明显跨数据库兼容性好代码可读性高还能自动处理防SQL注入。缺点就是对于特别复杂的查询ORM生成的SQL可能不够优化需要手动RawSQL或原生SQL。我当时的项目里没有特别复杂的查询所以全程用ORM就够了。第三个问题是如何理解Django的MTV架构。Model负责数据处理Template负责页面展示View负责业务逻辑。和传统MVC相比Django把Controller的角色并入了View再加上URLconf做分发。重点是用你自己的项目模块举例比如新闻模块的模型、模板、视图分别是哪些文件这样答出来就非常实。第四个问题是中间件在项目中用过哪些。Django的中间件是处理请求和响应的钩子常见的有SessionMiddleware、AuthenticationMiddleware、CsrfViewMiddleware。我在项目里自定义了一个访问日志中间件记录每个请求的IP、URL和耗时用来做简单的访问统计。这个例子既实用又能展示你对中间件机制的理解。第五个问题是如何防止SQL注入和XSS。Django的ORM参数化查询天然防住了SQL注入模板默认开启自动转义可以防止大部分XSS。但如果你用了富文本编辑器正文里确实需要存储HTML这时候一定要用Django的bleach库或者类似方案清洗掉危险标签或者限定允许的标签白名单。答辩的时候主动提这个点很加分。具体到答辩演示你至少要准备三条演示路径第一条是前台正常浏览展示新闻列表、课程信息、失物招领等第二条是用户登录后发布信息和收藏课程第三条是管理员登录后台审核和管理内容。演示的时候边点边讲背后的表结构和业务逻辑证明这个项目真的是你做的而且你对里面的实现细节有清晰的认识。最后再分享一个答辩前的小技巧把你的项目在服务器上跑稳然后提前准备好一张技术架构图和一张数据库ER图答辩的时候直接投屏讲解。一张图能讲清楚的事情比你在台上说十分钟还管用。我自己答辩那天的经验是导师通常不会纠缠特别偏门的代码细节更多是在确认你能不能把整个系统的数据流向和模块关联讲清楚。把上图讲明白了项目这一块的分数基本就稳了。