ARTICLE DETAIL

资讯详情

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

SpringBoot+Vue+MyBatis+MySQL企业车辆管理系统实战

SpringBoot+Vue+MyBatis+MySQL企业车辆管理系统实战 前后端分离的企业车辆管理系统SpringBootVueMyBatisMySQL这套组合是Java后端和前端开发里最能练手、也最适合落地的一套技术栈。企业里车辆管理这个业务没有高并发但流程长、角色多、数据关系复杂从车辆档案、驾驶员信息到派车记录、油耗维修每一块都需要扎实的CRUD和权限设计正好能把前后端分离、接口规范、数据库设计这些基本功串起来。这篇文章我基于自己从零写的一版完整源码把系统架构、数据库表设计、后端接口、前端页面、以及最后的打包部署全部讲透。如果你想做一套类似系统或者正在准备二次开发按这套思路走基本能避开我踩过的那些坑。1. 项目概述与核心需求1.1 这个系统到底要管什么很多人在动手写企业车辆管理系统时第一反应就是“建一张车辆表然后做增删改查”真做起来才发现根本不是这么简单。企业车辆管理的核心不是把车牌号、品牌这些静态信息存进数据库而是要解决车辆使用过程中产生的审批、调度、成本统计、到期提醒一系列问题。我调研过几家中型企业的实际需求最后归纳出来的核心模块是这些车辆信息登记包括车牌、品牌、座位数、购买日期、当前里程车辆状态管理包括空闲、出车、维修、报废驾驶员档案包括员工号、手机号、驾照类型、驾照到期日派车申请与审批包括谁用车、去哪、预计归还时间行车记录包括出车日期、起始里程、结束里程、产生的费用维修保养记录包括保养提醒、维修费用、下次保养里程。最后再配一个统计看板让管理层能快速看到各部门用车频次、月度油费趋势、车辆年检到期情况。如果只做一张车辆信息表那这个系统基本就是摆设。真正能用的系统必须把“管车”和“管流程”连起来。比如一辆车被申请出车申请审批通过后车辆状态自动改成“出车”归还时填写里程数并计算本次行驶里程同时把状态恢复成“空闲”。这一套状态流转才是车辆管理系统的灵魂。1.2 为什么选择前后端分离架构以前做这种管理系统最常见的做法是SpringBoot配Thymeleaf或JSP页面由后端渲染前端逻辑全部混在Controller里。项目小的时候确实省事但一旦需要调整页面交互比如给表格加一个筛选下拉框、改动弹窗样式都要同时修改后端代码前端开发人员没法独立工作测试和上线也必须一起发布。前后端分离之后后端只暴露纯JSON接口前端完全控制页面渲染两边并行开发互不干扰。现在企业里普遍是“后端一个团队、前端一个团队”的分工模式用Vue做前端、SpringBoot做后端是招聘和协作成本最低的组合。MyBatis作为ORM层可以把复杂的SQL写在XML里车辆管理这种多条件组合查询非常常见MyBatis的动态SQL比JPA的派生查询更直观、更好调优。而且这套组合的资源极多遇到问题一搜就有答案不像冷门框架那样踩了坑只能自己翻源码。1.3 技术栈选型的细节考量SpringBoot解决的是配置繁杂和环境搭建问题内嵌Tomcat打包成jar就能跑部署非常省事。Vue负责组件化和数据驱动视图配合Element Plus这类UI库一天就能搭出管理后台。MyBatis胜在SQL可控性高车辆管理这类报表统计往往需要多表join和条件拼接XML里的SQL看得见摸得着出问题也好排查。MySQL不用多说业务量不大的企业内部系统稳定、免费、资料多。版本搭配上要特别注意SpringBoot 2.7.x对应JDK8或JDK11配合mybatis-spring-boot-starter 2.x版本完全没有问题SpringBoot 3.x要求JDK17且MyBatis官方适配版本也要升级到3.0以上。新手我建议优先用SpringBoot 2.7 MySQL 5.7或MySQL 8.0这套稳定组合先把项目跑通再考虑升级的事。版本乱配是这类项目最大的隐形杀手我后面会在常见问题里专门讲。2. 数据库设计与核心表结构2.1 建库与字符集选择数据库设计是这类管理系统最见功力的地方。很多人一上来就写建表语句结果做到后面发现缺字段、缺索引又要回头改表非常痛苦。我的建议是先画清楚实体关系再落SQL。核心表不需要太多我最终设计的是车辆表vehicle、驾驶员表driver、派车记录表dispatch_record、维修保养表maintenance_record、加油记录表refuel_record外加系统用户表sys_user。建库时直接指定utf8mb4字符集不要再用utf8。utf8在MySQL里最多支持3字节存emoji或者生僻字会直接报错utf8mb4才能完整支持。排序规则用utf8mb4_general_ci或utf8mb4_0900_ai_ci企业内部系统怎么选差别不大保持统一就行。CREATE DATABASE IF NOT EXISTS vehicle_system DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci;2.2 车辆表和驾驶员表设计车辆表要注意几个关键点。车牌号必须唯一用unique约束车辆类型建议存字典值而不是直接存中文比如1小型轿车、2客车、3货车这样以后扩展和维护都方便购买日期、保险到期日、年检到期日这三个字段用date类型不要用datetime因为业务上根本不需要时分秒。状态字段我建议用tinyint0代表空闲、1代表出车、2代表维修、3报废。千万不要用boolean代表状态因为你会发现以后还可能需要“待年检”“待保养”这种中间状态boolean根本不够用。CREATE TABLE vehicle ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, plate_no VARCHAR(20) NOT NULL UNIQUE COMMENT 车牌号, brand VARCHAR(30) COMMENT 品牌, model VARCHAR(30) COMMENT 车型, color VARCHAR(10) COMMENT 颜色, vehicle_type VARCHAR(20) COMMENT 车辆类型, purchase_date DATE COMMENT 购买日期, insurance_expire DATE COMMENT 保险到期日, annual_check_expire DATE COMMENT 年检到期日, current_km DECIMAL(10,2) DEFAULT 0 COMMENT 当前里程, status TINYINT DEFAULT 0 COMMENT 状态0空闲1出车2维修3报废, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) COMMENT 车辆信息表;驾驶员表要注意记录驾照到期日很多系统把这字段忽略了结果后期要人工通知续驾照时根本查不到。另外车辆和驾驶员的关系要设计得灵活一点。一辆车通常会固定分配一个驾驶员但出车时也可能临时指定别人所以我既不搞成一对一也不搞成多对多而是让driver表里带一个default_vehicle_id作为默认绑定关系派车记录里再单独记录实际驾驶员。这样固定关系和临时调度就都覆盖到了。2.3 派车、维修和油耗记录的设计要点派车记录是整个系统里数据量增长最快的表字段设计直接影响后面统计能不能做。核心字段包括vehicle_id、driver_id、applicant申请人、reason、start_time、end_time、start_km、end_km、status。status我用0待审批、1已通过、2已驳回、3已完成审批通过后前端会立刻把车辆状态改成出车归还时填写结束里程系统自动算出本次行驶里程。维修保养表和加油记录表要注意金额字段用decimal不要用float或double否则累计统计时会出现精度误差。维修表至少要记录vehicle_id、maintenance_type保养类型、maintenance_date、current_km、cost、next_maintenance_km这样就能自动算下一次保养里程。加油记录表记录加油时的里程数和加油量、单价、总金额通过这次里程减上次里程可以得到单次油耗累计下来就是一份真实的油耗报表。我个人的习惯是不给这些表加物理外键只在逻辑上维护关联关系通过索引和联查实现。物理外键在迁移数据、清理测试数据时非常麻烦而且MySQL在并发写入时外键校验也有一定开销。只要在vehicle_id、driver_id上建立普通索引查询性能完全够用。2.4 统计SQL要提前规划很多做CRUD的人容易忽略一点统计报表的SQL应该在建表阶段就考虑好。比如“每月维修费用”这个统计本质上是对maintenance_record表按月份分组求和SELECT DATE_FORMAT(maintenance_date, %Y-%m) AS month, SUM(cost) AS total_cost FROM maintenance_record WHERE maintenance_date DATE_SUB(CURDATE(), INTERVAL 12 MONTH) GROUP BY month ORDER BY month;这类SQL看起来简单但如果维护表中没有maintenance_date字段或者日期的类型设计成varchar统计就会变得极痛苦。所以我在建表时就统一约定所有日期都用date或datetime类型所有金额都用decimal所有状态都用tinyint。这个约定能保证后续所有统计SQL都好写。3. 后端核心模块实现SpringBoot MyBatis3.1 分层架构与项目包结构后端项目我习惯按controller、service、mapper、entity、dto、config、common七层来组织。controller负责接收请求和参数校验service写业务逻辑mapper只负责SQL操作entity对应数据库表字段dto对应前端传参和返回参数config放拦截器和跨域配置common放统一返回体、异常类、常量类。这样分层之后团队协作非常清晰新人接手也能快速定位问题。实体类里有一个细节日期字段统一用LocalDateTime和LocalDate不要用java.util.Date。虽然Date是老项目里最常见的写法但它和JSON序列化、MyBatis类型处理都会有些小摩擦LocalDateTime配合Jackson的JsonFormat注解更容易控制格式。3.2 动态SQL实现多条件筛选车辆管理列表页最常见的需求是既可以根据车牌模糊查又可以根据品牌、状态、车辆类型过滤还可以按时间范围筛选。如果每个条件都写一个Mapper方法那方法数量会爆炸。MyBatis的动态SQL标签就是为这种场景设计的。select idselectVehicleList resultTypecom.example.fleet.entity.Vehicle SELECT * FROM vehicle where if testplateNo ! null and plateNo ! AND plate_no LIKE CONCAT(%, #{plateNo}, %) /if if testbrand ! null and brand ! AND brand #{brand} /if if teststatus ! null AND status #{status} /if if testvehicleType ! null and vehicleType ! AND vehicle_type #{vehicleType} /if /where ORDER BY create_time DESC /select这里要特别注意 标签会自动处理掉第一个条件前面的AND所以千万不要再写WHERE 11这种老套路。 不仅可读性好还能避免索引失效时全表扫描前多做一次无谓判断。模糊查询要用CONCAT(%, #{plateNo}, %)不要直接用%${plateNo}%后者有SQL注入风险。3.3 身份认证与权限控制系统有三类角色管理员、普通员工、驾驶员。管理员可以维护车辆和驾驶员档案、审批派车单普通员工可以申请用车、查看自己的申请记录驾驶员可以看到被派给自己的出车任务。权限模型我用了RBAC的基础思路用户表关联角色角色关联权限登录时把角色信息写进JWT。实现上我用SpringBoot拦截器统一校验Token登录接口放行其他接口都走拦截器。拦截器解析Token后把用户ID和角色放到ThreadLocal里后续Service层随时能取到当前登录人。Token过期时间我设置为2小时管理员可以操作“强制下线”接口来让某个用户重新登录。这里有一个容易忽略的点拦截器放行的路径必须包含登录接口和静态资源路径否则前端访问就会莫名被拦截排查起来很费时间。3.4 统一返回体与全局异常处理前后端分离之后最忌讳的就是每个接口返回格式都不一样。我定义了一个统一的Result类包含code、message、data三个字段。code为200表示成功500表示业务异常401表示未登录或Token过期403表示没有权限。前端拿到任何响应都先判断code而不是盲目信任HTTP状态码。配合RestControllerAdvice全局异常处理所有异常都转换成统一格式这样前端只需要一份错误处理逻辑。public class ResultT { private Integer code; private String message; private T data; // 省略构造方法和getter/setter }业务异常我建议抛自定义异常BizException而不是直接返回一个Result对象。这样Servlet层的逻辑不会被Controller干扰统一由全局异常处理器收口日志也能完整记录下来。参数校验用javax.validation的Valid配合NotBlank等注解校验失败的消息也会被异常处理器捕获并返回给前端。4. 前端核心模块实现Vue4.1 工程初始化和目录规划前端我用的Vue3 Vite Element Plus相比Vue CLIVite在启动速度上舒服太多。src目录下按api、components、router、store、utils、views拆分。api目录单独放接口请求函数页面组件里不直接写axios这样后端接口一变只需要改api层。比如车辆模块就有getVehicleList、addVehicle、updateVehicle、deleteVehicle几个函数页面里直接引用语义非常清晰。每个页面的组件我习惯拆成三个文件页面入口、表格组件、表单弹窗组件。车辆列表页就是这样的结构车辆表格通过props接收数据通过事件触发刷新弹窗组件接收表单初始值提交时通过emit把数据传给父组件。组件化之后新增一个“驾驶员分配”弹窗可以直接复用大部分代码不用复制粘贴。4.2 Axios封装和路由守卫Axios封装几乎是每个Vue管理后台的标配。我在utils/request.js里创建axios实例baseURL设置成/api开发环境的代理会把/api转发到后端。请求拦截器里加Token响应拦截器里统一处理错误码。Token过期时清空本地存储并跳转到登录页。request.interceptors.response.use( response { const res response.data if (res.code ! 200) { if (res.code 401) { router.push(/login) } ElMessage.error(res.message) return Promise.reject(new Error(res.message)) } return res }, error { ElMessage.error(网络异常请稍后重试) return Promise.reject(error) } )路由守卫我用的是beforeEach钩子。判断当前访问的页面是否需要登录如果没登录就跳转登录页登录了但访问了无权限的页面就跳转401页面。动态路由在这个项目里我暂时没有上因为车辆管理系统的菜单相对固定用静态路由加meta.roles做按钮权限控制已经足够。动态路由虽然看起来很炫但刷新页面路由丢失的问题需要额外处理复杂度不低。4.3 核心页面车辆列表、派车表单和图表车辆列表页使用el-table展示配合el-pagination分页。表格里的状态字段我会用el-tag渲染空闲显示绿色、出车显示蓝色、维修显示橙色、报废显示红色。分页切换时重新请求接口同时把查询条件带上这是后台管理页最标准的交互。派车表单使用el-form字段有申请人、用车事由、车辆、驾驶员、预计归还时间。提交前做表单校验比如车辆和驾驶员不能为空。审批按钮会根据当前记录状态和用户角色判断是否显示审批通过后调用后端接口更新状态并联动修改车辆状态。这种“一条数据带动另一条数据状态变化”的逻辑用我前面提到的Service层事务管理来做最能体现后端业务的完整性。统计看板我用ECharts画了三个图表近12个月维修费用趋势、各部门用车次数占比、车辆状态分布。ECharts数据格式要求比较固定后端接口返回的结构我会直接按图表需求设计比如维修趋势返回月份和金额两个字段的数组。注意一个坑某些月份没有数据后端一定要补0否则图表会断轴看起来像是数据缺失。4.4 前后端接口约定前后端分离项目里接口约定往往比代码本身更影响开发效率。我和前端约定了几条硬规则时间字段统一返回yyyy-MM-dd HH:mm:ss字符串不做时区转换分页参数统一用pageNum和pageSize返回体统一用total和records金额字段统一返回字符串前端显示时保留两位小数状态字段统一返回数字前端负责翻译成文案。这几条约定在项目启动前就要写清楚否则联调阶段会疯狂扯皮。5. 完整部署教程源码到上线5.1 环境准备和版本选择部署前先把环境理清楚。JDK我建议至少用8但如果要上SpringBoot 2.7.xJDK8完全够用MySQL用5.7或8.0都可以我项目里用的是MySQL 8.0前端构建需要Node环境Node 16或18都行vite对Node版本有要求太老会直接报错。Nginx用来托管前端静态文件同时反向代理后端接口。有个小技巧开发环境用Windows服务器用Linux两边环境最好做到一致。比如本地用MySQL 8.0服务器也尽量用8.0否则连接驱动、时区处理这些细节容易有差异。如果本地装的是MySQL 5.7服务器上装了8.0接口可能本地正常、线上报时区错误排查时会很痛苦。5.2 数据库初始化后端项目里我会附带一个sql目录里面是建表语句和初始化数据脚本。部署时先在服务器上建好数据库然后导入脚本。如果用MySQL命令行导入注意指定字符集mysql -uroot -p --default-character-setutf8mb4 vehicle_system init.sql如果数据库里已经有表直接导入可能会报重复创建的错误这时候可以用CREATE TABLE IF NOT EXISTS或者导入前先drop掉旧库。生产环境千万别直接改表结构建议用增量脚本方式维护。5.3 后端打包与进程守护后端打包用Maven命令很简单mvn clean package -DskipTests打包后的jar在target目录下。为了测试环境、生产环境分开管理我把数据库配置放在application-prod.yml里启动时指定profile。生产库的密码不要写死在源码里建议用环境变量引用比如spring: datasource: url: jdbc:mysql://${DB_HOST}:3306/vehicle_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: ${DB_USER} password: ${DB_PASSWORD}生产环境启动时用systemd托底。写一个vehicle.service文件[Unit] DescriptionVehicle System Afternetwork.target [Service] Userroot WorkingDirectory/opt/vehicle ExecStart/usr/bin/java -Xms256m -Xmx512m -jar /opt/vehicle/vehicle-system.jar --spring.profiles.activeprod Restarton-failure RestartSec10 [Install] WantedBymulti-user.target这样进程崩了会自动重启省去手工拉起的麻烦。5.4 前端构建与Nginx配置前端构建时建议先npm install如果网络慢可以换国内镜像。构建命令是npm run build产物在dist目录。部署时把dist目录整个传到服务器然后配置Nginx。一个完整可用的配置块是这样的server { listen 80; server_name your-domain.com; root /opt/vehicle/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } gzip on; gzip_types text/plain text/css application/json application/javascript; }这里最关键的是location /中的try_files配置。Vue是单页应用路由由前端控制如果用户直接访问/list路径Nginx找不到这个文件没有try_files的话就会返回404。有了try_filesNginx会把所有未知路径都回退到index.html由Vue Router接管路由。5.5 服务器部署全流程我把整个部署流程串成一条线先装环境后传代码再启动服务。假设一台全新CentOS或Ubuntu服务器操作步骤是安装JDK、MySQL、Nginx、Node构建前端用创建/opt/vehicle目录上传jar包和dist导入数据库脚本创建systemd服务并启动后端把前端dist上传到/opt/vehicle/dist配置Nginx并重启开放防火墙端口。每一步都有体检点后端启动后先curl一下健康检查接口确认返回JSON再检查Nginx能否访问前端页面最后点击页面上的查询功能看接口是否正常响应。如果防火墙没有放行80端口前端页面会直接打不开如果没有放行3306端口但后端在服务器本机连接数据库那不受影响远程客户端才需要放行。注意生产服务器上MySQL监听地址最好只留127.0.0.1避免数据库端口暴露到公网。6. 常见问题与排查技巧实录6.1 版本冲突与MyBatis配置这类项目最常见的问题就是版本冲突。前两年很多人直接用SpringBoot 3.0最新版结果发现mybatis-spring-boot-starter还是2.x启动时一直报找不到SqlSessionFactory其实就是版本和JDK不匹配。SpringBoot 3.x必须配合mybatis-spring-boot-starter 3.0并且JDK必须17以上否则运行时会直接抛ClassNotFoundException。我最终选择的是SpringBoot 2.7.17配合mybatis starter 2.3.2JDK8这套组合经过大量生产项目验证非常稳。另外如果项目引入了PageHelper分页插件要注意PageHelper和SpringBoot的版本兼容问题。PageHelper分页原理是ThreadLocal加拦截器它只对第一条执行的SQL生效如果Service层先查询了其他数据再执行分页查询分页就会作用到错误的SQL上。排查分页失效时先看执行日志里第一条SQL是不是业务查询。6.2 条件查询不生效MyBatis动态SQL中最容易翻车的场景是数字类型的条件判断。比如status字段是Integer前端传0表示空闲如果XML里写了status ! 这个判断OGNL对Integer和空字符串比较会产生奇怪的结果导致status0时条件被跳过。解决办法很简单数字类型只判断! null不要加空字符串判断。还有一个常见坑是like模糊查询。很多人在XML里写成LIKE %#{plateNo}%这是SQL语法错误因为#{}会被预编译成占位符不能嵌在字符串里。要么用CONCAT(%, #{plateNo}, %)要么用like ${plateNo}后者有注入风险强烈不建议。排查这类问题时打开MyBatis的SQL日志最直观logging: level: com.example.fleet.mapper: debug把mapper包日志级别调成debug就能看到实际执行SQL和参数绑定问题一目了然。6.3 前端刷新404与跨域前端路由刷新404基本只发生在生产环境开发环境很少遇到。原因就是Nginx没有配置try_filesVue Router的history模式无法找到对应文件。解决方式我在部署教程里已经写过location /里加上try_files $uri $uri/ /index.html即可。如果你用的是hash模式则不会出现这个问题但URL里会多一个#号不够美观。跨域问题则可能出现在两种场景开发环境直接请求后端地址没有走代理或者生产环境Nginx没配proxy_pass。开发环境强烈建议用Vite的proxy而不是在后端写CORS。如果既用了proxy又开了CORS某些浏览器的预检请求会重复处理响应头导致莫名其妙请求失败。排查跨域时看一眼浏览器NetWork面板看请求的URL是相对路径还是绝对路径再确认响应头里有没有Access-Control-Allow-Origin。6.4 MySQL连接时区与编码问题MySQL 8.0的默认时区是UTC而国内大部分后端连接串没有指定serverTimezone就会出现“The server time zone value UTC is unrecognized”的启动报错。解决方式是在JDBC URL后面加上serverTimezoneAsia/Shanghai。同时加上useUnicodetruecharacterEncodingutf8保证中文不乱码。如果连接的是MySQL 8.0以上还需要考虑allowPublicKeyRetrievaltrue特别是使用caching_sha2_password认证时某些驱动版本会在连接时报Public Key Retrieval is not allowed。导入SQL时如果中文乱码多半是命令行客户端字符集没对上。执行导入前先用SET NAMES utf8mb4或者使用mysql命令行参数--default-character-setutf8mb4。数据库表创建时也要统一用utf8mb4否则即使连接串没问题字段本身的字符集还是错的。6.5 上线后的运维小技巧系统跑起来之后最重要的两个文件是后端日志和Nginx日志。后端日志建议用Logback按天滚动保留30天就够。排查线上问题时先看日志有没有BizException和SQLException再结合请求参数复现。Nginx的access_log记录了所有接口的访问状态如果前端页面白屏先看access_log里静态资源是否全部200再看有没有404的js文件。还有一个很容易被忽略的点jar包运行目录的权限。如果以root用户启动Java进程上传文件的目录也要给root权限如果用普通用户启动jar包和dist目录就不能放在root主目录下否则读写会遇到权限问题。我习惯统一用/opt/vehicle作为部署目录并把这个目录的所有者设为运行用户避免一堆Permission denied的报错。最后分享一个我亲手踩过的坑Nginx的proxy_pass如果写成了http://127.0.0.1:8080请求是保留原始URI的比如代理目标会变成http://127.0.0.1:8080/api/list但如果写成了http://127.0.0.1:8080/location /api/中的/api前缀会被去掉后端收到的路径直接变成/list后端的Controller如果定义的是/api/list就会404。这个斜杠的有无直接决定转发的路由长相配置的时候一定要按后端实际接口路径来算清楚。我个人做这套车辆管理系统的最大收获是明白了前后端分离项目的重点不在某个单一框架的奇技淫巧而在于把所有环节的约定统一起来数据库字段约定、接口返回约定、前端状态管理约定、部署路径约定。把这些约定定清楚哪怕团队只有两三个人开发效率也会翻倍。如果你正准备做类似系统按我上面的思路先把表结构和接口文档理清再动手写代码后面会省掉大量返工的时间。
返回列表