恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

Spring Boot租车管理系统:从状态流转到并发控制的实践

  • 首页
  • 资讯中心
  • /
  • Spring Boot租车管理系统:从状态流转到并发控制的实践

相关资讯

CDH5.14.0集群环境准备:从主机规划到系统调优完整指南 2026/10/16 10:57:57
ZCC485高速低功耗RS485/RS422收发芯片设计与调试实战 2026/10/16 10:57:57
C# WinForms 自绘仪表控件:圆表、直表、旋钮、开关、指示灯从零实现与避坑指南 2026/10/16 10:57:57

最新资讯

贡献法与单调栈:O(n)求解所有子数组最小值之和
图像锐化与边缘检测:从拉普拉斯算子到Canny的实战调参指南
CKEditor 4 Emoji 下拉面板搜索框焦点轮廓(Outline)样式解析:从 Manual Test 到源码实现
Mac上安装Claude Code完整教程:从环境检查到常见报错
GPU容器化与AI训练平台实践:从Kubernetes调度到资源治理
Flume快速入门:安装配置与监控端口实例详解

今日推荐

Java八种基本类型深度解析:从内存布局到实战选型
基础:常用快捷键——IDEA、VS Code、PyCharm 与 Linux 终端效率速查
解决ultraedit下verilog .v文件中文注释乱码:把编码配置改到TaoToken辅助排查

本周热门

UE动画修改实战:从资产编辑到重定向与蒙太奇驱动
统计随机数生成器攻击下的KLJN安全密钥交换协议Matlab仿真
政务API安全治理:资产测绘、低代码编排与行标对标实践

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

Spring Boot租车管理系统:从状态流转到并发控制的实践

发布时间:2026/10/16 11:02:57
Spring Boot租车管理系统:从状态流转到并发控制的实践 做租车管理系统这个项目是我接手过的比较典型的业务型Spring Boot应用之一。一开始拿到需求的时候觉得无非就是CRUD但真正把用户、车辆、订单、计费这些模块串起来之后才发现里面的状态流转、并发控制、金额精度这些问题每一个都值得单独拿出来说。这篇文章就基于这套系统的完整开发过程把从需求拆解到表结构设计再到核心逻辑实现和问题排查的整个链路梳理一遍希望能给正在做类似管理系统的朋友一些可落地的参考。如果你正准备用Spring Boot做一套带前后台的管理系统或者正在纠结租车订单的状态怎么设计、计费逻辑怎么写、并发下订单怕不怕超卖那这篇内容应该能帮你省不少试错时间。我尽量把每一步的“为什么这么做”也讲清楚而不是只贴代码。1. 项目概述租车系统到底在管什么1.1 业务场景与核心需求租车管理系统的本质是围绕“车”和“人”两个核心对象管理一段完整的租赁生命周期。和电商系统相比它最大的特点是订单状态更复杂用户选车、下单、取车、用车、还车、结算每一个环节都会改变车辆和订单的状态而且这些状态之间有严格的先后关系。拆开来看系统需要解决的核心问题有三个车辆资源的管理车在库、车已租、车在保养、车下架这些状态必须实时准确。还车之后车辆要能自动恢复到可租状态否则前台展示的车辆和实际库存就对不上。订单流程的闭环从用户提交订单开始到管理员审核、用户取车、最终还车结算整条链路的每一步都要有迹可循。费用结算的准确性租金不是简单的一口价会涉及租赁天数、超时费用、押金扣除等逻辑。这里哪怕差一分钱都容易引起用户投诉。除了这些核心业务系统还要兼顾用户管理、车辆分类、公告发布、统计报表等辅助功能。从实际开发的角度看这套系统很适合作为Spring Boot前后端分离项目的练手案例因为它覆盖了权限拦截、文件上传、分页查询、复杂状态流转这些常见开发场景。1.2 功能清单梳理在做技术选型和表结构设计之前我习惯先输出一份功能清单把不同角色能做什么事情画清楚。这套系统我按照使用角色做了拆分角色核心功能普通用户注册登录、浏览车辆、按条件筛选、提交租车订单、查看订单状态、还车操作系统管理员车辆增删改查、车辆上下架、订单审核、订单列表管理、用户管理、公告管理系统后台首页数据统计车辆总数、订单总数、营收概览、分类管理这里有个容易被忽略的点租车订单的提交方式。我在这套系统里设计为“用户提交订单后由管理员审核”而不是用户下单后直接扣款生成有效订单。这样的好处是贴近真实租车场景——线下门店还需要确认车辆状况、用户资质审核环节可以避免车辆已经被预约但系统状态没来得及更新的问题。2. 技术选型与项目架构为什么是Spring Boot2.1 技术选型的几个考量租车管理系统在技术难度上属于中等偏下的Web应用但它对开发效率、代码可维护性的要求并不低。我在选型时主要考虑了下面几点开发效率Spring Boot的自动配置机制能省掉大量XML配置一套标准的三层架构很快就能搭起来。生态成熟度Spring家族在权限Spring Security / Sa-Token、持久层MyBatis-Plus / Spring Data JPA、缓存Redis等方向都有非常成熟的方案遇到问题能搜到的资料也多。部署便捷性Spring Boot内置Tomcat打包成单个JAR文件就能跑起来对本地部署和小型项目交付特别友好。这套系统最终的技术栈组合是Spring Boot作为基础框架MyBatis-Plus作为持久层框架MySQL作为业务数据库Redis用于缓存热点车辆数据和用户会话前端管理后台则采用Thymeleaf模板引擎渲染。2.2 项目分层结构与包规划很多新手拿到这类项目会比较懵不知道Controller、Service、Mapper之间该怎么划分职责。我的做法是严格遵循经典三层架构同时按照业务模块来组织包结构com.example.carrental ├── controller # 控制层接收前端请求返回JSON或页面 ├── service # 业务逻辑层处理核心业务 │ └── impl ├── mapper # 数据访问层与数据库交互 ├── entity # 实体类与数据库表字段一一对应 ├── dto # 数据传输对象用于前后端数据交互 ├── vo # 视图对象用于页面展示 ├── config # 配置类拦截器、WebMvc配置、跨域配置 ├── common # 通用工具类、统一返回结果封装 └── interceptor # 登录、权限拦截器这里我特别想强调的是Controller和Service的职责边界。在开发过程中经常看到有人把业务逻辑直接写在Controller里这可能是因为“只是调一个Mapper的方法嘛不值得单独建方法”。但在订单流转这种需要多处调用的场景里只要业务逻辑写在Controller一次后续有第二个入口需要同样的逻辑就很容易出现重复代码甚至因为两处逻辑改得不一致而产生数据问题。所以我的习惯是Controller只做参数接收、调用Service、返回结果所有业务规则一律下沉到Service层。2.3 统一返回结果与异常处理前后端对接时最怕的就是每个接口返回的JSON结构都不一样前端解析的时候要写一堆if else。我在这套系统里定义了一个通用的返回结果类所有接口都用它来包装public class Result { private Integer code; // 状态码200表示成功 private String message; // 提示信息 private Object data; // 返回数据 }配合全局异常处理器代码中只需要正常返回数据任何异常都会被统一拦截并包装成Result返回。这样前端只需要判断code大大减少了联调成本。3. 数据库设计与核心表结构3.1 表结构整体规划数据库设计是这个项目里最需要提前规划的环节。如果建表时状态字段、金额字段设计得不到位后面写业务代码时会处处受制。这套系统的核心表有六张用户表、车辆表、车辆分类表、订单表、公告表、管理员表。表名用途说明user存储用户账号、密码、手机号、身份证号等信息car存储车辆基本信息、车牌号、日租金、押金、状态car_category车辆分类轿车、SUV、商务车等car_order订单主表记录用户下单、取还车、费用信息notice系统公告信息admin后台管理员账号3.2 车辆表的字段设计要点车辆表是信息含量最大的一张表我的设计思路是基础属性品牌、车型、颜色 业务属性车牌号、日租金、押金 状态属性状态字段。其中状态字段我用了tinyint类型通过数字标记可租、已租、保养中、已下架等状态。CREATE TABLE car ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(100) DEFAULT NULL COMMENT 车辆名称, brand varchar(50) DEFAULT NULL COMMENT 品牌, category_id int(11) DEFAULT NULL COMMENT 分类ID, plate_number varchar(20) DEFAULT NULL COMMENT 车牌号, color varchar(20) DEFAULT NULL COMMENT 颜色, daily_rent decimal(10,2) DEFAULT NULL COMMENT 日租金, deposit decimal(10,2) DEFAULT NULL COMMENT 押金, status tinyint(1) DEFAULT 0 COMMENT 状态0可租 1已租 2保养中, picture varchar(255) DEFAULT NULL COMMENT 车辆图片, create_time datetime DEFAULT NULL, update_time datetime DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里的几个设计细节值得说一下金额字段统一用decimal我见过有人用double存金额最后计算差个0.1的案例太多了。decimal(10,2)能覆盖绝大多数租金和押金范围。图片字段存URL路径而不是二进制文件上传的图片保存到服务器本地或对象存储数据库只存访问路径。这样数据库的体积不会膨胀查询性能也好。3.3 订单表的设计与状态流转订单表是整个系统的核心它的字段设计直接决定了后续计费和状态管理是否顺畅。我的订单表关键字段包括订单编号、用户ID、车辆ID、租车开始时间、预计还车时间、实际还车时间、总费用、押金、订单状态、创建时间。CREATE TABLE car_order ( id int(11) NOT NULL AUTO_INCREMENT, order_no varchar(32) DEFAULT NULL COMMENT 订单编号, user_id int(11) DEFAULT NULL COMMENT 用户ID, car_id int(11) DEFAULT NULL COMMENT 车辆ID, start_time datetime DEFAULT NULL COMMENT 取车时间, end_time datetime DEFAULT NULL COMMENT 预计还车时间, return_time datetime DEFAULT NULL COMMENT 实际还车时间, total_price decimal(10,2) DEFAULT NULL COMMENT 总费用, deposit decimal(10,2) DEFAULT NULL COMMENT 押金, status tinyint(1) DEFAULT 0 COMMENT 状态0待审核 1已通过 2使用中 3已完成 4已取消, create_time datetime DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;订单状态我设计了五个离散值待审核0、已通过1、使用中2、已完成3、已取消4。这里需要特别说明状态流转的方向我在开发时遇到过一个需求变更是“用户取车后还要再确认一下才能进入使用中”这导致状态层多了一个分支。但最终确认的流程是提交订单 - 管理员审核通过同时车辆状态改为已租 - 用户还车订单变为已完成车辆状态重置为可租。这个流程比较贴合线下租车门店的运作习惯。4. 核心功能实现从登录鉴权到还车结算4.1 登录鉴权与拦截器实现租车系统的登录鉴权没有用Spring Security那么重的框架而是采用了拦截器 Session的方式。因为系统角色只有用户和管理员两种权限模型比较简单用Session配合拦截器已经足够。拦截器实现的核心逻辑是从Session中获取当前登录用户如果没有获取到就重定向到登录页并放行登录、注册、车辆列表这些公开接口。public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录、注册等公开接口 String uri request.getRequestURI(); if (uri.contains(/login) || uri.contains(/register) || uri.contains(/car/list)) { return true; } Object user request.getSession().getAttribute(user); if (user null) { response.sendRedirect(/login); return false; } return true; } }这里有一个实际踩坑的经验拦截器放行静态资源。第一次写完往浏览器上一跑发现登录页的CSS、JS全挂了控制台全是404。原因很简单——拦截器把所有请求都拦了包括/css/**、/js/**这些静态资源路径。解决办法是在WebMvcConfigurer里添加静态资源放行registry.addInterceptor(new LoginInterceptor()) .addPathPatterns(/**) .excludePathPatterns(/css/**, /js/**, /images/**, /error);4.2 车辆列表与条件检索车辆列表页属于典型的高频查询接口用户打开首页就会请求。为了提高响应速度我在Service层做了两个优化一是按条件拼接SQL时优先使用索引字段二是把热点车辆数据缓存到Redis。用户端筛选条件通常包括车辆分类、品牌、日租金区间。使用MyBatis-Plus的QueryWrapper可以很简洁地表达动态条件public ListCar getAvailableCars(CarQueryDTO dto) { LambdaQueryWrapperCar wrapper new LambdaQueryWrapper(); wrapper.eq(Car::getStatus, 0); // 只看可租车辆 if (StringUtils.hasText(dto.getCategoryId())) { wrapper.eq(Car::getCategoryId, dto.getCategoryId()); } if (StringUtils.hasText(dto.getBrand())) { wrapper.eq(Car::getBrand, dto.getBrand()); } if (dto.getMaxPrice() ! null) { wrapper.le(Car::getDailyRent, dto.getMaxPrice()); } wrapper.orderByDesc(Car::getCreateTime); return carMapper.selectList(wrapper); }提示条件查询里的StringUtils.hasText要记得判断空字符串。前端传参时如果某个筛选条件没有选容易传成空字符串如果直接拼进SQL会导致条件不生效。我在这套系统里统一对入参做了去空格处理。4.3 提交订单与并发控制提交订单是整个系统里并发风险最高的操作。用户点击下单的那一刻如果恰好有两个用户同时预约同一辆车后端的查询和更新动作如果不加控制就可能出现“一辆车被成功租给两个人”的情况。解决思路是在更新车辆状态时加上条件判断利用数据库的行级锁来保证原子性。MyBatis-Plus中可以直接在update时带条件boolean update carMapper.update(null, new LambdaUpdateWrapperCar() .eq(Car::getId, carId) .eq(Car::getStatus, 0) // 只有车辆当前是可租状态才能更新为已租 .set(Car::getStatus, 1)); if (!update) { throw new BusinessException(车辆已被预约请选择其他车辆); }这段代码的关键在于eq(Car::getStatus, 0)这个条件它在数据库层面保证了并发更新时只有一个请求能成功把状态从0改成1。相比在应用层加锁或者用Redis分布式锁这种做法的实现成本最低而且和业务结合得非常自然。下单成功后还需要生成唯一的订单编号。我使用的生成规则是“日期 用户ID 三位随机数”例如20250614123001。如果对唯一性要求更严格可以再加一个全局自增序列但在这个量级的项目里上面的规则已经够用。4.4 还车计费逻辑还车是结算的环节也是整个系统里最容易出bug的地方。计费规则看起来很简单“租金 日租金 x 租赁天数”但一加上超时费就变得有些复杂。我这套系统的计费规则设定如下基础租赁费用按自然日计算如果用户超过预计还车时间还车超时部分按小时收取日租金的十分之一。还车时的后端逻辑可以分解为三步public ReturnResult returnCar(Integer orderId) { CarOrder order orderMapper.selectById(orderId); Car car carMapper.selectById(order.getCarId()); // 1. 计算租赁天数向上取整不足一天按一天计算 long days ChronoUnit.DAYS.between(order.getStartTime(), LocalDateTime.now()); days Math.max(days, 1); // 2. 计算超时费用 LocalDateTime expectEnd order.getEndTime(); BigDecimal basePrice car.getDailyRent().multiply(BigDecimal.valueOf(days)); BigDecimal extraPrice BigDecimal.ZERO; if (LocalDateTime.now().isAfter(expectEnd)) { long overHours ChronoUnit.HOURS.between(expectEnd, LocalDateTime.now()); extraPrice car.getDailyRent() .divide(BigDecimal.valueOf(10), 2, RoundingMode.HALF_UP) .multiply(BigDecimal.valueOf(overHours)); } // 3. 更新订单状态与车辆状态 order.setReturnTime(LocalDateTime.now()); order.setTotalPrice(basePrice.add(extraPrice)); order.setStatus(3); orderMapper.updateById(order); car.setStatus(0); carMapper.updateById(car); return new ReturnResult(basePrice.add(extraPrice)); }这里有个细节ChronoUnit.DAYS.between计算的是两个时间相差的整天数。如果你取车是早上10点当天晚上9点还车逻辑上租了一天但between算出来的结果是0天。所以我用了Math.max(days, 1)来保证至少按一天计算。这是个非常容易漏掉的边界条件测试的时候如果没覆盖到就会算出0元订单。关于金额计算的精度之前提过必须用BigDecimal除法还要显式指定精度和舍入模式。我在这套系统里统一使用RoundingMode.HALF_UP四舍五入避免浮点运算导致的精度丢失。5. 开发中遇到的典型问题与排查实录5.1 时间字段的JSON序列化格式问题这是前后端分离项目里几乎必踩的坑。后端返回的LocalDateTime字段默认序列化出来是一串带T的标准格式比如2025-06-14T12:30:00前端控件却期望2025-06-14 12:30:00导致页面展示直接报错或者显示异常。解决办法是在application.yml里统一配置日期格式spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8需要注意的是time-zone如果不配置默认使用服务器时区。我遇到过服务器是UTC时区导致前端时间差8小时的问题排查了半天才发现是时区配置的问题所以这个配置建议一开始就加上。5.2 车辆状态和订单状态同步问题这个问题的典型场景是管理员在后台把某辆车下架了但用户之前提交的订单还处于“待审核”状态。此时管理员完成审核操作系统发现车辆已经下架就会产生逻辑矛盾。我的处理方式是在审核前增加一道校验如果车辆状态不是可租状态就直接拒绝审核并给出提示“车辆当前不可出租”。这个校验逻辑虽然简单但能避免不少脏数据。类似的校验在还车时也需要做——如果订单还没到“已通过”状态用户是不能执行还车操作的。5.3 分页查询时总数和列表不一致使用MyBatis-Plus的分页插件时我遇到过查询条件写在Page对象上导致统计SQL和列表SQL不一致的情况。具体表现是首页显示数据量是10条但点下一页没数据或者总记录数和实际数据对不上。排查后发现问题在于分页参数生效顺序Page对象要先创建然后传入selectPage方法如果条件拼接发生在分页之后或者多个queryWrapper对象混用就容易出现统计和列表各查各的现象。规范做法是只维护一个LambdaQueryWrapper在同一个wrapper上拼接所有查询条件然后一次性传给selectPage。5.4 数据库连接中断导致的偶发报错项目跑了一段时间后偶尔会出现“Too many connections”或者连接超时的报错。原因是默认的连接池配置不太满足实际使用场景。我在连接池配置里增加了以下参数spring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000 max-lifetime: 1800000这里重点说下max-lifetime它要设置得比数据库的wait_timeout稍短一些否则会出现数据库已经把连接断开了连接池却还在用这个失效连接的场景导致每次请求都报连接异常。我遇到过几次这个问题后才理解了低配版连接池和数据库超时机制之间需要保持一致性。6. 这个项目还可以怎么扩展基础版租车系统跑通之后有不少可以继续做的方向。个人觉得最有价值的几个扩展点引入真正的权限框架目前是拦截器 Session的方式如果后续要支持多级管理员如店长、财务、超级管理员建议换成Sa-Token或Spring Security能省去自己维护权限模型的时间。增加消息通知用户在还车后可以给用户推送结算明细订单审核通过或驳回时也需要状态通知。目前可以通过WebSocket或集成消息推送服务实现站内信功能。接入支付与发票模块线下租车场景下可以补充押金预授权、在线支付租金、电子发票申请等链路这也是最能提升系统完整度的扩展点。升级为微服务版当车辆管理、订单管理、用户中心都需要独立部署和扩容时可以考虑按业务边界拆分成多个服务再配合网关统一入口。不过对于这个体量的项目单体架构完全够用不建议过早微服务化。我在实际项目中体会到学习和开发这类管理系统最有价值的部分不是用了多新的技术而是把“订单状态流转”和“资源并发控制”这类复用性极强的逻辑彻底吃透。这些经验换一个业务场景照样能用比如会议室预约系统、图书馆座位管理系统本质都是“资源——预约——状态流转”的模型。真要说扩展能把这一套思路迁移到更多场景比堆砌一堆新技术框架实在得多。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号