1. 项目概述与适用人群
先说结论:这是一个用 SpringBoot + Vue 前后端分离架构实现的“语言考试信息报名系统”,覆盖了用户登录注册、考试信息发布、在线报名、后台审核、数据统计等完整闭环。技术栈是 Java + MySQL + Vue,从选型到落地的路线非常“标准科班”,特别适合计算机相关专业的毕业设计、课程设计,以及想完整走一遍前后端分离开发流程的学习者。
说完项目能干什么,我猜很多同学真正关心的是:这种项目到底能做不能做、难不难做、用了哪些技术、答辩时能讲出什么深度来。这些问题我在下文全部拆开讲,至少让你读完心里有底,知道从哪下手,哪块容易翻车,哪块能做得出彩。
整个系统我按“用户前台 + 管理后台”两部分拆解。用户侧关注的是——浏览可报名的语言考试(比如大学英语四六级、专四专八、雅思、托福、日语等级考等),注册登录后提交报名信息、支付状态记录、查看审核结果、下载打印准考证;管理侧关注的是——考试场次管理、报名批次开关、考生信息审核、导出报名名单、考场分配、数据看板。折腾下来之后你会发现,它就是一个典型的“信息发布 + 业务流转 + 权限控制”三件套系统。
适合谁来参考,我说得直接一点:
| 目标人群 | 怎么用这份源码 / 文章 |
|---|---|
| 毕设 / 课设学生 | 直接以此为蓝本二次开发,换皮换业务或加深功能,毫厘之内能做到“像自己做的” |
| 想练全栈的新手 | 按文章里的路线手动重写一遍,遇到问题再回头看源码,收获最大 |
| 想跳槽 / 面试的人 | 把它包装成项目经历,重点吃透权限、事务、报表这些面试高发点 |
提示:本项目的前后端分离模式是当前主流企业开发的基本形态,把这个吃透,等于一只脚踩进了真实的业务开发领域,所以这套东西不是“玩具”,而是有真实行业映射的训练场。
2. 整体设计思路与方案选型逻辑
2.1 为什么是 SpringBoot + Vue,而不是其他组合
很多刚接触全栈开发的同学经常会问:做毕设选什么框架比较稳妥?市面上的答案很多:SSM、SpringBoot、前后端一体、JSP 等等。坦白讲,如果你不是对某种老技术栈有特殊执念,我强烈建议选SpringBoot + Vue + MySQL这套组合。理由不复杂,且听我逐个拆:
一是就业市场需求端高度匹配。现在 Java 岗位的普遍要求就是 SpringBoot/SpringCloud 微服务体系,前端要会 Vue/React。这个组合写了、用了、解决了实际问题,等于是把简历上的技术关键词变成了真实项目证据。
二是开发效率真的高。SpringBoot 的自动配置极大帮你省掉了 Spring 时代繁琐的 XML 配置,起步一个 Controller 加一个 Service 就能把接口跑起来;Vue 的组件化开发、路由管理、状态管理,让页面逻辑变得非常清爽。前后端分离之后,接口写完了、Mock 数据一联调,两个人协作也能干出三个人的活。
三是资料多、坑少且社区成熟。这套技术栈遇到问题搜一下就有一堆解决方案,不管是环境配置、跨域、版本兼容,还是部署优化,踩坑成本低。对毕设党来说,时间是最稀缺的资源,选一个生态成熟的技术栈,等于直接把“未知风险”藏进了可控范围。
从系统的实际规模来看,语言考试报名系统属于典型的中小型 CRUD + 业务状态机项目,不需要分布式、不需要微服务、不需要高并发中间件,单体应用绰绰有余。这恰恰是 SpringBoot 最舒服的发力场景。
2.2 核心功能模块拆解与边界划分
拿到需求后第一件事不是写代码,而是想清楚这个系统要管哪些事。我把整体模块分为用户端和管理端两条主链路:
用户端主链路:
- 注册登录模块:手机号/邮箱 + 密码注册、JWT 登录态校验、用户信息维护。
- 考试信息浏览模块:展示所有语言考试的报考公告,包括考试名称、考试时间、报名开始/截止时间、费用、报名状态(未开始/报名中/已截止)。
- 报名模块:填写考生信息(姓名、身份证号、学校/单位、报考等级)、提交报名、确认缴费状态、等待审核。
- 个人中心模块:查看我的报名记录、当前审核状态、已通过的考试下载准考证。
- 消息通知模块(可选进阶):报名审核结果以站内信/邮件形式通知用户。
管理端主链路:
- 考试场次管理:创建考试类型、设置批次时间、开关报名通道。
- 报名审核模块:后台列表筛选出“待审核”报名单,核对报考条件,通过或退回并填写备注。
- 考生管理:查看已报名学生信息,按场次、状态、院校筛选导出。
- 数据看板:统计总报名人数、待审核数量、各考试报名热度、每日新增报名趋势。
这个边界划分有一个非常实用的好处:前后端职责可以彻底分开开发。前端只需要管页面跳转和数据渲染,后端只提供 JSON 接口,两者通过 API 文档对齐。你在答辩的时候也可以把这个设计引入“模块低耦合、前后端分离协作”的项目管理方法论里,绝对加分。
2.3 项目目录结构与分层设计
我习惯把一个前后端分离项目在早期就按标准目录结构去搭,因为后面功能一多,结构混乱的代码会让你怀疑人生。
后端目录(SpringBoot 标准分层):
com.example.exam ├── controller # 接口层:接收前端请求 ├── service # 业务层:处理具体业务逻辑 │ └── impl ├── mapper # MyBatis-Plus 数据访问层 ├── entity # 实体类,与数据库表对应 ├── dto # 数据传输对象,避免实体直接暴露 ├── vo # 视图返回对象 ├── config # 配置类:跨域、MyBatis-Plus、拦截器等 ├── utils # 工具类:JWT、日期处理等 ├── common # 统一返回结果、异常处理、常量等 └── ExamApplication.java前端目录(Vue 3 + Vite 标准结构):
src ├── api # 接口请求定义,按模块拆分 ├── assets # 静态资源 ├── components # 通用组件(分页、表格、弹窗等) ├── router # 路由配置与守卫 ├── store # Pinia 状态管理 ├── views # 页面视图(用户端 + 管理端) ├── utils # 请求封装、token 处理等 └── App.vue注意:分层不是为了装样子。你以后写真实项目也一定是这个思路。Controller 只做参数接收和结果返回,Service 专注业务规则,Mapper 只跟数据库打交道。如果面试官问你“为什么不做成 Controller 里直接查库”,你要能答上来——后期维护、扩展、测试都依赖这个边界。
3. 数据库设计与核心表结构
到数据库这一步,就没有纯“理论选手”的活路了,每一张表、每一个字段都应该是从真实业务里倒推出来的。我拿我当时建表的核心思路来举例。
3.1 E-R 分析与关键实体
一个语言考试报名系统的核心实体大概有:用户(考生)、考试场次、报名记录、审核记录(可理解为订单状态变更日志)。如果你的系统还要做更丰富的功能,可以加:院校/单位、考场分配、支付流水,但初级毕设做到前面四个实体即可闭环。
它们之间的关系:
- 一个用户可以产生多个报名记录(1:N)
- 一个考试场次可以被多个用户报名(1:N)
- 一个报名记录有唯一的当前状态(待审核 / 已通过 / 已退回 / 已取消)
- 审核记录专属于某一个报名记录,留下操作痕迹
3.2 sys_user 用户表
这个表是基础表,所有系统通用,但有几个字段值得琢磨。
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键,自增 |
| username | varchar(50) | 用户名,唯一 |
| password | varchar(255) | BCrypt 加密后的密码密文 |
| real_name | varchar(50) | 真实姓名,报名必填 |
| id_card | varchar(18) | 身份证号,唯一校验 |
| phone | varchar(20) | 手机号 |
| varchar(100) | 邮箱 | |
| school / company | varchar(100) | 学校或单位 |
| role | tinyint | 0-普通用户,1-管理员 |
| status | tinyint | 0-正常,1-禁用 |
| create_time | datetime | 注册时间 |
这里存在一个看似简单但特别容易踩坑的设计点:用户表和考生信息到底要不要拆表?我的答案是,如果业务只有“语言考试报名”一个场景,不拆,直接在 user 表里加字段,省去联表查询的麻烦。如果系统还要承载多个考试系统(比如有普通话考试、计算机等级考试),那你就要拆user和candidate_info两张表,考试报名时再快照一份信息,防止以后用户修改了资料影响历史报名单。这是扩展性的取舍,没有标准答案,关键看你当场怎么讲清楚。
3.3 exam_info 考试场次表
这表是整个“信息报名”的源头,考试能不能报、什么时候报、报多少钱,全在它身上。核心字段:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| exam_name | varchar(100) | 考试名称,比如“2024年上半年全国大学英语四级考试” |
| exam_type | varchar(50) | 考试类别(英语四级/六级/专四/专八/雅思等) |
| exam_date | datetime | 考试时间 |
| registration_start | datetime | 报名开始时间 |
| registration_end | datetime | 报名截止时间 |
| fee | decimal(10,2) | 报名费用 |
| max_people | int | 报名容量上限 |
| status | tinyint | 0-未发布,1-报名中,2-报名结束,3-已完成 |
| description | text | 报名公告说明 |
有个特别实用的设计是:不要在查询时动态判断“当前时间是否在报名窗口内”,而是在存储层就落一个 status 字段。用定时任务或者管理员手动触发,将到期的场次批量改为“报名结束”,查询列表时 SQL 直接where status = 1,省下大量时间比对逻辑。这一点写进系统设计里,会让你的代码“看起来经验丰富”。
3.4 sign_up_record 报名记录表
这是系统的核心业务表,也是后端逻辑含量最高的地方。
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| user_id | bigint | 报名用户 id |
| exam_id | bigint | 考试场次 id |
| sign_up_no | varchar(32) | 报名编号,业务流水号 |
| candidate_name | varchar(50) | 考生姓名(报名时快照) |
| id_card | varchar(18) | 身份证号(快照) |
| school | varchar(100) | 学校/单位(快照) |
| status | tinyint | 0-待审核,1-已通过,2-已退回,3-已取消 |
| audit_remark | varchar(255) | 审核备注(退回原因等) |
| auditor_id | bigint | 审核人 id |
| audit_time | datetime | 审核时间 |
| pay_status | tinyint | 0-未支付,1-已支付 |
| create_time | datetime | 报名提交时间 |
这里有一个需要重视的约束:唯一索引要建立在 (exam_id, user_id) 上,或者 (exam_id, id_card)。一个用户对同一场考试只能报名一次。这事你在数据库层面兜底,代码里也做校验,双保险。我见过太多系统只在代码里判断,结果高并发或者连点情况下照样生成重复报名单,这在答辩演示时非常掉价。
3.5 数据统计划重点
统计需求大多是“按考试场次报了多少人”“审核通过/退回比例”“近几天新增趋势”,这些都可以用一条 SQL 解决,不需要另建统计表。例如:
SELECT exam_id, COUNT(*) AS total, SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) AS approved FROM sign_up_record GROUP BY exam_id;但如果你想要“实时看板大屏”,长周期报表,那就需要建一张日统计聚合表,用定时任务每天凌晨把昨天数据算好存起来,页面直接查表展示,性能好得多。这属于进阶优化点,面试的时候可以主动提。
4. 后端核心功能实现与难点拆解
4.1 SpringBoot 项目初始化与统一返回结构
这里用 Spring Initializr 生成项目,选 Java 8或11、SpringBoot 2.7.x。为什么建议 2.7.x?因为它和现在的 MyBatis-Plus、SpringDoc 等配套兼容性最成熟,对新手最友好。SpringBoot 3.x 要求 Java 17 及以上,部分老教程代码跑不起来,容易劝退。
建完项目第一件事是搭一个统一的返回结构。我用的是最常见的Result<T>类:
@Data public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage("success"); result.setData(data); return result; } }所有 Controller 都返回Result<T>,前端在 request 封装里统一拦截code做后续判断。你可不要小看这一步,它能让前后端联调省下很多沟通成本,全局异常处理器也基于这个结构返回错误信息。
4.2 登录认证与权限控制的两种方案
方案一(适合快速实现):登录成功后把用户 ID 和角色放进拦截器。用一个自定义注解标识需要管理员权限的接口,没有权限直接返回 403。这种方案简单直接,适合课程设计时间紧的情况。
方案二(更标准,推荐):JWT + Spring Security 或拦截器双重配合。登录成功后签发 token,前端每次请求在 header 带上Authorization: Bearer <token>,后端写一个 JWT 工具类负责解析。同时配合拦截器统一校验 token 的合法性。我个人建议用拦截器 + JWT,不用引入 Spring Security 那套复杂配置,因为 Spring Security 对新手来说配置成本太高,权当毕业设计的话有点划不来。
认证流程大致是:用户提交用户名密码 → 查询数据库 → BCrypt 校验密码 → 生成 token 返回前端 → 前端存储到 localStorage 或 Pinia → 请求拦截器携带 token → 后端解析 token 获取 userId → 注入请求上下文。
4.3 报名功能的“事务 + 状态机”双保险
报名这个动作是项目中唯一一个必须加@Transactional的地方。为什么?因为报名涉及多步操作:校验考试场次状态 → 校验是否重复报名 → 创建报名记录 → 更新该场次当前报名人数。任何一步失败,前面的数据都要回滚,否则就会出现“报名失败了但人数增加了”的脏数据。
@Transactional(rollbackFor = Exception.class) public Result<Void> signUp(Long examId, SignUpDTO dto) { // 1. 校验考试场次是否存在且状态为报名中 ExamInfo exam = examMapper.selectById(examId); if (exam == null || exam.getStatus() != 1) { return Result.error("该考试不可报名"); } // 2. 校验是否已经报过名 LambdaQueryWrapper<SignUpRecord> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(SignUpRecord::getExamId, examId) .eq(SignUpRecord::getUserId, currentUserId); if (signUpRecordMapper.selectCount(wrapper) > 0) { return Result.error("您已报名过该考试,请勿重复报名"); } // 3. 校验人数是否达到上限 if (exam.getCurrentPeople() >= exam.getMaxPeople()) { return Result.error("该考试报名人数已满"); } // 4. 创建报名记录并更新人数 SignUpRecord record = new SignUpRecord(); // ... 组装数据 signUpRecordMapper.insert(record); exam.setCurrentPeople(exam.getCurrentPeople() + 1); examMapper.updateById(exam); return Result.success(); }这个接口看着简单,但它包含了三层核心逻辑:业务状态校验、幂等防重、数据一致性。你答辩时能把这三层讲明白,导师就知道你真的动手实操了,不是只看视频敲了一遍。
4.4 审核模块的权限校验与 Remark 落库
审核是管理员的核心操作,调用接口前必须校验当前用户角色。管理员能看到待审核列表,点击通过或退回时更新状态、写入审核人 ID、审核时间和备注。
这里我也走过弯路:最初审核接口只更新了status,没记审核人和审核备注,导致后期学生来咨询“为什么退回了”完全无据可查。所以这块一定要老老实实把auditor_id和audit_remark都写进去,这既是业务留痕,也是代码的自解释性。
4.5 文件上传:Excel 导入与准考证生成
很多考试报名系统还需要支持和 Excel 对接:管理员批量导入考生名单、导出报名表。这里我直接用 EasyExcel,几行代码搞定,比 POI 原生 API 省太多事。
// 导出报名记录 EasyExcel.write(outputStream, SignUpRecordExcelVO.class) .sheet("报名记录") .doWrite(list);准考证生成有两种做法:一种是生成 HTML 模板再转 PDF,另一种是直接用 Java 后端返回一个带参数的页面链接,让用户自行打印。毕设阶段用第二种够用了,但如果你想让项目看起来更完整,可以引入 Hutool 的 PDF 工具类或者 iText 生成简单 PDF。这部分属于加分项,按自己的时间预算决定。
5. 前端 Vue 页面实现与关键交互
5.1 Vue3 + Vite 的项目搭建与请求封装
前端我建议直接用 Vue3 + Vite + Element Plus。Vue3 是当前主流,Vite 启动速度快,Element Plus 组件库拿来即用,做后台管理系统完全可以做到“快且不难看”。
项目初始化就用:
npm create vite@latest exam-frontend -- --template vue cd exam-frontend npm install npm install element-plus @element-plus/icons-vue axios pinia vue-router然后做一个request.js统一封装 axios:
// utils/request.js import axios from 'axios' import { ElMessage } from 'element-plus' import router from '../router' const request = axios.create({ baseURL: '/api', timeout: 10000 }) request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) request.interceptors.response.use( response => { // 如果后端返回业务错误码,统一在这里弹提示 if (response.data.code !== 200) { ElMessage.error(response.data.message) return Promise.reject(new Error(response.data.message)) } return response.data.data }, error => { if (error.response?.status === 401) { localStorage.removeItem('token') router.push('/login') } ElMessage.error(error.message) return Promise.reject(error) } ) export default request有了这个封装,业务层的 api 调用就非常清爽:
// api/exam.js import request from '../utils/request' export function getExamList(params) { return request.get('/exam/list', { params }) } export function signUpExam(examId, data) { return request.post(`/exam/${examId}/sign-up`, data) }提示:跨域问题在本地开发时几乎一定会遇到,别慌。在 Vite 的
vite.config.js里配个 server.proxy 转发到后端 8080 端口,让前端请求走相对路径/api,就能完美绕过跨域。生产环境部署时再用 Nginx 反代一层,一回事。
5.2 用户端页面设计与组件划分
用户端我拆了四个核心视图:
1. 首页 / 考试列表页:卡片或表格展示考试场次,状态标签(报名中 / 未开始 / 已截止)从后端返回的 status 渲染。卡片上放“查看详情”“立即报名”两个操作按钮,未登录用户点击跳登录页。
2. 考试详情页:展示完整的考试公告、时间、费用,下面跟随报名表单。
3. 报名表单页:核心组件是表单校验。姓名、身份证号、手机号都要有正则校验,身份证 18 位、手机号 11 位这种低级错误不能放过。Element Plus 的form.validate会用得非常多。
const rules = { candidate_name: [{ required: true, message: '请输入姓名', trigger: 'blur' }], id_card: [ { required: true, message: '请输入身份证号', trigger: 'blur' }, { pattern: /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/, message: '身份证号格式不正确', trigger: 'blur' } ] }4. 个人中心:展示我的报名列表、状态时间线。这里按需求嵌入一个“取消报名”的操作,但要注意:审核通过后应禁止取消,这也要前后端双重判断。
5.3 管理端页面设计
管理端我直接套用 Element Plus 的后台布局模板(左侧菜单 + 顶部栏 + 主内容区),靠路由守卫判断管理员身份进入。
关键页面:
- 考试场次管理:表格 + 新增/编辑弹窗 + 上下架开关。
- 报名审核页:这是最核心的页面。用
tabs切换“待审核 / 已通过 / 已退回”,列表项操作按钮放“通过 / 退回”,退回时用ElMessageBox.prompt强制填写退回理由。 - 数据看板页:用 ECharts 画几个图表,比如各考试报名人数柱状图、报名趋势折线图。前后端分离后,前端只管把后端统计接口的 JSON 填进 ECharts 配置,难度不高,但视觉效果非常加分。
5.4 路由守卫与登录态管理
Vue Router 的全局前置守卫用来处理登录态跳转:
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') const role = localStorage.getItem('role') if (to.meta.requiresAuth && !token) { next('/login') return } if (to.meta.requiresAdmin && role !== '1') { next('/') return } next() })这是很基础但必要的设计。不要想着在组件里每个按钮去判断角色,路由层统一做拦截,安全、省事。
6. 常见问题与避坑指南
6.1 MyBatis-Plus 版本与 SpringBoot 版本不兼容
这属于新手第一大坑。SpringBoot 2.7.x 请配mybatis-plus-boot-starter的 3.5.x 版本;如果你手滑用了 SpringBoot 3.x,那必须配 3.5.5+ 对应版本,否则启动就报创建 mapper 失败之类的异常。经验做法:直接用 SpringBoot 2.7 + MyBatis-Plus 3.5.3.1,实测最稳。
6.2 时间字段的时区与格式
数据库datetime类型、Java 的LocalDateTime、前端传给后端的 JSON 字符串,这三者很容易在时区上打架。解决方案:
- MySQL 连接串加上
serverTimezone=Asia/Shanghai; - Java 实体类时间字段统一用
LocalDateTime; - JSON 序列化时统一格式:
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss。
不管你在哪个环节碰到“时间少了 8 小时 / 多了 8 小时”,先检查这三处配置。
6.3 JWT token 过期后,前端仍然在页面停留
前端要把 401 处理做成全局的,token 过期后自动跳登录页。我见过许多初学者只在某个页面的回调里处理错误,导致用户在其他页面操作时毫无反应。统一在 axios 的响应拦截器里处理,一次搞定。
6.4 SQL 的 GROUP BY 忘记处理非聚合列
写统计 SQL 时,如果SELECT和GROUP BY之外的列不在聚合函数里,MySQL 5.7+ 下默认会报错。比如:
SELECT exam_id, exam_name, COUNT(*) FROM sign_up_record GROUP BY exam_id;exam_name不在GROUP BY里,如果ONLY_FULL_GROUP_BY模式开启就会报错。要么把exam_name也加进GROUP BY,要么用ANY_VALUE(exam_name),或者先按exam_id分组再子查询拿详细信息。
6.5 前端身份证校验写了,后端却漏了
前端校验永远只是体验优化,真正的安全边界在后端。JSR 303 注解或者手动校验都要做,后端再校验一遍不是为了冗余,而是为了防绕过。这个习惯最好从学生时代就养成,入职后这是基本职业操守。
6.6 数据库表的字符集与排序规则
建表时我推荐统一用utf8mb4,不要用utf8。原因很简单:utf8在 MySQL 里最多存 3 字节,一些生僻字或者表情符号直接报错。你以后做国际化业务基本都会遇到 emoji 入库,到时候再改表字符集就痛苦了。
7. 打包部署与常见部署方案
这一步很多同学到了答辩前夜才想起,结果手忙脚乱。我把流程整理出来。
后端打包:
mvn clean package -DskipTests打包出来的jar文件直接扔服务器,运行:
java -jar exam-backend-0.0.1.jar --spring.profiles.active=prod前端打包:
npm run build生成dist静态目录。部署方案我给出两种:
方案一:前后端分开部署(本地跑通最省事)后端起在 8080 端口,前端用 Nginx 托管dist目录,并配置代理把/api转发到 8080:
server { listen 80; server_name localhost; root /opt/exam/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }方案二:把前端放到 JAR 的 static 目录,打包成一个应用这个适合演示环境,把dist下面的静态文件全部拷贝到src/main/resources/static目录,重新打包,一个端口全部搞定。省掉 Nginx 的配置,但你要稍微注意一下前端静态文件如果更新了,后端 jar 也要重新打。毕设答辩阶段用这个方案省事得多。
8. 写在最后的实操感受
这个项目从头到尾跟下来,我能明显感觉到“系统思维”在逐渐形成——从需求拆分到数据库设计,从接口约定到页面渲染,每一步都不是孤立存在的。往回看,我觉得最有价值的设计反而不是某个炫技的技术点,而是那些“边界感”:什么时候在哪一层做校验、哪些状态只允许哪些角色操作、哪些修改必须留审计痕迹。
我也踩过不少坑,其中最让我印象深的是“重复报名”的问题——最初只在代码里判断,结果用户连点两次提交按钮,生成了两条报名记录。后面加上了数据库唯一索引,才彻底根治。这种问题触发器可能只是“用户多点了下按钮”,但它牵扯出来的教训是全局的:代码层面的判断永远是最后一道堤坝,数据库约束才是地基。希望读者在做这类系统时,把这句话放在心里。
如果你要用这套代码做毕设,我建议你不要满足于启动成功,而是亲手改一两个功能点,比如加一个“补考报名”或“成绩查询”模块,把这个过程走一遍,你对 SpringBoot 和 Vue 的理解深度会提升一个量级。希望这篇文章能帮你少走弯路。