Python课程设计选课系统源码深度拆解:从数据库设计到并发控制
简介本资源是高校《Python程序设计》课程的大作业实战项目——基于面向对象思想开发的课程选课系统面向Python初学者与课程实践者解决多角色协同管理学校、课程、讲师、学员及班级的核心业务建模问题。压缩包共53个文件含13个核心Python源码如admin_interface.py、student_interface.py、models.py等、18张功能界面截图涵盖管理员创建学校/课程、学生选课/登录、教师查名单/录成绩等关键操作、12个编译缓存文件及1个需求文档大作业要求.docx整体759KB结构清晰模块分层明确interface/core/conf/db等目录完整。已有50人学习下载提供完整可运行方案支持武汉/长沙双校区、三门课程差异化开课、角色权限隔离视图、全部数据通过pickle持久化存储并附带详细操作流程图与界面效果验证截图便于理解MVC雏形设计与真实业务落地逻辑。 Python 课程设计交作业的季节又到了。每年这个时候后台都能收到大量“选课系统源码”相关的搜索我自己读书时也写过这类系统现在也帮人看过不少份课程大作业。这个标题——Python 程序设计课程大作业-课程选课系统 Python 源码看起来就是一个很典型的综合实训项目但它背后的门道比一个“能跑的代码”要多得多。今天我结合自己带项目、评审课程设计的经验从需求拆解、数据库设计、核心业务实现到答辩避坑完整拆解一个合格甚至能拿高分的学生选课系统该怎么做。这门大作业不仅考察 Python 语法更考察你对业务流程的理解、数据建模能力、异常处理意识以及代码组织水平。很多同学能写出来但写不出“对”的系统——要么并发选课时数据乱了要么权限控制形同虚设要么时间冲突判断全是漏洞。这篇文章就是帮你把这些平时老师不细讲、书上也不展开的细节补齐。无论你是正在准备这门课设的学生还是想自己动手写一个完整 Web/桌面项目的初学者又或者是想找一份高质量参考源码的开发者今天的拆解都值得你耐心看完。我会把核心的原理、业务逻辑、数据库设计以及常见坑位都讲透。1. 项目整体设计与思路拆解1.1 选课系统到底在解决什么问题选课系统本质上是一个多人并发操作同一批资源的管理系统。用户分角色学生要查课、选课、退课、查成绩老师要开课、维护课程信息、导出选课名单管理员要管账号、管课程、管选课时间窗口。这背后有三个核心问题必须解决数据一致性、业务规则约束、权限隔离。很多同学一上来就写界面、写增删改查结果写到一半发现选课逻辑一团糟。正确的做法是先抽象出业务规则。比如每门课有容量上限选满就不能再选同一时间不能选两门课选课后总学分不能超过上限退课要释放名额选课窗口期内才能操作。这些规则不是靠界面 “判断一下” 就完事的它们必须在后端服务层统一执行并且要放在数据库事务里才能防止多个学生同时点选课时数据出错。我在帮别人 review 代码时见过最简单粗暴的实现把课程容量存在一个字段里选课的时候先select出来看看人数是不是小于容量小于就update加一。单机测没问题但只要两台电脑同时提交两个请求都读到“当前人数49”于是两个人同时通过判断最后人数变成 51超出容量。——这就是并发控制缺失造成的经典 bug也是课程答辩时老师最喜欢追问的点之一。1.2 技术栈选型背后的逻辑课程大作业的选型要考虑“出效果”和“控制复杂度”的平衡更要考虑你能不能在答辩时把原理讲清楚。我见过三种主流路线各有利弊。路线常用技术优点缺点适合人群桌面端 GUITkinter / PyQt5界面直观不依赖网络环境部署简单业务扩展性差并发演示困难观感略“学生气”Python 刚入门不久、数据库不太熟的同学轻量级 WebFlask / FastAPI Bootstrap前后端分离思路清晰界面现代方便演示需要理解 HTTP、请求上下文、Session/Cookie想拿高分、愿意多查资料的同学重型框架 WebDjango Django Admin自带 Admin 后台、ORM、认证体系开发效率极高框架封装多底层原理容易被遮蔽答辩被问到底层容易卡壳想快速产出完整项目、但需要额外补原理的同学个人建议大多数同学选择Flask SQLite/MySQL Bootstrap这条路。Flask 足够轻核心代码量完全在你自己手里答辩时你想讲哪块都能讲清楚Bootstrap 不费力就能出一个能看的界面数据库如果只用 SQLite免安装交作业时老师直接跑起来不依赖环境。有人会问“学习是不是应该用 PyQt 更简单”其实 GUI 方案的致命伤是选课场景本身就是服务端并发的典型场景桌面程序很难自然体现“多人同时使用”的业务属性。答辩时老师一句“你这系统怎么同时让多个学生登录操作”你就得现场演示开多个客户端比较尴尬。Web 方案天然符合场景浏览器一开就是多用户。1.3 项目目录结构规划避免一坨代码代码组织是大作业最容易拉开差距的地方。很多人写到最后变成一个几百行的单app.py这不是不行但老师翻到项目时心理预期已经降了一档。我的建议是至少按 model / view / controller 的思路分文件course_selection_system/ ├── app.py # 程序入口注册蓝图和扩展 ├── config.py # 配置项密钥、数据库地址、上传路径等 ├── models/ # 数据模型层 │ ├── __init__.py │ ├── user.py # 用户模型学生/老师 │ ├── course.py # 课程模型 │ ├── enrollment.py # 选课记录模型 │ └── announcement.py # 公告模型 ├── views/ # 蓝图路由层 │ ├── __init__.py │ ├── auth.py # 登录注册 │ ├── student.py # 学生端接口 │ ├── teacher.py # 教师端接口 │ └── admin.py # 管理端接口 ├── services/ # 业务逻辑层 │ ├── selection_service.py │ └── course_service.py ├── utils/ # 工具函数 │ ├── decorators.py # 登录校验装饰器 │ └── validators.py # 表单校验 ├── static/ # 静态文件CSS/JS ├── templates/ # Jinja2 模板 └── requirements.txt # 依赖清单分层的最大好处是每个函数都短逻辑清晰。老师不用从几十个函数里找选课逻辑答辩时你也能快速定位到selection_service.py里的核心方法打开就讲。2. 核心细节解析与实操要点2.1 数据库设计的核心五张表与四种关系我见过不少“一张大表存天下”的作业。当时看起来跑得通可一旦要做“查询某学生选了哪些课”“统计每门课选课人数”SQL 就写得极其痛苦。正确做法是拆分表让每张表职责单一。一个完整的选课系统至少需要五张表users用户表id、username、password_hash、rolestudent/teacher/admin、name、student_no/teacher_no、major、created_atcourses课程表id、course_code、name、credit、teacher_id外键、capacity、selected_count、schedule上课时间、classroom、semester、descriptionenrollments选课记录表id、student_id外键、course_id外键、statusnormal/dropped、selected_at、dropped_atannouncements公告表id、title、content、publisher_id、created_atsemesters学期表id、name、start_date、end_date、selection_start、selection_endenrollments表尤其重要。很多初学同学只建前两张表选课时直接在courses表里把selected_count 1退课时-1导致查不到“谁选过这门课”。这不仅会让老师导出名单时无从下手退课逻辑也会烂掉。正确的做法是选课记录作为中间表持久化selected_count字段只是一份冗余计数真正判断容量和退课时以enrollments表为准。这里贴一段表结构 DDL 供参考CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password_hash VARCHAR(128) NOT NULL, role VARCHAR(10) NOT NULL DEFAULT student, name VARCHAR(20) NOT NULL, student_no VARCHAR(20), major VARCHAR(50), created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE courses ( id INTEGER PRIMARY KEY AUTOINCREMENT, course_code VARCHAR(20) UNIQUE NOT NULL, name VARCHAR(50) NOT NULL, credit DOUBLE NOT NULL, teacher_id INTEGER NOT NULL, capacity INTEGER NOT NULL, selected_count INTEGER NOT NULL DEFAULT 0, schedule VARCHAR(50) NOT NULL, classroom VARCHAR(50) NOT NULL, semester VARCHAR(20) NOT NULL, description TEXT, FOREIGN KEY (teacher_id) REFERENCES users(id) ); CREATE TABLE enrollments ( id INTEGER PRIMARY KEY AUTOINCREMENT, student_id INTEGER NOT NULL, course_id INTEGER NOT NULL, status VARCHAR(10) NOT NULL DEFAULT normal, selected_at DATETIME DEFAULT CURRENT_TIMESTAMP, dropped_at DATETIME, FOREIGN KEY (student_id) REFERENCES users(id), FOREIGN KEY (course_id) REFERENCES courses(id), UNIQUE(student_id, course_id, status) );2.2 密码安全不能明文存储再说一个我复盘过很多次的细节密码处理。课程作业里 90% 的项目密码都是明文直接入库。但一旦老师问“如果数据库泄露了你怎么办”这个问题就是送命题。正确做法是用werkzeug.security的generate_password_hash和check_password_hash或者用hashlib加盐处理实际开发中还要考虑 bcrypt 或 argon2但课程阶段掌握 Werkzeug 就够了。from werkzeug.security import generate_password_hash, check_password_hash # 注册时 password_hash generate_password_hash(form.password.data) # 登录校验时 if not check_password_hash(user.password_hash, form.password.data): flash(密码错误)这里有个易忽略的细节不要把密码哈希计算放在数据库事务里执行因为哈希计算是 CPU 密集型操作会占用事务时间。正确做法是先算完哈希再开事务写入用户记录。2.3 “同一时间不能选两门课”的判断逻辑这是选择系统里最容易出 bug 的业务规则。首先schedule字段如果只存一个字符串比如周一 3-4节那程序怎么判断两个课程时间是否冲突纯字符串匹配只能比较是否完全相同却无法处理周一 1-2节和周一 2-3节这种部分重叠。我的建议是把课程时间格式规范化拆成weekdaystart_sectionend_section或者设计成 JSON 数组字段。比如{ weekday: 1, start: 3, end: 4 }这样判断时间冲突就变成区间重叠判断同一周几且新课程的起始节次落在已有课程的区间内或新课程的结束节次落在已有课程的区间内即视为冲突。代码可以写成def check_schedule_conflict(student_id, new_course, session): selected_courses ( session.query(Course) .join(Enrollment, Enrollment.course_id Course.id) .filter( Enrollment.student_id student_id, Enrollment.status normal, ) .all() ) for course in selected_courses: if courses_time_overlap(new_course.schedule, course.schedule): return True return False存储形式选好了展示时再格式化成周一 3-4节的友好文本即可。这个设计既保证了判断逻辑可计算又不影响界面展示。2.4 权限控制的三层防线权限控制是很多课程作业的薄弱点。常见错误是前端隐藏了“管理员入口”后端却不校验或者学生直接构造请求 URL 就能访问/admin/xxx页面。前端隐藏只是视觉层面的后端必须有硬校验。建议采用三层防线路由层装饰器定义login_required和role_required(admin)放在所有受保护路由上。这样即使 URL 被猜到没有正确身份也进不去。模型层查询限定查询数据时用当前登录用户 ID 作为条件防止水平越权比如学生 A 访问学生 B 的选课记录。操作层逻辑校验选课时再次确认学生有权限选这门课、当前在选课时间内、课程未满。以 Flask 为例一个简单的装饰器长这样from functools import wraps from flask import session, redirect, url_for, abort def role_required(*roles): def wrapper(func): wraps(func) def inner(*args, **kwargs): role session.get(role) if not role: return redirect(url_for(auth.login)) if role not in roles: abort(403) return func(*args, **kwargs) return inner return wrapper很多系统被老师一测就崩就是因为“改个 URL 就能蹭进管理后台”。加上装饰器后接口安全性能提升一大截。3. 实操过程与核心环节实现3.1 选课的核心事务防止超选和重复选选课场景最典型的并发问题是“超容量”和“同课重复选”。前者的本质是读改写三步操作之间插入了并发请求后者是没有唯一约束兜底。我的实现方案分两层第一层应用层开启数据库事务在事务内部先锁定课程行再检查容量然后插入选课记录最后更新计数器。第二层数据库层给选课记录表加UNIQUE(student_id, course_id)约束数据库层面杜绝同一条记录插入两次。下面是一段注释详细的选课逻辑伪代码def select_course(student_id, course_id): try: # 开启事务事务结束后统一提交 with db.session.begin_nested(): # 使用 with_for_update 锁定该课程行避免并发读同一旧值 course ( db.session.query(Course) .filter(Course.id course_id) .with_for_update() .first() ) if not course: raise BusinessError(课程不存在) if course.selected_count course.capacity: raise BusinessError(课程容量已满) existed ( db.session.query(Enrollment) .filter( Enrollment.student_id student_id, Enrollment.course_id course_id, Enrollment.status normal, ) .first() ) if existed: raise BusinessError(你已经选过这门课) # 检查学分上限 total_credit get_student_total_credit(student_id) if total_credit course.credit MAX_CREDIT: raise BusinessError(总学分超过上限) # 检查时间冲突 if check_schedule_conflict(student_id, course, db.session): raise BusinessError(上课时间与已选课程冲突) # 插入选课记录 更新计数器 enrollment Enrollment(student_idstudent_id, course_idcourse_id) db.session.add(enrollment) course.selected_count 1 db.session.commit() return True, 选课成功 except BusinessError as e: db.session.rollback() return False, str(e)这里的with_for_update()是 MySQL/PostgreSQL 的“行级锁”写法SQLite 下虽然它是 no-op但这个写法的意义在于你在答辩时可以明确告诉老师“我在事务里锁住了这条课程记录防止多个请求同时读到旧的 count 值”。3.2 选课时间窗口的“开关”设计选课不是任何时候都能点的通常学校会设置选课起止时间。很多简单系统把时间判断写在界面里不到时间就隐藏按钮。但界面上隐藏按钮完全不安全直接构造 POST 请求照样能把数据写进去。正确做法依然是在服务端判断当前时间是否在窗口内。可以设计一个config表维护窗口时间也可以直接在学期表里维护。推荐把selection_start和selection_end放在semesters表中并在选课业务开始时统一读取。def can_select_now(semester): now datetime.now() return semester.selection_start now semester.selection_end再加一个细节服务端时间统一用“服务器本地时间”还是“UTC”我的习惯是数据库统一存 UTC展示时转本地时区。这样做的好处是如果将来系统要部署到海外服务器时间才不会乱。课程作业阶段也可以直接用本地时间但要避免“服务器时间跟本地电脑时间不一样导致选课窗口错乱”的尴尬。3.3 退课后名额的释放时机退课逻辑看似简单实际上有坑。最常见的错误是“退课时直接把容量字段减一”但前面提到selected_count是冗余计数真正应该做的是把选课记录的status改成dropped把dropped_at记为当前时间然后更新selected_count。这个流程中的关键点是必须在同一事务里完成。不然退课记录改了计数器没减课程显示满了但实际已经空了。退课的实现def drop_course(student_id, course_id): try: with db.session.begin_nested(): enrollment ( db.session.query(Enrollment) .filter( Enrollment.student_id student_id, Enrollment.course_id course_id, Enrollment.status normal, ) .with_for_update() .first() ) if not enrollment: raise BusinessError(没有找到有效的选课记录) course ( db.session.query(Course) .filter(Course.id course_id) .with_for_update() .first() ) enrollment.status dropped enrollment.dropped_at datetime.now() course.selected_count max(0, course.selected_count - 1) db.session.commit() return True, 退课成功 except BusinessError as e: db.session.rollback() return False, str(e)注意这里用了max(0, ...)的兜底防止因为历史数据问题导致计数器变负数。3.4 用户登录与 Session 会话保持Web 系统必须处理用户身份状态。课程阶段用 Flask 自带的session就够了它会把数据签名后写入浏览器 Cookie。有几个细节要注意SECRET_KEY必须配置否则 session 无法签名用户数据可以被篡改。不要往 session 里塞密码只保存user_id和role。因为 session 的数据是存在客户端的虽然签名防篡改但用户可直接 Base64 解码看到内容密码放里面等于变相泄露。登录后更新 session 时要调用session.permanent True并设置PERMANENT_SESSION_LIFETIME否则浏览器一关就掉线。from datetime import timedelta app.config[SECRET_KEY] your-secret-key-here app.config[PERMANENT_SESSION_LIFETIME] timedelta(hours8)登录接口示例def login(): form request.form username form.get(username, ).strip() password form.get(password, ) user db.session.query(User).filter(User.username username).first() if not user or not check_password_hash(user.password_hash, password): flash(用户名或密码错误) return redirect(url_for(auth.login)) session[user_id] user.id session[role] user.role session.permanent True return redirect(url_for(index))3.5 学生端、教师端、管理端功能清单做一个选课系统最怕“功能边界不清”。我建议你按照下面这张清单去对照开发进度避免遗漏被老师扣分角色核心功能加分功能学生登录注册、浏览课程、按条件筛选、选课、退课、查看已选课程、查看个人课表选课时间提醒、学分统计图表、邮件选课成功通知教师登录、发布课程、维护课程信息、查看选课学生名单、导出名单 CSV课程容量修改提醒、按班导出成绩管理员登录、学生/教师账号管理、课程全局管理、选课时间配置、公告发布、系统数据统计日志审计、一键重置选课数据、批量导入账号很多同学的老师端就只有“加课程、改课程”两个功能这是最低配。加上“导出选课名单”这个功能之后系统完整度会明显提升而且实现非常简单熟悉一下 CSV 标准库就能搞定import csv from flask import Response def export_course_students(course_id): enrollments ( db.session.query(Enrollment, User) .join(User, Enrollment.student_id User.id) .filter( Enrollment.course_id course_id, Enrollment.status normal, ) .all() ) output [] for enrollment, student in enrollments: output.append([student.student_no, student.name, student.major]) csv_buffer StringIO() writer csv.writer(csv_buffer) writer.writerow([学号, 姓名, 专业]) writer.writerows(output) return Response( csv_buffer.getvalue(), mimetypetext/csv, headers{Content-Disposition: fattachment;filenamecourse_{course_id}_students.csv}, )3.6 从源码 zip 到可运行环境准备与依赖管理拿到任何一份 Python 课程设计源码第一步永远是“把它跑起来”。很多同学在自己电脑上运行没问题但交到老师手里跑不起来问题几乎都出在依赖环境上。因此你在写项目时就必须想清楚别人怎么复现。requirements.txt是提分利器Flask3.0.0 Flask-SQLAlchemy3.1.1 Werkzeug3.0.1安装只需一条命令pip install -r requirements.txt如果用的是 SQLite不需要额外装数据库服务这是课程作业里最稳妥的选择。使用 MySQL 虽然显得“生产级”但需要老师在答辩机上装 MySQL、初始化账号增加了不确定性。我的建议优先 SQLite答辩时把 SQLite 的优势说出来——零配置文件、单文件存储、适合中小型应用场景反而显得你有选型判断力。同时在你的 README 里写下确切的运行步骤这是很多学生完全忽略但老师非常看重的点# 运行步骤 1. 安装 Python 3.9 2. pip install -r requirements.txt 3. 初始化数据库python init_db.py 4. 启动服务python app.py 5. 访问 http://127.0.0.1:50004. 常见问题与排查技巧实录4.1 常见问题速查表写代码的时候总会遇到各种问题这里把我最常见的“学生作业 bug 集”整理成一张表方便你排查问题现象常见原因解决思路中文乱码数据库连接字符集未设置或模板文件编码不统一统一 UTF-8MySQL 连接串加charsetutf8mb4模板文件首行标注{# -*- coding: utf-8 -*- #}选课人数与明细对不上selected_count和enrollments表不同步运行数据修复脚本以后一律通过事务同时更新同一门课重复选成功缺少唯一约束给enrollments表加UNIQUE(student_id, course_id, status)页面刷新后重复提交选课表单重复 POST提交后redirect到结果页PRG 模式第三方库装不上网络源慢 / Python 版本不兼容使用国内镜像源pip install -i https://pypi.tuna.tsinghua.edu.cn/simple 包名程序一启动就报数据库表不存在忘记初始化建表检查init_db.py是否执行检查是否设置了create_all()老师账号登录后看到学生菜单前端只判断是否登录没判断角色后端模板据session[role]分角色渲染菜单并用装饰器硬校验这里重点说一下“中文乱码”。这是课程作业最容易翻车的问题尤其是用 PyCharm 直接跑 Flask 的同学。处理要点是确保三处统一Python 源码文件头部保存为 UTF-8HTML 模板meta charsetutf-8必须存在如果连 MySQL连接字符串配置好charset参数。4.2 数据丢失怎么办备份与重置答辩时最怕什么是数据搞乱了或者不小心删库了。最稳妥的方案是给项目预置一份初始化脚本和多组测试账号。你可以在init_db.py里预置管理员账号admin / admin123老师账号teacher1 / teacher123学生账号student1 ~ student5 / student123再准备一个reset_db.py一键删除所有表并重建、填充默认数据。答辩现场万一数据弄乱跑一下重置脚本所有状态干净如初。这个“细节”很多时候能救你一命。# reset_db.py from app import app, db from models.user import User from models.course import Course from models.enrollment import Enrollment def reset(): with app.app_context(): db.drop_all() db.create_all() create_admin() create_teacher() create_students() create_sample_courses() print(数据库已重置并写入测试数据) if __name__ __main__: reset()4.3 排查技巧从“不报错”到“能用”的调试思路很多同学项目“跑起来了”但一操作就“没反应”。原因多半出在“没看日志”。Flask 开发模式下控制台会打印 SQLAlchemy 的 SQL 语句、请求路径和异常堆栈这些是排查问题的第一手资料。我的建议是开发时打开 SQL 日志app.config[SQLALCHEMY_ECHO] True这样每次数据库操作都会在控制台打印出对应 SQL。比如你点了一个“选课”按钮控制台如果出现INSERT INTO enrollments...说明请求确实到达了后端如果连日志都没有那问题更可能出在路由、表单提交或前端 JS。这个排查套路能帮你快速定位是“前端没发请求”还是“后端逻辑挂了”。还有一种隐蔽的错误是“开发环境正常但打包后不能跑”。课程作业常见的坑是项目代码里使用了绝对路径比如C:\Users\yourname\course_system\data.db。换电脑后这个路径当然不存在。解决办法是在代码里使用相对路径import os BASE_DIR os.path.abspath(os.path.dirname(__file__)) app.config[SQLALCHEMY_DATABASE_URI] sqlite:/// os.path.join(BASE_DIR, data.db)这样只要整个项目文件夹被拷贝走数据库会自动创建在项目目录下。老师收到你的 zip解压后一条命令就能跑。4.4 提升答辩表现的小技巧课程设计除了代码本身“答辩表达”也是评分的重要部分。几个我亲测有效的建议准备一张业务流程图。不用画得多专业但要把“学生选课 - 服务端校验 - 数据库写入 - 返回结果”这条链路讲清楚。演示时先跑通主流程学生登录、查课、选课、退课、教师查看名单、管理员查看统计。不要卡在冷门边角给老师留出时间问问题。主动讲一个你踩过的坑。比如“您看这里我一开始用selected_count直接存储但后来发现并发选课可能超容量于是加了事务和行锁”。这句话在老师眼里比“我项目里用了 Flask”有价值十倍。准备好回答“为什么不用 X 框架”。如果你用了 Flask老师可能问“那你为什么不直接用 Django”参考答案课程项目规模适中Flask 更轻量能让我更聚焦在业务逻辑本身同时我对 Flask 的请求生命周期、Session 机制掌握得更细。这个回答不卑不亢。5. 代码之外的实用设计与库应用5.1 用 Flask-WTF 做表单校验避免手动写 if课程作业里很多人做了几十个字段的表单校验全用if request.form.get(xxx)手写又长又枯燥。引入 Flask-WTF 之后校验逻辑可以集中定义在表单类中代码量骤减而且可读性强很多from flask_wtf import FlaskForm from wtforms import StringField, PasswordField, IntegerField from wtforms.validators import DataRequired, Length, NumberRange class CourseForm(FlaskForm): name StringField(课程名称, validators[DataRequired(), Length(max50)]) credit IntegerField(学分, validators[NumberRange(min1, max10)]) capacity IntegerField(容量, validators[NumberRange(min1, max200)])模板里渲染也简单form methodpost {{ form.hidden_tag() }} div {{ form.name.label }} {{ form.name() }} {% for error in form.name.errors %} span classerror{{ error }}/span {% endfor %} /div /form使用了表单库之后很多“忘记填字段、格式错误”的校验代码不需要自己写了。唯一要注意的是在视图函数里调用form.validate_on_submit()它会自动完成 POST 判断和校验。5.2 使用 Pagination 处理课程列表当课程数量很多时一页全部显示会非常卡。这是“细节处理”的加分项。Flask-SQLAlchemy 自带paginate方法配合 Bootstrap 的分页组件几十行就能做得干净page request.args.get(page, 1, typeint) per_page request.args.get(per_page, 10, typeint) courses Course.query.paginate(pagepage, per_pageper_page, error_outFalse)模板里显示分页按钮nav ul classpagination {% if courses.has_prev %} lia href{{ url_for(course.list, pagecourses.prev_num) }}上一页/a/li {% endif %} {% for p in courses.iter_pages() %} {% if p %} li class{{ active if p courses.page else }} a href{{ url_for(course.list, pagep) }}{{ p }}/a /li {% else %} lispan.../span/li {% endif %} {% endfor %} {% if courses.has_next %} lia href{{ url_for(course.list, pagecourses.next_num) }}下一页/a/li {% endif %} /ul /nav5.3 用 Bootstrap 快速完成一套不丑的界面不要求你有审美天赋Bootstrap 的栅格和组件能让你用极低成本做出专业的观感。最关键的是掌握它的布局结构!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title{% block title %}课程选课系统{% endblock %}/title link relstylesheet hrefhttps://cdn.jsdelivr.net/npm/bootstrap5.3.0/dist/css/bootstrap.min.css /head body nav classnavbar navbar-expand-lg navbar-dark bg-dark div classcontainer a classnavbar-brand href{{ url_for(index) }}选课系统/a div classnavbar-nav ms-auto {% if session.get(user_id) %} span classnav-link欢迎{{ session.get(name) }}/span a classnav-link href{{ url_for(auth.logout) }}退出/a {% else %} a classnav-link href{{ url_for(auth.login) }}登录/a a classnav-link href{{ url_for(auth.register) }}注册/a {% endif %} /div /div /nav main classcontainer mt-4 {% with messages get_flashed_messages() %} {% if messages %} div classalert alert-info {% for message in messages %}{{ message }}br{% endfor %} /div {% endif %} {% endwith %} {% block content %}{% endblock %} /main /body /html这个基础模板可以让你所有页面共用导航栏和样式后面每个页面只写内容块十分高效。5.4 使用装饰器封装页面访问控制刚才提到角色的三层防线最方便的是写一个装饰器。但实际操作中很多同学只给函数写了一层忘记处理“未登录跳转后返回原页面”的体验。一个小优化登录后跳回来源页面而不是固定跳到首页。def login_required(func): wraps(func) def wrapper(*args, **kwargs): if not session.get(user_id): flash(请先登录) return redirect(url_for(auth.login, nextrequest.path)) return func(*args, **kwargs) return wrapper登录成功后处理next参数next_url request.args.get(next) if next_url and next_url.startswith(/): return redirect(next_url) return redirect(url_for(index))注意这里的next_url.startswith(/)是防开放性重定向的否则攻击者可以构造恶意跳转链接。虽然课程作业可能用不上这么安全的细节但答辩时讲出来老师会觉得你有安全意识。6. 打磨一个“看起来像作品”的项目6.1 项目 README 的写法很多同学认为 README 毫无用处直接空着或写一句“课程设计”. 但实际上一份清晰的项目 README 能显著提升作业的完整度。老师打开 zip 第一眼看到的是 README而不是代码。模板参考# 课程选课系统 基于 Flask 的简单选课系统实现学生选课、教师开课、管理员管理等功能。 ## 功能特性 - 学生注册、登录、浏览课程、选课/退课、查看个人课表 - 教师开课、维护课程信息、导出选课名单 - 管理员账号管理、课程管理、选课时间配置、数据统计 ## 技术栈 - 后端Python 3 Flask Flask-SQLAlchemy - 前端Bootstrap 5 Jinja2 模板 - 数据库SQLite也可切换 MySQL ## 快速开始 ...这部分内容不需要长但一定要有。它是老师快速了解你项目的窗口也是网上分享源码时别人判断“这个项目值不值得下载”的第一依据。6.2 测试账号的重要性项目里最好预置测试账号并写清楚账号密码。这看起来是小事但对评审体验影响巨大。如果你的系统需要老师自己注册、自己建课、自己选课体验就非常琐碎。预置好账号和样例课程数据之后老师打开系统就能立刻进入“演示模式”而不是先花五分钟研究你的操作流程。6.3 源码中的注释风格最后说一个我个人的看法注释不是为了凑行数而是为了体现“你确实理解这段代码在做什么”。不要每行都写但要在关键业务节点留下注释。比如# 这里对课程行加锁避免选课人数超过容量 course db.session.query(Course).filter(...).with_for_update().first()另外函数名和变量名要尽量语义化不要用a、b、tmp这类无意义命名。一段好的代码看变量名就知道在表达什么这一点在答辩时很容易给老师留下“思路清晰”的印象。7. 后续还可以怎么升级这个项目如果你做完基础功能还有余力想拿去参加项目评比或者写进简历我建议按下面的顺序做增量升级引入 Celery 异步任务选课成功后发邮件通知体验瞬间提升一个档次。使用 Redis 做选课队列应对高并发抢课时用 Redis 的原子自增做库存扣减再异步写入 MySQL这就是高并发场景下的“削峰填谷”思路。加入 Excel 批量导入导出把教师端导出 CSV 升级为 Excel使用pandas或者openpyxl对没有技术背景的使用者非常友好。加入简单的数据可视化用 ECharts 或 Chart.js 展示“课程热度排行”“选课人数趋势”老师看了都会眼前一亮。单元测试与 CI写几个关键业务选课、退课、时间冲突的单元测试如果还能贴一张 GitHub Actions 的测试通过徽章基本就是课程设计里的“降维打击”。我个人在实际评审中看到那些能主动做额外功能的学生往往不是代码写得最炫的而是愿意花时间打磨细节的。一条友好的错误提示、一个可用的导出功能、一份干净的 README这些细节的价值往往被低估但恰恰是这些细节决定了你的作业是在“能用”还是“好用”之间。8. 关于这份“课程选课系统 Python 源码”你还需要知道的现在网络上流传的课程设计源码很多质量参差不齐。有些是培训机构批量生成的模板功能完整但没有灵魂代码堆砌不说连注释风格都千篇一律有些是往届学长学姐的原汁原味作品虽然有小瑕疵但结构真实适合学习。我的建议是源码可以拿来参考但不要直接交作业。一是因为老师很可能已经见过那份流行源码二是你自己不亲手写一遍答辩时连基本逻辑都讲不清反而露馅。参考源码时重点关注它的项目组织方式、业务规则覆盖、异常处理细节而不是把代码复制粘贴改个名字就完事。有一句我觉得特别适合课程设计阶段的话“大作业不是为了做一个完美的产品而是为了证明你掌握了设计一个完整系统的方法论。”你选择技术栈的思路、设计数据库的思路、处理并发和异常的思路这些过程本身就是老师评分的主体。拿这份选课系统来说你完全可以在基础功能之上加上自己的思考。比如加入“选课志愿优先级”的策略或者做一个“热门课程等待队列”这些虽然不是必须但能让你的思考和同龄人明显区分开来。这也是我把项目做得足够多之后最深的体会。本文还有配套的精品资源点击获取