SpringBoot+Vue前后端分离教学辅助系统:从JWT鉴权到部署实践
发布时间:2026/9/9 17:37:38 作者:尧图编辑部 阅读量:1,286

从教学管理痛点出发我把这套SpringBootVue教育辅助系统完整拆给你看如果你在学校信息中心、培训机构或者自己做独立开发大概率遇到过这样的场景排课靠Excel选课靠人工统计成绩录入要手动汇总学生请假还要纸质审批流转到教务处。一套能真正跑起来的教学辅助系统往往比想象中的更刚需。最近我基于SpringBootVue的前后端分离架构整理并重建了一套教学辅助系统功能覆盖用户登录认证、课程管理、选课退课、成绩录入、公告发布、班级管理等核心教育场景。这篇文章不是简单的项目介绍而是把我研究这套JavaWeb源码时的设计思路、核心实现、部署过程和踩坑记录全部整理出来希望对正在做毕业设计、课程设计或者刚接触前后端分离项目的开发者有帮助。这套系统最大的价值在于它的工程结构完整且克制后端用SpringBoot搭建RESTful API前端用Vue实现单页应用数据库采用MySQL鉴权用JWT没有引入太多花哨的中间件非常适合用来理解一个真实JavaWeb教育项目从0到1的构建脉络。如果你正在找springboot和vue结合的项目案例或者想学习javaweb项目完整案例mysql版本如何组织表结构和接口设计这套系统是一个很好的参考样本。下面我从设计思路、核心实现、前端细节、部署实操和问题排查五个维度逐层拆解。1. 项目整体设计与技术选型思路1.1 为什么选择SpringBootVue而不是传统JSP模式早几年的JavaWeb教学项目主流方案是JSPServletTomcat所有页面由后端渲染前端和后端代码耦合在一个war包里。这种模式对小型Demo来说确实简单但一旦业务复杂起来痛点非常明显前端工程师没法独立开发后端每次改页面都要重新打包部署接口和页面逻辑纠缠在一起维护成本极高。SpringBootVue的前后端分离方案正好解决这些问题。SpringBoot负责提供纯JSON接口Vue负责页面渲染和数据交互两者通过HTTP通信互不干扰。开发阶段用Vue自带的devServer做代理转发生产阶段将Vue打包后的dist目录交给Nginx或者SpringBoot静态资源映射来托管整个链路非常清晰。我在实际使用中最大的感受是前后端分离之后调试接口和调整页面变成了两条独立的流水线后端可以用Postman测接口前端可以Mock数据调页面效率提升非常明显。1.2 教学辅助系统的核心业务模块拆解在动手写代码之前我习惯先把业务模块画清楚。这套系统我拆成了六个核心模块每个模块对应一组独立的Controller、Service、Mapper接口用户认证模块登录、注册、JWT签发与校验、退出登录角色分管理员、教师、学生三种。课程管理模块课程信息维护、课程列表查询、课程状态管理可选/已满/已结课管理员有全部操作权限教师可以维护自己名下的课程。选课退课模块学生查看可选课程、提交选课、退课选课名额校验防止超选。成绩管理模块教师录入成绩、修改成绩学生查询自己的成绩管理员查看整体成绩统计。公告管理模块发布教学通知、置顶公告、按时间线展示公告列表。班级与学生管理模块管理员维护班级信息、导入学生账号、重置密码等。这样一个模块化设计的好处是每个功能点都有明确的分层归属新人拿到源码后能很快定位到相关的Java类和Vue组件学习成本大幅降低。如果你自己在设计类似系统我也建议先把角色和权限矩阵画出来再动工写代码不然后面改起来会很痛苦。1.3 开发环境与版本选型参考我在本地复现这套系统时使用的环境如下也推荐你用相近的版本组合能少踩很多版本兼容性的坑组件版本说明JDK1.8SpringBoot 2.x的稳定基础SpringBoot2.7.x稳定版社区资料丰富MyBatis-Plus3.5.x增强ORM自带分页插件和自动填充MySQL5.7 或 8.0生产常用版本均可Vue2.6.x 配合Vue CLI 4.x本源码采用Vue2生态成熟Element-UI2.15.x配合Vue2使用最稳妥Node.js14.x 或 16.x版本过高会导致依赖安装报错Maven3.6.x后端依赖管理IDEA2022.x 以上开发IDE其中SpringBoot版本是一个很值得注意的点如果你使用springboot 3.x那么javax包会全部改为jakarta很多旧源码直接运行会报包找不到。我建议先按项目的原始版本跑通再去考虑升级这样能省掉不少排查环境问题的时间。2. 后端核心实现从登录鉴权到业务接口2.1 JWT登录鉴权的完整闭环登录鉴权是绝大部分JavaWeb项目的第一道门槛这套系统采用JWT方案。JWT的机制可以理解为用户登录成功之后后端生成一串包含用户身份信息的加密字符串返回给前端前端后续每次请求都把这个字符串放在请求头里后端通过拦截器解析并验证用户身份整个过程不需要在服务端保存会话数据非常适合前后端分离的场景。后端的关键实现在于两点一是JWT工具类负责生成和解析Token二是拦截器负责拦截需要认证的请求。以登录接口为例用户提交用户名和密码后UserService会先调用MyBatis-Plus的LambdaQueryWrapper去数据库比对密码若匹配则生成Token返回否则抛出自定义业务异常。生成Token的核心代码如下public String generateToken(Long userId, String username, String role) { // 设置过期时间这里设置24小时 Date expireDate new Date(System.currentTimeMillis() 24 * 60 * 60 * 1000); return Jwts.builder() .setSubject(username) .claim(userId, userId) .claim(role, role) .setIssuedAt(new Date()) .setExpiration(expireDate) .signWith(SignatureAlgorithm.HS256, secretKey) .compact(); }解析Token的代码如下public Claims parseToken(String token) { return Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(token) .getBody(); }有个容易被忽略的细节JWT的secretKey一定不要硬编码在代码里我实际开发中是放在application.yml配置文件中并通过Value注解注入这样不同环境的密钥可以独立配置避免安全隐患。还有一点是Token过期处理前端axios拦截器中如果收到401状态码应自动跳转到登录页并清除本地用户信息这个联动逻辑我会在后面的前端部分详细讲。2.2 拦截器实现与角色权限控制有了JWT解析能力之后还需要有拦截器来统一校验不然每个接口都写一段Token解析代码会非常冗余。我在这套系统中定义了一个JwtInterceptor实现HandlerInterceptor接口的preHandle方法Component public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录、注册等白名单接口 if (handler instanceof HandlerMethod) { String token request.getHeader(Authorization); if (token null || token.isEmpty()) { throw new BusinessException(401, 未登录或Token已失效); } // 校验并解析Token Claims claims jwtUtil.parseToken(token.replace(Bearer , )); // 将用户信息存入ThreadLocal方便后续获取当前用户 UserContext.set(claims); } return true; } }为什么这里要加handler instanceof HandlerMethod的判断因为如果没有这个判断当请求的是一个静态资源或者对应用路径的Resource处理器时拦截器也一样会试图解析Token造成不必要的报错。这个小坑我踩过一次排查了半天才明白是拦截器拦截了静态资源导致的。更细的权限控制我这里建议配合自定义注解来做比如定义RequireRole(ADMIN)在拦截器中读取HandlerMethod上的注解再对比JWT中的role字段不一致就返回403。这套方案比单独写三个拦截器要灵活得多新增加接口时只要在方法上加上对应注解权限控制就完成了。2.3 基于MyBatis-Plus的表结构与自动建表技巧数据库设计这部分我使用的是MySQL数据库名定义为edu_assistant。核心表包括sys_user用户表、sys_role角色表、course课程表、course_selection选课表、score成绩表、notice公告表、classes班级表。这里重点说下选课表它其实是一个中间关联表字段包含选课ID、学生ID、课程ID、选课时间同时设置了唯一索引student_id, course_id从数据库层面杜绝重复选课。关于建表一个很实用的技巧是配合MyBatis-Plus的自动建表能力。虽然MyBatis-Plus本身不直接支持自动建表但可以通过在配置中扫描实体类结合TableName注解和TableField注解在系统启动时执行DDL语句。如果你的表还不存在可以写一个简单的SchemaInitializer在ApplicationRunner中判断表是否存在若不存在则执行建表SQL。这个小功能对于快速部署项目非常友好我经常在演示项目中使用。Component public class DatabaseInitializer implements ApplicationRunner { Autowired private JdbcTemplate jdbcTemplate; Override public void run(ApplicationArguments args) { String checkTableSql SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMAedu_assistant AND TABLE_NAMEsys_user; Integer count jdbcTemplate.queryForObject(checkTableSql, Integer.class); if (count null || count 0) { // 执行建表脚本 jdbcTemplate.execute(CREATE TABLE sys_user (...)); } } }这个属于锦上添花的方案实际项目中更常用的做法是维护一份schema.sql脚本在部署时手动导入数据库。源码中一般会附带的editor.sql就是这种脚本直接导入就可以了。但自动建表适合给不懂数据库的人演示用省去导入步骤。2.4 选课与成绩模块的业务逻辑边界选课模块有一个常见的坑就是超选问题。假设一门课的容量是50人两个学生同时提交选课请求如果没有做并发控制可能两个请求都查到了当前选课人数为49然后都执行插入就变成51人。解决方式有两种一种是悲观锁在事务中SELECT ... FOR UPDATE锁定课程记录另一种是乐观锁在课程表中加一个version字段更新时比较version是否一致。这套系统里的实现相对简单在选课Service中加入事务并在插入前先做一个update操作来占住名额Transactional public void selectCourse(Long studentId, Long courseId) { // 先扣减课程剩余名额返回受影响的行数 int updated courseMapper.reduceRemainCount(courseId); if (updated 0) { throw new BusinessException(500, 课程已满选课失败); } // 插入选课记录 courseSelectionMapper.insert(new CourseSelection(studentId, courseId)); }这里的关键点是先执行update再插入而不是先查询再插入因为update语句本身会被行锁保护在高并发下能有效避免超卖问题。这也是我在实际处理库存类业务时比较推荐的做法简单有效不用引入分布式锁。成绩模块相对简单但要注意的一个设计是成绩等级的自动划分。教师录入的是百分制分数系统可以根据分数段自动映射为优秀、良好、中等、及格、不及格几个等级这个逻辑在Service层做一个工具方法即可不需要前端参与。如果你后续要扩展教师端成绩分析功能比如班级平均分、最高分、各分数段占比直接基于成绩表做聚合查询就行。3. 前端Vue实现细节从环境搭建到组件封装3.1 Vue环境配置与项目初始化前端部分我先说说环境配置因为这里是最容易让新手卡住的地方。Vue项目的开发依赖Node.js环境我强烈建议安装Node.js 14或16版本不要用最新的20否则安装依赖时很容易遇到node-sass编译失败、依赖版本不兼容等问题。如果你用的是nvm管理Node版本切换起来会非常方便。# 查看当前Node和npm版本 node -v npm -v # 安装Vue CLI脚手架 npm install -g vue/cli4.5.15 # 创建项目 vue create edu-frontend创建项目的时候建议选择Manually select features然后勾选Router、Vuex、CSS Pre-processors。如果直接用默认的Default预设后续可能需要自己手动安装router和vuex反而多一步操作。项目创建完成后别忘了安装UI组件库和HTTP请求库npm install element-ui axiosVue安装依赖时我遇到过不少次因为镜像地址访问缓慢导致安装失败的情况解决办法是切换到npm淘宝镜像源npm config set registry https://registry.npmmirror.com这样速度会快很多安装依赖的体验会好不少。如果你还是遇到卡在安装某个依赖上可以删除node_modules和package-lock.json再重新执行npm install。3.2 基于Vue Router的前端路由与权限控制前端路由权限控制是很多初学者容易忽略但又非常重要的部分。这套系统的菜单是根据用户角色动态渲染的学生登录后看不到成绩管理菜单教师登录后看不到用户管理菜单管理员则拥有全部权限。实现思路是在Vue Router中定义路由时给每个路由增加meta信息比如{ path: /admin/users, name: UserManage, component: () import(/views/admin/UserManage.vue), meta: { title: 用户管理, roles: [ADMIN] } }然后在路由守卫beforeEach中判断当前用户的角色是否在路由meta.roles中如果不在则跳转到401页面或首页。router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (!token to.path ! /login) { next(/login); return; } const userRole store.getters.role; if (to.meta.roles to.meta.roles.indexOf(userRole) -1) { next(/403); return; } next(); });路由懒加载也值得提一下() import(/views/xxx.vue)这种方式会让每个页面单独打包成一个chunk首屏加载速度明显更快推荐所有项目都用这种写法。前端路由参数传递也是高频使用点比如课程详情页需要知道点击的是哪门课可以用this.$route.params.id或者this.$router.push({ path: /course/detail, query: { id: courseId } })。query方式的参数会出现在URL上刷新后参数不丢失params方式如果不用动态路由匹配刷新后参数会丢失这也是一个容易踩到的坑。3.3 axios请求封装与统一异常处理axios封装是我的Vue项目中必做的一个环节没有封装直接在每个页面里写axios.get会导致代码大量重复而且后端返回结构不统一时每个页面都要做异常处理极容易漏处理。我习惯这样封装创建api/request.js通过axios.create创建一个配置了baseURL和超时时间的实例然后添加请求拦截器和响应拦截器。请求拦截器把本地存储的token加在请求头上service.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers[Authorization] Bearer ${token}; } return config; });响应拦截器统一处理后端返回的错误码比如401跳转登录页、500弹出错误提示等service.interceptors.response.use( response { const res response.data; if (res.code 200) { return res; } Message.error(res.msg || 请求失败); return Promise.reject(new Error(res.msg || 请求失败)); }, error { if (error.response error.response.status 401) { localStorage.removeItem(token); router.push(/login); } Message.error(网络请求异常); return Promise.reject(error); } );这样一来业务页面里只需要调用封装的api方法不用再关心错误处理整体代码会清爽很多。另外建议把每个模块的接口单独建一个js文件比如api/course.js、api/score.js按模块维护URL避免所有接口都堆在页面里。3.4 课程视频播放场景用video.js实现m3u8流播放这套教学辅助系统里有一个和课程内容展示相关的功能在线播放教学视频。在实际项目中教学视频往往以HLS流媒体格式提供也就是说视频地址是一个.m3u8的索引文件。在Vue中播放m3u8推荐使用video.js配合videojs-contrib-hls插件这样兼容性比较好。npm install video.js videojs-contrib-hls然后在组件中引入并初始化播放器import videojs from video.js; import video.js/dist/video-js.css; import videojs-contrib-hls; mounted() { this.player videojs(this.$refs.videoPlayer, { sources: [{ src: this.videoUrl, type: application/x-mpegURL }], controls: true, autoplay: false, preload: auto }); }, beforeDestroy() { if (this.player) { this.player.dispose(); } }需要特别注意的是播放m3u8时视频源必须允许跨域访问否则浏览器会直接拦截视频请求页面会一直黑屏。如果视频服务器是单独的域名记得在视频服务端加好CORS响应头或者通过后端代理转发视频请求这是实际部署中非常容易踩坑的地方。还有一个细节是播放器销毁在组件销毁时一定要调用player.dispose()释放播放器实例否则切换到其他页面后视频的音频可能还在后台继续播放这个小问题排查起来让人很抓狂。4. 本地部署与服务器发布完整实操4.1 后端环境准备与数据库初始化拿这套源码在本地跑起来我建议按顺序操作。先处理数据库打开Navicat或者命令行客户端创建一个edu_assistant数据库设置好字符集为utf8mb4然后将源码中附带的SQL脚本导入。为什么要用utf8mb4而不是utf8因为utf8mb4能完整支持中文和emoji表情而且MySQL 8.0默认字符集就是utf8mb4保持一致能避免不少乱码问题。导入完成后修改后端application.yml配置文件核心配置有三块数据库连接信息、Redis连接信息如果用到了缓存和JWT密钥。数据库配置示例spring: datasource: url: jdbc:mysql://localhost:3306/edu_assistant?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8用IDEA打开后端项目后点击Maven侧边栏的Reload All Maven Projects等待依赖下载完成。如果下载速度慢可以在Maven的settings.xml中配置阿里云镜像。依赖加载完毕后直接运行主启动类Application确认控制台输出SpringBoot启动成功的日志并且没有报数据库连接错误后端就算是跑通了。4.2 前端本地启动与跨域代理配置前端部分在项目根目录执行npm install安装依赖然后执行npm run serve启动开发服务器。但是这里有个关键问题Vue开发服务器默认运行在localhost:8080后端接口运行在localhost:8081跨域问题会直接拦截请求所以需要配置vue.config.js中的devServer代理module.exports { devServer: { port: 8080, proxy: { /api: { target: http://localhost:8081, changeOrigin: true, pathRewrite: { ^/api: } } } } }这里的规则是前端所有以/api开头的请求都会被代理转发到后端8081端口同时去掉/api前缀。比如前端请求/api/user/login实际后端收到的是/user/login。这样在开发阶段就绕过了跨域限制不需要后端单独开启CORS。4.3 云服务器部署的整体思路部署到云服务器其实核心就三件事后端打包、前端打包、配置Nginx反代。后端打包用Maven的package命令执行mvn clean package -DskipTests在target目录下生成一个jar包。然后用java -jar edu-assistant.jar启动。如果想把启动过程放到后台可以用nohup命令nohup java -jar edu-assistant.jar --server.port8081 app.log 21 前端打包执行npm run build生成dist目录把dist目录上传到服务器。然后配置Nginx将80端口的请求分发到前端静态资源和后端接口server { listen 80; server_name your-domain.com; location / { root /usr/share/nginx/html/dist; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://localhost:8081; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这里说一下try_files的作用Vue是单页应用路由切换是通过前端history模式实现的如果用户直接访问/course/detail这样的路径Nginx需要把请求重新导向到index.html让Vue自己解析路由try_files就是干这个事情的。不加这一行刷新非首页路径时经常会报404开发部署时十个人有八个会踩这个坑。4.4 Windows服务器上Tomcat或Apache的搭配发布除了Linux服务器有些学校或者单位的Windows服务器也会用来部署JavaWeb项目。虽然SpringBoot内嵌了Tomcat直接用java -jar可以跑但如果你的服务器上已经部署了其他JavaWeb应用有独立的Tomcat也可以把SpringBoot项目打成war包发布到Tomcat的webapps目录下。打成war包需要两步一是pom.xml中把打包方式改为packagingwar/packaging二是主启动类继承SpringBootServletInitializer并重写configure方法SpringBootApplication public class EduApplication extends SpringBootServletInitializer { Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(EduApplication.class); } }演示一下Windows Server上ApacheTomcat搭配发布的方式用Apache作为前端入口处理静态资源比如Vue打包后的dist目录动态请求转发给Tomcat的8080端口Tomcat负责执行SpringBoot的war包。Apache通过mod_proxy模块进行反向代理配置上比Linux Nginx稍显繁琐但在老牌Windows服务器环境里还是很常见的。至于Redis如果系统用到了Redis做缓存或Token存储在Windows服务器上需要手动安装Redis的Windows版本或者使用Memurai这类兼容方案。我给这个系统做扩展时就在后台接入了Redis来缓存课程列表热点数据设置10分钟过期时间实测接口响应从200ms降到了30ms以内效果非常明显。5. 常见问题与排查技巧实录5.1 前端安装依赖报错处理跑Vue项目中npm install阶段大概率会遇到问题最常见的有node-sass安装失败、模块版本不兼容、peerDependencies冲突等。我遇到最多的是node-sass的安装失败因为node-sass需要根据Node版本编译原生模块Node版本太新或者太旧都会导致安装时报错。解决办法是改用sassdart-sass在package.json中替换依赖然后重新安装npm uninstall node-sass npm install sass --save-dev另外如果npm install报ERESOLVE错误说明依赖树有冲突可以试试npm install --legacy-peer-deps这个参数会按照npm旧版本的解析逻辑跳过peerDependencies检查很多时候能一次性装通。5.2 SpringBoot启动报数据库连接失败的排查后端启动时最常见的错误是Access denied for user rootlocalhost或者Communications link failure。第一种是用户名密码错误或者用户没有远程访问权限本地测试时建议直接使用root账号并确认密码无误第二种是数据库地址无法访问先把jdbc:mysql://localhost:3306/edu_assistant中的localhost和端口号核对一遍然后确认MySQL服务有没有启动。一个很容易被忽略的点是MySQL驱动版本和MySQL服务版本的匹配。如果你的MySQL是8.0版本但pom.xml中引用的mysql-connector-java是5.x的启动时会有时区相关的报错。这时候应该使用MySQL官方的Connector/J 8.x同时把驱动类改为com.mysql.cj.jdbc.DriverURL参数中加上serverTimezoneAsia/Shanghai问题就能解决。5.3 接口请求404或跨域问题的快速定位联调阶段经常遇到前端访问接口返回404或者被CORS策略拦截。如果是404先确认请求的URL路径和后端Controller的RequestMapping是否完全一致特别要注意路径上有没有斜杠差异比如/user/login和/user/login/在SpringBoot中虽然能兼容但在某些严格配置下可能不匹配。如果是CORS跨域错误开发环境优先检查vue.config.js中的代理配置是否正确因为一旦配置了代理浏览器看到的请求是同源的不应该出现CORS错误。如果出现了多半是代理没有生效npm run serve之后没有重启或者路径没有以/api开头。生产环境则需要在Nginx中正确配置proxy_pass让前端路径和后端路径的映射关系对应上。还有一个WebSocket相关的坑如果某些功能用到了WebSocket比如在线考试防切屏提示代理配置需要额外处理proxy_set_header Upgrade $http_upgrade;和proxy_set_header Connection upgrade;否则WebSocket连接建立失败前端会一直报连接错误。5.4 内存溢出和接口超时的排查思路在服务器上跑了一段时间后如果发现系统变慢或者抛出OutOfMemoryError优先考虑做两点一是给JVM设置合理的堆内存参数比如-Xms256m -Xmx512m二是排查代码中是否有慢SQL或者数据量大的查询没有做分页。如果教学辅助系统里学生选课记录超过几万条查询选课列表的时候用全表扫描速度会慢到让人崩溃一定要给表加上合适的索引比如选课表的(student_id, course_id)联合索引。线上环境排查CPU飙高的问题我一般会先用top命令看哪个进程占用CPU高再用jstack导出线程快照看是否有线程长时间处于Runnable状态。很多时候都是死循环或者某个接口在频繁执行大查询导致的定位到具体代码后再优化。5.5 一套实用的问题排查速查表现象可能原因快速排查方法前端npm install卡住网络问题/镜像慢切换npm源为国内镜像后端启动报驱动类找不到pom中依赖版本问题改用mysql-connector-j 8.x接口返回401Token过期/未携带检查请求头Authorization部署到服务器后前端白屏静态资源路径错误检查dist上传位置和Nginx root刷新页面404缺少try_files配置在Nginx中配置fallback到index.html视频无法播放跨域或m3u8地址不可达检查CORS和视频服务状态选课人数超限并发问题改用update扣减名额方案这套速查表是我实际开发中逐步积累下来的能覆盖大多数JavaWeb前后端分离项目的常规问题。遇到问题先对照排查一遍大部分情况都能解决剩下的再通过日志深入了解。写在最后我的实际体会与扩展建议我复盘这套SpringBootVue教学辅助系统的源码时最大的感受是结构清晰的项目比功能堆砌的项目更有学习价值。它没有引入过多复杂的中间件核心链路是SpringBootMyBatis-PlusJWTVueElement-UI每一环都是JavaWeb技术栈中最主流、资料最多的选择。如果你是一名学生可以用它完成毕业设计并借此讲清楚前后端交互原理如果你是一名初级开发者可以用它理解真实项目中的用户角色、事务处理和部署流程。如果后续要扩展我建议优先考虑两个方向一是引入Flowable工作流引擎来替换现在的公告审批和请假审批让流程可视化这部分在热词中也提到了springboot使用flowable是一个进阶好方向另一个是接入Redis缓存课程列表和验证码顺便学习缓存穿透、缓存击穿这些经典问题的应对方案。教学辅助系统的业务边界还可以扩大到在线考试、作业提交、学生考勤等场景底层这套用户角色和课程体系已经具备了较好的扩展基础。实际动手做一遍踩过几个坑之后你对SpringBootVue这套组合的理解会远超看一百篇教程的效果。项目不算大但五脏俱全把它吃透足以应付绝大多数教育类JavaWeb项目的开发需求。