☰
SpringBoot+Vue+微信小程序多端教务系统源码实战:排课冲突与数据一致性
2026/10/7 23:18:02 网站建设 项目流程

简介:这是一套基于SpringBoot后端与Vue前端构建的多端教务管理系统源码,同时兼容微信小程序运行环境,面向教育培训机构的技术开发者与二次开发学习者,用于解决多校区教务协同、招生财务与教学管理等业务场景的数字化需求。压缩包共359个文件,约6.98MB,其中Java源码260个构成核心业务逻辑,另有27张PNG与1张JPG图形资源、4个XML与2个YML配置文件、properties属性配置及说明文档等,目录结构清晰,便于按模块检索与二次开发。系统支持跨区域多校区运营,功能覆盖课程排期、学员档案、财务稽核、招生渠道、团体报名与分销、在线直播录播教学,并提供PC管理后台、家长小程序与微信小程序管理端三种访问方式。目前已有70人学习下载,适合希望研究多端教务系统架构、借鉴SpringBoot与Vue工程实践或进行功能裁剪复用的开发者参考。

1. 多端教务系统到底解决什么问题:从一次排课冲突说起

去年帮一所高职做教务系统迁移,最头疼的不是功能多少,而是同一份数据要在三个地方保持一致:教务处老师在 PC 后台排课,辅导员在微信小程序里查班级课表,学生用小程序看自己的课和成绩。旧系统是单体的 JSP 后台,老师改完课表,学生端要等半小时缓存刷新,期间冲突的教室安排照样显示,投诉电话直接打到信息中心。这就是多端教务管理系统要解决的核心矛盾——一套业务数据,多个终端实时消费。

标题里的技术组合「SpringBoot + Vue + 微信小程序 + PC 管理后台」不是随便堆的。SpringBoot 负责统一的后端 API 和权限、事务、排课冲突校验;Vue 做 PC 管理后台,适合表格密集、批量操作的教务场景;微信小程序承接学生和教师的高频轻查询,免安装、能拿手机号做身份绑定。三者共用一套 REST 接口和一套 JWT 鉴权,数据只有一份,改完即生效。

这套方案适合谁?中小院校、培训机构、院系级教务,团队 2~5 人,想用一套源码快速搭起可维护的多端系统。不适合超大规模选课秒杀场景,那是另一个量级的架构问题。下面按「后端怎么搭 → 前端怎么接 → 小程序怎么登 → 坑在哪 → 怎么验证」的顺序讲透。

2. SpringBoot 后端:把排课冲突校验做成可复用的服务

后端是整个系统的地基,教务系统的复杂度几乎全在业务规则上:一个教室同一时间段只能排一门课,一个教师同一时间段不能跨校区,一个班级的课程不能撞车。这些规则如果散落在各个 Controller 里,改一处漏一处。我一般把它们收敛到 Service 层,用统一的时间段冲突检测方法。

2.1 项目结构与依赖选型

常见做法是按功能分包,而不是按技术分层。教务系统的模块边界很清晰:course(课程)、schedule(排课)、student(学生)、teacher(教师)、auth(鉴权)。每个包内部再分 controller/service/mapper/entity,这样改排课逻辑时不会误伤学生模块。

依赖上,SpringBoot 版本别追最新。热词里有人问「springboot版本太高」怎么办,这是血泪经验:SpringBoot 3.x 默认要求 JDK 17,很多学校的服务器还停在 JDK 8,而且 3.x 对javax到jakarta的包名迁移会让一堆老代码编译不过。稳妥选 SpringBoot 2.7.x + JDK 8/11,生态最全,MyBatis-Plus、JWT、Redis 客户端都验证充分。

<!-- pom.xml 关键依赖,版本按 2.7.x 线选 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>

这段依赖里,mybatis-plus省掉大量单表 CRUD 的 XML,教务系统里学生、教师、教室的基础查询基本靠它;jjwt负责签发和校验 token,PC 端和小程序端共用同一套;Redis 用来存 token 黑名单和课表缓存。参数上注意jjwt 0.9.1依赖javax.xml.bind,JDK 11 以上要额外引jaxb-api,否则启动报ClassNotFoundException,这是新手最容易卡住的地方。

2.2 排课冲突检测的核心实现

冲突检测的本质是区间重叠判断。把每节课抽象成[startTime, endTime)半开区间,两节课冲突当且仅当a.start < b.end && b.start < a.end。用半开区间而不是闭区间,是为了让「第一节 8:00-8:45」和「第二节 8:45-9:30」不算冲突,这个边界坑很多人踩过。

@Service public class ScheduleConflictService { @Autowired private ScheduleMapper scheduleMapper; /** * 检测待排课程是否与已有课表冲突 * @param req 含 classroomId/teacherId/classId/weekday/startTime/endTime * @return 冲突详情,null 表示无冲突 */ public ConflictResult check(ScheduleRequest req) { // 查同一天、同一教室/教师/班级的所有已排课程 List<Schedule> existing = scheduleMapper.selectByDayAndResources( req.getWeekday(), req.getClassroomId(), req.getTeacherId(), req.getClassId()); for (Schedule s : existing) { // 半开区间重叠判断,注意用 isBefore 而非 compareTo <= 0 boolean overlap = req.getStartTime().isBefore(s.getEndTime()) && s.getStartTime().isBefore(req.getEndTime()); if (overlap) { return new ConflictResult(s, resolveReason(req, s)); } } return null; } private String resolveReason(ScheduleRequest req, Schedule s) { if (req.getClassroomId().equals(s.getClassroomId())) return "教室占用"; if (req.getTeacherId().equals(s.getTeacherId())) return "教师时间冲突"; return "班级课程冲突"; } }

逻辑说明:先按「星期 + 三类资源」一次性捞出候选课程,避免在循环里反复查库;再用半开区间判断重叠。参数上,weekday用 1~7 表示周一到周日,startTime/endTime用LocalTime,别用字符串比较,字符串比较在跨小时时会出错。resolveReason返回具体冲突原因,前端能直接提示「3 号教学楼 201 教室已被占用」,比笼统报「排课失败」体验好太多。

提示:冲突检测要在事务里做,并且对同一教室的排课加行级锁或 Redis 分布式锁,否则两个老师同时排同一教室,检测都通过,最后双写冲突。并发量不大的院校用SELECT ... FOR UPDATE就够。

2.3 统一响应与全局异常

多端系统最怕接口返回格式不统一,小程序端解析一套、PC 端解析另一套。我一般定义一个Result<T>包装类,code/message/data 三字段,配合@RestControllerAdvice全局捕获异常。这样前端只写一次拦截器,两端复用。参数校验用@Valid+ JSR-303 注解,把「课程名不能为空」「学分必须为正」这类规则写在实体上,Controller 里不用写 if-else。

3. Vue PC 管理后台:表格密集场景下的路由与权限

PC 后台是教务老师的主战场,特点是表格多、批量操作多、权限细。Vue 选型上,Vue 3 + Element Plus 是当前主流,Vue 2 + Element UI 在老项目里还大量存在。如果团队没有历史包袱,直接上 Vue 3,组合式 API 在权限逻辑复用上比选项式清爽。

3.1 环境搭建与动态路由

热词里「vue安装及环境配置」「vue路由」「vue动态路由」问得最多。标准流程是 Node 16+、用 Vite 建项目,比 Vue CLI 快很多。装依赖时如果卡在vue安装依赖,多半是镜像源问题,换国内源即可。

# 用 Vite 创建 Vue3 项目 npm create vite@latest edu-admin -- --template vue cd edu-admin npm install # 装路由和 UI 库 npm install vue-router@4 element-plus axios pinia npm run dev

装完后核心是路由设计。教务后台的菜单要按角色动态生成:教务主任能看排课和教师管理,普通老师只能看自己的课表。做法是登录后拿到后端返回的权限码列表,前端用router.addRoute动态挂载。

// router/permission.js 路由守卫核心逻辑 import router from './index' import { useUserStore } from '@/store/user' const whiteList = ['/login'] // 免登录白名单 router.beforeEach(async (to, from, next) => { const userStore = useUserStore() if (userStore.token) { if (to.path === '/login') { next('/') } else { // 已登录但还没拉过权限,先拉权限再放行 if (!userStore.routesLoaded) { const routes = await userStore.generateRoutes() routes.forEach(r => router.addRoute(r)) next({ ...to, replace: true }) // replace 避免历史记录残留 } else { next() } } } else { whiteList.includes(to.path) ? next() : next('/login') } })

逻辑说明:generateRoutes里根据后端返回的权限码,从本地路由表过滤出可访问的路由。next({ ...to, replace: true })这行是关键,动态添加路由后必须重新触发一次导航,否则首次进入会白屏,这是 Vue 动态路由最经典的坑。参数上routesLoaded标记防止每次跳转都重新拉权限,登出时要重置为 false。

3.2 课表组件的性能处理

教务后台的课表是一个二维网格:行是节次,列是星期。用 Element Plus 的el-table硬拼会很别扭,常见做法是用 CSS Grid 自己画。一个班一周 25 节课,一个年级几十个班,如果每个格子都渲染完整课程卡片,页面会卡。优化手段是虚拟滚动或分页切换班级,别一次性渲染所有班级的课表。

数据请求上,课表接口按「班级 + 周次」查询,返回该周的课程数组,前端按weekday和section定位到格子。注意周次参数,教务系统有单双周、调课周的概念,接口要支持weekType字段,否则单周课会错误显示在双周。

3.3 打包后放进 SpringBoot 的两种方式

热词里「vue打包放进springboot中」是高频需求,很多院校只给一个端口,前后端要合并部署。两种做法:一是把npm run build产出的dist拷到 SpringBoot 的src/main/resources/static,二是用 Nginx 反代。前者简单,适合单机部署。

# 前端打包,注意 base 路径要和后端 context-path 一致 npm run build # 产物在 dist 目录,拷到后端静态资源目录 cp -r dist/* ../edu-server/src/main/resources/static/

拷进去后,SpringBoot 要配置一个 fallback,让前端路由(history 模式)刷新不 404。写个WebMvcConfigurer,把非/api开头的请求都转发到index.html。参数上注意vite.config.js里的base要和后端访问路径匹配,如果后端配了server.servlet.context-path=/edu,前端 base 也要改成/edu/,否则静态资源 404。

4. 微信小程序端:登录、手机号与课表查询

小程序端面向学生和教师,核心功能就三个:登录绑定身份、查课表、查成绩。功能不复杂,但登录链路是新手最容易翻车的地方。

4.1 登录与手机号获取的完整链路

热词里「微信小程序登录获取手机号」是刚需。完整链路是:小程序调wx.login拿 code → 后端用 code 换 openid 和 session_key → 小程序用getPhoneNumber按钮拿加密的 phone code → 后端解密或调接口换手机号 → 匹配学生表完成绑定。

// pages/login/login.js 小程序端登录 Page({ data: { phone: '' }, // 第一步:静默登录拿 code onLoad() { wx.login({ success: (res) => { // code 有效期 5 分钟,立刻发给后端换 openid wx.request({ url: 'https://your-domain/api/auth/wx-login', method: 'POST', data: { code: res.code }, success: (r) => { wx.setStorageSync('token', r.data.data.token) } }) } }) }, // 第二步:用户点击授权按钮拿手机号 getPhone(e) { if (e.detail.errMsg !== 'getPhoneNumber:ok') return wx.request({ url: 'https://your-domain/api/auth/bind-phone', method: 'POST', header: { Authorization: wx.getStorageSync('token') }, data: { code: e.detail.code }, // 新版用 code,旧版用 encryptedData+iv success: (r) => { if (r.data.code === 200) wx.reLaunch({ url: '/pages/schedule/index' }) } }) } })

逻辑说明:wx.login的 code 只能用一次且 5 分钟过期,必须立刻换 openid,别缓存。手机号获取现在推荐用e.detail.code传给后端,由后端调微信接口换取,比前端解密encryptedData更安全,也避开了 session_key 失效问题。参数上bind-phone接口要校验 token 有效,并把手机号和学生表里的预留手机号比对,匹配上才允许绑定,防止随便一个微信号就能查别人课表。

注意:getPhoneNumber必须由用户点击按钮触发,不能自动调用,这是微信的硬性限制。按钮要用<button open-type="getPhoneNumber" bindgetphonenumber="getPhone">,普通 view 绑事件无效。

4.2 课表页面的渲染与缓存

小程序课表用scroll-view或自定义网格。顶部导航栏高度在不同机型不一样,热词里「微信小程序顶部导航栏高度」就是问这个。用wx.getSystemInfoSync()拿statusBarHeight和胶囊按钮位置,动态算导航栏高度,别写死。

// 计算自定义导航栏高度 const sysInfo = wx.getSystemInfoSync() const menuButton = wx.getMenuButtonBoundingClientRect() // 导航栏高度 = 胶囊底部到状态栏底部的距离 * 2 + 胶囊高度 const navBarHeight = (menuButton.top - sysInfo.statusBarHeight) * 2 + menuButton.height

课表数据缓存策略:学生一周看课表频率高但数据变化少,用wx.setStorageSync缓存课表,加一个时间戳,超过 1 小时或用户手动下拉才重新请求。这样打开小程序秒显,体验好很多。注意缓存 key 要带学号和周次,否则换周次会读到旧数据。

4.3 与 PC 后台共用接口的注意事项

小程序和 PC 共用一套 REST 接口,但请求头不同:小程序没有 Cookie,全靠Authorization头传 token。后端鉴权拦截器要同时支持从 header 读 token,别只依赖 session。另外小程序的wx.request默认不带 referer,如果后端做了防盗链要放行。返回数据上,小程序端流量敏感,列表接口要支持分页,别一次返回全校学生。

5. 多端联调避坑:5 个真实踩过的坑

5.1 跨域配置在开发环境生效、生产环境失效

现象:本地 Vue 调后端接口正常,打包部署后全部 403。原因:开发时用 Vite 的proxy代理,生产环境走 Nginx 或直接静态资源,跨域规则没同步。解决:后端CorsConfig里用allowedOriginPatterns而不是allowedOrigins,并把生产域名加进去;或者干脆前后端同源部署,用第 3.3 节的方案,从根上避开跨域。

5.2 JWT token 过期后小程序端白屏

现象:学生用了一上午,下午打开小程序所有接口 401,页面空白。原因:token 过期后前端没做统一拦截,请求失败但页面没提示。解决:封装wx.request,在fail和状态码 401 时清 token 并跳登录页;PC 端在 axios 响应拦截器里做同样处理。token 有效期别设太短,教务场景设 7 天,配合 Redis 黑名单支持主动登出。

5.3 排课并发导致教室双占

现象:两个教务老师同时给同一教室排课,系统都提示成功,课表出现重叠。原因:冲突检测和插入之间没有锁,两个请求都通过了检测。解决:对classroomId + weekday加 Redis 分布式锁,锁住检测到插入的整个过程;或者数据库层面对该教室该时段加唯一索引,插入冲突时抛异常回滚。后者更可靠,推荐。

5.4 小程序手机号解密失败

现象:bind-phone接口报-41003或解密乱码。原因:session_key 过期,或者前端传的是旧版encryptedData而后端按新版 code 解析。解决:统一用新版e.detail.code方案,后端调phonenumber.getPhoneNumber接口换取,不再自己解密。如果必须解密,session_key 要在wx.login后立刻存 Redis,有效期和微信保持一致。

5.5 Vue 打包后静态资源 404

现象:SpringBoot 里放好 dist,访问首页正常,刷新子路由或加载 JS 报 404。原因:Vue Router 用了 history 模式,后端没配 fallback;或者vite.config.js的base和后端 context-path 不一致。解决:后端加index.html转发配置,前端 base 改成相对路径./或与 context-path 一致。这两个改完基本就通了。

6. 怎么验证这套源码值不值得投入:三个可落地的检验动作

拿到一套多端教务源码,别急着改业务,先做三个验证动作,半小时就能判断它的工程质量。

第一个动作,跑通登录闭环。启动后端,确认能连上数据库和 Redis;启动 Vue 后台,用默认账号登录,看菜单是否按角色渲染;用微信开发者工具打开小程序,走一遍wx.login+ 手机号绑定。这一步能暴露 80% 的环境配置问题,比如 JDK 版本、数据库字符集、小程序 appid 是否配置。

第二个动作,压一次排课冲突。手动构造两个冲突的排课请求,用 Postman 或 JMeter 并发发出去,看系统是正确拦截一个还是两个都成功。这一步验证的是核心业务逻辑的严谨性,也是教务系统最容易出事故的地方。如果并发下双写成功,说明锁没做对,这套源码的排课模块要重写。

第三个动作,检查多端数据一致性。在 PC 后台改一门课的时间,立刻在小程序刷新课表,看是否同步。如果小程序有缓存,确认缓存失效策略是否合理。这一步验证的是架构设计,数据源是否真的统一。

检验项通过标准常见失败原因
登录闭环三端都能登录并拿到正确权限JDK 版本、appid 未配、Redis 未启动
排课冲突并发下只成功一条缺分布式锁或唯一索引
数据一致性改完即生效或缓存合理失效多数据源、缓存 key 设计错误

我自己的习惯是,任何一套源码先看它的鉴权拦截器和事务边界,这两处写清楚了,业务代码再乱也能救;这两处含糊,功能再多也是定时炸弹。多端教务系统尤其如此,数据一致性是命根子,宁可功能少做,也不能让课表对不上。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询