SpringBoot+Vue+MyBatis构建车辆管理系统:设计、实现与部署详解
2026/9/14 23:54:47 网站建设 项目流程

做车辆管理系统这套东西,其实比想象中有意思得多。先说结论:SpringBoot + Vue + MyBatis + MySQL 这个组合,到今天依然是中小型后台管理系统里最成熟的方案之一。市面上很多所谓“车辆管理系统源码”要么乱得没法看,要么功能砍到只剩增删改查。我这次整理的这个项目,从车辆档案、司机管理、调度记录到维修保养、油耗统计都有覆盖,前端用 Vue 3 + Element Plus,后端 SpringBoot 提供 RESTful API,数据层走 MyBatis 操作 MySQL,整套东西跑起来之后,拿来改改就能接真实业务。如果你正准备找一套能学习的完整项目,或者想在自己的毕设、企业内训、小团队自用系统里快速落一座可扩展的框架,这篇会很有参考价值。

我先把话说清楚,这套系统的代码不是那种“能跑就行”的玩具工程。它在设计上有几条明确的取舍:后端按 controller / service / mapper 分层,业务逻辑和 SQL 分开;数据库表结构做了必要的三范式拆解加冗余字段,查询性能有保证;前端封装了统一的 request 工具,登录态用 token 管理;分页、搜索、下拉联动这些后台系统的常见交互都是完整实现的。下面我会从整体设计讲到数据库表结构,再到前后端具体实现,最后把我在实际联调和部署时踩过的坑一并列出来,按项目复现的路线走一遍,你能少走不少弯路。

1. 项目概览:一个车辆管理系统到底要管什么

1.1 功能模块拆解

车辆管理系统这个名字看着简单,真正要落地的功能拆开其实能分成六大块:

  • 车辆档案管理:登记车辆的基础信息,车牌号、品牌型号、车辆类型、座位数、发动机号、车架号、购买日期、车辆状态(可用/维修中/已报废)等。这里最容易被忽略的是车辆状态的管理,车辆一多,哪些能调度、哪些在维修、哪些已经到期该年检,系统都得有明确的标识和提醒入口。

  • 司机信息管理:司机姓名、手机号、驾照类型、驾照有效期、入职时间、状态(在职/离职/停职)。这块要注意驾照有效期,系统里面通常会加一个到期提醒的逻辑,没到期的列表置灰或者标红。

  • 调度管理:这是车辆管理系统的业务核心。申请用车、审批、派车、归还、里程登记,形成一个完整的闭环。调度记录表里要关联车辆ID、司机ID、使用人、起始时间、结束时间、起始里程、结束里程、用途说明。

  • 维修保养管理:记录每次保养或维修的时间、类型(保养/小修/大修)、费用、维修厂、经办人,以及车辆当前的保养状态。这里有个细节,维修中的车辆在调度模块选车时应该禁用,不能又派出去干活。

  • 油耗与费用管理:记录每次加油的数量、金额、单价、当前里程数,用于计算百公里油耗和车辆运营成本。

  • 统计报表:月度用车统计、车辆使用率、司机出车排行、维修费用比等。报表不需要太复杂,前端用 ECharts 画柱状图和饼图就够用。

1.2 技术栈选型背后的考量

这套系统选择 SpringBoot + Vue,不是因为它们“最新最火”,而是因为它们在实际工程里的生态足够完善。

后端用 SpringBoot 的原因很简单:约定大于配置,起步快。SpringBoot 内置 Tomcat,打包出来一个 jar 直接 java -jar 就能跑。它对 MyBatis 的整合也做得很好,mybatis-spring-boot-starter 一引入就自动配置了 SqlSessionFactory,不用像 SSM 时代那样手动配一堆 bean。而且 SpringBoot 的自动装配机制让整个人力成本摊得很薄,写业务逻辑时基本不用关心框架底层。

前端选 Vue 3 + Element Plus,核心原因是 Vue 的响应式数据绑定让表格、表单这类后台页面写起来特别顺手。Element Plus 是 Element UI 的 Vue 3 版本,表格组件、表单校验、对话框、消息提示这些后台管理系统的标配组件开箱即用。组件库本身又是按需引入的,打包体积可控。

MyBatis 最大的优势是 SQL 控制力。车辆管理系统里有不少多表关联查询,比如调度记录要关联车辆、司机两张表,统计报表要按月份分组聚合,用 MyBatis 写 XML 里的 SQL 可以精确控制每一条语句,复杂查询时比 Hibernate / JPA 更直接、更好调优。MySQL 则是不用多说的标准答案,轻量、免费、社区活跃,8.0 版本对 JSON 类型和窗口函数的支持也让查询设计更灵活。

2. 数据库设计与建模:先定好地基再盖楼

2.1 MySQL 初始化与字符集配置

我先单独把 MySQL 的初始配置拎出来讲,因为很多人在这里踩坑。MySQL 8.0 安装完后,默认的字符集是 utf8mb4,这个一般不用改。但连接时如果没有显式指定编码和时区,往往会出现中文乱码或者 Server returns invalid timezone 的报错。

连接串建议这样写:

jdbc:mysql://localhost:3306/vehicle_manager?useUnicode=true&characterEncoding=utf8mb4&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true

注意两个容易被忽视的参数:allowPublicKeyRetrieval=true是 MySQL 8.0 使用 caching_sha2_password 认证时需要的,否则连接会报 Public Key Retrieval is not allowed;serverTimezone=Asia/Shanghai是解决时间字段差 8 小时问题的关键。如果你用 Navicat 或者 DataGrip 建库,建议用下面的语句把库和账号一起建好:

CREATE DATABASE IF NOT EXISTS vehicle_manager DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; CREATE USER 'vehicle'@'%' IDENTIFIED BY 'vehicle123'; GRANT ALL PRIVILEGES ON vehicle_manager.* TO 'vehicle'@'%'; FLUSH PRIVILEGES;

2.2 核心表结构与字段设计

数据库我设计了 6 张核心表:vehicle_info(车辆信息表)、driver_info(司机表)、dispatch_record(调度记录表)、maintain_record(维修保养表)、fuel_record(加油记录表)、sys_user(系统用户表)。下面是重点表的核心字段设计。

vehicle_info 表:

字段名类型说明
idbigint主键,自增
plate_novarchar(20)车牌号,唯一索引
brand_modelvarchar(50)品牌型号
vehicle_typetinyint1-轿车 2-SUV 3-商务 4-货车
engine_novarchar(50)发动机号
frame_novarchar(50)车架号
purchase_datedate购买日期
statustinyint1-可用 2-维修中 3-已报废
create_timedatetime创建时间

这里要注意status字段使用 tinyint 而不是 varchar。一方面数据库存储更省空间,另一方面前端 ele-tag 组件可以根据数字状态映射成不同颜色的标签,显示“可用 / 维修中 / 已报废”就是一行 map 的事。千万别把状态存成中文,后期要改状态名或者做多语言就痛苦了。

dispatch_record 表加了几个比较关键的字段:driver_idvehicle_iduse_date(用车日期)、start_mileage(起始里程)、end_mileage(结束里程)、purpose(用途)、status(0-待审批 1-已审批 2-已拒绝 3-已归还)。这里的设计意图很明确:一张表跑完整个调度的生命周期。审批通过后状态改 1,归还车辆时填结束里程再改 3,所有流程都有记录可查。

2.3 索引设计与 SQL 性能兜底

车辆管理系统的数据量级通常到不了大数据量,但查询场景很固定,所以索引策略相对简单。我建了三个核心索引:

ALTER TABLE dispatch_record ADD INDEX idx_vehicle_date (vehicle_id, use_date); ALTER TABLE dispatch_record ADD INDEX idx_driver_date (driver_id, use_date); ALTER TABLE fuel_record ADD INDEX idx_vehicle_time (vehicle_id, create_time);

联合索引的设计逻辑是车辆 + 日期、司机 + 日期,这两个维度的查询在系统里出现频率最高。例如统计某辆车这个月出了多少次车,走 idx_vehicle_date 索引直接覆盖利用,SQL 不需要回表。还有一点,在给 MySQL 加索引时注意区分哪些是等值查询、哪些是范围查询,把等值字段放联合索引前面,这是最基础的索引优化规则。

3. 后端落地:SpringBoot + MyBatis 的正确打开方式

3.1 工程结构规划与基础配置

我习惯按功能模块分包,而不是按技术分层分包。特别是在车辆管理系统这种业务边界清晰的场景里,模块化分包能让你快速定位某块功能的全程链路。项目结构大致是这样:

com.example.vehicle ├── config # 配置类(CORS、MyBatis、拦截器) ├── controller # 接口层 ├── service # 业务逻辑层 ├── mapper # MyBatis Mapper接口 ├── entity # 实体类 ├── dto # 数据传输对象 ├── vo # 视图对象(给前端返回的封装结构) ├── common # 通用类(统一返回结果、全局异常处理) └── utils # 工具类(JWT、日期等)

SpringBoot 版本这边,我用的是 2.7.x,而不是 3.x。选 2.7.x 是综合考量的结果:SpringBoot 3.x 要求 JDK 17,且 javax.servlet 包名改成了 jakarta.servlet,很多老的第三方依赖和网上的示例代码不再兼容。如果你是在校生或者要参考网上的大量历史资料,SpringBoot 2.7.x + JDK 8/11 的组合是最稳的。当然,如果是全新项目且团队已经用上 JDK 17,SpringBoot 3.x 也完全没问题,只是遇到兼容问题时要有心理准备。

3.2 统一返回结果与全局异常处理

前后端分离的项目,后端接口一定要有一个统一的返回结构,否则前端每个接口都要适配不同的返回值。我这里定义了一个 Result 类:

public class Result<T> { private Integer code; // 200 成功,500 失败 private String message; // 提示信息 private T data; // 返回数据 public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage("操作成功"); result.setData(data); return result; } public static <T> Result<T> error(String message) { Result<T> result = new Result<>(); result.setCode(500); result.setMessage(message); return result; } }

全局异常处理用 @RestControllerAdvice 实现。这里有个容易被忽略的点:数据校验异常和业务异常要分开处理。业务异常(如“该车辆正在维修中,无法调度”)用自定义的 BusinessException 抛出,在全局异常处理器里返回 code=500 和业务提示信息;参数校验异常(如 @NotBlank 触发的 MethodArgumentNotValidException)则返回 code=400。这样前端可以根据 code 判断是提示错误信息还是重新校验表单,体验差很多。

3.3 MyBatis 的映射文件与缓存机制

MyBatis 的 Mapper XML 我分成了两类写:单表操作尽量用注解或者 MyBatis-Plus 的 Wrapper,复杂多表关联和统计报表放在 XML 里写。举个例子,调度记录的分页列表要关联车辆表、司机表,查询结果是多张表拼出来的,用 XML 的 resultMap 做映射最清晰。

<select id="selectDispatchPage" resultMap="DispatchVO"> SELECT d.id, d.use_date, d.start_mileage, d.end_mileage, d.purpose, d.status, v.plate_no, v.brand_model, dr.name AS driver_name FROM dispatch_record d LEFT JOIN vehicle_info v ON d.vehicle_id = v.id LEFT JOIN driver_info dr ON d.driver_id = dr.id <where> <if test="eq.vehicleId != null"> AND d.vehicle_id = #{eq.vehicleId} </if> <if test="eq.status != null"> AND d.status = #{eq.status} </if> <if test="eq.startDate != null and eq.startDate != ''"> AND d.use_date &gt;= #{eq.startDate} </if> <if test="eq.endDate != null and eq.endDate != ''"> AND d.use_date &lt;= #{eq.endDate} </if> </where> ORDER BY d.create_time DESC </select>

MyBatis 的缓存机制也是经常在面试里被问到的点。一级缓存是 SqlSession 级别的,默认开启,同一个 SqlSession 里执行两次相同的查询会命中缓存,但 Spring 整合 MyBatis 后每次请求通常都是新建 SqlSession,所以一级缓存的作用范围很有限。二级缓存是 Mapper 级别的,默认关闭,要开启需要在 XML 里加<cache/>标签。我对这个项目的建议是:默认不要开启二级缓存。车辆管理系统的数据实时性要求高,调度状态一变就要立刻生效,缓存反而容易造成数据不一致。等真有高并发查询同一个表的场景,再针对性地做 Redis 缓存,比用 MyBatis 的二级缓存灵活得多。

3.4 批量操作与接口性能优化

车辆管理系统里最容易遇到性能问题的场景是一次性导入车辆或司机数据,这时候千万别在 Java 里 for 循环调用单条 insert,要改用 MyBatis 的批量插入。

<insert id="batchInsert" parameterType="list"> INSERT INTO vehicle_info (plate_no, brand_model, vehicle_type, status) VALUES <foreach collection="list" item="item" separator=","> (#{item.plateNo}, #{item.brandModel}, #{item.vehicleType}, #{item.status}) </foreach> </insert>

但批量插入这里有一个不算少见的问题:如果你用的是 ExecutorType.SIMPLE(默认),values 拼接成一个巨大的 SQL,如果一次性插 5000 条,SQL 长度会超过 MySQL max_allowed_packet 的限制。解决办法有两个:一个是每 500 条拆一次批,执行 commit 后再继续;另一个是在 MyBatis 配置里改批量执行器,但改了之后要注意它的缓存刷新策略和普通模式不一样,容易在某些操作上报 Cache is not enabled 的错。最稳妥的做法,其实是控制 batchSize,1000 条一批,配合事务一起提交,既保证性能,也避免单一 SQL 过大。

3.5 登录鉴权与 JWT 落地

后台管理系统登录鉴权,我选的是 JWT + 拦截器的方式,不引入 Spring Security,因为一个内部管理系统的权限需求没那么重,引入安全框架反而把配置复杂度抬得很高。JWT 的实现流程是:登录成功后,后端用用户的 ID 和用户名生成 token 返回给前端,前端把 token 存到 localStorage,每次请求在 header 里带上 Authorization: Bearer ,后端拦截器解析 token 并校验有效性。

这里有一个细节值得注意:JWT 本身不依赖服务端保存会话,所以无法主动让某个 token 失效,如果用户修改了密码或者被管理员禁用,原来的 token 依然可以用到过期。我在这个项目里的做法是给 sys_user 表加了一个 token_version 字段,用户密码被修改时 token_version +1,生成 token 时把 token_version 也签发进去,拦截器校验时比对当前 token_version,不一致就判定为无效。这个方案既轻量又能解决 JWT 无法强制失效的问题,非常实用。

4. 前端实现:Vue 从 0 到能跑的完整链路

4.1 创建项目与依赖安装

前端我用 Vite 创建 Vue 3 项目,Vite 比 Vue CLI 快太多了,开发模式下启动基本秒开。创建命令:

npm create vite@latest vehicle-web -- --template vue cd vehicle-web npm install npm install vue-router@4 pinia element-plus axios echarts

说说为什么前端依赖选这些:vue-router 4 是 Vue 3 对应的路由版本,pinia 是 Vue 3 官方推荐的 state 管理库,axios 是 HTTP 请求库,echarts 是图表库,统计报表页面要用。Element Plus 安装后最好按需引入,不要全量引入,按需引入能砍掉至少 30% 的打包体积。如果你用的是 Vite 插件方式,在 vite.config.js 里配置 unplugin-vue-components 和 unplugin-auto-import 即可。

4.2 路由设计、状态管理与接口封装

前端路由我按模块拆分,登录页是独立的,登录成功后进入主布局。主布局里是侧边栏菜单 + 内容区域的结构:

const routes = [ { path: '/login', component: Login }, { path: '/', component: Layout, redirect: '/dashboard', children: [ { path: 'dashboard', component: Dashboard }, { path: 'vehicle', component: VehicleList }, { path: 'dispatch', component: DispatchList }, { path: 'maintain', component: MaintainList }, { path: 'fuel', component: FuelList }, ] } ]

路由守卫是必须的,否则未登录也能直接访问页面:

router.beforeEach((to, from, next) => { const token = localStorage.getItem('token'); if (to.path !== '/login' && !token) { next('/login'); } else { next(); } });

前端接口封装是最容易被人忽视的部分。很多人图省事直接在每个页面里写 axios.get,结果后端改了接口路径,前端十几个页面挨个手动改,非常痛苦。我封装了一个统一的 request.js,主要做了三件事:统一请求前缀、请求拦截器里自动带上 token、响应拦截器统一处理 code 码和错误提示。

service.interceptors.response.use( response => { const res = response.data; if (res.code !== 200) { ElMessage.error(res.message || '请求失败'); return Promise.reject(new Error(res.message)); } return res; }, error => { if (error.response?.status === 401) { localStorage.removeItem('token'); router.push('/login'); } ElMessage.error('网络请求异常'); return Promise.reject(error); } );

这样处理之后,页面里调用接口时只需要关心返回的数据,错误提示和登录跳转都被统一接管了。页面代码能瘦一大圈。

4.3 核心页面实现要点

列表页的原型非常重要。车辆管理的列表页长这样:顶部是搜索栏(车牌号、状态、购买日期范围),中间是工具栏(新增、导出),下面是一个 el-table 显示车辆数据,分页用 el-pagination。核心的搜索逻辑是搜索条件变化时,查询参数重置到第 1 页再调用接口,这个细节不做好会出现“你在第 5 页点了搜索,结果数据变成了第 1 页的内容,但分页器还停在 5”。

表单页这边,新增和编辑共用一个弹窗组件。我用v-if="isEdit"控制表单标题,打开弹窗时如果是编辑,就调用后端接口填充数据。这里要注意回显字段的格式,比如时间字段,后端返回的是2025-01-05 12:00:00,el-date-picker 需要的是 Date 对象或者2025-01-05这种格式,直接在表单里初始化字段时做一次格式化,不要等到提交时才处理,否则时间字段会出现回显了但校验不过的诡异问题。

4.4 与后端联调的跨域与参数问题

前后端分离开发模式,联调时最核心的问题就是跨域。我的处理方案很简单,后端加一个 CORS 配置类,指定允许的来源和请求头。前后端都在本地时,Vite 默认跑在 5173 端口,SpringBoot 跑在 8080 端口,CORS 如果不放开,浏览器控制台会直接给你报一堆红。写一个 WebMvcConfigurer 的实现类,重写 addCorsMappings 方法即可:

@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }

跨域问题解决之后,还有一个参数传递的坑很典型。比如调度记录的列表查询,要传 vehicleId、status、startDate 三个查询参数。前端 axios 默认会把对象序列化成 JSON,但后端如果用 @RequestParam 接收,就要求前端用 params 传参而不是 data。项目里我统一约定:GET 请求的查询参数全部用 params,POST 请求的表单数据用 data。这个约定定死之后,前后端联调时就不会出现“参数我传了,但你那边收不到”这种反复浪费时间的问案。

5. 打包部署与线上排查

5.1 前端打包后的路径与 404 问题

前端开发完成之后,npm run build 会生成 dist 目录,里面是静态文件。这里非常多的人会遇到一个问题:把 dist 里的文件部署到服务器上,打开首页一片空白或者资源加载 404。

这个问题的根源在于 Vite 的 base 配置。如果你的系统部署在域名根路径(比如 https://example.com),那默认的 base: '/' 就能正常工作;但如果部署在子路径(比如 https://example.com/vehicle/),就必须把 base 配置成 '/vehicle/',否则页面引用的 JS/CSS 路径全部找不到资源。

处理完资源路径,接着会遇到 vue-router 的 history 模式问题。如果你用了 history 模式(对,我建议用 history 而不是 hash,因为路由地址好看,而且没有 # 号),在 Nginx 服务器上需要配置 try_files 把所有路径都重定向到 index.html,否则你在页面上点刷新或者直接访问某个子路由,Nginx 找不到对应的文件就会返回 404。Nginx 的配置大概是这样:

location / { try_files $uri $uri/ /index.html; }

5.2 SpringBoot 生产环境配置

后端打包部署我分成两步。第一步,把 SpringBoot 项目打成 jar 包:

mvn clean package -DskipTests

第二步,用 java -jar 运行:

java -jar vehicle-system.jar --spring.profiles.active=prod

为了让开发环境和生产环境的配置分离,我在 resources 目录下建了 application.yaml(公共配置)、application-dev.yaml(开发环境)、application-prod.yaml(生产环境)。生产环境的数据库地址、账号密码、日志级别都放在 prod 配置里,打包时用 --spring.profiles.active=prod 指定环境。千万别把生产数据库密码写在 application.yaml 里然后提交到 Git,这是很多人犯过的低级错误,脱了裤子挨打的事。

生产环境我还额外配了一个启动脚本,用 nohup 后台运行并输出日志到文件,方便排查问题:

nohup java -jar vehicle-system.jar --spring.profiles.active=prod > vehicle.log 2>&1 &

5.3 后端打包后前端资源如何整合

这个项目里有个可选做法:把前端 build 出来的 dist 目录里的静态文件,直接复制到 SpringBoot 的 src/main/resources/static 目录下,然后把前端的历史模式路由改成 hash 模式。这样整套系统最终只需要跑一个 SpringBoot 服务,不需要单独部署 Nginx。适合那种“不想维护前后端两个服务”的场景。

不过我的建议是,前后端还是分开部署更规范。毕竟前端静态文件走 Nginx 有缓存控制、gzip 压缩等天然优势,后端 jar 包只管 API,职责清晰。之前做过一个项目贪图省事把前端塞进 jar 包里,结果前端要发版时还得重新打包整个后端服务,操作烦琐不说,一旦后端部署挂了前端也跟着挂了。这个坑我踩过一次就长记性了。

6. 实际项目中踩过的坑与排查速查表

6.1 SpringBoot 版本太高的兼容性问题

标题热搜词里有“SpringBoot版本太高”,这个痛点确实存在。SpringBoot 3.x 推出后,不少人新建项目直接用了最新版,结果发现很多旧项目的代码跑不起来。除了前面提到的 javax 变 jakarta,还有更隐蔽的问题:

MyBatis 和老版本的分页插件 PageHelper 在 SpringBoot 3.x 下需要更新到专门适配的版本(PageHelper 需要 6.0.0+);连接 MySQL 的驱动也从 mysql-connector-java 变更为 com.mysql:mysql-connector-j,包名由 com.mysql.cj.jdbc.Driver 变为 com.mysql.cj.jdbc.Driver,但坐标变了。如果你在搭建项目时遇到诡异报错,先看下 SpringBoot 版本、JDK 版本和依赖的版本号是否匹配,这是排查这类问题最快的方式。

6.2 MyBatis 中数字字符比较的隐患

热搜词里有一条“mybatis 单个数字字符比较”,这个问题很刁钻,但确实容易踩中。假设你要判断一个 status 字段是否等于数字 1,如果在 XML 里写:

<if test="status != null and status != ''">

然后传入的 status 是前端传过来的数字字符串 "1",这时 MyBatis 的 OGNL 表达式里,status != ''会对字符串和空字符串做判断,但问题是"1"和空字符串比较时会被转成字符比较,逻辑结果可能和你想象的不一样。更经典的问题是:<if test="status != ''">当 status 是单个字符时,OGNL 会把字符和字符串的比较按 ASCII 码解释,导致条件判断失效。

最稳妥的写法是显式判断,不要吝啬写全条件:

<if test="status != null and status.toString() != ''"> AND status = #{status} </if>

或者直接在 Java 代码里把查询参数的类型定死。使用 Integer 类型接收 status,就完全没有这个烦恼。所以你能看到很多经验丰富的老程序员的 Mapper 参数都写得很细,这不是啰嗦,是用教训换来的严谨。

6.3 MySQL 创建索引与慢查询排查

如果你在实际运行中发现某个页面加载特别慢,先别急着改代码,先用 EXPLAIN 看看 SQL 的执行计划。我见过太多人一上来就给 SQL 加索引,但加的位置不对,加了等于白加。EXPLAIN 里重点看 type 字段,如果看到 ALL,说明是全表扫描,这时候才需要优化索引。

EXPLAIN SELECT * FROM dispatch_record WHERE vehicle_id = 1 AND use_date BETWEEN '2025-01-01' AND '2025-01-31';

type 从好到差的顺序大概是这样:system > const > eq_ref > ref > range > index > ALL。平时查询只要能达到 range 或 ref 级别就算合格了。另外还有一个很实用的排查方法:在 MyBatis 的配置文件里打开 SQL 日志输出,可以看到每一条 SQL 实际执行了什么:

mybatis: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

联调时打开它,线上环境关掉。这样你就能在控制台直接看到传参和完整 SQL,排查问题效率翻倍。

6.4 Vue 打包后布局异常

不少人在前端本地开发时页面正常,打包部署到服务器之后布局乱了,最典型的现象是字体图标显示为方框、样式错乱。这类问题绝大多数是路径导致的:Element Plus 的字体文件默认是从相对路径加载的,如果你部署到子路径但没有正确配置 Vite 的 base,字体文件 404,图标自然就变成了方框。还有一个可能是 CSS 的 scoped 属性在某些情况下打包后顺序错乱,导致样式互相覆盖,这个问题在 Element Plus 和自定义样式混用的时候偶尔出现。排查时先用浏览器开发者工具看 Console 面板的 404 报错,锁定是不是资源路径问题,再检查样式覆盖问题。

6.5 常见问题速查表

现象根本原因快速解决
接口报 Public Key Retrieval is not allowedMySQL 8.0 认证机制连接串加 allowPublicKeyRetrieval=true
时间字段差 8 小时未指定时区或默认时区是 UTC连接串加 serverTimezone=Asia/Shanghai
SpringBoot 3.x 启动报 javax 不存在JDK 17 + Jakarta API用 SpringBoot 2.7.x 或全局替换 jakarta 包名
MyBatis 批量插入失败或超慢循环单条插入 / SQL 过大foreach 批量插入 + 每 500~1000 条提交一次
Vue 打包后图标显示方框字体文件路径错误配置正确的 Vite base 路径
刷新子路由出现 404Nginx 不支持 history 路由location 加 try_files $uri $uri/ /index.html
MyBatis 数字字符比较结果不对OGNL 表达式自动类型转换用 Integer 类型参数或 toString() 显式判断
数据库查询慢但不知道是哪个 SQL未开启 SQL 日志MyBatis log-impl 设为 StdOutImpl

这套系统从数据库建表到前后端联调,整个流程走下来,基本上就把主流 Java Web 项目的开发套路串明白了。说句实话,车辆管理系统本身并不复杂,真正有价值的是你在实现每个模块时对细节的处理方式,比如状态字段怎么设计、批量操作怎么做、跨域和鉴权怎么落地。明白了这些,以后再做类似的订单系统、仓库系统、办公系统,无非是换一套业务字段而已,架构和思路是通用的。

最后补一句我在部署这套系统时的一点体会:别把代码写得“看起来高级”当成目标,把业务流程梳理顺、把接口定义好、把异常处理干净,这套系统的价值就已经超过市面上大部分教学项目了。如果你要把它二次改造成别的管理系统,优先改数据库表结构和前端菜单,后端架构基本不用动。希望这篇能帮你把这个项目真正跑起来,并理解它的每一步。

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

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

立即咨询