ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

SpringBoot+Vue选课系统实战:前后端分离架构设计与落地

2026/9/9 0:46:58 拓冰建站 浏览量
SpringBoot+Vue选课系统实战:前后端分离架构设计与落地 简介本资源是一套基于SpringBoot与Vue.js实现的前后端分离学生选课管理系统专为计算机专业本科毕设bishe及Java全栈初学者设计覆盖课程管理、选课退课、师生信息维护、成绩录入等高校教务核心业务场景助力学习者快速掌握主流技术栈的工程化落地能力。压缩包共107个文件含49个Vue组件实现学生/教师/管理员三端交互界面、24个Java后端服务类含SCTService、CourseTeacherService等业务逻辑、14个MyBatis映射XML、6个JS工具脚本以及SQL建库脚本、YML配置、HTML入口页等整体仅266KB轻量易部署。已有1284人学习下载资源经严格调试可直接运行附完整源码、数据库脚本与清晰分层目录结构特别适合毕设开题、课程设计与SpringBootVue实战练手。1. 这不是又一个“Hello World”项目为什么选课系统是前后端分离的黄金练兵场你点开这个标题大概率正面临三种情况刚学完SpringBoot和Vue基础想找个不 trivial 又不至于太复杂的项目来串联知识正在准备技术面试需要一个能讲透、能延展、能体现工程思维的实战案例或者手头真有个校内小需求得快速搭个可用的选课后台。这三类人我都带过——从大三学生到三年经验的开发最后都卡在同一个地方不是写不出功能而是写出来的东西一碰就散改个字段要前后端一起动加个权限要重写半套逻辑部署时发现前端静态资源和后端接口根本对不上号。而“学生选课管理系统”恰恰是那个最合适的切口业务边界清晰学生、课程、教师、选课记录四张核心表流程可闭环登录→查课→选课→退课→查成绩但又足够真实——它天然包含角色权限学生只能看自己课教师能管自己课管理员全览、状态流转选课中/已确认/已退选、并发控制热门课抢课、数据一致性选课成功必须扣库存记日志这些工业级问题。我去年帮某高校信息中心做轻量版选课工具时第一版用传统JSP硬怼上线三天就因并发选课失败被叫停第二版直接上SpringBootVue分离架构用JWT做会话Redis缓存课程余量MyBatis-Plus自动生成CRUD两周内交付稳定版本。关键不在技术多炫而在整个链路里每个环节都经得起推敲前端Vue路由守卫拦未登录请求后端SpringBoot拦截器校验JWT并注入用户上下文数据库事务保证选课与库存更新原子性。这不是教科书里的理想模型而是每天都在发生的现实约束。2. 架构设计不是画PPT为什么必须拆成前后端以及拆到什么程度2.1 前后端分离不是“把HTML扔给Vue”而是职责的物理隔离很多人以为前后端分离就是“后端写API前端调API”结果代码仓库里还混着Thymeleaf模板Controller里塞着ModelAndView跳转逻辑。真正的分离是让两个团队能完全独立演进。举个具体例子当教务处突然要求在选课页面增加“课程难度星级”和“往届学生评语摘要”前端团队拿到接口文档后30分钟就能在Vue组件里加两个字段渲染而后端只需在Course实体类里加两个ApiModelProperty注解MyBatis-Plus自动映射连SQL都不用改。但如果还是老式MVC前端改HTML后端改Controller返回Model还要同步更新JSP模板一次小需求就得拉通会议、协调测试、重新发版。我见过最惨的案例是某职校系统因为一个“课程封面图”字段新增前后端各改了5个文件联调花了两天最后发现是Nginx配置漏了图片路径代理。2.2 SpringBoot后端只做三件事数据管道、业务规则、安全闸门后端的核心价值从来不是渲染页面而是成为可靠的数据中枢。在本系统中SpringBoot层严格限定为数据管道通过MyBatis-Plus操作MySQL所有DAO层方法命名遵循selectByStudentIdAndStatus()这类语义化规范避免queryList()这种模糊命名使用TableField(exist false)明确标出非数据库字段防止误插入。业务规则引擎选课逻辑绝不放在前端校验。比如“同一学期同一课程只能选一次”、“退课截止时间前才能操作”这些规则全部在Service层用Transactional包裹配合Valid校验DTO错误时抛出统一BusinessException由全局异常处理器转成400响应体。安全闸门JWT鉴权不是简单加个PreAuthorize(hasRole(STUDENT))。我们实际实现中Token解析后将用户ID、角色、学院ID存入ThreadLocal后续所有DAO查询自动拼接AND college_id ?条件彻底杜绝越权查看其他学院课程数据。提示别用Spring Security默认的内存用户管理。真实场景中用户信息来自LDAP或学校统一认证平台我们用UserDetailsService定制加载逻辑密码校验走BCryptToken过期时间设为2小时——既防暴力破解又避免学生选课到一半突然掉线。2.3 Vue前端不是“套壳浏览器”而是独立应用生命周期管理Vue项目必须脱离后端模板独立存在。我们采用vue-cli创建标准项目public/index.html里只留一个div idapp/div所有路由、状态、API调用全部在src目录下完成。关键设计点API分层src/api下按模块划分course.js,selection.js每个文件导出函数如getCourseList(params)内部用Axios封装baseURL、超时、错误拦截。绝不允许在组件里直接写axios.get(/api/course/list)。状态管理克制Vuex只存全局状态用户信息、菜单权限课程列表、选课表单等页面级状态直接用ref()或reactive()管理。曾有团队过度使用Vuex导致调试困难一个课程搜索条件变更要追踪7个mutation。路由守卫实战router.beforeEach()不只是判断登录态。我们额外检查to.meta.requiresRole若用户角色不匹配则跳转403页对/course/detail/:id这种动态路由守卫中预加载课程数据并存入Pinia避免组件内重复请求。3. 核心功能落地从数据库建模到接口联调的完整链路3.1 数据库设计用MyBatis-Plus反向驱动开发节奏我们放弃手动建表全程用MyBatis-Plus的AutoGenerator生成。先定义实体类再让框架建库——这倒逼你思考数据结构本质。以核心的Selection选课记录表为例TableLogic // 逻辑删除避免误删历史数据 public class Selection { TableId(type IdType.ASSIGN_ID) // 雪花算法ID避免MySQL自增主键暴露业务量 private String id; TableField(value student_id) private String studentId; // 外键但不设数据库约束由应用层保证一致性 TableField(value course_id) private String courseId; TableField(value status) private Integer status; // 0-待确认, 1-已选中, 2-已退选, 3-已取消管理员操作 TableField(value created_time, fill FieldFill.INSERT) private LocalDateTime createdTime; }生成后MySQL表结构自动带is_deleted字段和created_time时间戳。重点在于student_id和course_id不设外键——线上环境外键会拖慢高并发写入我们用Service层的双重校验兜底选课前查studentId是否存在查courseId是否有效且未关闭。3.2 关键接口实现选课动作的原子性保障选课是最易出错的环节。前端点击“立即选课”按钮后后端必须保证三件事同时成功或同时失败1插入选课记录2课程余量减13记录操作日志。SpringBoot用Transactional声明式事务但需注意两点传播行为Transactional(propagation Propagation.REQUIRED)是默认值确保同一事务内异常类型默认只回滚RuntimeException所以Service方法里抛new RuntimeException(库存不足)而非new Exception()。实际代码中我们用Redis做库存预占避免数据库锁竞争Service public class SelectionService { Autowired private RedisTemplateString, Object redisTemplate; Transactional public Result selectCourse(String studentId, String courseId) { // 1. Redis预占库存Lua脚本保证原子性 String key course:stock: courseId; Boolean result redisTemplate.execute( (RedisCallbackBoolean) connection - connection.eval(if redis.call(decr, KEYS[1]) 0 then return 1 else redis.call(incr, KEYS[1]); return 0 end.getBytes(), ReturnType.INTEGER, 1, key.getBytes()) ); if (!result) { throw new BusinessException(课程已满请稍后再试); } // 2. 插入选课记录 Selection selection new Selection(); selection.setStudentId(studentId); selection.setCourseId(courseId); selection.setStatus(1); // 已选中 selectionMapper.insert(selection); // 3. 记录日志异步不影响主流程 logService.asyncLog(学生 studentId 选课成功, courseId); return Result.success(); } }注意Redis库存和MySQL实际库存需定时任务对账。我们每晚2点执行SELECT COUNT(*) FROM selection WHERE course_id ? AND status 1与Redis值比对差异超阈值则告警。3.3 Vue前端关键交互如何让选课过程“看得见摸得着”前端不是简单调API。以课程列表页为例我们做了三层体验优化加载态精准控制不用全局loading而是为每个卡片设置loading状态。点击“选课”按钮后该卡片显示“选课中...”其他课程仍可操作。错误反馈具象化API返回{code: 400, msg: 库存不足}时不弹alert而是在按钮下方显示红色提示“❌ 该课程剩余名额0”3秒后自动消失。本地状态即时同步选课成功后立即执行this.courseList.find(c c.id courseId).selected true避免刷新页面——用户感知就是“点一下就变绿了”。路由配置也暗藏技巧/course/select/:id页面用beforeRouteEnter守卫预加载课程详情但若用户直接输入URL访问需在mounted钩子中补加载防止SSR场景下数据为空。4. 部署与运维从本地开发到阿里云上线的避坑实录4.1 开发环境用Docker Compose一键拉起全栈依赖本地开发最怕“在我机器上是好的”。我们用docker-compose.yml统一环境version: 3.8 services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: course_db ports: - 3306:3306 redis: image: redis:7-alpine ports: - 6379:6379 nginx: image: nginx:alpine volumes: - ./nginx.conf:/etc/nginx/nginx.conf ports: - 8080:80 depends_on: - frontend - backend frontend: build: ./frontend # 构建后生成dist供nginx挂载 backend: build: ./backend environment: - SPRING_PROFILES_ACTIVEdev ports: - 8081:8081关键点Nginx配置nginx.conf里location /api/代理到后端location /指向前端dist目录。这样前端访问/api/course/listNginx自动转发到http://backend:8081/api/course/list彻底解决跨域问题且与生产环境一致。4.2 生产部署阿里云ECS上的最小可行方案上云不等于堆配置。我们用2核4G ECS约80/月跑通全流程后端SpringBoot打成jar包用nohup java -jar app.jar --spring.profiles.activeprod 启动配合systemd服务管理进程。前端npm run build生成dist用scp上传到ECS的/var/www/htmlNginx配置静态文件服务。数据库直接用阿里云RDS MySQL 5.7白名单只放ECS内网IP。HTTPS阿里云免费SSL证书Nginx配置ssl_certificate和ssl_certificate_key强制return 301 https://$host$request_uri;。最常踩的坑是Nginx代理超时。选课接口可能因Redis锁等待耗时较长需在location /api/块中加proxy_connect_timeout 30s; proxy_send_timeout 30s; proxy_read_timeout 60s;4.3 监控与日志没有监控的系统等于裸奔上线后第一件事不是测功能而是看日志。我们用ELKElasticsearchLogstashKibana收集后端日志Logback配置RollingFileAppender按天滚动保留30天Nginx日志开启log_format main $remote_addr - $remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent $request_time;$request_time是关键能定位慢接口。在Kibana里建看板重点关注request_time 3s的请求TOP10status 500的错误趋势/api/selection/select接口的QPS和成功率。曾发现某次选课高峰request_time突增排查发现是Redis连接池耗尽。解决方案不是加机器而是调整Lettuce连接池参数spring: redis: lettuce: pool: max-active: 20 # 默认8调高 max-wait: 1000 # 毫秒避免线程阻塞5. 面试与毕设高频问题把项目变成你的技术名片5.1 面试官最爱问的三个深度问题Q1如果选课并发量达到1000TPS当前架构怎么优化答分三层应对。第一层Nginx加limit_req zoneapi burst100 nodelay限流防雪崩第二层Redis用集群模式课程库存Key按courseId % 16分片避免单节点热点第三层数据库读写分离SELECT走从库INSERT/UPDATE走主库并用ShardingSphere做水平分表按studentId哈希。Q2JWT Token泄露怎么办如何实现Token主动失效答Token本身无法主动销毁但我们用“黑名单短时效”组合拳。Redis存blacklist:{token}过期时间Token剩余有效期登录时生成Token的同时将jti唯一标识存入Redis每次请求校验Token后检查jti是否在黑名单。同时Token有效期设为2小时降低泄露风险。Q3Vue路由懒加载后首屏白屏时间长怎么优化答不止是import()。我们在vue.config.js中配置configureWebpack: { optimization: { splitChunks: { chunks: all } } }把Ant Design Vue组件单独打包用prefetch预加载首页必需的课程列表模块最关键的是服务端渲染SSR——用Nuxt.js重构但成本高建议毕设阶段用prerender-spa-plugin生成静态首页。5.2 毕设答辩必答清单为什么选MyBatis-Plus而不是JPAJPA的OneToMany在选课这种多对多场景下关联查询容易N1且学校MySQL版本较老5.6JPA某些特性支持不全。MyBatis-Plus的LambdaQueryWrapper写法更贴近SQL思维调试时直接看到执行语句。如何证明系统安全性展示三点1所有接口用PreAuthorize标注权限2SQL注入测试——在课程名输入 OR 11后端日志显示MyBatis-Plus自动转义3XSS防护——前端用v-html的地方全部经过DOMPurify.sanitize()过滤。项目的创新点在哪不是技术堆砌而是解决真实痛点。比如“课程余量实时显示”我们用WebSocket推送库存变更学生列表页不用轮询再如“选课冲突检测”前端在选课前调用/api/selection/conflict?studentIdxxxcourseIdyyy接口后端查该生本学期已选课程时间是否重叠提前拦截。最后分享个血泪教训某次部署后发现选课总是失败查日志全是Connection refused。折腾两小时才发现ECS安全组没开6379端口Redis连不上。所以现在我的上线checklist第一条就是“telnet rds-host 3306 telnet redis-host 6379”。技术再牛也得先让网络通。本文还有配套的精品资源点击获取