Spring Boot+Vue车辆管理系统实战:从数据库设计到前后端分离部署
车辆管理系统这个项目说实话在Java全栈里已经算是一个很经典的业务系统了。它的定位很简单把公司内部的车、司机、用车申请、维修保养这些事从纸质台账和微信群里捞出来变成一套可以查、可以审批、可以统计的线上流程。我这次用Spring Boot Vue MySQL MyBatis这套组合完整做了一遍前端负责页面交互后端提供REST接口车辆信息、司机档案、申请审批、维保记录这些模块全部打通。不管你是准备拿它做毕业设计还是刚学完Java和Vue想找一个完整项目练手这套前后端分离的代码结构都值得参考。文章不会只甩源码我会把数据表怎么建、接口怎么分层、前端怎么对接、哪些地方容易踩坑一条条捋清楚。1. 项目定位与整体设计思路1.1 车辆管理系统到底要解决什么问题很多中小公司的车辆管理其实一直处于“半失控”状态。车辆档案可能躺在行政部的Excel里谁借了车、车什么时候保养、油费花了多少基本靠问人或者翻聊天记录。这套系统的第一个价值就是把“人找信息”变成“系统找信息”所有车辆档案、司机资料、申请记录全部落到数据库里随时能查。第二个痛点是流程不透明。以前员工要用车得先发消息问行政有没有车行政再去查台账、找人协调中间很容易漏信息。车辆管理系统用“申请—审批—派车—归还”这条状态线把用车过程固定下来员工提交申请后能看到当前是待审核还是已经通过管理员在后台一眼就能看到今天哪些车被借走了哪些车还空闲整个人力协调成本会明显降下来。第三个痛点是数据统计困难。车辆每个月油费多少、维修花了多少、哪台车出险率高这些如果靠手工登记月底汇总能让人头大。系统里专门设计了维保记录、加油记录和统计看板费用和时间都能按车、按月筛出来管理者做决策也更有依据。所以这个项目虽然叫车辆管理系统本质上是在解决企业的资产数字化和流程规范化问题。1.2 技术选型为什么是Spring Boot Vue MyBatis这套组合在今天已经不算新鲜但胜在实用。先说后端。Spring Boot最大的好处是降低了工程搭建成本不需要像传统SSH那样写一堆XML配置内置Tomcat一个main方法就能启动整个Web服务。配合Spring官方生态权限校验、参数校验、事务管理这些能力都是现成的适合快速迭代。而且对刚开始学项目的同学来说Spring Boot的社区资料非常全遇到问题一搜就能找到解决方案。MyBatis在数据库操作层很有优势。它不是完全自动化的ORMSQL是开发自己写的所以你能精确控制每一条查询语句遇到多表关联、动态条件、批量更新这类复杂需求时写法比Hibernate更直观。特别是车辆管理这种业务列表页经常要按车牌号、状态、车辆类型做组合筛选用MyBatis的where和if标签写动态SQL比拼字符串优雅太多。前端用Vue是因为它的学习曲线相对平缓组件化开发特别适合后台管理系统。一个车辆列表页可以拆成搜索栏组件、表格组件、弹窗表单组件哪里改动就动哪里不会牵一发而动全身。再配合Element UI这种成熟组件库表格、分页、日期选择器、表单校验就是几行代码的事开发效率非常高。用生活里的话说选技术栈就像选装修队。Spring Boot是给整个房子搭水电的把基础环境通好MyBatis像是具体的泥瓦工每个砖头怎么放都由你说了算Vue则是负责软装和界面设计的让住在里面的人用得舒服。三者各管一段配合起来条理清晰。1.3 功能模块总览整套系统按角色主要分成两类使用端普通员工和管理员。普通员工能看到车辆信息、提交用车申请、查看自己的申请记录管理员除此之外还能维护车辆档案、管理司机、审核申请、登记维保和加油记录。具体功能模块可以这么拆登录与用户管理用户名密码登录简单RBAC角色区分后台统一管理用户账号。车辆档案管理新增、编辑、删除车辆信息维护车牌号、品牌、车型、购买日期、当前里程和车辆状态。司机信息管理司机姓名、身份证、电话、驾驶证号的增删改查。用车申请与审批员工创建申请单选择车辆和时间段填写事由管理员审核通过后车辆状态联动更新。维修保养管理登记维修项目、费用、日期可查询每台车的维保历史。加油管理记录加油量、费用、时间支持按月统计。数据看板首页展示车辆总数、在途车辆数、本月用车申请量、本周维保费用等统计信息。模块之间不是孤立的比如申请审批通过后不能只是把申请单状态改成“已通过”还要把对应车辆的状态改为“使用中”。这些跨表联动的业务逻辑既是项目的难点也是面试时能拿出来讲的亮点。2. 数据库设计表结构拆解2.1 核心数据表与字段说明数据库设计是这类管理系统的基础。我建议先画清楚实体关系再动手建表。这套项目里最重要的几个实体是用户、车辆、司机、申请单、维保记录、加油记录。下面是我实际建表时的核心字段设计。用户表sys_user字段名类型说明idbigint主键自增usernamevarchar(50)登录账号唯一passwordvarchar(100)加密后的密码real_namevarchar(50)真实姓名roletinyint角色1管理员2普通员工create_timedatetime创建时间statustinyint账号状态1启用0禁用车辆表vehicle字段名类型说明idbigint主键plate_novarchar(20)车牌号vehicle_typevarchar(20)车辆类型如轿车、SUVbrandvarchar(50)品牌型号statustinyint状态1空闲2使用中3维修中4报废buy_datedate购买日期current_mileagedecimal(10,2)当前里程公里driver_idbigint当前绑定司机逻辑外键remarkvarchar(255)备注create_timedatetime创建时间司机表driver_info字段名类型说明idbigint主键namevarchar(50)司机姓名id_cardvarchar(20)身份证号phonevarchar(20)联系电话driver_novarchar(30)驾驶证号statustinyint1可用0停用用车申请表apply_record字段名类型说明idbigint主键user_idbigint申请人IDvehicle_idbigint车辆IDapply_reasonvarchar(255)申请事由start_timedatetime预计用车开始时间end_timedatetime预计归还时间statustinyint状态1待审核2已通过3已驳回4已结束auditor_idbigint审核人IDaudit_timedatetime审核时间create_timedatetime申请时间维保记录表maintenance_record字段名类型说明idbigint主键vehicle_idbigint车辆IDmaintenance_typevarchar(50)维修/保养类型costdecimal(10,2)费用maintenance_datedate维保日期remarkvarchar(255)备注加油记录表fuel_record结构和维保表类似加上油量油费字段。这里不做过多展开。id字段统一用bigint自增主键能保证数据量大了以后不容易撞主键。create_time这类时间字段全部用datetime不搞时间戳查数据时一眼能看懂。角色、状态这些枚举值都用tinyint后端定义好常量或枚举类数据库里只存数字不存中文这样存储小、查询快也不会出现“空闲”和“空闭”这种手工登记时才会打的错别字。2.2 索引和外键怎么取舍数据库设计里有个容易被新手忽略的点外键到底建不建我在这类管理系统里倾向于不建物理外键只保留逻辑外键也就是在业务表里存关联表的ID但不声明FOREIGN KEY约束。原因很简单。物理外键在插入、更新时会触发数据库层面的完整性校验单机小系统没问题但一旦以后做读写分离或者微服务拆表物理外键会成为严重制约。比如申请单里的vehicle_id如果声明了外键想按月份归档旧申请单就很难操作。所以更常见的做法是在vehicle_id、user_id、auditor_id这些字段上建普通索引用代码保证数据的逻辑正确。索引要建的字段主要有三类一是查询频繁的状态字段比如vehicle.status、apply_record.status二是关联字段比如apply_record.vehicle_id、maintenance_record.vehicle_id三是时间范围查询字段比如apply_record.start_time、fuel_record.fuel_date。列表页最常见的搜索方式是“某段时间内某状态的车辆申请”这种场景建一个(status, start_time)的联合索引效率比两个单列索引更高。我在本地测试过数据量在两三万条时索引不索引区别不太明显但一旦日志表和申请单积累到几十万条没有索引的count查询可能从几十毫秒变成一两秒体验差距非常大。所以建表的SQL里别忘了把索引一起写进去比如CREATE INDEX idx_apply_vehicle_status ON apply_record (vehicle_id, status); CREATE INDEX idx_apply_user_time ON apply_record (user_id, start_time);2.3 初始化数据和状态枚举项目能不能跑起来很大程度看初始化数据全不全。我会准备两个数据脚本一个是schema.sql负责建库建表另一个是data.sql插入管理员账号、几辆测试车辆、几个司机和几条模拟申请记录。这样项目拉下来以后不用自己手动造数据启动完直接就能登录体验。状态枚举建议在后端建一个常量类统一管理比如public class VehicleStatus { public static final int IDLE 1; public static final int USED 2; public static final int REPAIRING 3; public static final int DISABLED 4; }这样在写业务逻辑时代码里不会散落各种魔法数字别人接手时也能快速看懂状态含义。前端再对应维护一份状态和标签文字、颜色的映射页面里就能把1渲染成“空闲”绿色标签2渲染成“使用中”橙色标签。整个数据一致性就靠这套枚举串起来。3. 后端实现Spring Boot MyBatis3.1 项目包结构与分层后端代码我习惯按controller - service - mapper三层来组织。刚开始写项目时很容易把所有逻辑都塞进Controller后患无穷接口一多就乱。推荐结构如下com.example.vehicle ├── VehicleApplication.java ├── controller │ ├── VehicleController.java │ ├── ApplyController.java │ └── AuthController.java ├── service │ ├── VehicleService.java │ └── impl │ ├── VehicleServiceImpl.java │ └── ApplyServiceImpl.java ├── mapper │ ├── VehicleMapper.java │ └── ApplyMapper.java ├── entity │ ├── Vehicle.java │ ├── ApplyRecord.java │ └── SysUser.java ├── dto │ ├── ApplyDTO.java │ └── LoginDTO.java ├── vo │ ├── ApplyVO.java │ └── VehicleVO.java ├── common │ ├── Result.java │ └── PageResult.java └── config ├── WebConfig.java └── MybatisPlusConfig.javaentity对应数据库表结构dto用于接收前端入参vo用于返回前端展示数据。为什么不直接用entity返回给前端因为在列表页里申请单返回时要显示申请人姓名、车辆车牌号这些需要关联查询后拼装用单独的ApplyVO正好把这些字段接住不会污染实体类。Result是统一返回结构我常用的是code, message, data三个字段Success的时候code为200失败时根据业务返回4xx或5xx。前端axios拦截器只需要关注这个统一格式处理起来非常省事。3.2 核心配置和启动类Spring Boot项目的核心配置全在application.yml里。这里要提醒一句不要一味追求最新版本。Spring Boot 3.x虽然已经出来很久了但它要求JDK 17以上而且很多老版本的MyBatis Starter、PageHelper插件会出现兼容性问题。如果只是做车辆管理系统这种常规业务Spring Boot 2.7.x配合JDK 8是最稳定的组合生产环境也大量在用。一个基础的数据源配置长这样server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/vehicle_manage?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.vehicle.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case必须开启这样数据库里的plate_no才能自动映射到实体的plateNo字段不然你必须在XML里手动给每个字段写as别名非常麻烦。配置log-impl后控制台会打印SQL和参数排查问题时能直接看到实际执行语句。启动类只需要一个SpringBootApplication注解加main方法。如果你用事务记得在Service方法或类上加Transactional。比如申请审核通过后既要改申请单状态又要改车辆状态这两步必须在一个事务里只要有一步失败就整体回滚。3.3 车辆管理接口的后端逻辑车辆的增删改查是这套系统最常规的接口。以列表查询为例前端会传页码、每页条数、车牌号关键字、车辆状态、车辆类型这几个参数。后端不能写死SQL得用MyBatis动态SQL拼接条件。Mapper接口写法public interface VehicleMapper { ListVehicleVO selectVehicleList(Param(query) VehicleQueryDTO query); }对应的Mapper XML关键片段select idselectVehicleList resultTypecom.example.vehicle.vo.VehicleVO select v.id, v.plate_no, v.vehicle_type, v.brand, v.status, v.current_mileage, d.name as driver_name from vehicle v left join driver_info d on v.driver_id d.id where if testquery.plateNo ! null and query.plateNo ! and v.plate_no like concat(%, #{query.plateNo}, %) /if if testquery.status ! null and v.status #{query.status} /if if testquery.vehicleType ! null and query.vehicleType ! and v.vehicle_type #{query.vehicleType} /if /where order by v.create_time desc /select这段SQL里left join driver_info是为了把司机的名字一起查出来VehicleVO里既有车牌又有司机名前端表格一列接一列直接显示。这里用left join而不是inner join是因为车辆可能没有绑定司机用inner join会把这类车辆过滤掉不符合查询预期。分页我用PageHelper插件在Service里写PageHelper.startPage(query.getPageNum(), query.getPageSize()); ListVehicleVO list vehicleMapper.selectVehicleList(query); PageInfoVehicleVO pageInfo new PageInfo(list);调用PageHelper.startPage之后的第一次查询会被自动拦截拼上limit业务代码不用手动传起始数非常方便。但有个坑是startPage只对紧接着的一条查询生效所以中间千万别夹其他查询语句否则分页条件会被吞掉。删除车辆时我建议用逻辑删除。给vehicle表加一个deleted字段删除时执行update vehicle set deleted 1 where id #{id}查询时统一带and deleted 0。这样用户误删数据还能恢复面试时讲出来也算一个亮点。3.4 用车申请状态流转用车申请是这个系统里最体现业务逻辑的模块。状态流转建议用状态机思维设计不要用户点了审核通过就随便改字段整个流程应该是员工创建申请单状态变为“待审核”管理员点击通过状态变为“已通过”同时把对应车辆status改为“使用中”车辆归还后管理员确认结束申请单状态变为“已结束”同时车辆status改回“空闲”。核心Service逻辑大概是Transactional public void auditApply(Long applyId, Integer auditResult) { ApplyRecord apply applyMapper.selectById(applyId); if (apply null) { throw new BusinessException(申请单不存在); } if (!apply.getStatus().equals(ApplyStatus.WAIT)) { throw new BusinessException(只有待审核的申请才能审核); } if (auditResult.equals(ApplyStatus.PASS)) { apply.setStatus(ApplyStatus.PASS); applyMapper.updateStatus(applyId, ApplyStatus.PASS); vehicleMapper.updateStatus(apply.getVehicleId(), VehicleStatus.USED); } else { apply.setStatus(ApplyStatus.REJECT); applyMapper.updateStatus(applyId, ApplyStatus.REJECT); } }这里最重要的是先校验当前状态再执行更新。否则一张已经被审核过的单子再次点审核状态会越改越乱。这就是为什么我不在Controller里直接写更新语句而是放到Service层通过一系列校验后再落库。驳回申请时还可以让后端生成一条驳回原因存到申请表的audit_remark字段里前端审核弹窗提示员工在申请记录里能看到结果减少来回沟通。3.5 MyBatis批量写操作和缓存避坑车辆管理系统里最常见的批量操作是批量导入车辆或者月底批量录入加油记录。MyBatis批量插入的标准做法是用foreach标签拼一条多值insert比循环调用单条插入快一个数量级。insert idbatchInsertFuel insert into fuel_record (vehicle_id, amount, fuel_cost, fuel_date, create_time) values foreach collectionlist itemitem separator, (#{item.vehicleId}, #{item.amount}, #{item.fuelCost}, #{item.fuelDate}, now()) /foreach /insert这里提醒一下foreach的集合参数如果没加Param(list)注解XML里用list可能拿不到值报错或插入为空数据。所以Mapper接口里写清楚int batchInsertFuel(Param(list) ListFuelRecord list);另外批量插入数据量太大时拼出来的SQL可能超过数据库max_allowed_packet限制建议每500条一批分批执行既稳定又不至于把数据库连接卡死。MyBatis缓存也值得讲。默认一级缓存是SqlSession级别的同一个SqlSession内多次查询相同SQL会命中缓存。但我们在Spring Boot中每次Mapper操作通常都会从连接池重新拿SqlSession一级缓存的生命周期很短基本感知不到。二级缓存是跨SqlSession的需要显式开启但车辆管理系统这类实时性要求高的业务不建议开因为一旦有更新操作缓存刷新不及时用户会看到车辆状态还是旧的产生误导。保持“不配置二级缓存”反而更安全。还有一个经常让新手卡半天的点MyBatis里单个数字字符比较。比如在XML里判断车辆状态if teststatus 1.toString() and v.status 1 /if直接写status 1在MyBatis解析OGNL表达式时会出问题因为1会被当成Character类型而Java的Character和String比较恒为false。我之前在这种小地方排查了很久最后发现加.toString()就正常了。如果你使用的是Java 9以上版本也可以用1.equals(status)这种写法本质都是让类型统一。4. 前端Vue页面与交互4.1 前端工程结构和请求封装前端我用的是Vue加Element UI。工程结构建议这样分src ├── api │ ├── vehicle.js │ └── apply.js ├── assets ├── components ├── router │ └── index.js ├── store │ └── user.js ├── views │ ├── login.vue │ ├── vehicle.vue │ ├── apply.vue │ └── dashboard.vue ├── utils │ └── request.js ├── App.vue └── main.js把接口请求统一放在api目录页面组件不直接写axios而是引入封装好的函数。比如api/vehicle.js里import request from /utils/request export function getVehicleList(params) { return request({ url: /vehicle/list, method: get, params }) }utils/request.js里封装一个带拦截器的axios实例import axios from axios const request axios.create({ baseURL: process.env.VUE_APP_BASE_URL || /api, timeout: 10000 }) request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization token } return config }) request.interceptors.response.use( response { const res response.data if (res.code ! 200) { this.$message.error(res.message) return Promise.reject(new Error(res.message)) } return res }, error { if (error.response error.response.status 401) { localStorage.removeItem(token) window.location.href /login } return Promise.reject(error) } )这样页面里不需要关心请求头、错误码、401跳转这些琐碎事只管拿到数据去渲染。4.2 登录鉴权与路由守卫登录页面负责收集用户名密码调后端登录接口拿到Token后存进localStorage再把用户ID和角色信息放进Vuex或sessionStorage。后端我这里用一个简化方案登录成功返回一个UUID生成的token存到Redis里后续请求再校验用户身份。Redis不是必须的初期项目可以全放内存Map但上线推荐用Redis重启不丢登录态也好做过期时间。前端路由守卫用来拦截未登录用户的访问router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.path /login) { next() return } if (!token) { next(/login) return } if (to.meta to.meta.role to.meta.role ! localStorage.getItem(role)) { next(/403) return } next() })登录跳转后动态生成侧边栏菜单。管理员能看到“车辆管理”“司机管理”“申请审批”“维保记录”“加油记录”等菜单普通员工只看到“车辆查询”“我的申请”“申请用车”。这种菜单级权限控制不需要太复杂后端接口加上角色判断前端配置好meta.role就够用。4.3 车辆列表和申请用车页面车辆列表页是整个后台的核心页面。页面结构大致是顶部搜索栏中间表格底部翻页右侧新增编辑按钮。表格列设置车牌号、车辆类型、品牌、当前里程、状态、绑定司机、操作按钮。“状态”列不要直接显示数字用Element UI的el-tag根据状态渲染不同颜色。比如el-table-column label状态 template slot-scopescope el-tag :typestatusTypeMap[scope.row.status] {{ statusTextMap[scope.row.status] }} /el-tag /template /el-table-column新增车辆用el-dialog弹窗包一个el-form表单校验规则按需配置车牌号必填、里程用input-number组件控制数字范围。提交时把所有表单字段组装成对象调新增接口成功后刷新列表。用车申请页面则更关注业务交互。用户选择一个车辆时最好能实时看到该车当前状态避免选中一台“使用中”的车还被允许提交。我在这里做了一个小功能申请单里车辆选择组件下拉数据只返回状态为“空闲”的车提交到后端后再校验一次状态双保险。这也是我在实际项目中踩过的坑——前端过滤了后端没校验结果两个人同时申请同一台车管理员审核后状态就乱了。申请列表页要注意时间条件查询。前端用el-date-picker的daterange提交给后端时转成startTime和endTime两个参数SQL里用apply.start_time #{startTime} and apply.start_time #{endTime}去匹配。这里千万别把时间范围字符串直接拼SQL一定要用预编译参数既能防注入又不会因为格式问题查错数据。4.4 打包部署和布局异常前端开发时用npm run serve上线时执行npm run build默认会生成到dist目录。很多新手把dist里的index.html直接双击打开看到的是一片空白控制台报各种加载路径错误。原因很简单构建资源默认用的是绝对路径/js/xxx.js本地打开时根目录不对。解决方式是在根目录的vue.config.js里设置publicPathmodule.exports { publicPath: ./ }重新打包后资源路径变成相对路径静态文件就能正常加载。如果后端用Nginx部署也可以在Nginx里配置location /的根目录指向dist并做try_files到index.html这样前端路由的history模式也不会刷新404。还有一个常见的“打包后布局异常”问题本地开发一切正常打包部署后页面的背景图、图标全丢了。这通常是因为CSS里引用的url()使用了绝对路径。规范的做法是把静态资源放到src/assets在代码中通过require或import引入Webpack打包时会自动处理资源路径而不是直接在public下写死引用。5. 本地部署和问题排查5.1 环境准备与启动顺序想要从零把这个项目跑起来你至少需要准备这些环境JDK 8推荐使用JDK 8兼容Spring Boot 2.7.x不用折腾模块和Java版本问题。MySQL 8.x安装时注意字符集选择utf8mb4建库用utf8mb4_general_ci避免中文乱码。Maven 3.6以上用于后端依赖下载和构建。Node.js 14以上前端Vue项目依赖npm打包。IDEA开发工具后端用IntelliJ IDEA前端也可以在IDEA里直接打开Vue工程。启动顺序有讲究。第一步先启动MySQL把schema.sql和data.sql导入建库第二步启动Redis如果用到登录Token缓存第三步启动后端Spring Boot项目看到Started VehicleApplication启动完成第四步在前端目录执行npm install安装依赖然后npm run serve启动前端开发服务。如果npm install下载慢或者卡住可以设置npm国内镜像源速度会好很多。执行命令npm config set registry https://registry.npmmirror.com。注意安装依赖时尽量使用package-lock.json固定版本避免团队同学装了不同依赖版本代码行为不一致。5.2 常见报错速查表做项目过程中一定会遇到各种报错我把高频问题整理成一张速查表方便你排查时直接对照。现象原因解决办法后端启动报端口占用8080端口被其他程序占用修改application.yml里的server.port或杀掉占用进程数据库连接失败MySQL密码错误或库名不对检查url的数据库名和username/password是否匹配中文存入数据库变成问号数据库、连接URL字符集不一致建库时指定utf8mb4连接URL加characterEncodingutf8前端请求接口CORS报错前后端端口不同浏览器跨域拦截后端配置CORS过滤器或前端用Vite代理转发MyBatis提示Invalid bound statementMapper接口和XML的namespace或方法名不匹配检查XML文件路径、namespace、方法名是否严格一致XML文件没被打包进targetMaven默认没有把src/main/java下的XML资源复制在pom.xml里增加resources配置把mapper/*.xml引入前端打包后路由刷新404使用了history模式但Nginx未配置重写Nginx配置try_files $uri $uri/ /index.html;车辆状态查询不到数据SQL里状态字段类型问题或前端传了字符串检查MyBatis参数是否加上Param状态比较用.toString()还记得我前面说的MyBatis单数字字符比较问题吗如果你遇到“状态明明传了1查询结果却是空”优先怀疑是不是OGNL表达式比较类型不一致。这种报错一般不会直接抛异常只会让条件失效查出来的数据变多或变少最难排查。5.3 调试与日志小技巧排查后端问题有个非常实用的技巧把MyBatis的SQL打印开起来。配置了log-impl: org.apache.ibatis.logging.stdout.StdOutImpl后每次执行SQL控制台会输出查询语句和所有参数值。我通常不会全项目打开而是只给需要排查的Mapper目录打开比如logging: level: com.example.vehicle.mapper: debug这样日志量可控SQL眼不乱。但要注意打印SQL时会暴露参数生产环境不建议一直开着最好只在测试环境开启。另外Spring Boot启动后如果数据一直不对可以先用Postman或Apifox单独调接口不要总是从前端页面点。接口返回的JSON里能看到真实数据定位问题比看浏览器控制台更直接。后端接口调试都通了再回到前端查渲染和数据绑定问题这是一个很高效的排查路线。6. 项目还能怎么扩展6.1 加上Redis缓存和异步导出基础版本跑通以后功能扩展方向还挺多的。第一个值得加的是Redis缓存。当前车辆状态、最新里程这种热点数据每次刷新列表都要查数据库字段量不大但访问频率高。可以把车辆状态先缓存到Redis过期时间设成1分钟列表页查询直接读缓存减少数据库压力。做审批操作时再更新缓存保证状态最终一致。还有一个方向是异步导入导出。比如管理员要把所有车辆信息导出成Excel上万条数据同步导出会让接口阻塞很久。可以改成前端发起导出请求后端用线程池执行导出任务导出完成后把文件路径放到Redis前端轮询查询状态任务完成后自动下载。这个问题正好也关联到Java里多线程“等待所有子任务完成”的场景可以用CompletableFuture.allOf来编排导出任务写起来比future.get方法简洁很多。6.2 接入车辆定位和视频监控如果项目想往企业版方向做可以在车辆信息里加入经纬度字段接入第三方地图API在管理后台实时展示车辆位置。车载硬件把GPS坐标上报到后端后端入库后通过WebSocket推送给前端车辆位置就能在地图上动起来。视频监控也是一个实用方向。现在很多行车记录仪或车载摄像头能输出HLS视频流也就是m3u8格式的地址。前端可以在车辆详情页嵌入vue-video-player传一个m3u8播放地址就能在浏览器里直接预览车辆周边画面。当然前提是视频流地址已经做了鉴权否则直接把流地址暴露出去会被人恶意盗看。做这部分时后端可以把播放地址请求参数加签名和时间戳生成临时有效的播放链接。单个车辆管理系统做完后再加这些功能既能练习SSE推送、WebSocket、异步任务这些高并发技术也让项目从“CRUD管理系统”变成更完整的可视化监控平台后续写进简历的含金量会明显不一样。做了几轮之后我自己最深的体会是这类管理系统不怕功能简单怕的是表结构乱、状态一改就蹦。真正值得花时间打磨的是那些不起眼的边界处理审核前的状态校验、车辆状态的联动更新、MyBatis动态SQL的条件判断。把这些细节一个个抠稳系统哪怕功能不多用起来也会非常顺手。最后再分享一个小技巧开发时尽量先用自己的数据库账号建一套测试数据多模拟几个并发场景比如两个人同时申请同一台车只有把异常流程都测过一遍交出去的系统才不算埋雷。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →