多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Django兴趣班预约系统源码解析:从数据模型到并发事务的毕设实战

Django兴趣班预约系统源码解析:从数据模型到并发事务的毕设实战 简介这份资源是面向高校学生与编程学习者的Django兴趣班预约管理系统完整项目包可作为毕业设计、课程设计、大作业或工程实训的参考方案帮助解决选题难、功能模块不完整、缺少可运行代码等问题。压缩包共735个文件约19.91MB涵盖39个py后端源码、41个vue前端组件、53个css样式、164个js脚本以及sql数据库文件、docx与pptx文档等前后端分离结构清晰便于按模块阅读与二次开发。系统基于Python3.7、Django、MySQL5.7与Vue实现包含管理员、教师、学生三种角色管理员负责教师、学生、课程、公告等信息的增删改查与批量操作教师可查看课程并审核学生的预约与取消申请学生可搜索课程、提交预约时长与原因、查看审核状态并取消预约。已有1891人学习下载适合希望掌握Django全栈开发、理解预约审核业务流程的进阶学习者参考借鉴。1. 从一份 Django 兴趣班预约系统源码说起它到底能解决什么问题每到学期初培训机构的教务老师最头疼的不是排课而是家长在微信群里刷屏报名——周三下午四点还有名额吗我家孩子换到周六上午行不行。手工登记在 Excel 里改一次要翻三张表退课还得挨个核对。基于 Django 的兴趣班预约管理系统本质就是把课程—时段—名额—学员这四件事用数据库约束住让报名、退课、名额扣减在一个事务里完成而不是靠人脑记。这套毕设源码通常包含 Django 工程、SQL 建表脚本和一份设计文档适合两类人一是正在做 Python 毕设、需要一套能跑通、能答辩的完整项目二是想借一个小型业务系统把 Django 的 ORM、模板、表单校验串起来的新手。它不复杂但麻雀虽小预约冲突、并发扣名额、状态流转这些真实问题一个都不少正好拿来练手。2. 拆开这套 Django 预约系统数据模型与核心表怎么设计2.1 四张核心表撑起整个预约逻辑不管源码里表名怎么起兴趣班预约的骨架跑不出这四张表课程表Course、班次/时段表Schedule 或 ClassSlot、学员表Student、预约记录表Booking。课程表存课程名、老师、总课时班次表存具体上课时间、容量上限、已报名人数学员表存姓名、联系方式预约记录表是核心它把学员和班次关联起来并带一个状态字段待确认/已确认/已取消。这里有个设计取舍已报名人数到底存不存。存的话查询快但每次报名都要同步更新容易和预约记录对不上不存的话每次实时 count数据永远准但班次多了会慢。我一般建议在班次表留一个enrolled字段做冗余同时用数据库事务保证它和预约记录一致这也是大多数毕设源码的做法。# models.py 核心模型示例 from django.db import models class Course(models.Model): name models.CharField(max_length100, verbose_name课程名) teacher models.CharField(max_length50, verbose_name授课老师) total_lessons models.IntegerField(default0, verbose_name总课时) class Schedule(models.Model): course models.ForeignKey(Course, on_deletemodels.CASCADE, verbose_name所属课程) start_time models.DateTimeField(verbose_name上课时间) capacity models.IntegerField(default20, verbose_name容量上限) enrolled models.IntegerField(default0, verbose_name已报名人数) # 冗余字段 class Student(models.Model): name models.CharField(max_length50) phone models.CharField(max_length20, uniqueTrue) class Booking(models.Model): STATUS ((pending, 待确认), (confirmed, 已确认), (canceled, 已取消)) student models.ForeignKey(Student, on_deletemodels.CASCADE) schedule models.ForeignKey(Schedule, on_deletemodels.CASCADE) status models.CharField(max_length10, choicesSTATUS, defaultpending) created_at models.DateTimeField(auto_now_addTrue) class Meta: unique_together (student, schedule) # 防止同一人重复预约同一班次逻辑说明unique_together是防重复预约的第一道闸数据库层面直接拦住同一学员对同一班次的二次提交。enrolled冗余字段配合事务使用报名时先select_for_update锁行再自增避免并发下超卖。参数上capacity默认给 20 是常见小班规模实际按机构情况改on_deletemodels.CASCADE表示课程删了班次跟着删如果业务上不允许删课程改成PROTECT更稳妥。2.2 用 SQL 建表脚本对照理解 ORM 到底生成了什么毕设包里一般会附一份.sql文件很多人直接导入就不管了。其实拿它和 Django 的makemigrations生成结果对照是理解 ORM 最快的方式。下面这段是预约记录表在 MySQL 里的典型建表语句CREATE TABLE booking ( id bigint NOT NULL AUTO_INCREMENT, status varchar(10) NOT NULL DEFAULT pending, created_at datetime(6) NOT NULL, schedule_id bigint NOT NULL, student_id bigint NOT NULL, PRIMARY KEY (id), UNIQUE KEY uniq_student_schedule (student_id, schedule_id), KEY booking_schedule_id_fk (schedule_id), CONSTRAINT booking_schedule_id_fk FOREIGN KEY (schedule_id) REFERENCES schedule (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;逻辑说明UNIQUE KEY对应模型里的unique_togetherFOREIGN KEY对应ForeignKey。注意student_id和schedule_id上单独建了索引因为按学员查我报了哪些课、按班次查这个班有谁都是高频查询。字符集用utf8mb4而不是utf8否则学员姓名里的生僻字或 emoji 会报错这是血泪经验。参数上bigint是 Django 3.2 之后 AutoField 的默认类型老版本可能是int导入脚本前先确认版本对得上。2.3 报名扣名额一个必须放进事务的操作报名这个动作看着简单实际包含三步查班次是否还有名额、写预约记录、班次已报名数加一。三步之间只要有一丝并发就会出现名额显示还剩 1 个两个人同时点都成功了的超卖。正确做法是放进transaction.atomic()并对班次行加锁from django.db import transaction from django.core.exceptions import ValidationError def book_schedule(student_id, schedule_id): with transaction.atomic(): # select_for_update 锁住这一行其他事务排队等待 schedule Schedule.objects.select_for_update().get(idschedule_id) if schedule.enrolled schedule.capacity: raise ValidationError(该班次名额已满) if Booking.objects.filter(student_idstudent_id, schedule_idschedule_id).exists(): raise ValidationError(你已预约过该班次) Booking.objects.create(student_idstudent_id, schedule_idschedule_id, statusconfirmed) schedule.enrolled 1 schedule.save(update_fields[enrolled])逻辑说明select_for_update()是行级锁必须放在事务里才生效它保证同一时刻只有一个请求能读到并修改这个班次。update_fields[enrolled]只更新这一个字段避免把整个对象写回去覆盖别的并发修改。参数上如果用的是 SQLiteselect_for_update支持有限毕设演示够用但真上生产建议换 MySQL 或 PostgreSQL。退课时反向操作把enrolled减一同时把预约记录状态改成canceled而不是直接删记录——保留历史才能查谁什么时候退的。3. 把项目在本地跑起来环境、依赖与启动步骤3.1 Python 与 Django 版本怎么选才不翻车拿到源码第一步不是急着runserver而是先看requirements.txt或文档里写的 Django 版本。Django 2.x、3.x、4.x 之间差异不小比如url()在 4.0 之后基本被path()取代ugettext_lazy也改了名。新手最容易踩的坑就是 Python 3.10 配 Django 2.2直接报兼容错误。稳妥组合是 Python 3.83.10 配 Django 3.2LTS 长期支持版或者 Python 3.10 配 Django 4.2。安装命令# 建议先建虚拟环境别污染全局 python -m venv venv # Windows 激活 venv\Scripts\activate # macOS / Linux 激活 source venv/bin/activate # 安装依赖版本以 requirements.txt 为准 pip install django3.2.18 pip install mysqlclient # 用 MySQL 时 # 或者用轻量的 sqlite毕设演示足够逻辑说明虚拟环境是后悔药装崩了直接删掉重建不影响系统 Python。mysqlclient在 Windows 上编译经常失败替代方案是pymysql在__init__.py里加pymysql.install_as_MySQLdb()即可。参数上如果只是本地跑通看效果直接用 SQLite 最省事把settings.py里DATABASES的 ENGINE 改成django.db.backends.sqlite3连数据库都不用装。3.2 数据库配置与 SQL 文件导入如果坚持用 MySQL先在 Navicat 或命令行里建库再导入附带的.sql文件。注意导入顺序先建库、再导表结构、最后导数据如果有。命令行方式# 建库字符集必须是 utf8mb4 mysql -u root -p -e CREATE DATABASE interest_class DEFAULT CHARSETutf8mb4; # 导入 sql 文件 mysql -u root -p interest_class interest_class.sql逻辑说明DEFAULT CHARSETutf8mb4一定要写否则中文和特殊字符会乱码。导入后进 Django 的settings.py改数据库连接DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: interest_class, USER: root, PASSWORD: 你的密码, HOST: 127.0.0.1, PORT: 3306, OPTIONS: {charset: utf8mb4}, } }参数说明OPTIONS里的charset和建库时保持一致两边不一致照样乱码。如果导入 SQL 后运行migrate报表已存在说明 SQL 文件里已经建好了表这时要么跳过 migrate要么先删表让 Django 自己建。常见做法是SQL 文件只用来参考表结构实际让 Django 通过migrate生成避免两边字段对不上。3.3 迁移、建管理员、启动服务数据库通了之后标准三步走python manage.py makemigrations # 生成迁移文件 python manage.py migrate # 应用到数据库 python manage.py createsuperuser # 建后台管理员按提示输账号密码 python manage.py runserver # 启动默认 127.0.0.1:8000逻辑说明makemigrations把模型变化翻译成迁移脚本migrate才真正动数据库两步分开是为了让你有机会检查生成的 SQL 对不对。createsuperuser建的是 Django Admin 后台账号登录/admin就能直接管理课程、班次、预约记录毕设答辩时演示这个后台很加分。启动后如果页面样式全丢多半是静态文件没配好检查settings.py里STATIC_URL和模板里的引用路径。4. 预约系统避坑与排查五个真实翻车现场4.1 现象两个人同时报名名额超了原因报名逻辑没加事务和行锁两个请求都读到enrolled19都判断没满都加一结果变成 21。解决按 2.3 的方式用transaction.atomic()包住并对班次行select_for_update()。验证方法是开两个浏览器同时点或者写个简单脚本并发请求看最终enrolled有没有超过capacity。4.2 现象中文姓名存进去变成问号原因数据库、表、连接三处字符集不统一常见是建库用了utf8而连接没指定。解决建库用utf8mb4settings.py的OPTIONS里加charset: utf8mb4已有表用ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4转换。这个坑在导入别人 SQL 文件时尤其常见因为文件本身的编码你控制不了。4.3 现象退课后名额没释放别人报不进来原因退课只改了预约记录状态忘了把班次的enrolled减一或者减了但没放进同一个事务中途报错导致数据不一致。解决退课和报名对称处理同一个事务里改状态、减计数。另外注意别用delete()删预约记录删了就没法追溯用状态字段标记取消才是正路。4.4 现象migrate报 no such column 或表不存在原因模型改了但没生成迁移或者 SQL 文件建的表和模型定义对不上。解决先makemigrations看有没有新迁移再migrate。如果 SQL 文件和模型冲突以模型为准删掉冲突的表让 Django 重建。排查时用python manage.py showmigrations看哪些迁移没应用用python manage.py sqlmigrate app 0001看具体会执行什么 SQL。4.5 现象后台能登录但看不到预约数据原因模型没注册到 admin或者注册了但list_display里写了不存在的字段。解决在对应 app 的admin.py里用admin.register(Booking)注册list_display只写模型里真实存在的字段名。如果报字段错误Django 会明确告诉你哪个字段找不到照着改就行。5. 让这套系统更像能交付的作品三个进阶技巧5.1 用状态机管住预约流转别让状态乱跳毕设里最常见的偷懒是把status当普通字符串随便改结果出现已取消的预约又被确认这种脏数据。进阶做法是给状态流转加约束只允许pending → confirmed、pending → canceled、confirmed → canceled其他一律拒绝。可以在模型save()里判断也可以单独写个transition方法ALLOWED { pending: [confirmed, canceled], confirmed: [canceled], canceled: [], } def change_status(booking, new_status): if new_status not in ALLOWED.get(booking.status, []): raise ValidationError(f不允许从 {booking.status} 变到 {new_status}) booking.status new_status booking.save(update_fields[status])逻辑说明ALLOWED字典把合法流转写死任何越界操作直接抛异常。这样即使前端传了乱七八糟的状态值后端也拦得住。参数上如果业务需要取消后重新预约那就不是改状态而是新建一条记录历史记录保持只读。5.2 名额查询用 annotate 一次算清别在模板里循环查新手常犯的错是在模板里对每个班次schedule.booking_set.count()班次一多就是 N1 查询页面卡到怀疑人生。正确做法是用annotate在数据库层一次算完from django.db.models import Count, F schedules Schedule.objects.annotate( bookedCount(booking, filterQ(booking__statusconfirmed)), remainingF(capacity) - F(enrolled) ).filter(remaining__gt0)逻辑说明annotate把统计下推到 SQL一次查询拿到所有班次的已报名数和剩余名额filter(remaining__gt0)直接筛出还有位置的班次。参数上filterQ(...)只统计已确认的预约待确认和已取消的不算数这个细节决定了名额显示准不准。5.3 答辩前必做的一次完整回归我自己的习惯是交付前一定手动走一遍全流程建课程 → 建班次 → 学员报名 → 名额减一 → 退课 → 名额加一 → 后台看记录状态。每一步都截图存好答辩时按这个顺序演示比临场乱点稳得多。另外把DEBUG在演示环境保持True方便看报错但文档里要写清楚生产环境必须改False并配ALLOWED_HOSTS这是基本素养。数据库记得提前备份一份演示前如果数据被点乱了直接还原别在现场手忙脚乱地改。这套系统不难难的是把边界情况都想到把状态和数据一致性守住——这也是它能写进简历、经得起追问的原因。希望帮到你。本文还有配套的精品资源点击获取
返回列表