这学期有门课程设计,我给自己选的题目是:把学校的官网门户 + 教务系统(学生端)完整重写一遍。不是做个「选课系统小作业」,而是从门户首页到选课、成绩、课表、监考编排的一条龙。
先说清楚定位:这是一个课程设计 / 作品集演示项目,全部使用模拟数据,与学校真实系统无关。技术栈是 单一 Vue 3 应用 + 单一 Spring Boot 后端,无冗余架构——我见过太多课设把项目拆成七八个微服务然后每个都只写了半截,我更想要一个「每个部分都完整」的东西。
站点结构
/ 官网首页(轮播 · 校情数据 · 新闻公告) ← 免登录
/about 学校概况(简介 · 沿革 · 校训 · 校区)
/departments 院系设置(22 个学院,支持检索)
/news 新闻中心(动态 / 公告双频道 + 详情页)
/digital 数字校园(教务系统主入口 + 服务矩阵)
/login 教务系统登录(SVG 验证码 + 防暴力破解)
/app/* 教务系统(学生 / 教师 / 管理员三角色)
门户是「招生宣传册」的形态,教务系统是「工具」的形态,中间用「数字校园」页衔接。登录态全站共享同一个 Token,门户顶条支持浅色 / 深色切换,官网还有中英双语版本。
教务系统里我比较得意的几块
自研周课表组件:节次轴按真实作息时间画,支持周次切换、导出图片 / ICS 日历文件、查看班级课表。
绩点模拟器:4.0 加权绩点 + 趋势图之外,可以「假如这门课考 90 分,绩点会变成多少」。同课多成绩(补考/重修)取最高为有效成绩——这个规则查了不少学校的教务细则才定下来的。
选课的库存与冲突:余量实时显示、时间冲突检测、库存原子扣减防超选、退课可重选。库存做了两套实现,后面压测会讲到。
成绩实时推送:教师在成绩录入表(总评 = 平时 40% + 期末 60% 自动计算)点「发布」→ 后端经 WebSocket(Sa-Token 握手校验)向全班学生推送 → 学生端全局弹出「成绩发布」通知。掉线 8 秒自动重连,离线期间错过的成绩登录后自然可见。
管理员端也没偷懒:学生/教师/课程教学班管理、公告新闻、申请审核、考试监考编排、毕设统筹、学业预警名单、数据大屏。
一场 200 人的选课压测
库存扣减我做了两种实现,于是干脆写了自研压测引擎(单文件 Java,零第三方依赖,JMeter 在离线环境装不了)对比一下:
- local:JVM 内
AtomicIntegerCAS 扣减 - redis:Redis Lua 脚本「查余量 + 扣减」原子执行
场景:200 个并发学生(同一毫秒发令枪开抢,各自自动解析 SVG 验证码真实登录),抢一个容量 50 的教学班。结果:
| 指标 | local CAS | Redis Lua |
|---|---|---|
| 选课成功 | 50 / 50(精确满座) | 50 / 50(精确满座) |
| 快速拒满 | 133 人 | 72 人 |
| 重试耗尽 | 17 人 | 77 人 |
两种方案都守住了「绝不超选」的底线,但体验曲线不同:CAS 多数人被快速拒绝,Redis Lua 则让更多人进入重试等待。「对」和「体验好」是两件事,这是压测教给我最直观的一课。
踩过的坑(印象深刻的两)
mysqld 读不了中文路径:项目放在中文目录里,MySQL 便携版启动就挂。最后用 subst X: 虚拟盘把项目目录映射成一个无中文的盘符绕过去——Windows 老牌坑, OSS 上连 MySQL 官方 issue 都有记录。
后端重启 = 全员掉线:Sa-Token 的登录态在内存里,重启后端所有 token 失效。演示环境无所谓,但这让我真切理解了为什么生产环境要把 session 外置到 Redis。
数据与复现
MySQL 8 便携版、Redis 便携版、JDK 17 全部放在项目 tools/ 里,数据目录全在项目内,双击 start.bat 一键拉起(幂等,端口占用视为已在运行)。数据库结构用 Flyway 迁移(V1~V8),重置就是删库重启。
仓库目前只在本地,整理后考虑开源。这个项目做完的最大感受:课设不是功能堆数量,而是把「选课会不会超卖」「成绩推送会不会丢」「重置能不能一键」这些真问题都回答一遍。