☰
Django多功能校园网站开发实战:前后端分离架构与核心功能实现
2026/10/1 3:14:38 网站建设 项目流程

1. 项目设计与选型:为什么是Django,为什么是前后端分离

“基于Django的多功能校园网站”这个标题,几乎是计算机毕业设计里最经典的组合之一。每年都有大量学生选它,但真正做出区分度、能拿优秀成绩的并不多。我刚带完几个毕设项目,今天就把这个题目从选题逻辑、技术选型到代码实现、答辩准备,完整拆一遍。

先说选题逻辑。校园网站本身的需求非常明确:用户注册登录、新闻通知、课程信息、失物招领、社团活动、论坛交流……这些功能贴近实际生活,需求好理解,评委一看就知道是真实场景,不会问出“你这个系统解决了什么痛点”这种让人不好回答的问题。更重要的是,这些模块天然适合拆分成“用户、内容、互动”三条线,每一条线都能用上Django的核心能力——ORM、DRF、分页、搜索、权限控制,做完全套下来,技术栈的覆盖面足够,论文也有东西可写。

再说技术选型。为什么首选Django而不选Flask?我跟很多学生说过一句话:Flask适合做玩具,Django适合做系统。这句话虽然绝对,但对毕设来说很实用。毕设有明确的截止时间,你的精力要优先花在“功能完整、逻辑正确、文档齐全”上,而不是纠结“我这个路由该怎么组织、这个ORM要自己封装一层吗”。Django内置Admin后台、ORM、表单校验、认证体系、Session管理、迁移工具,开箱即用,能帮你省下大量时间。而且到答辩现场,评委大概率会问“为什么用Django”,这时候你可以答出“因为Django内置了完整的MVT架构和ORM,开发效率高,安全机制相对完善”,远比“因为网上教程多”要体面得多。

至于前后端分离,这是当前Web开发的主流形态,也是很多企业真实项目的架构。分离之后,前端负责页面渲染和交互,后端只提供JSON接口,两边可以并行开发。校园网站在这个架构下还有一个额外的好处:未来如果要出小程序或移动端App,后端接口可以直接复用,不用重写。这一点写到论文的“扩展性分析”里,是很加分的论述。

需要说明的是,“前后端分离”不等于“不需要Django模板”。Django的模板引擎依然强大,但在前后端分离模式下,后端代码里基本只做接口返回,不写HTML。常用的搭配是 Vue.js / React + Element UI / Ant Design + axios,后端用Django Rest Framework(DRF)提供RESTful API。很多学生会纠结“我不会前端怎么办”,实际上Vue的学习曲线比想象中平缓,配合Element UI这种组件库,做一个后台管理界面和前端展示页面,不需要你会设计,拖拖组件就能做出来。

2. 核心技术与环境准备:从零搭起项目的每一步

2.1 环境与依赖:版本选对了能少踩一半坑

这一节很重要,先说结论:Django版本不要选最新,选稳定版本。以当前生态来说,Django 4.2 LTS是最稳妥的选择,它修复了历史版本的大量问题,同时兼容Python 3.10到3.12。别去追Django 5.x,很多第三方库可能还没适配,你在网上搜资料时也容易碰到版本不匹配的问题,明明是照抄的代码,跑起来却报错,浪费了大量时间。

具体安装流程我用的是这样的顺序:

python -m venv venv # 创建虚拟环境,避免污染全局Python venv\Scripts\activate # Windows激活(Linux/macOS用 source venv/bin/activate) pip install django==4.2.9 pip install djangorestframework==3.14.0 pip install django-cors-headers==4.3.1 pip install pymysql==1.1.0 # 如果要用MySQL pip install pillow # 图片处理,后面文件上传/头像功能必须 pip install djangorestframework-simplejwt==5.3.0 # JWT认证

我实测过的稳定组合是:Python 3.11 + Django 4.2.9 + DRF 3.14 + SimpleJWT 5.3。这个组合在网上能找到大量配套教程,出问题时可以通过搜索快速解决,这对毕设项目来说是极重要的考量。

数据库的选择上,如果只是毕设演示,SQLite完全够用,但考虑到很多学校推荐的数据库是MySQL,这里建议直接用MySQL。毕设答辩环境里,SQLite可能会出现一个尴尬的问题:你拷了数据库文件过去,文件路径变了导致打不开。MySQL就不会有这种问题。不过要注意,MySQL 8.x默认的认证插件是caching_sha2_password,PyMySQL对它的支持不如mysqlclient,但如果你用PyMySQL 1.1.0以上版本,配合Django 4.2基本不会出问题。

另外,务必配置一个.gitignore文件,把venv目录和本地配置文件排除在外。你可能会觉得毕设又不需要协同开发,但相信我,答辩前你一定会改很多次代码,有一个版本管理意识会让你少很多痛苦。哪怕不用Git,至少把项目压缩包的命名加上日期,比如campus_site_20250601.zip,免得最终答辩时找不到“最新最终版”。

2.2 创建Django项目:目录结构的合理规划

命令行先创建项目:

django-admin startproject campus_site cd campus_site python manage.py startapp users python manage.py startapp news python manage.py startapp courses python manage.py startapp lost_found python manage.py startapp forum

很多学生拿到Django之后喜欢把所有功能塞进一个app里,这其实是个坏习惯。校园网站虽然不算大型系统,但按功能域拆分成多个app,好处是显而易见的:每个app只负责自己的数据模型和视图逻辑,后期改动某一模块时不会牵动全局。如果出了Bug,你也能更快锁定问题出现在哪个模块。

创建完后的项目结构大概是这样:

campus_site/ ├── manage.py ├── campus_site/ # 项目配置目录 │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── users/ # 用户模块 ├── news/ # 新闻通知模块 ├── courses/ # 课程模块 ├── lost_found/ # 失物招领模块 └── forum/ # 论坛模块

配置文件settings.py是重中之重,需要修改的关键项包括:

INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', # DRF 'corsheaders', # 跨域配置 'users', 'news', 'courses', 'lost_found', 'forum', ] MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', # 位置放在前面 # ... 默认中间件 ] # 数据库配置(MySQL) DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'campus_site', 'USER': 'root', 'PASSWORD': '你的密码', 'HOST': '127.0.0.1', 'PORT': '3306', } } # 允许跨域 CORS_ALLOW_ALL_ORIGINS = True # 开发环境先这样,上线再收紧 CORS_ALLOW_CREDENTIALS = True # JWT认证配置 REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework_simplejwt.authentication.JWTAuthentication', ), }

CORS_ALLOW_ALL_ORIGINS = True只在开发阶段这么设。这是因为前后端分离下,前端跑在localhost:8080,后端跑在localhost:8000,端口不同就触发了浏览器的同源策略,如果不放开跨域限制,你前端发过去的请求都会被浏览器拦下来。等部署到服务器之后,建议把这个改成明确的允许域名列表,比如CORS_ALLOWED_ORIGINS = ['https://yourdomain.com']。

2.3 数据库建模:把校园网站的核心表设计好

Django的ORM允许你用Python类直接描述数据库表结构,然后通过迁移命令生成真实的表。这种设计方式对学生来说非常友好,因为你不必手写SQL,而且Django会根据你的模型自动创建主外键关系。

以多功能校园网站为例,核心数据表至少包含这几张:

用户表(users_user)——扩展Django自带的AbstractUser,增加学院、学号、手机号、头像等字段,这样既能复用Django完备的用户认证逻辑,又能存储校园业务所需的额外信息。

新闻表(news_article)——标题、正文、封面图、发布人、发布时间、浏览量。这里要注意发布时间建议用auto_now_add,让数据库自动写入创建时间,前端展示时格式化好即可。

课程表(courses_course)——课程名称、授课教师、上课地点、上课时间、学分、限选人数和已选人数。选课逻辑可以写成事务,保证同一门课不会出现超选的情况。

失物招领表(lost_found_item)——物品名称、物品图片、丢失/拾获地点、描述、联系方式、状态(待认领/已找回)。这个模块是“多功能”的加分项,评委看到会眼前一亮,因为很多学生的校园网站只做了新闻和课程,缺少这种贴近生活的功能。

论坛帖子表(forum_post)——标题、内容、发帖人、发布时间、点赞数、回复数。回复表可以单独建,用外键关联到帖子。

以下是用户表和新闻表的核心代码示例,其余表的结构可以举一反三:

from django.contrib.auth.models import AbstractUser from django.db import models class User(AbstractUser): student_id = models.CharField(max_length=20, unique=True, null=True, blank=True, verbose_name='学号') college = models.CharField(max_length=50, null=True, blank=True, verbose_name='学院') phone = models.CharField(max_length=11, null=True, blank=True, verbose_name='手机号') avatar = models.ImageField(upload_to='avatars/', null=True, blank=True, verbose_name='头像') class Meta: db_table = 'users_user' verbose_name = '用户' verbose_name_plural = '用户' def __str__(self): return self.username
from django.db import models from django.contrib.auth import get_user_model User = get_user_model() class Article(models.Model): title = models.CharField(max_length=200, verbose_name='标题') content = models.TextField(verbose_name='正文') cover = models.ImageField(upload_to='covers/', null=True, blank=True, verbose_name='封面图') author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='articles', verbose_name='发布人') category = models.CharField(max_length=50, choices=( ('notice', '通知公告'), ('news', '校园新闻'), ('activity', '活动信息'), ), default='news', verbose_name='分类') views = models.PositiveIntegerField(default=0, verbose_name='浏览量') created_at = models.DateTimeField(auto_now_add=True, verbose_name='发布时间') class Meta: db_table = 'news_article' ordering = ['-created_at'] verbose_name = '新闻资讯' verbose_name_plural = '新闻资讯' def __str__(self): return self.title

这里想特别说一个细节:新闻表的content字段用TextField还是用JSONField?这取决于你的富文本方案。如果前端用的是wangEditor这类富文本编辑器,它提交给后端的是带HTML标签的字符串,那么TextField就够用。如果你希望正文以结构化方式存储,那可以用JSONField。但建议直接用TextField,因为简单可靠,前端v-html渲染一下就是排版好的格式。

3. 核心功能模块实现:从登录认证到联调发布

3.1 用户注册与JWT认证:前后端分离下的会话保持

前后端分离架构下,传统的Session-Cookie方式很别扭,前后端接口分离后不便维护登录状态。更主流的做法是用JWT(JSON Web Token):用户登录成功后,后端返回一个加密的Token,前端把它存在LocalStorage或Pinia/Vuex中,之后每次请求都带着这个Token,后端验证通过即放行。

在Django中用SimpleJWT实现这个过程很顺。安装完成后,视图代码只需要这样写:

from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView # urls.py 中注册 urlpatterns = [ path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), ]

这样前端就能通过POST请求/api/token/,把用户名密码拿过去,换取一对令牌:access和refresh。access的有效期建议设置为2小时,refresh设置为7天。为什么不用单个长有效期Token?因为一旦泄露,长时间有效会让别人可以一直冒用你的身份。所以主流做法是短token搭配长refresh,快过期时前端自动刷新,用户无感知。

有同学问过:SimpleJWT默认的username/password登录不够用,我想支持学号登录怎么做?可以自定义一个TokenObtainPairSerializer,然后改造视图类。这个属于进阶操作,有基础的同学可以试一下。

注册接口我用DRF的序列化器来实现,关键代码如下:

from rest_framework import serializers from .models import User class RegisterSerializer(serializers.ModelSerializer): password = serializers.CharField(write_only=True, min_length=6, error_messages={'min_length': '密码长度不能少于6位'}) class Meta: model = User fields = ['id', 'username', 'password', 'student_id', 'college', 'phone'] def create(self, validated_data): password = validated_data.pop('password') user = User(**validated_data) user.set_password(password) # 对密码做哈希加密,绝不能存明文 user.save() return user

注意create方法里用了set_password(),这是Django对密码加密的标准方式,默认使用PBKDF2算法。很多毕设项目这里容易犯一个错:直接把前端传上来的password赋值给user,导致密码明文存进数据库。答辩时如果评委点开数据库看到明文密码,那基本就告别高分了。哪怕你自己不在意安全,答辩老师不可能不在意。

3.2 新闻通知模块:接口分页、搜索与浏览计数

新闻列表、搜索、详情是校园网站的门面功能。这里展现的技术点主要有三个:

第一,ListView视图的分页。Django Rest Framework自带分页机制,配置一下settings就能用:

REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'PAGE_SIZE': 10, }

前端传?page=1、?page=2,后端返回的数据结构里会带count(总条数)和next、previous链接,前端只要解析这组JSON,渲染分页组件即可。

第二,搜索结果。可以用Django的Q对象实现模糊搜索:

from django.db.models import Q from rest_framework.views import APIView class ArticleListView(APIView): def get(self, request): keyword = request.query_params.get('search', '') category = request.query_params.get('category', '') articles = Article.objects.all() if keyword: articles = articles.filter( Q(title__icontains=keyword) | Q(content__icontains=keyword) ) if category: articles = articles.filter(category=category) # 之后交给分页器处理

icontains会生成LIKE查询,这样标题中包含关键词的文章就能被搜到。

第三,浏览量的累加。这里有个容易被忽略的细节:浏览量应该“点击详情时+1”,还是在列表页就不加?正确做法是详情接口中修改,但如果每次打开详情都加,用户可以刷页面刷出高浏览量,看起来就很假。我建议限制为每个用户对同一篇文章的多次访问在一定时间段内只计一次。简单实现就是用一个decorator,通过session判断。不过为了简单,我用了这样一个思路:

from django.utils import timezone from datetime import timedelta def record_view(request, article_id): key = f'article_viewed_{article_id}' last_view = request.session.get(key) if not last_view or (timezone.now() - datetime.fromisoformat(last_view)) > timedelta(hours=2): Article.objects.filter(id=article_id).update(views=F('views') + 1) request.session[key] = timezone.now().isoformat()

3.3 选课模块:事务与并发限制

选课功能是校园网站里最有“技术含量”的模块之一,也是论文里可以重点展开描写的地方。先看核心代码:

from django.db import transaction from django.db.models import F @transaction.atomic def select_course(request, course_id): course = Course.objects.select_for_update().get(id=course_id) if course.selected_count >= course.max_students: return Response({'error': '该课程已满员'}, status=400) Selection.objects.create(user=request.user, course=course) Course.objects.filter(id=course_id).update(selected_count=F('selected_count') + 1) return Response({'success': True})

这段代码有几个要点:

用select_for_update()锁定该行记录,防止两个用户同时看到剩余名额为1而同时抢课。如果用先查询,再判断,最后更新的普通方式,并发量大时会出现超选。毕设虽然压测量不大,但写对事务逻辑,答辩时评委可能会揪着并发场景问。

用F('selected_count') + 1做原子更新,这能保证“读取-计算-再写入”三个步骤不会被并发操作打断。如果用read-then-write方式,偶数并发时结果可能出问题。

选课记录表和课程表之间要有唯一约束,保证一个学生对同一门课只选一次。可以在Selection模型的Meta里加unique_together = ['user', 'course']。

3.4 失物招领与论坛模块:文件上传和嵌套评论

失物招领模块是典型的“图片+基础字段”场景。图片上传需要配置MEDIA路径:

# settings.py MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media') # 主urls.py from django.conf import settings from django.conf.urls.static import static urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

上传的图片会存在media目录下,访问url是/media/lost_items/xxx.jpg。这里务必注意一个问题:开发环境下Django能自己处理media文件,但如果部署到生产服务器,媒体文件必须交给Nginx等服务器来处理,Django不适合扛静态资源。写论文的部署章节时,这一点要说明白,否则导师会认为你对部署链路理解太浅。

论坛模块可以做回复功能。一种简单的方法是建Post和Comment两张表,Comment通过外键关联Post和User。前端展示时,一次性取出该帖子所有的回复,在前端按时间线渲染。如果非要做楼中楼,建议用parent字段做自关联,渲染时前端做递归展示,不要在后端写复杂递归。

3.5 实时推送与WebSocket:让校园网站“活”起来

如果你想让校园网站更出彩,可以在新闻发布模块中加入WebSocket实时推送。比如管理员发布了一条紧急通知,前端页面不需要刷新就能收到提示。Django生态里,Django Channels是WebSocket的标准实现,它能让Django像处理HTTP请求一样处理WebSocket协议。

具体实现流程是:

pip install channels pip install channels-redis

然后在settings.py里加上channels的配置,把ASGI_APPLICATION指向你的路由文件。当你通过Admin或后台发布新闻时,在save方法中发送一个channel_layer消息,前端通过WebSocket订阅这个频道,收到消息后弹出提示。

这里有一种特殊情况——官方发布新闻后如何推送给在线用户?Django Channels的group_send机制允许你按分组广播。你可以把所有在线用户都加入一个news_group,发布时group_send一下,前端就能收到实时通知。这个功能对“多功能”这个词的含金量加成很大,也适合写成论文的“系统亮点”章节。

当然这个功能在中大型项目中才有明显体验提升,如果你的毕设时间紧张,可以不做WebSocket,但如果你做完基础模块还有富余时间,强烈建议加上,答辩效果会比单纯CRUD好不少。

4. 前后端联调与常见问题排查实录

4.1 跨域问题的完整排查思路

前后端分离项目,十个有九个第一次联调都挂在跨域上。现象是前端axios请求一直报错,浏览器控制台显示CORS policy,或者干脆请求都是404。

排查步骤是这样的:

第一,确认后端CORS中间件已安装且环境正确。django-cors-headers装好后,中间件必须放在MIDDLEWARE列表里且尽量靠前。

第二,确认CORS_ALLOW_ALL_ORIGINS = True在开发环境设置。但注意,如果你同时设置了CORS_ALLOW_CREDENTIALS = True,某些版本的django-cors-headers会强制要求你不能用ALLOW_ALL_ORIGINS,必须指定具体域名。这可能产生一个很隐蔽的坑:前端明明配置了withCredentials,却发现跨域还是失败。

第三,区分“预检请求”和“实际请求”。当你的请求不是简单的GET/POST且携带了自定义Header如Authorization时,浏览器会先发一个OPTIONS请求,后端要能正确响应这个OPTIONS。django-cors-headers会自动帮你处理OPTIONS请求,但如果你的URL配置对这个路径没有匹配,OPTIONS请求也会404,从而卡住后续请求。

所以我常建议:前端统一封装axios,都走一个api实例,baseURL指向后端地址。这样后续如果要调整线上接口地址,只改一处就行。封装代码大概是:

import axios from 'axios' const api = axios.create({ baseURL: 'http://127.0.0.1:8000/api', timeout: 5000, }) api.interceptors.request.use(config => { const token = localStorage.getItem('access') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) export default api

4.2 查询性能与N+1问题

校园网站数据量不大,但答辩时如果你的新闻列表页很慢,会非常拉低印象分。出现这个问题的常见原因就是N+1查询。比如新闻列表页面展示作者名称时,如果这样写:

articles = Article.objects.all()

随后序列化器里每次取article.author.username,就会对每一条新闻都执行一次作者表的查询。看起来代码很干净,实际上每查10条新闻,Django总共会执行11条SQL。解决办法是使用select_related,它会通过JOIN把外键字段一并查出来:

articles = Article.objects.select_related('author').all()

课程模块如果有外键指向教师表,同理也用select_related。这个优化对几百条数据来说可能感觉不明显,但评委一旦问你“这个查询有没有性能隐患”,你答出“我用select_related做了预加载,避免N+1查询”,你的技术深度分就上去了。

4.3 数据库迁移问题:改models不生效怎么办

开发过程中你大概率会频繁修改models字段,比如加了学院字段,又删除一个联系方式字段,然后python manage.py migrate报错,提示Column 'xxx' doesn't exist。这个问题的根源是:你删除了某个模型里的字段,但数据库中还有对应的列,Django的迁移系统没法自动帮你删干净。

我给你的建议是:尽量把表结构一次设计到位,后面主要通过新增字段来演进,少删字段。如果确实要恢复,可以执行以下命令重置app的迁移:

python manage.py migrate users zero python manage.py makemigrations users python manage.py migrate

这个操作会把这个app的所有表删掉重建,数据会丢,所以只适用于开发阶段。如果你的数据已经测试过,就不建议这样做了,宁可写一个数据迁移脚本也不要去重置。

4.4 文件上传踩坑:图片不显示或访问404

这个问题在失物招领模块里最容易出现。前台上传图片后,img标签的src是/media/xxx.jpg,但浏览器显示404。排查步骤是:

先看media目录下文件是否存在,如果存在说明上传成功,问题出在静态文件服务。开发环境下服务器需要配置好MEDIA_URL访问映射。如果你用的是python manage.py runserver,默认会自动处理;但如果你的前端是独立跑在Vite的8080端口,那么你访问的路径是http://localhost:8080/media/xxx.jpg,Vite服务器根本没有这个文件,自然404。正确的做法是前端img标签的src直接指向后端域名,也就是http://localhost:8000/media/xxx.jpg,而不是相对路径。

4.5 前后端联调时的时间格式化问题

Django默认的DateTimeField返回给前端时是这样的格式:2025-06-01T10:30:00.123456Z。前端直接用new Date()解析没问题,但Element UI的日期组件显示时默认要的是yyyy-MM-dd格式。不同前端页面处理时间格式的方式五花八门,有的用dayjs,有的用自定义函数,很容易出现“详情页时间格式和列表页时间格式不一致”这种细节错误。

建议后端的序列化器里统一返回格式化后的时间字符串,专门写一个处理时间的序列化方法:

class ArticleSerializer(serializers.ModelSerializer): created_at = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S') # ...

这样所有时间都长成一个样,避免前端自我发挥。

4.6 前端的token过期刷新与状态保持

JWT模式的一个常见问题:access token过期后,用户可能还停留在页面上,点击某个按钮时突然收到401错误,然后整个页面就“卡死”了。比较好的做法是在axios响应拦截器里统一处理401:

api.interceptors.response.use( response => response, error => { if (error.response && error.response.status === 401) { return refreshToken().then(() => { error.config.headers.Authorization = `Bearer ${newToken}` return api(error.config) }) } return Promise.reject(error) } )

简单写法是:401时,调用refresh接口拿new_token,然后重新发送刚才失败的请求。如果refresh也失败,就清除本地token并跳回登录页。这一步做到位,答辩时前端交互体验很加分。

5. 部署上线、文档写作与答辩准备的完整建议

5.1 经典部署链路:nginx + uwsgi/asgi + mysql

毕设验收通常要求“能跑”,但如果你能把项目真正上线到服务器,那答辩效果完全是另一个层级。最经典的部署链路是:

第一步,在服务器上装好Python环境,拉代码进目录,用虚拟环境装依赖。

第二步,用uWSGI或Gunicorn跑Django后端。uWSGI配置一个campus.ini:

[uwsgi] chdir = /path/to/campus_site module = campus_site.wsgi:application master = true processes = 4 socket = /tmp/campus_site.sock chmod-socket = 664 vacuum = true

这里用的是socket文件而不是端口,这是为了和Nginx配合。Nginx的用户必须有权限访问这个sock文件,否则会报502。很多时候学生部署失败就是因为忘了chmod-socket = 664。

第三步,Nginx做反向代理和静态资源服务:

server { listen 80; server_name 你的域名或IP; location /api { include uwsgi_params; uwsgi_pass unix:///tmp/campus_site.sock; } location /media { alias /path/to/campus_site/media; } location / { root /path/to/dist; try_files $uri $uri/ /index.html; } }

这里有一个关键点:/location /是前端静态文件目录,也就是你Vue项目build之后生成的dist目录。如果你没有单独部署前端,想快速演示,也可以把dist目录放到这里直接让Nginx托管,这样前后端共享同一个80端口,跨域问题在正式环境里也就顺带解决了。

如果你用了前面提到的Django Channels做WebSocket,那么还需要在Nginx的配置里加上对WebSocket升级协议的支持:

location /ws/ { proxy_pass http://127.0.0.1:8000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; }

5.2 文档报告与源码注释:毕设评分的隐藏大头

很多学生对源码和文档的态度是“能交就行”,但毕设评分里论文/文档占比通常能达到50%以上,这个比例是很多人不知道的。代码写得再好,文档一塌糊涂,分数一样上不去。

Django项目的论文大纲大致是:摘要 → 绪论(背景、意义、国内外现状) → 相关技术介绍(Django、Rest Framework、Vue、MySQL、前后端分离概念) → 系统需求分析(用例图、功能需求、非功能需求) → 系统设计(架构图、ER图、功能模块划分) → 系统实现(每个模块的截图+核心代码片段说明) → 系统测试(测试用例表+结果分析) → 总结与展望。

代码注释上,建议重点类和方法写docstring,比如:

def create(self, validated_data): """创建用户,使用set_password对密码进行哈希加密,不保存明文"""

这样的注释既能让评委快速读懂你的代码,也能在代码讲解环节直接引用。不要写一堆没意义的注释,什么“# 定义一个变量”这种反而拉低印象分。

还有一点容易被忽视:README文件。在项目根目录写一个条理清晰的README,包括运行前需要改哪些配置、数据库初始化方式、账号密码、运行命令,这样答辩时换一台电脑也能快速启动项目。如果评委看到你连README都没有,连打开项目都不知道怎么操作,那体验会很差。

5.3 答辩准备:被追问时如何不慌张

答辩时评委最爱追问的问题,我总结了高频的几个,提前准备好就能从容应对:

为什么用Django而不用Spring Boot?——Django生态成熟,内置ORM和Admin,对Python开发者友好,开发效率高,适合中小型Web系统的快速交付。

什么是前后端分离?它和传统MVC模式有什么区别?——前后端分离指后端只提供API,前端负责页面渲染与交互,通过JSON进行数据交互。职责清晰、并行开发、易于扩展多端。

Token和Session有什么区别?JWT的优势是什么?——Session存储在服务端,需要扩展存储;JWT是无状态的,用户认证信息在Token中自带,适合分布式部署。但JWT也有无法立即注销的缺点,所以设置了较短有效期。

数据库表结构为什么这么设计?有没有考虑索引?——你的news表可以按created_at建索引,users表按student_id建唯一索引。答出这一点,能证明你考虑了查询效率。

你的系统如何保证数据安全?——用户密码哈希存储、JWT过期机制、后端接口权限校验、前端路由守卫,这几个点可以展开讲。

如果评委让你现场改一个功能,比如“把新闻列表改成按点击量排序”,你只要在视图里改一行Order_by('-views')就行,事先想想这类改动可能会考哪些,提前在代码里留活口,临场就不会慌。

5.4 这个题目的后续扩展:让毕设价值最大化

多功能校园网站做完之后,想要继续扩展的方向不少。首先是移动端适配,因为很多学生都在校园里用手机访问题库或选课系统,你可以做一个H5响应式版本,或干脆用uni-app打包成小程序。后端接口已经是RESTful的,前端换一个壳就能复用。其次是消息中心功能,把失物招领的认领通知、课程表变动提醒汇总到站内信。还可以接入邮箱验证码或短信验证码,提升注册校验的安全度。

如果学有余力,可以把数据统计做成可视化大屏,用ECharts展示新闻发布趋势、选课热度等,这类图放论文里非常抓眼球。

我个人在实际操作中体会最深的一点是:毕设项目的价值不在于惊艳的技术堆砌,而在于你能把一条完整的业务链路做闭环。用户注册、发布内容、权限控制、异常处理、部署上线,每一环都亲身趟过一遍,这个收获比论文分数本身重要得多。哪怕过程中踩坑无数,这些坑才是你答辩时能从容回答问题的底气。

最后再分享一个小技巧:开题之后第一时间去把数据库表和接口定义清楚,拿给导师看一眼,确认没问题再开始写代码。很多学生闷头写了一两周,最后发现模块设计和导师预期完全不符。前期多沟通五分钟,后期就能省五个晚上。这个题目本身不难,难的是做得完整、做得扎实。只要你把基础功能做稳,再加一个像失物招领、选课事务、WebSocket推送这样的亮点,评分自然会有明显优势。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询