ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Django的学生选课系统:从数据库设计到部署答辩全解析

基于Django的学生选课系统:从数据库设计到部署答辩全解析 1. 毕设选题前先把“学生选课系统”这事想透每年到毕设季都会有学弟学妹来问我选什么题目我的建议一直是宁可做一个小而完整、逻辑闭环的系统也别碰那种看起来高大上、实际上自己根本讲不清楚的大项目。学生选课系统就是一个典型的“小而完整”选题——它在业务上有明确的角色划分在技术上有清晰的CRUD主线在展示上又方便演示和截图应付开题、中期、答辩这三个环节都非常顺手。先说清楚这一版系统到底做了什么这是基于Django框架实现的选课系统角色分为学生、教师和管理员。学生可以浏览课程、选课、退课、查看已选课程和成绩教师可以发布课程、维护课程信息、录入学生成绩管理员负责管理学生账号、教师账号、课程审核以及系统基础数据。整体业务不算复杂但该有的模块都有而且每一条链路都能跑通。技术栈用的是Django SQLite后续可以无缝切换到MySQL前端是Django模板引擎配合Bootstrap没有引入重型前端框架这样不管是你自己写还是后期讲解都更容易把逻辑讲清楚。项目结构按照Django的MVT模式组织models负责数据模型views负责业务逻辑templates负责页面渲染forms负责表单校验天然就适合用来回答答辩时“你是怎么理解MVC/MVT”这类经典问题。如果你正准备做毕设或者只是想通过一个完整的Web项目来练手Django这篇内容会从数据库设计、核心功能实现、前端页面处理、部署演示、答辩准备五个方面把这一套选课系统的实现思路和踩坑记录完整拆开讲一遍。文章里的所有方案都基于真实可运行的Django项目实践不是那种只有空壳代码的“玩具项目”。2. 数据库建模一张ER图定下整个系统的地基2.1 核心实体与字段设计选课系统的数据库设计其实非常典型它涉及到用户、课程、选课关系、成绩这几个核心实体下面我把每个模型的关键字段和设计理由列出来这也是答辩时老师几乎必问的部分。User模型用户我用的是Django自带的AbstractUser扩展在原有基础上增加user_type字段区分学生、教师、管理员三种角色。用AbstractUser而不是自定义全新的用户模型原因是Django内置的认证系统登录、session、密码哈希可以直接复用省下大量造轮子的工作量而且后期扩展权限体系也更方便。from django.contrib.auth.models import AbstractUser from django.db import models class User(AbstractUser): USER_TYPE_CHOICES ( (1, 管理员), (2, 教师), (3, 学生), ) user_type models.IntegerField(choicesUSER_TYPE_CHOICES, default3, verbose_name用户类型) student_id models.CharField(max_length20, blankTrue, nullTrue, verbose_name学号) teacher_id models.CharField(max_length20, blankTrue, nullTrue, verbose_name工号) class Meta: verbose_name 用户 verbose_name_plural verbose_name这个模型的关键细节是同时预留了student_id和teacher_id两个字段但用user_type来做判定。这样做的好处是后面无论是用Django自带的login_required做登录保护还是用自定义装饰器做角色权限控制判断逻辑都非常直观。Course模型课程课程表是整个系统的核心资源字段包括课程名称、课程代码、授课教师、学分、上课时间、上课地点、容量上限、已选人数、课程简介等。这里有一个很容易被忽略的坑——授课教师字段的外键关联。很多学生会把教师字段直接关联到User表但没有限制只能关联教师角色导致数据库层面无法保证数据正确性。class Course(models.Model): course_code models.CharField(max_length20, uniqueTrue, verbose_name课程代码) name models.CharField(max_length100, verbose_name课程名称) teacher models.ForeignKey(User, on_deletemodels.CASCADE, limit_choices_to{user_type: 2}, verbose_name授课教师) credit models.FloatField(verbose_name学分) schedule models.CharField(max_length200, verbose_name上课时间) location models.CharField(max_length100, verbose_name上课地点) capacity models.IntegerField(verbose_name容量上限) enrolled models.IntegerField(default0, verbose_name已选人数) description models.TextField(blankTrue, verbose_name课程简介) status models.IntegerField(default1, choices((1, 开放选课), (0, 已关闭)), verbose_name选课状态) class Meta: verbose_name 课程 verbose_name_plural verbose_name def __str__(self): return f{self.course_code} - {self.name}limit_choices_to{user_type: 2}是Django ORM提供的外键候选限制它本身不是数据库级约束但在后台管理页面和表单校验层能起到极大的便捷作用。当然业务代码里还是要写一层防御逻辑我稍后会讲到。Selection模型选课记录这是选课系统的核心关联表负责记录“哪个学生选了哪门课”。字段包括学生外键、课程外键、选课时间、退课时间可选、成绩允许为空。这里必须提一个关键设计成绩字段到底是放在选课记录里还是单独建一张成绩表我最终决定放在选课记录里。原因是在真实教务系统里一门课的成绩确实只跟“某个学生选这门课”这个事实绑定放在Selection里天然就是一一对应的不需要额外的关联查询。如果单独建成绩表反而要维护外键关系增加系统的复杂度而且对于毕设来说完全没有必要。class Selection(models.Model): student models.ForeignKey(User, on_deletemodels.CASCADE, related_nameselections, verbose_name学生) course models.ForeignKey(Course, on_deletemodels.CASCADE, related_nameselections, verbose_name课程) selected_at models.DateTimeField(auto_now_addTrue, verbose_name选课时间) grade models.FloatField(nullTrue, blankTrue, verbose_name成绩) class Meta: unique_together (student, course) verbose_name 选课记录 verbose_name_plural verbose_nameunique_together这个约束非常重要它从数据库层面保证了“同一个学生不能重复选同一门课”。如果没有这一条仅靠业务代码去查重在高并发场景下虽然毕设一般不会有但你要让老师看到你有这个意识会出现重复选课的问题。2.2 业务逻辑中的并发与事务问题选课系统的经典业务挑战是并发选课下的超额问题。想象一下课程容量是50人当前已选49人两个学生同时在最后一秒提交选课请求。如果没有任何防护两个人的请求都读到“已选49人”然后都执行“已选人数1写回”最终课程记录变成51人超载了。常见解决方案有两种思路第一种是通过select_for_update()加行级锁在开启事务的前提下锁定课程记录这是最直接的做法from django.db import transaction transaction.atomic def select_course(request, course_id): course Course.objects.select_for_update().get(pkcourse_id) if course.enrolled course.capacity: return JsonResponse({code: 1, msg: 课程已满员}) if Selection.objects.filter(studentrequest.user, coursecourse).exists(): return JsonResponse({code: 2, msg: 请勿重复选课}) Selection.objects.create(studentrequest.user, coursecourse) course.enrolled 1 course.save(update_fields[enrolled]) return JsonResponse({code: 0, msg: 选课成功})第二种是用Django F表达式做原子更新不需要手动加锁from django.db.models import F course Course.objects.filter(pkcourse_id, enrolled__ltF(capacity)).update(enrolledF(enrolled) 1) if course 0: return JsonResponse({code: 1, msg: 选课失败课程可能已满员})我实际项目里用的是第一种方案因为select_for_update()的逻辑更容易在答辩时讲清楚先锁住资源再检查、再修改符合传统数据库事务的直觉。而且在MySQL下性能完全够用。要切换到PostgreSQL或MySQL时需要保证数据库支持行级锁SQLite在低并发下也能正常工作但毕设演示完全没问题这点不用过度担心。2.3 为什么用Django自带User而不是自定义用户表很多毕设项目会把用户表设计成自己的独立表然后所有验证逻辑全部自己写。我的观点是能用Django自带的AbstractUser就绝不要自己造用户模型。Django的auth应用已经帮你做好了密码加密PBKDF2/SHA256、会话管理session、权限系统permissions、后台管理admin这些功能如果全部自己实现工作量远超你想象的。而且你在答辩时可以说“密码字段我采用的是Django认证系统的密码哈希算法数据库里不存明文”这是一个很加分的表述。再补充一个细节Django 5.x开始部分方法比如is_authenticated从方法变成了属性有细微变化如果你的代码是基于Django 4.x写的在迁移到Django 5.x时要注意检查。我自己的项目用的是Django 4.2 LTS版本这是一个长期支持版本稳定性有保障建议做毕设的同学直接用4.2 LTS或者更新的稳定版不要追最新特性而忽视生态兼容性。3. 核心功能模块从登录权限到选课退课每条链路都有细节3.1 登录、注册与角色权限控制系统的入口是登录页。我实现了学生和教师共用一套登录入口用户名密码验证通过后根据user_type跳转到不同的首页。核心代码如下from django.contrib.auth import authenticate, login from django.shortcuts import render, redirect def login_view(request): if request.method POST: username request.POST.get(username) password request.POST.get(password) user authenticate(usernameusername, passwordpassword) if user is not None: login(request, user) if user.user_type 2: return redirect(teacher_dashboard) elif user.user_type 1: return redirect(admin_index) else: return redirect(student_dashboard) else: return render(request, login.html, {error: 用户名或密码错误}) return render(request, login.html)这里有一个毕设高频问题“你是怎么处理权限控制的”如果你的回答只是“根据用户类型跳转到不同页面”那显然太单薄了。完整方案需要配合Django的装饰器对每个视图函数做角色校验from django.contrib.auth.decorators import login_required from django.core.exceptions import PermissionDenied def student_required(view_func): login_required def wrapper(request, *args, **kwargs): if request.user.user_type ! 3: raise PermissionDenied(仅学生可以访问) return view_func(request, *args, **kwargs) return wrapper自定义装饰器的好处是每个业务视图只需要加一行student_required整个函数就有了登录校验和角色校验两层保护。模板里做按钮级控制时配合{% if request.user.user_type 3 %}判断是否显示“选课”按钮即可这样即使URL被猜到了后端的权限拦截也会兜底。关于注册功能我建议做一个简化版本学生可以自助注册但注册时必须填写学号教师账号由管理员后台创建。这样一方面控制注册入口的合规性另一方面也避免答辩时被问到“谁都可以注册教师账号怎么办”这种尴尬问题。3.2 选课与退课前端交互和后端校验的配合选课页面是系统里最核心也最容易被老师反复查看的页面。我设计了两种选课入口课程列表页直接点击“选课”按钮以及课程详情页里的大按钮选课。两条入口都走同一个后端接口只是URL参数不同。前端按钮的交互要处理好“三种状态”未选显示“选课”、已选显示“已选”并且禁用、课程已满显示“已满”并禁用。后端渲染时直接把状态字段传给模板# views.py def course_list(request): courses Course.objects.filter(status1) selected_ids Selection.objects.filter(studentrequest.user).values_list(course_id, flatTrue) if request.user.user_type 3 else [] course_data [] for course in courses: course_data.append({ course: course, selected: course.id in selected_ids, full: course.enrolled course.capacity, }) return render(request, course_list.html, {course_data: course_data})这里提一下一次查询的优化细节——用values_list(course_id, flatTrue)把已选课程ID集合一次性取出然后转换成Python的set做判断避免在循环里反复查询数据库也就是避免N1查询问题。虽然课程量不大时性能差距不明显但这是答辩老师非常喜欢听的一个性能优化点。退课功能的逻辑正好反过来检查选课记录是否存在存在就删除记录并将课程enrolled减1。在做删除之前我用了一个transaction.atomic包裹保证“删除选课记录”和“已选人数减一”这两个操作要么同时成功要么同时回滚避免数据不一致。from django.db import transaction transaction.atomic def drop_course(request, course_id): course Course.objects.select_for_update().get(pkcourse_id) deleted_count, _ Selection.objects.filter(studentrequest.user, coursecourse).delete() if deleted_count 0: return JsonResponse({code: 1, msg: 你还没有选这门课}) course.enrolled F(enrolled) - 1 course.save(update_fields[enrolled]) return JsonResponse({code: 0, msg: 退课成功})3.3 成绩管理教师录入与学生查看的权限分隔教师端成绩录入是很多人会忘记做的模块但它恰恰是完整业务闭环的重要一环。没有成绩模块的话系统就只有选课和退课缺少一条“结课出成绩→学生查看成绩”的后续链路会在答辩时暴露业务逻辑的不完整性。教师端成绩录入页面按课程维度展示选课学生列表每条记录后面有一个分数输入框支持批量保存。这里要注意成绩的更新/插入用update_or_create方法比先查后改更能避免脏数据。if request.method POST: grade_data request.POST.dict() for key, value in grade_data.items(): if key.startswith(grade_): selection_id key.split(_)[1] try: selection Selection.objects.select_for_update().get( pkselection_id, course__teacherrequest.user ) grade float(value) if value else None if grade is not None and (grade 0 or grade 100): return JsonResponse({code: 1, msg: 成绩必须在0-100之间}) selection.grade grade selection.save(update_fields[grade]) except Selection.DoesNotExist: return JsonResponse({code: 1, msg: 数据异常请刷新页面重试})学生端查看成绩时只显示自己的课程和分数用Selection.objects.filter(studentrequest.user).select_related(course)一次查询拿到关联课程信息避免循环查询。显示时对成绩做一次“绩点”换算这种延伸功能虽然不复杂但在答辩时可以作为“业务拓展点”来讲述显得你除了完成基本功能还有主动思考。3.4 后台管理Django Admin的合理复用与自定义很多学生不愿意用Django Admin觉得“这是框架自带的写出来显得没技术含量”但我强烈建议一定要用Admin并且要自定义。Django Admin的价值在于你不需要为管理员单独写一套完整的管理前端注册模型之后增删改查、搜索、筛选、分页全都有了。你要做的核心工作是在admin.py里注册Course、Selection、User模型配置list_display、search_fields、list_filter让列表页更清晰对于Selection这种关联表配置好list_select_related避免列表页产生大量查询用admin的actions实现批量操作比如批量关闭课程、批量导入学生账号。admin.register(Course) class CourseAdmin(admin.ModelAdmin): list_display [id, course_code, name, teacher, credit, capacity, enrolled, status] search_fields [course_code, name, teacher__username] list_filter [status, credit] list_editable [status] actions [close_courses, open_courses] def close_courses(self, request, queryset): queryset.update(status0) self.message_user(request, f已关闭 {queryset.count()} 门课程) close_courses.short_description 关闭选课list_editable允许管理员直接在列表页勾选修改“选课状态”这一个功能就能在演示时省很多口舌——不用点进详情页列表页直接改字段视觉效果好逻辑也清楚。4. 前端页面与交互设计模板引擎、Bootstrap和AJAX4.1 基础布局与页面骨架Django的模板继承机制非常适合这个项目。我建了一个base.html作为公共骨架里面包含导航栏、Bootstrap CSS/JS引用、消息提示区域和内容块{% block content %}。所有子页面只需要继承它并填充自己的内容块即可。这样不但代码复用率高改导航栏时也只需要改一个文件。!-- templates/base.html -- !DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title{% block title %}学生选课系统{% endblock %}/title link hrefhttps://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.3.0/css/bootstrap.min.css relstylesheet /head body nav classnavbar navbar-expand-lg navbar-dark bg-primary !-- 导航栏内容 -- /nav main classcontainer mt-4 {% if messages %} {% for message in messages %} div classalert alert-{{ message.tags }}{{ message }}/div {% endfor %} {% endif %} {% block content %}{% endblock %} /main /body /html这里补充一个中文界面经常踩的坑页面显示乱码的地方多半不是Django的问题而是settings.py里LANGUAGE_CODE和TIME_ZONE没有配置好。建议设置LANGUAGE_CODE zh-hans、TIME_ZONE Asia/Shanghai、USE_TZ True。另外在HTML文件顶部务必加meta charsetUTF-8否则某些浏览器会用默认编码解析导致乱码。4.2 选课按钮的AJAX交互实现选课这个动作如果用传统表单提交每次点击都要刷新整个页面演示体验比较差。我用了原生JavaScript的fetch发送AJAX请求后端返回JsonResponse前端根据返回的code值决定是弹出成功提示还是失败提示。这里用原生JS而非jQuery或框架是为了让代码逻辑更容易讲解也不需要额外引用库。function selectCourse(courseId, btnElement) { fetch(/student/select/${courseId}/, { method: POST, headers: { X-CSRFToken: getCookie(csrftoken), Content-Type: application/json }, body: JSON.stringify({}) }) .then(response response.json()) .then(data { if (data.code 0) { alert(data.msg); btnElement.textContent 已选; btnElement.disabled true; // 更新已选人数显示 document.getElementById(enrolled_${courseId}).textContent parseInt(document.getElementById(enrolled_${courseId}).textContent) 1; } else { alert(data.msg); } }) .catch(error console.error(Error:, error)); } function getCookie(name) { let cookieValue null; if (document.cookie document.cookie ! ) { const cookies document.cookie.split(;); for (let i 0; i cookies.length; i) { const cookie cookies[i].trim(); if (cookie.substring(0, name.length 1) (name )) { cookieValue decodeURIComponent(cookie.substring(name.length 1)); break; } } } return cookieValue; }这一段getCookie函数是Django的CSRF防护对应的前端标配。很多新手会在AJAX请求上忘记送X-CSRFToken结果后端疯狂报403错误。虽然还有更优雅的csrf_exempt方案但我不建议为了省事而直接关掉CSRF防护——在答辩时被问到网络安全问题时保住CSRF防护是加分项关掉是减分项。4.3 响应式与美观度处理毕设系统的前端不需要炫技但要整体干净、看着舒服。我用的是Bootstrap 5的栅格系统和卡片组件课程列表以卡片形式展示课程名称、教师、学分、时间地点、容量进度条和选课按钮。Bootstrap的进度条天然适合展示“已选人数/容量”颜色还可以根据比例变化低于60%显示蓝色60%-90%显示黄色超过90%显示红色。这里有一个前端开发经验想分享样式文件不要写太多自定义CSS尽量复用Bootstrap的utility class比如text-muted、mt-3、card shadow-sm。自定义CSS越少后期改样式越轻松页面也越不容易出现布局塌陷的问题。真正需要自定义的往往只有导航栏颜色、卡片圆角等几个全局变量改样式时优先去改Bootstrap的CSS变量而不是写一大堆覆盖样式。5. 部署与运行从本地开发到线上演示一把梭的完整流程5.1 本地运行环境配置不管你的系统最终是提交文档、演示视频还是实际部署到服务器能在本地一键跑起来是基本要求。我习惯把整个流程写成README.md放在项目根目录读者拿到代码后按步骤执行即可复现。第一步是创建虚拟环境并安装依赖python -m venv venv source venv/bin/activate # Windows 下是 venv\Scripts\activate pip install django4.2.14 pillow这里注意如果你的系统里有图片上传功能比如课程封面就必须安装pillow库否则Django在迁移时使用ImageField会报错。没有图片字段的可以不用装但装了也无妨反正体积不大。第二步是数据库迁移和创建超级管理员python manage.py makemigrations python manage.py migrate python manage.py createsuperuser第三步是准备初始演示数据。为了让答辩演示不出现空荡荡的界面我写了一个fixtures/initial_data.json文件内置了几个学生账号、教师账号、十几门课程和若干选课记录通过一行命令即可导入python manage.py loaddata initial_data.json这里要特别提醒不要把SECRET_KEY、数据库密码等硬编码进settings.py后提交到Github虽然毕设项目泄露风险一般可控但要养成用环境变量或.env文件管理的习惯把这个作为独立的小亮点写进项目文档里import os from dotenv import load_dotenv load_dotenv() SECRET_KEY os.getenv(DJANGO_SECRET_KEY, django-insecure-default-key) DEBUG os.getenv(DJANGO_DEBUG, True) True5.2 线上部署方案Nginx Gunicorn SQLite的保守打法如果你需要部署到服务器给评委远程演示我推荐一个最保守、最不容易出问题的方案Nginx Gunicorn SQLite不引入Docker、不引入MySQL、不引入Redis。理由是毕设演示环境往往网络和资源有限组件越多出问题的概率越高而SQLite完全能支撑演示级并发。具体步骤如下在服务器上安装Python环境和Nginx命令略常规系统包管理即可把项目代码上传到服务器创建虚拟环境安装依赖执行迁移命令加载初始数据安装Gunicorn并启动pip install gunicorn gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 3 --daemon配置Nginx反向代理到8000端口并托管静态文件。Nginx配置里最容易踩的坑是静态文件路径。Django开发环境下静态文件由runserver直接处理部署后必须执行python manage.py collectstatic把所有静态文件收集到一个目录然后在Nginx的location /static/里指向这个目录否则页面CSS/JS全部丢失。server { listen 80; server_name your_domain_or_ip; location /static/ { alias /path/to/your/project/staticfiles/; } 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; } }5.3 部署验证清单部署完成后不要急着关终端按以下清单逐项测试访问首页确认登录页正常加载学生账号登录测试选课、退课确认AJAX请求没有跨域问题或403错误教师账号登录确认课程管理、成绩录入页面渲染正常管理员登录Django Admin确认模型管理页面可以增删改查数据直接访问一个不允许学生访问的URL确认权限拦截生效返回403页面而不是报错页面刷新多次选课请求确认并发场景下容量数据不会错乱。这个清单我建议也写进项目文档的“部署测试”小节答辩时直接说“我是按这个清单完成验证的”会让老师觉得你的项目工程化程度高而不只是“能运行”的水平。6. 代码讲解思路拿到这份源码后如何高效地讲清楚自己的项目6.1 从URL路由反推项目结构快速建立全局观很多同学拿到一套源码后喜欢按文件列表顺序从头看到尾比如从models.py一直看到views.py结果看到后面忘了前面。我的做法相反先从config/urls.py开始看。因为URL配置是整个系统的“入口地图”它显示了哪些路径由哪个应用、哪个视图函数处理看完urls就大致知道项目有几个模块、每个模块有哪些功能页面。以这个选课系统为例urls.py里可能包含这些路由urlpatterns [ path(admin/, admin.site.urls), path(, views.login_view, namelogin), path(logout/, views.logout_view, namelogout), path(student/dashboard/, student_views.dashboard, namestudent_dashboard), path(student/courses/, student_views.course_list, namecourse_list), path(student/select/int:course_id/, student_views.select_course, nameselect_course), path(student/drop/int:course_id/, student_views.drop_course, namedrop_course), path(student/my_courses/, student_views.my_courses, namemy_courses), path(student/grades/, student_views.grade_list, namegrade_list), path(teacher/dashboard/, teacher_views.dashboard, nameteacher_dashboard), path(teacher/course/manage/, teacher_views.manage_courses, namemanage_courses), path(teacher/course/create/, teacher_views.create_course, namecreate_course), path(teacher/course/int:course_id/grades/, teacher_views.manage_grades, namemanage_grades), ]看完URL后接下来按“一个视图函数对应一个页面”的方式逐个深入先看views.py里函数怎么取数据数据传给哪个模板模板里怎么展示。这样一个闭环就理解了不用把所有代码读完只需要对照自己讲解时的重点模块深入即可。6.2 答辩现场的讲解节奏与演示脚本答辩演示一般只有5-10分钟很多同学一上台就想把所有模块都点一遍反而像走马观花。我的建议是准备一个“演示脚本”按下面这个节奏走注册/登录30秒让学生账号登录强调登录用的是Django自带认证机制课程列表与选课2分钟浏览课程卡片点击选课选中后按钮变灰容量数字变化顺带提一句“这里用了AJAX请求页面无刷新更新数据”选课后看我的课表1分钟进入“我的课程”页面展示选课记录与课程信息然后退掉一门课再回去看容量下降教师端录成绩1.5分钟切换教师账号登录进入成绩录入页面给个别学生打分保存后提醒“等学生端刷新就能看见”学生端看成绩30秒回到学生账号刷新成绩页面展示刚录入的分数管理员后台1分钟登录Admin后台展示课程列表、筛选、关闭选课状态的操作。这个节奏的巧妙之处在于它完整展示了一条业务闭环从选课到退课到录成绩到查成绩每一步都有因果关联评委能快速理解系统的完整逻辑。如果只是零散地展示“这里有选课”“那里有课程管理”缺乏故事线效果会大打折扣。6.3 常见追问与高情商回答模板答辩会有几个高频问题提前准备好回答话术比现场临场发挥要稳得多“为什么用Django不用Flask”回答思路选课系统涉及多个业务模块的权限管理和模型关联Django自带ORM、Admin、认证系统、迁移工具开箱即用开发效率更高更适合一个完整的工程化项目。“课程表的已选人数和选课记录不会不一致吗”回答思路我在更新已选人数时使用了select_for_update()行级锁加事务原子性保证加减操作在同一事务中完成而且选课记录的增加和已选人数的递增是同步完成的。“如果把数据库换成MySQL需要改哪些地方”回答思路只需要在settings.py中修改数据库配置ORM层的模型代码基本不用改动因为Django的ORM在底层已经做了方言适配。这是个标准答案也是体现ORM优势的最佳回答。“系统有什么不足之处或改进方向”回答思路可以说目前缺少基于Redis的缓存层在大量并发选课场景下可以考虑引入消息队列削峰另外可以增加课程的选课时间窗口控制。主动承认不足并提供改进方案比硬着头皮说“这个系统已经很完美了”效果好得多。7. 从毕设到工程化这套系统还能延伸出哪些加分玩法写完一个能运行的选课系统只是第一步如果你有多余时间或者想让项目显得更饱满下面这几个延伸玩法成本不高、但答辩效果很好。玩法一增加Redis缓存热点课程。在课程列表页设置一个“热门课程排行榜”用Redis的Sorted Set存储课程访问热度每有用户查看课程详情就给该课程的score加1。代码量很少但能引出“缓存设计”“热点数据处理”等话题非常加分。import redis redis_client redis.StrictRedis(hostlocalhost, port6379, db0) def course_detail(request, course_id): redis_client.zincrby(hot_courses, 1, course_id) course get_object_or_404(Course, pkcourse_id) return render(request, course_detail.html, {course: course})玩法二加入Excel导入导出。教师批量录入学生名单或导入课程表是教务系统的刚需。用openpyxl库实现一个“导入课程Excel文件”的功能批量创建课程记录用csv模块实现“导出选课名单”直接在浏览器下载CSV文件。这个功能只需一个上传表单和一个解析循环但演示效果很强。玩法三仪表盘可视化。用Chart.js在教师端和管理员端做一个简单的统计面板展示每个院系的选课人数分布、各课程选课率、成绩分布直方图。Chart.js引入极其方便后端只需要提供一个JSON数据接口前端渲染图表代码量在100行以内但视觉冲击力很大尤其适合在答辩最后一页PPT切换时展示。我个人的建议是不要所有玩法都堆上去选一到两个和你的开题报告、项目文档里提到的方向一致的功能做延伸就够了。做太多反而增加讲不清楚的风险记住**“少而精”永远比“多而杂”更安全**。8. 写在最后的心得这一套学生选课系统做完最大的体会是毕设项目的难点从来不在某一个技术点的炫酷而在于把整条业务链路做顺、做完整、讲清楚。很多同学一开始痴迷于用什么高深算法、什么分布式架构最后连基本的登录认证和数据库关系都理不顺。反而是这种踏踏实实把用户、角色、课程、选课、退课、成绩这条主线一步步打磨完整的项目能让你在答辩时底气十足。代码本身的量级不算大但每一块功能背后都有值得深挖的细节并发选课怎么防超卖、CSRF防护怎么处理、外键字段怎么限制角色、Nginx静态文件怎么配置。把这些细节吃透你会发现哪怕只是一个小系统也足够体现一名工程人员在设计、编码、调试、部署全过程中的综合能力。这也是导师和评委真正想看到的——不是“会用框架”而是“懂设计、能动手、会思考”。如果你正准备动手做这个题目我的建议是先别急着写代码花两三天把数据库表设计好、把角色和权限梳理清楚、把页面流程画出来想清楚再动手。好的开始是成功一半这句话在毕设项目里尤其真实。
返回列表