ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于SpringBoot+Vue的驾校信息管理系统开发实践

基于SpringBoot+Vue的驾校信息管理系统开发实践 驾校信息管理系统听起来好像是个很老套的业务系统但真正做下来你会发现里面藏着的细节一点都不少。这篇文章是一个SpringBoot后端配合Vue前端、完全前后端分离的驾校管理系统开发记录涵盖学员管理、教练排课、车辆信息、考试流程等核心业务不是那种只贴个登录页的演示项目而是真正能跑到生产环境的完整方案。无论你是准备拿它当毕设还是手上正好接到驾校这类传统行业的项目都可以拿来参考。我在做的过程中最大的感受是这类系统技术难度不高但业务边界很宽。学员、教练、车辆、课程、考试、支付、统计报表每块单独拆出来都简单凑到一起就需要认真设计表结构和接口划分。文章后面会讲到技术选型、环境搭建、核心模块设计、前后端联调还有一些真实踩过的坑——比如SpringBoot版本选型不对导致的一系列麻烦、Vue打包后布局错乱的问题、以及那次让我折腾到半夜的HeapDump泄露排查。1. 项目整体设计与技术选型思路1.1 驾校管理系统到底管什么先说清楚业务范围因为很多开发者在拿到需求时容易一上来就建表结果做着做着发现怎么还有这么个模块没设计。驾校的实际运营大致围绕几条线展开学员线报名、档案、科目一至科目四学习进度、约考记录、考试成绩。教练线教练信息、带教学员、排班时间、评价反馈。车辆线车辆档案、维修保养、使用分配、保险年检提醒。课程训练线科目二/科目三的场地训练、时段预约、签到。系统管理线用户登录、角色权限、操作日志。我做的这个系统以学员和课程预约为主同时把教练和车辆做了基础管理。你可以根据自己的场景增删模块但核心的一定是学员、课程、预约这三张主表之间的关系这个理清楚之后整个系统的骨架就稳了。1.2 为什么选SpringBoot加Vue这套组合现在已经是JavaWeb开发事实上的标配了原因不是跟风而是它确实解决了传统项目里最头疼的几个问题。后端用SpringBoot主要看中的是自动配置和起步依赖。以前用SSH或者SSM光配置文件就够写半天现在一个spring-boot-starter-web就把内嵌Tomcat、SpringMVC全带进来spring-boot-starter-data-jpa或MyBatis-Plus把数据访问层也解决掉。开发阶段连部署都不需要main方法一启动就能调试效率比传统方式高太多。前端用Vue核心原因是组件化和数据驱动。驾校后台有大量表格、表单、弹窗用jQuery的话DOM操作会非常碎。Vue把这些东西收敛成了组件比如我封装了一个通用的学员信息表格组件只改props和事件监听就能在预约管理、考试管理里复用几百行代码的重复量直接砍掉。再加上Vue Router和Vuex/Pinia管路由和状态一套后端API可以同时支撑管理后台、教练端、甚至在学员端小程序里复用。前后端分离还有一个实际好处后端只需要提供JSON接口不用关心页面长什么样前端的页面改动独立发布不影响接口逻辑。这在后期改需求时特别舒服驾校那边经常改字段、调布局我只改前端就行后端接口只要保证字段兼容。1.3 技术栈清单正式列一下这个项目用到的技术组件层次技术选型版本参考后端框架SpringBoot2.7.x持久层MyBatis-Plus3.5.x数据库MySQL8.0鉴权JWT 拦截器jjwt 0.11.x前端框架Vue 22.6.x / 2.7.xUI组件Element UI2.15.xHTTPAxios1.x构建Maven / npm-2. 开发环境与项目搭建2.1 版本选型千万别一上来就选最新版这里先说一个关于SpringBoot版本的大坑因为很多人新建项目时都习惯选最新版本我在一开始也吃了这个亏。SpringBoot 3.x发布之后JDK被迫升到17同时很多老版本的第三方依赖兼容性会出现问题比如某些版本的MyBatis-Plus和代码生成器还没跟上启动就报错。如果你是为了快速完成项目我建议直接锁定SpringBoot 2.7.x。这个版本非常成熟JDK 8就能跑市面上的绝大多数教程、开源代码都是基于2.x写的遇到问题一搜就能找到答案。JDK版本也尽量用8或者11不要追求17或21在一个业务管理系统里新语法带来的便利远小于兼容性问题带来的痛苦。前端部分我用的Vue 2.6 Element UI。虽然Vue 3已经普及但Vue 2的生态太完善了各种组件、文档、踩坑记录一抓一大把。对一个驾校管理系统来说Vue 2 Element UI仍然是效率和稳定性的最佳组合。2.2 用IDEA快速创建SpringBoot后端项目创建SpringBoot项目其实很简单IDEA里直接File - New - Project选择Spring Initializr然后填好Group和Artifact就可以。依赖那里我实际用到的有这几个刚开始不用全选按需加Spring Web提供MVC和REST接口能力。MySQL Driver数据库驱动。Lombok简化实体类代码省了getter/setter。Spring Boot DevTools热重启改代码后自动重启开发必选。MyBatis-Plus和JWT这些是后面手动加到pom.xml里的因为Spring Initializr里没有直接搜到MyBatis-Plus。推荐用MyBatis-Plus而不是MyBatis主要是省去大量XML映射文件单表操作直接继承BaseMapper就能用分页查询有现成的分页插件比原生MyBatis写完一个列表接口至少少写一半代码。一个建议项目创建后第一时间配置application.yml把端口、数据库连接、日志级别写好不要拖到最后再配。我见过很多同学先把代码写完再配数据库结果跑起来发现一堆连接问题回头排查的难度比自己想象的大得多。server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/driving_school?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: auto2.3 Vue环境配置与依赖安装Vue环境这块很多初学者卡在node版本和依赖安装上。首先去Node官网下载LTS版本用node -v和npm -v确认安装成功。国内网络环境下建议把npm源切到淘宝镜像不然装依赖等到崩溃。npm config set registry https://registry.npmmirror.com用Vue CLI创建项目npm install -g vue/cli vue create driving-school-web创建时选择Manually select features勾上Router和Vuex其他默认即可。进入项目目录后需要再装Element UI、Axios、Sassnpm install element-ui npm install axios npm install sass sass-loader10安装sass-loader时要注意版本。如果Vue 2项目里用sass-loader的12以上版本编译时会报错因为新版sass-loader默认使用了Webpack 5的API而Vue CLI默认还是Webpack 4所以必须降级到sass-loader10这是网上搜不到、但实际运行一定会遇到的问题。3. 后端核心模块与数据库表设计3.1 数据库表怎么设计才不返工数据库是这类系统的命根子表结构没设计好后期改起来极其痛苦。我先画出核心表的对应关系一个教练有多个学员一个学员有多个预约记录一次预约关联一辆车和一个训练科目考试成绩和学员一对一。顺着这个思维设计出下面几张核心表sys_user系统登录用户表包含用户名、密码、角色、真实姓名、手机号。student学员信息表包含姓名、身份证号、联系电话、报名时间、当前科目、学员状态。coach教练信息表包含姓名、手机号、驾驶证号、教学科目、车辆ID、带教学员数。vehicle车辆信息表包含车牌号、品牌型号、购买日期、年检日期、保养状态。course课程表包含科目名称、课时数、课程价格。appointment预约表包含学员ID、教练ID、车辆ID、课程ID、预约时间、状态。exam_record考试记录表包含学员ID、科目、考试时间、考试成绩、是否通过。每张表都要加create_time和update_time字段这两个字段在排问题、做统计时非常有用。还有一个容易被忽略的点就是状态字段比如学员状态有两类学习状态正常、暂停、结业和审核状态待审核、通过、驳回放在一个字段里会乱分开更清晰。以学员表为例建表SQL大概长这样CREATE TABLE student ( id int NOT NULL AUTO_INCREMENT, name varchar(50) NOT NULL, id_card varchar(18) DEFAULT NULL, phone varchar(11) DEFAULT NULL, enroll_date date DEFAULT NULL, current_subject int DEFAULT 1, status varchar(10) DEFAULT 正常, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;设计时要注意一下id_card这类字段虽然只存了18位文本但严格来说属于敏感个人信息数据库中可以作为普通字段保存但接口返回给前端时按业务需求做脱敏比如只显示前六位和后四位。3.2 登录认证与权限控制驾校系统一般有两种角色管理员和教练有些系统还会加一个前台学员。我用JWT实现了无状态登录认证。流程很简单用户登录后后端校验用户名密码密码用BCrypt加密存储不能用MD5因为MD5彩虹表秒破。校验通过后生成一个包含用户ID、用户名、角色的JWT设置过期时间一般是12小时或者1天。前端把这个Token存到localStorage每次请求在HTTP Header里带上Authorization: Bearer {token}。后端写一个拦截器拦截需要认证的接口解析Token校验通过才放行。核心拦截器代码public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (request.getMethod().equals(OPTIONS)) { return true; } String token request.getHeader(Authorization); if (token ! null token.startsWith(Bearer )) { token token.substring(7); } try { Claims claims JwtUtil.parseToken(token); request.setAttribute(userId, claims.get(userId)); request.setAttribute(role, claims.get(role)); return true; } catch (Exception e) { response.setStatus(401); response.getWriter().write({\code\:401,\msg\:\登录已过期\}); return false; } } }角色权限我采用最朴素的做法登录时把角色写入Token拦截器解析后存到request属性中各Controller在需要校验角色时自行判断。这种方式对驾校这样角色不多、权限粒度不细的系统完全够用。不要为了追求高大上去引入Spring Security加OAuth2那套学习成本高出问题也不容易排查。如果你后面想扩展成多租户或者功能更复杂的系统再迁移到Security也不迟。3.3 业务代码里的那些实际细节后端业务代码本身不复杂但有几个地方容易踩坑。分页查询。用MyBatis-Plus时一定要先配置分页插件Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }如果不配置这个插件你调selectPage的时候会发现返回的总条数是0数据也只有一页查了很久才发现是插件没配。时间类型处理。前端的日期选择器传过来的是字符串2025-04-10 10:00:00后端如果用java.util.Date接收需要加DateTimeFormat注解如果用LocalDateTime需要在application.yml里加spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8不然后端返回给前端的日期格式会出现偏移八小时或者格式不对的问题。逻辑删除。学员、教练等数据不要物理删除用逻辑删除字段deletedMyBatis-Plus中在实体上加TableLogic注解自带软删除功能。驾校运营过程中经常需要查历史记录物理删掉数据等于把账本撕了。统计报表。SQL层面就可以完成大部分统计比如统计每月报名人数SELECT DATE_FORMAT(create_time, %Y-%m) AS month, COUNT(*) AS cnt FROM student WHERE create_time DATE_SUB(CURDATE(), INTERVAL 12 MONTH) GROUP BY month ORDER BY month;这种统计SQL不需要额外引入报表工具直接用接口返回给前端用ECharts展示柱状图完全够用。4. 前端页面实现与前后端联调4.1 路由规划和页面结构前端这块路由规划直接决定项目的可维护性。我是按模块划分的登录页单独放在外面登录之后进入一个整体布局框架Layout里面是侧边栏加内容区域的结构。const routes [ { path: /login, component: Login }, { path: /, component: Layout, redirect: /dashboard, children: [ { path: dashboard, component: Dashboard }, { path: student, component: StudentManagement }, { path: coach, component: CoachManagement }, { path: vehicle, component: VehicleManagement }, { path: appointment, component: AppointmentManagement }, { path: exam, component: ExamManagement } ] } ];使用Vue Router时路由守卫一定要加上否则用户直接访问/student也能看到页面数据接口虽然有拦截器但白屏一两秒再跳到登录页体验很差。router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (to.path /login) { next(); } else if (!token) { next(/login); } else { next(); } });4.2 Axios封装与跨域问题的正确处理前端和后端的跨域问题很多人问我是前端解决还是后端解决。我的建议是后端解决为主前端做好统一封装。后端加上一个全局的CORS配置Configuration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.addAllowedMethod(*); config.addAllowedHeader(*); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }注意当你启用了JWT拦截器时CORS的OPTIONS预检请求必须放行否则浏览器会报跨域错误但这个错误其实和后端拦截器有关系。我就是在这个地方卡了半小时。前端Axios封装核心是请求拦截器和响应拦截器axios.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers.Authorization Bearer token; } return config; }); axios.interceptors.response.use( response response.data, error { if (error.response error.response.status 401) { localStorage.removeItem(token); router.push(/login); } return Promise.reject(error); } );这里把响应统一拦截成response.data之后所有请求拿到直接就是后端返回的JSON不用到处写.data.data代码会清爽很多。4.3 几个常用页面的设计细节学员管理页面是整个系统的核心交互页面用了Element UI的el-table加el-pagination。这里我踩过一个比较常见的坑表格数据更新后页码和查询条件未重置导致用户搜了新条件却看不到第一页的数据。解决方案是在查询方法里固定把currentPage重置为1。预约页面比较有意思。学员预约训练时前端需要展示教练的可约时段同时把车辆信息一并带出。我用了一个联动的下拉框选择教练后根据教练ID去查询他名下的车辆再把可预约的时间段用日期选择器配合picker-options禁用已约满的时间。这是驾校系统里很典型的一个联动交互设计。表单提交页有一个常见的坑状态字段。在学员表单里我用了el-select选项值是数字还是字符串要全局统一。比如状态字段在数据库里是字符串正常/暂停那前端提交时也要传字符串不要传数字1或2否则会出现明明数据库允许的值查出来却是空的情况。5. 常见的坑与问题排查实录5.1 SpringBoot版本太高导致的自动配置问题我在用SpringBoot 3.x的时候遇到过两个卡壳很久的问题。第一个是javax.servlet变成了jakarta.servlet所有导入的包名都变了第三方库如果不兼容就直接启动失败。第二个是SpringFox的Swagger和SpringBoot 3不兼容只能用springdoc很多旧教程的方法就全部失效了。解决思路很简单降级到2.7.x所有老问题立刻消失。SpringBoot 2.7还在维护期内安全更新和bug修复都在继续用在这个项目上完全没有被淘汰的问题。热词里也常提到SpringBoot自动装配的面试题这里顺便说一句SpringBoot的核心就是EnableAutoConfiguration它通过加载spring.factories里配置的自动配置类按条件注解ConditionalOnClass去判断是否生效。理解了这个机制配置类冲突排查就变得很容易想排除某个自动配置可以用SpringBootApplication(exclude XX.class)。5.2 Vue打包后布局异常的问题开发环境一切正常npm run serve跑得好好的结果npm run build之后部署到Nginx上页面样式乱了图标不见了旋转加载的慢。这基本就是以下两个原因。第一是静态资源路径问题。Vue CLI打包默认的资源路径是绝对路径/如果部署在子目录就会404。解决办法是在vue.config.js里把publicPath改为./module.exports { publicPath: ./, outputDir: dist };第二是路由的history模式。Vue Router用history模式时直接访问子路由会返回404需要Nginx配置try_files或改用hash模式。为了省事我直接用了hash模式URL里带个#但是在Nginx下零配置就能跑。如果上线后发现还是乱布局打开浏览器控制台看一下资源加载路径F12一看就知道是不是资源404了这个排查思路比瞎猜版本问题快得多。5.3 关于HeapDump敏感信息泄露这个隐患做系统时查过和SpringBoot HeapDump相关的安全话题这里也提一句。SpringBoot Actuator如果配置不当heapdump端点可能把JVM堆内存里的敏感信息暴露出来比如内存中的数据库口令、用户Token等。网上有很多圈外人把这个问题当成攻防演练的噱头但在项目实践里我们要做的事情恰恰是防守。我处理的办法有这几个生产环境不暴露Actuator端点或者只开放health端点management: endpoints: web: exposure: include: health如果确实需要监控给Actuator设置独立的端口或路径不要和业务接口混在一起。数据库口令、密钥这类配置不要硬编码在配置文件里用环境变量注入。这些问题解决的本质不是堵漏洞而是从一开始就养成“最小暴露面”的习惯。该关的端口不露出来该脱敏的数据不原样返回系统整体的安全风险就是可控的。5.4 其他边角问题速查把我在开发中遇到的一些零碎问题整理成一个小表遇到类似情况可以直接对着查现象原因解决办法npm安装依赖特别慢默认源是国外源切换镜像源启动后端口被占用本地有其他进程占用8080换端口或杀掉进程数据库中文乱码连接URL没指定编码加characterEncodingutf8接口返回的时间少了8小时时间时区未设置数据库连接加serverTimezoneAsia/Shanghai列表页排序错乱每页条数变化顺序不稳定明确指定order by id descJWT失效后页面一直刷新响应拦截器没有跳转在401时清Token并跳登录页这几个问题看起来小但每一个都能卡住半天提前知道的话能省下不少时间。6. 结尾的一些经验心得项目做完之后我把整套代码重新整理了一遍写文档、补注释、打包部署整个过程走下来最大的体会就是这种业务管理系统其实拼的不是技术多高深而是对业务的理解和代码结构的把控。学员、教练、车辆、预约、考试几条线理清楚数据库表设计好后面写接口就是往模板里填东西。我个人在实际操作中的习惯是每做一个模块先把表结构画在纸上再写接口再做页面坚决不跳过设计直接写代码。因为我吃过这样的亏一开始图快直接写业务表字段缺了再补前端页面改来改去一个简单模块搞了几天还不如重新设计来得快。另外一个感受是后端接口命名和返回格式一定要统一。我全部返回{code: 200, msg: 成功, data: ...}这样的结构前端拿到后统一处理这样无论加多少接口前端的Axios拦截器都不用改。这个决定在做项目前期看不出好处等到了联调阶段就会感谢自己当初定了这个规矩。如果你也打算做一套类似的系统我的建议是先花一天时间把原型页面用草图画清楚把角色和流程理清再开始动代码。后面无论用SpringBoot还是Vue都只是工具层面的问题。希望这篇记录对你有帮助哪怕只是少踩一个坑这篇文章就没白写。
返回列表