简介:基于SpringBoot的车辆管理系统是一套面向本科与高职毕业设计的项目资源,系统以管理员和用户两类角色展开,覆盖个人中心、用户管理、车辆管理、保养登记、事故登记、维修登记、违章登记等核心模块,采用SpringBoot+Vue+MySQL+Maven技术组合,适合Java方向学生用于毕设参考、课程设计或二次开发练习。资源包共5个文件,含源码zip、数据库sql、设计文档docx、答辩PPT和说明txt,整体约16.69MB。目前已有46人学习下载。源码配合数据库脚本即可快速运行,文档覆盖需求分析、数据库设计、模块实现的完整思路,PPT可帮助梳理答辩流程;借助这套资源,读者能减少环境搭建与材料整理时间,掌握车辆登记、违章、事故、维修等业务场景的落地实现,适合需要高效完成毕业设计系统并准备答辩的高校学生。
1. 基于SpringBoot的车辆管理系统:先问自己要不要做,再问怎么做
一家有50台车的运输公司,最值钱的信息往往不在ERP里,而在调度员的Excel和微信聊天记录中。基于SpringBoot的车辆管理系统,核心任务是把车辆档案、驾驶员信息、行程台账、维修保养和保险过户这些零散数据收进一个在线后台,让查询、统计和交接不再依赖个人经验。
它最适合两类人:一类是校招、毕设或刚入职的开发新人,需要一套结构完整、能跑通的案例;另一类是只有几十台车的中小企业,想用一个内部系统替代表格管理。
这套编号326的交付包只要把源码、数据库脚本、项目文档和PPT理顺,起步门槛反而在需求边界。先把范围定成「管车、管司机、管行程、管维修」,再考虑要不要接OA和财务系统。
2. 把SpringBoot后端跑起来:SpringBoot配置、工程结构与核心依赖
2.1 管理类系统为什么选SpringBoot而不是SSH或纯Servlet
这个系统属于典型的管理信息系统,业务集中在增删改查、审批流和报表,没有高并发、没有复杂分布式事务。SpringBoot的价值不是让代码写得少,而是把SSH里那一堆XML配置、监听器、事务代理收进自动配置里,开发者能把精力放在车表、订单表、维修表这些业务对象上。换到现在的招聘环境,懂SpringBoot的候选人也远比懂SSH的多,后续维护成本更低。
如果用纯Servlet加JDBC去写,一个车辆列表页面至少要写八层代码,连分页都要自己拼limit,遇到事务还得手动管理连接。而用SpringBoot加Spring MVC,一个@RestController注解加一个Service方法就能完成同样的事。我一般会提醒刚入门的人:管理类系统的技术栈选择,第一条标准是「团队是否接手得住」,第二条才是性能。
SpringBoot版本选择是个容易被忽略的细节。2.7.x仍然是管理类项目里最稳妥的选择,一方面它基于javax,和现成脚手架、MyBatis-Plus 3.x兼容性最好,另一方面它不需要Java 17,拿毕业设计或者做企业内网部署时少踩很多系统环境问题。SpringBoot 3.x当然更好,但如果你没有足够时间处理Jakarta命名空间和版本变更,还是慎用。
2.2 SpringBoot项目结构:按业务模块拆包,别把全家桶堆在controller里
我见过不少车辆管理系统把controller、service、mapper按层拆成三个大包,结果业务一复杂,新增一个「车辆维修」功能要跨六个包改文件。更好的做法是先把业务模块分清楚,再在每个模块里做MVC分层。下面是这套系统推荐的基础目录结构:
vehicle-system/ ├── src/main/java/com/car/admin/ │ ├── common/ # 统一返回体、异常处理、结果码 │ ├── config/ # 分页插件、拦截器、Jackson时间序列化 │ ├── modules/ │ │ ├── auth/ # 登录认证、用户Token │ │ ├── vehicle/ # 车辆信息 │ │ ├── driver/ # 驾驶员档案 │ │ ├── trip/ # 出车/行程记录 │ │ ├── maintenance/ # 维修保养 │ │ └── report/ # 统计报表 │ └── VehicleApplication.java ├── src/main/resources/ │ ├── mapper/ # 手写SQL的XML文件 │ ├── sql/ # 数据库初始化脚本 │ └── application.yml └── pom.xml按模块拆包的好处很直接:改车辆状态时只需要打开modules/vehicle,不会误碰驾驶员模块的代码。common、config这种横切层单独放,避免模块之间互相引用时出现循环依赖。resource目录里单独放sql文件,这样拿到源码的新人不会在几百行初始化SQL里找不到入口。
这里有一个细节:虽然MyBatis-Plus支持BaseMapper直接CRUD,但复杂报表查询还是建议用XML写SQL。所以编译期配置里要保留mapper-locations,不要把mapper目录删掉。另外,启动类的位置放在com.car.admin根包下,SpringBoot默认扫描才能覆盖所有子包,不然@Component和@Mapper都扫不到。
2.3 从空项目到第一个接口:SpringBoot配置和依赖的取舍
新建一个SpringBoot项目时,pom.xml里只需要保留真正用到的依赖。管理类系统最常用的是spring-boot-starter-web、MyBatis-Plus、MySQL驱动、Lombok,再加一个参数校验的starter。这是经过验证的依赖配置:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.18</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.5</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency> </dependencies>这里没有引入spring-boot-starter-data-jpa,也没有引Druid连接池,因为Druid在MyBatis-Plus环境里需要额外配置监控项,对小型管理后台价值有限。如果后续报表查询压力上来了,再加连接池和缓存也不迟。注意MyBatis-Plus版本要和SpringBoot 2.7兼容,换到3.x需要确认是否已经切换到Jakarta命名空间。
接下来是本系统的核心配置文件application.yml。这里最容易翻车的是数据库连接串:
server: port: 8380 servlet: context-path: /vms spring: datasource: url: jdbc:mysql://localhost:3306/vehicle_system?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml global-config: db-config: id-type: auto logic-delete-field: deletedserverTimezone=Asia/Shanghai必须写,否则MySQL的CST时区会跟本机时间产生8小时偏差;context-path=/vms让所有接口都带/vms前缀,方便前后端联调时直接区分服务。logic-delete-field配置加了之后,MyBatis-Plus执行delete方法时不会物理删除,而是把deleted字段改成1,这对车辆档案这种要留痕的数据很关键。
启动类要放在根包下,并且不要忘记扫描Mapper:
@SpringBootApplication @MapperScan("com.car.admin.modules.*.mapper") public class VehicleApplication { public static void main(String[] args) { SpringApplication.run(VehicleApplication.class, args); } }写完之后运行mvn spring-boot:run,浏览器访问/vms/vehicle/ping,能看到返回结果就说明SpringBoot基础链路通了。后面的auth模块再按同样的方式补,整个骨架就立起来了。
提示:如果MySQL版本是8.0以上,驱动类必须写com.mysql.cj.jdbc.Driver,老版本才用com.mysql.jdbc.Driver。
3. 把数据库增删改查写进车辆模块:6张表、索引与分页查询
3.1 车辆管理系统的数据模型:6张表让业务闭环
车辆管理系统最常见的失败是表建得不够,或者建得太散。一个可以闭环的最小数据模型至少包括6张表:车辆表、驾驶员表、行程表、维修保养表、保险表、用户表。车辆表关联驾驶员作为当前负责人,行程表记录每一次出车,维修保养表记录维修和保养记录,保险表记录保险到期和理赔信息,用户表负责登录和权限。
下表是这套系统的核心表字段设计,拿过来可以直接参考:
| 表名 | 核心字段 | 说明 |
|---|---|---|
| vehicle | id, plate_number, vehicle_type, brand_model, buy_date, status, deleted | 车辆唯一档案,车牌唯一 |
| driver | id, name, license_no, phone, status | 驾驶员档案,驾驶证号唯一 |
| trip | id, vehicle_id, driver_id, start_mileage, end_mileage, start_time, end_time, purpose | 每次出车的里程和事由 |
| maintenance | id, vehicle_id, type, amount, maintenance_date, remark | 维修保养记录,金额精确到分 |
| insurance | id, vehicle_id, insurer, expire_date, amount | 保险到期提醒 |
| sys_user | id, username, password, real_name, role_id | 登录用户与权限 |
车辆表的核心字段我会加三样东西:车牌唯一索引、状态字段、逻辑删除标记。车牌号是业务天然唯一键,必须直接在数据库层挡住重复数据;status用TINYINT表示0正常、1维修、2停用、3报废,比VARCHAR更容易做报表统计;deleted字段配合MyBatis-Plus的逻辑删除,避免删除车辆后行程记录变成孤儿数据。下面给出建表SQL:
CREATE TABLE vehicle ( id BIGINT PRIMARY KEY AUTO_INCREMENT, plate_number VARCHAR(20) NOT NULL COMMENT '车牌号', vehicle_type VARCHAR(30) COMMENT '车辆类型', brand_model VARCHAR(50) COMMENT '品牌型号', buy_date DATE 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, deleted TINYINT DEFAULT 0 COMMENT '逻辑删除标记', UNIQUE KEY uk_plate (plate_number), KEY idx_status (status) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='车辆信息表';建表SQL里使用了utf8mb4字符集和InnoDB引擎。外键约束不推荐在表之间强制加,因为MyBatis-Plus的关联查询能力有限,加了外键反而给逻辑删除和数据导入带来麻烦。其他5张表按同样的规则建,金额字段全部用DECIMAL(10,2),时间字段用DATETIME,不用字符串存时间。
3.2 MyBatis-Plus还是JPA:管理类系统我选MyBatis-Plus的三个理由
争论MyBatis-Plus和JPA谁更好的话题几乎每个项目都要来一轮,我的结论很直接:做这种以数据库增删改查为主的管理后台,无脑选MyBatis-Plus。第一是CRUD零SQL,BaseMapper和LambdaQueryWrapper能覆盖八成以上的单表查询;第二是分页插件统一,不用手动写limit;第三是它在国内生态里普及度高,团队成员上手成本低。
JPA被诟病最多的是黑匣子问题,在实体关系映射上尤其明显。车表关联行程表、司机表,用JPA一段@OneToMany就能把数据带出来,但懒加载异常和N+1查询排查起来像剥洋葱。MyBatis-Plus虽然也提供关联查询,但更多时候还是靠SQL控制,数据权限、报表统计反而更好写。所以我在架构阶段就会定下:主框架用MyBatis-Plus,复杂统计用XML手写SQL,两边各管各的。
分页插件必须单独配置,否则selectPage不会触发MySQL的limit逻辑。
@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }PaginationInnerInterceptor的参数DbType.MYSQL是必填的,不填的话分页插件会猜测数据库类型,在MySQL环境下有可能生成错误的分页方言。加了之后,调用selectPage时它会自动在查询SQL上追加limit、offset,代码里不需要再手工拼。
3.3 车辆模块的增删改查:条件分页、车牌校验和逻辑删除
接下来直接看车辆模块的一个完整查询接口。这个接口同时处理车牌号模糊查询和状态筛选,是管理端列表最常遇到的需求:
@RestController @RequestMapping("/vms/vehicle") @RequiredArgsConstructor public class VehicleController { private final VehicleService vehicleService; @GetMapping("/page") public Result<Page<Vehicle>> page(@RequestParam(required = false) String plateNumber, @RequestParam(required = false) Integer status, @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size) { return Result.ok(vehicleService.pageQuery(plateNumber, status, page, size)); } }Controller只做参数接收和结果包装。核心逻辑在Service实现里,这也是方便加数据权限拦截的地方:
public Page<Vehicle> pageQuery(String plateNumber, Integer status, int page, int size) { LambdaQueryWrapper<Vehicle> wrapper = Wrappers.<Vehicle>lambdaQuery() .like(StringUtils.hasText(plateNumber), Vehicle::getPlateNumber, plateNumber) .eq(status != null, Vehicle::getStatus, status) .orderByDesc(Vehicle::getCreateTime); return vehicleMapper.selectPage(new Page<>(page, size), wrapper); }LambdaQueryWrapper里的第一个参数是布尔条件,plateNumber为空就直接跳过,不会拼出like '%%'这种浪费性能的条件。orderByDesc用create_time而不是update_time,因为列表页要的是新增顺序而不是修改顺序。这里的参数说明很重要:分页查询不要用前端传来的页码直接当limit起始值,MyBatis-Plus的Page对象内部会处理current、size到offset的转换,业务层不需要关心。
新增车辆时最常见的错误是只在前端判断车牌重复,后端不做任何防护。如果两个人同时提交,还是会插入两条重复车牌。所以Service里新增方法要同时做数据库唯一索引和代码层校验:
public Result<Vehicle> addVehicle(Vehicle vehicle) { long count = vehicleMapper.selectCount(Wrappers.<Vehicle>lambdaQuery() .eq(Vehicle::getPlateNumber, vehicle.getPlateNumber())); if (count > 0) { return Result.fail("车牌号已存在"); } vehicle.setStatus(0); vehicleMapper.insert(vehicle); return Result.ok(vehicle); }多一道selectCount的意义在于能给前端返回「车牌号已存在」这样的可读提示,而不是让用户面对一个DuplicateKeyException。数据库唯一索引是最后一道防线,两个要同时存在。修改车辆时用updateById,它会根据主键更新,但要注意status为空时不会覆盖原值,这是MyBatis-Plus的默认行为。删除车辆不调用deleteById,而是改为更新deleted=1,或者用MyBatis-Plus自带的逻辑删除拦截器去处理。
4. 车辆管理系统排查手册:5个常见翻车点的现象、原因、解决
这类项目跑起来不难,难的是上线后出现一堆看似没有规律的问题。我把最常见的5个翻车点按「现象、原因、解决」记录在这里,开发时可以提前设防,维护时也可以直接对号入座排查。
4.1 数据层的三个坑:时间偏差、车牌重复、字符集报错
坑一:页面保存的用车时间,入库后少了8小时。
现象:前端选择14:00创建行程,数据库里存的是06:00,列表页读出来还是06:00,对账直接对不上。
原因:JDBC连接串里没配serverTimezone,或者MySQL所在服务器时区是UTC/CST混用。Spring Boot容器取系统时区,JDBC驱动按数据库会话时区转换,两边各算各的,就出现了固定的8小时偏差。
解决:把jdbc url改成serverTimezone=Asia/Shanghai,同时执行SET time_zone = '+08:00';把MySQL会话时区同步。改完后先重启应用,再从库里用select now()对照一次,确认前后一致。这个配置要写在每个环境的数据源URL里,不能只改开发机。
坑二:同一车牌号的车辆在列表里出现两条。
现象:车辆管理列表里看到“京A12345”两条记录,导出Excel后才发现重复,但新增页面明明做了重复提示。
原因:数据库表没给plate_number加唯一索引,应用层校验被绕过。手工导入、脚本刷数据时,两次insert都没走Controller里的selectCount校验,自然就插进去了。
解决:先清理重复数据,然后执行ALTER TABLE vehicle ADD UNIQUE KEY uk_plate (plate_number);把最后一道防线补上。Service里的selectCount校验保留,它负责返回友好提示,避免前端直接面对DuplicateKeyException。
坑三:保存司机姓名或备注时,报“Incorrect string value”。
现象:新增司机接口输入姓名或备注,值里带emoji或生僻字,提交后接口直接500,后台日志抛出SQLException。
原因:数据库表和连接串的字符集还是utf8。MySQL的utf8只是utf8mb3,只能存3字节字符,emoji是4字节,根本存不进去。
解决:建库建表时都用DEFAULT CHARSET=utf8mb4,jdbc url里写characterEncoding=utf8。这里的utf8指客户端编码,不是MySQL存储编码,不要多写一个mb4进去。已经产生乱码的表,先ALTER TABLE vehicle CONVERT TO CHARACTER SET utf8mb4;转换,再确认业务数据没有丢失。
4.2 接口和统计的两个坑:越权修改和SUM返回null
坑四:普通登录用户可以修改其他部门的车辆状态。
现象:一个普通调度员调用修改车辆接口,直接把其他分公司的车辆全改成报废状态,前端页面根本没有对应的按钮。
原因:后端只做了登录拦截,没做数据权限。修改接口只接收vehicleId,service层没有校验这条车辆是否属于当前用户所在部门。前端隐藏按钮只能挡住操作路径,拦不住直接请求后端的同事。
解决:在service层从登录上下文拿当前用户的部门ID,修改前先查一次vehicle的companyId或部门ID,不一致直接返回“无权限”。查询列表时也要在queryWrapper上强制加部门过滤条件,而不是靠前端传参。
坑五:维修费用报表SUM结果为null,金额合计出现一分钱误差。
现象:报表页面维修费用和保险金额为空,导出Excel后金额小数位对不上,财务找过来质疑数据。
原因:空表上SUM(amount)返回NULL,许多模板引擎渲染NULL就是空白。金额字段如果用了FLOAT或DOUBLE,二进制浮点累加必然出现精度漂移。
解决:报表SQL改成SELECT IFNULL(SUM(amount), 0) AS total FROM maintenance WHERE vehicle_id = #{id};金额字段建表用DECIMAL(10, 2),Java侧用BigDecimal接收。开发环境先用空表跑一次SQL,不要等上线后再测。
5. 万字文档和PPT的组织:把源码变成能答辩、能交付的东西
5.1 文档结构:四个部分撑起一万字,不靠截图堆页数
标题里的「万字文档」通常意味着交付给甲方或者导师评审的东西,不是随手写的Readme。我一般会按四个部分组织文档,保证每个部分都指向「为什么这样做」和「怎么跑起来」。下面是一份可以直接套用的章节结构:
| 文档章节 | 核心内容 | 篇幅 |
|---|---|---|
| 需求分析 | 项目背景、用户角色、功能需求用例、数据流 | 2500字 |
| 系统设计 | 总体架构、技术选型、模块划分、数据库设计 | 3000字 |
| 核心实现 | 登录认证、车辆CRUD、行程记录、报表统计、关键代码说明 | 3000字 |
| 测试与部署 | 测试环境、核心测试用例、运行步骤、打包部署 | 2500字 |
写需求分析时,文字在精不在多,但用例表一定要有,比如「管理员新增车辆」「调度员创建行程」「财务查看维修费用报表」,这些才是车辆管理系统的差异化内容。系统设计部分把第三章的表结构和接口返回值粘贴进去,再加一页ER图。核心实现部分别贴完整Controller代码,而是贴关键方法并逐行解释参数。测试与部署部分写清楚JDK版本、数据库版本、Maven仓库地址、启动命令,缺一个别人都跑不起来。
这套结构的另一个作用是能直接生成目录树,让评审专家一眼看到工作量。很多万字文档会把需求描述、界面截图、代码注释都算进去,但只要每个表格不是空壳,看的人不会觉得注水。
5.2 答辩PPT:10页逻辑让评委快速听懂车辆管理系统
PPT是用来讲故事的,不是用来放文档的。10页已经足够,每页只留一个核心观点。我习惯把内容做成下面这10页骨架:
| 页面 | 标题 | 关键内容 |
|---|---|---|
| 1 | 封面 | 项目名、技术栈、姓名 |
| 2 | 背景与问题 | Excel管理车辆的痛点 |
| 3 | 功能模块图 | 六大模块一张图 |
| 4 | 技术架构 | SpringBoot + MyBatis-Plus + MySQL |
| 5 | 数据库设计 | 6张表核心表结构和ER图 |
| 6 | 核心功能演示 | 车辆列表、行程录入、报表 |
| 7 | 关键代码展示 | 条件分页、逻辑删除 |
| 8 | 测试结果 | 核心接口的测试用例和截图 |
| 9 | 项目难点与解决 | 并发车牌防重、统计精度 |
| 10 | 总结与展望 | 可扩展方向 |
其中第6页是决定答辩效果的一页。宁可少放两张架构图,也要把车辆列表查询接口的实时效果录成动态图,或者在现场操作一遍。第9页讲一个真实的坑,比罗列五个功能有用得多。每页PPT的正文不超过3行,其余用截图和表格支撑。
5.3 源码交付清单:三步跑起来,避开交付翻车
最后,把源码交给别人时,我一定会检查四类内容:源码目录是否包含初始化SQL、配置文件是否去掉了真实密码、文档里的运行步骤是否和实际代码一致、PPT里的功能图是否需要更新。最常翻车的地方是数据库脚本版本和代码实体字段不一致,导致别人导入后启动直接报错。
为了减少来回沟通,这一套编号326的交付包,源码、SQL、文档、PPT四个目录缺一不可。数据库初始化脚本一定要能用一个命令跑完:
mysql -uroot -p -e "CREATE DATABASE vehicle_system DEFAULT CHARACTER SET utf8mb4;" mysql -uroot -p vehicle_system < sql/vehicle_system.sql第一个命令创建数据库并指定字符集,第二个命令导入初始化数据。注意第二个命令会把sql/vehicle_system.sql里的建表和初始数据一次性导入。如果MySQL 8.x的密码包含特殊字符,建议改用-e "source /path/to/sql"方式,否则会解析错。
导入完成后,把application.yml里的spring.datasource.password改成自己的本地密码,再执行mvn spring-boot:run,访问/vms/vehicle/ping。交付源码时不要把target目录和三方jar包含进去,只发src、sql和doc三个目录,另外在README里写明JDK版本、Maven版本、MySQL版本,就能省掉大量答疑时间。
注意:交付前一定要在全新环境执行一次mvn clean package,确认没有遗漏本地依赖,再打zip包发出去。
6. 最小冒烟测试:一条命令验明车辆管理系统真正跑通
接手任何一个SpringBoot项目,我第一件事不是看设计文档,而是先跑冒烟。我把最小冒烟定义为:服务能启动、登录能拿到Token、列表接口能返回数据。这个三层链路验证下来,数据库连接、权限配置、Mapper扫描都没问题,后面往上叠功能才有底。
下面这段脚本可以直接放到项目根目录,假设默认登录账号admin,密码admin123,端口8380:
#!/usr/bin/env bash BASE_URL=http://localhost:8380/vms TOKEN=$(curl -s -X POST $BASE_URL/auth/login \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"admin123"}' | jq -r '.data.token') curl -s "$BASE_URL/vehicle/page?page=1&size=10" \ -H "Authorization: Bearer $TOKEN" | jq '.data.total'如果机器上没有jq,可以把jq换成python3 -c "import sys,json; print(json.load(sys.stdin)['data']['total'])"。这串命令里最关键的是Authorization头,它说明车辆管理系统的接口并不是暴露给所有人的,登录鉴权已经生效。返回.total字段如果大于等于0,说明Mapper层、数据源、分页插件都正常,不需要再登录前端系统。
冒烟脚本的覆盖范围要刻意收敛,不把新增车辆、删除车辆这些写操作放进去。写操作会污染测试数据,尤其在做毕业设计演示时,删掉一条重要记录就尴尬了。真正上线前,还要单独跑一遍事务回滚测试和权限测试,冒烟测试只管「通不通」,不管「对不对」。
我现在带项目时已经形成一个习惯:凡是新同事接手这套代码,第一天只允许跑冒烟脚本和看文档,不允许改动任何Mapper XML。跑通了说明环境没问题,接下来再谈改代码。这个习惯帮我省下太多排障时间,也希望帮到你。
本文还有配套的精品资源,点击获取