☰
云租车平台开发复盘:Spring Boot+Vue全栈实战与核心设计
2026/9/28 8:18:50 网站建设 项目流程

做这个云租车平台项目之前,我一直觉得租车业务无非就是“车辆表、订单表、用户表”三张表的事,等真正动手把基于Java和Vue的云租车平台系统完整跑通,才发现这里面的坑远比自己预想的多。这篇内容我想把整套项目的开发过程、源码结构、数据库设计和联调部署经验完整复盘一遍,给准备做类似管理系统、毕业设计,或者想接外包项目的同学一个可以直接参考的路线,少走弯路。

整套项目我采用的是Spring Boot作为后端基础框架,前端用Vue全家桶,数据库选用MySQL,配合Redis做缓存和登录状态管理,最终交付形式包括完整源码、建表SQL脚本和一套开发文档。下面按照我做这个项目的实际推进顺序,把每个环节为什么这么设计、具体怎么落地、联调时踩了哪些坑,一条条讲清楚。

1. 从租车订单到车辆库存:云租车平台的需求拆解与技术选型

1.1 先把业务边界划清楚

很多人在做租车平台时一上来就画一大堆功能,其实租车业务的核心闭环非常集中:用户浏览车辆、选择用车时间和门店、提交订单、支付押金或租金、到店取车、用车、还车、结算。围绕这个闭环,系统至少要拆成用户端和管理端两块。

用户端重点解决“找车”和“下单”的效率问题,核心功能包括车辆列表展示、按品牌/座位数/日租金筛选、车型详情、门店信息、下单流程、订单状态查看、取消订单、在线支付(或模拟支付)等。管理端则是运营人员使用的后台,重点解决车辆管理、订单审核、还车确认、门店管理和用户管理。

做这个项目的时候我给自己定了一条原则:先把业务主链路做通,再补管理端细节。所以第一版只保留了最基本的角色划分——普通用户和管理员,没有引入复杂的RBAC权限模型,而是用一个简单的角色字段加拦截器控制页面访问。后面如果你需要扩展成多角色系统,再引入Spring Security也不迟。

1.2 为什么是Spring Boot加Vue,而不是别的组合

技术选型时我对比过几套方案:一是PHP加原生HTML,优点是上手快,但代码维护成本高,前后端耦合严重;二是纯Java Servlet加JSP,能跑但页面体验很一般;三是Spring Boot加Vue,这也是目前企业级项目里最常见的组合之一。

后端选择Spring Boot,核心原因是它把配置、依赖和部署都做了大量简化。我只需要引入spring-boot-starter-web、spring-boot-starter-data-jpa或MyBatis-Plus相关的依赖,就能快速搭起REST接口。再加上Spring Boot自带的嵌入式Tomcat,最后打成jar包就能直接跑,部署非常省事。对于云租车这种带订单、带金额、带状态的业务系统来说,Java在事务处理和并发控制上的成熟方案也比脚本语言更稳。

前端选择Vue,是因为页面上有大量“状态切换”的场景,比如订单从待支付、已支付到用车中的状态变化,Vue的响应式数据绑定可以让我少写大量DOM操作。配合Vue Router做页面跳转,Vuex或Pinia管理用户登录信息,整体开发体验很顺畅。

1.3 项目总体架构如何组织

我采用的是前后端分离结构,但为了控制项目复杂度,没有拆成多个工程,而是分成两个子项目:

  • cloud-car-backend:Spring Boot后端工程
  • cloud-car-frontend:Vue前端工程(用户端和管理后台合在一起,靠路由和角色字段区分)

后端按常见的三层结构组织:controller层接收前端请求,service层写业务逻辑,mapper层对接MySQL。前端则按“页面”划分模块,比如views/user下面的CarList.vue、CarDetail.vue、OrderConfirm.vue,views/admin下面的CarManage.vue、OrderManage.vue等。

数据库方面我用的是MySQL 8.0,字符集统一utf8mb4,表结构通过SQL脚本初始化。项目里同时放了一份初始化脚本和一份测试数据脚本,方便别人拿到源码后直接跑起来看效果。

2. 数据库设计:租车业务的订单状态机与车辆库存扣减

2.1 核心表结构设计

数据库是整个云租车平台最值得花时间设计的地方。我最终设计了7张核心表:用户表、车辆表、门店表、订单表、订单明细表(实际上和订单表可以合并,我合并了)、还车记录表、公告表。下面列出最关键的三张表字段设计思路。

用户表(sys_user)主要字段包括id、username、password、phone、role(0表示普通用户,1表示管理员)、create_time。密码存储时不做明文,我用的是BCrypt加密,这是Spring Security里自带的一种加密方式,安全性比MD5高很多。

车辆表(car)主要字段包括id、car_name、brand、seats、gearbox(自动/手动)、daily_price、deposit(押金)、store_id(所属门店)、car_status(0可租、1已出租、2维修中)、image_url、car_desc。这里最关键的是car_status字段,它决定了车辆在列表页能不能被搜索到。

订单表(order)字段要稍微细一点,包括id、order_no、user_id、car_id、store_id、start_date、end_date、total_amount、deposit_amount、status(0待支付、1已支付待取车、2用车中、3已还车、4已取消、5异常)、create_time、pay_time、return_time。订单状态流转是整个系统的灵魂,后面我会专门展开。

门店表(store)比较简单,就是id、store_name、address、phone、business_hours。第一次设计时我忽略了门店表,认为车辆不需要绑定门店,后来发现如果不分门店,用户根本不知道去哪里取车还车,所以还是老老实实加上了。

2.2 订单状态机到底该怎么定义

订单状态是租车业务里最容易出错的地方。我一开始只设计了待支付、已支付、已完成三个状态,实际运营逻辑根本撑不住。后来我重新梳理了一个可用的状态机:

  • 待支付:用户提交订单后未支付,此状态下车辆需要“预占库存”,但不真正扣减。
  • 已支付待取车:用户完成支付,车辆状态变为已出租,等待用户到店取车。
  • 用车中:用户取车后,订单进入用车中,此时车辆不可被其他用户租用。
  • 已还车:用户归还车辆,车辆恢复可租状态,订单完成。
  • 已取消:用户在支付前取消,或超时未支付系统自动取消。
  • 异常:支付成功但未取车、超时未还车等特殊情况,需要人工介入。

这个状态机在代码里体现为几个固定的流转路径:待支付可以到已支付,也可以到已取消;已支付可以到用车中,也可以申请取消(但要走人工审核);用车中只能到已还车或异常。写接口时我强制校验状态流转的合法性,比如一个已还车的订单不能被再次支付,这可以在很大程度上防止脏数据。

2.3 并发重复下单与库存扣减问题

云租车系统的车辆只有一辆,但同一时间可能有很多人同时看中。第一次写下单接口时我用的是“先查车辆状态,是0就改成1,再插入订单”的方式,后来用JMeter模拟20个并发请求,发现超卖了——同一辆车被多个订单同时占用。

问题的根因在于查询和更新之间存在时间差,两个请求同时读到car_status=0,然后都往下执行。解决办法有两个层面。一是用数据库层面的幂等约束,在订单表里对order_no做唯一索引,同时在下单事务里使用“UPDATE car SET car_status=1 WHERE id=? AND car_status=0”这种带条件的更新语句,如果影响行数为0,说明车辆已被占用,直接抛出“车辆已被抢租”的提示。二是引入Redis分布式锁,但考虑到当前项目是单机部署,带条件的UPDATE其实已经够用。

2.4 SQL脚本和初始化数据要注意什么

提供的数据库脚本里我放了两部分内容:一是建表语句,二是测试数据。测试数据很关键,很多人拿到项目跑起来发现页面是空的,往往就是因为没有导入测试数据。我准备的测试数据包括10辆车、3个门店、1个管理员账号和几个普通用户账号。管理员账号我是手动改的BCrypt密文,而不是明文,这样登录接口才能真正跑通。

另外,所有涉及金额的表字段都用DECIMAL(10,2),不要用FLOAT或DOUBLE,否则计算金额时会出现0.1加0.2不等于0.3这类浮点精度问题。

3. 后端实现:Java接口设计中的关键流程

3.1 后端工程结构和依赖选取

后端工程我按功能模块分包,而不是按技术层次堆大杂烩。具体目录是com.cloud.car下分成controller、service、mapper、entity、config、common几个包。entity里是数据库表对应的实体类,service里是接口加impl实现,controller只负责接参数和返回结果。

依赖方面我只保留了核心的几个:spring-boot-starter-web、mybatis-plus-boot-starter、mysql-connector-java、lombok、spring-boot-starter-validation、jjwt(JWT生成与解析)。没有引入Spring Security全家桶,原因是我只需要登录校验和接口鉴权,自己写拦截器加JWT反而更直观。

3.2 车辆查询接口:多条件筛选如何实现

车辆列表页需要支持按品牌、座位数、日租金区间、档位类型筛选,同时要考虑“当前时间段可租”这个条件。最直接的写法是在CarMapper里写一个带动态SQL的查询方法,用MyBatis-Plus的LambdaQueryWrapper来做条件拼接。

核心逻辑是这样:如果前端传了brand,就加eq条件;传了minPrice和maxPrice就加between;传了startDate和endDate,则需要关联订单表,排除那些在时间段内已出租的车辆。这里有个细节,判断车辆是否可租不是简单看car_status,而是要判断“该车辆是否已有订单覆盖用户选择的租期”。我用了一段子查询:select count(*) from order where car_id=? and status in (1,2) and start_date < ? and end_date > ?,如果数量大于0,说明该车辆在这个时间段被占用,需要过滤掉。

3.3 下单接口:事务、库存与金额计算

下单接口是整套系统里事务性最强的接口。用户提交订单时,前端会传carId、startDate、endDate、取车门店,后端要完成四件事:根据车辆日租金和天数计算总金额、校验车辆在时间段内是否可租、创建订单记录、将车辆状态改为已出租。这四步必须在一个事务里,其中任何一步失败都要全部回滚。

我在service实现类上加了@Transactional注解,然后手动写了一段租期冲突校验。这里有个容易被忽略的点:当天取车当天还车,时间差是多少?我的算法是把开始日期和结束日期转换成LocalDate,计算天数用endDate.toEpochDay() - startDate.toEpochDay(),如果小于等于0直接报错。金额计算则用ChronoUnit.DAYS计算天数再乘以dailyPrice,避免直接用时间戳相除造成的误差。

订单号生成也值得一提。不能用数据库自增id当订单号,那样太容易被猜到,我采用“yyyyMMddHHmmss + 4位随机数”的方式,虽然极端情况下可能重复,但对这个项目足够,更重要的是order_no字段上建了唯一索引,万一真重复会直接报错而不是产生脏数据。

3.4 支付回调与状态流转的实现

因为是项目演示,我没有对接真实的支付宝或微信支付,而是做了一个模拟支付接口:前端点“模拟支付”后,后端直接把订单状态从“待支付”改为“已支付待取车”,同时更新车辆状态和支付时间。真正商用时要对接支付平台,但状态机设计是通用的,支付回调进来后调用的其实也是同一个状态更新逻辑。

状态更新我封装了一个方法changeOrderStatus(orderId, targetStatus),里面先查订单当前状态,然后和允许的流转路径比对。比如从待支付改已支付是允许的,从已支付改回待支付是不允许的。这样即使前端被绕过,接口层面也能挡住非法状态修改。

3.5 登录鉴权:拦截器加JWT

用户登录后,后端返回一个Token,前端存在localStorage里,每次请求在Header里带上Authorization。我写了一个LoginInterceptor,通过HandlerInterceptor实现preHandle方法,在方法里解析Token、校验有效期、从Redis里查用户会话。如果Token无效就返回401状态码,前端axios统一拦截后跳回登录页。

这里有个实际教训:一开始我把用户的角色信息直接写进Token里,后来发现修改权限要重新签发Token,很麻烦。改成从Token只解析userId,再通过userId查询数据库拿用户信息,虽然多了一次查询,但对这个体量的项目完全够用。

4. 前端实现:Vue页面如何串起“选车—下单—支付—还车”

4.1 Vue工程结构和路由设计

前端工程我用Vue CLI创建,Vue版本是2.6,虽然没有用最新的Vue 3,但整个项目跑下来非常稳定。如果你现在新开项目,直接用Vite加Vue 3也完全可以,思路是一样的。

路由设计我分成两组:用户端和管理端。用户端的路由包括首页、车辆列表、车辆详情、下单确认、订单列表、订单详情、登录注册、个人中心;管理端路由包括车辆管理、订单管理、门店管理、用户管理。访问管理端页面时,我在路由守卫里判断当前用户的role,如果不是管理员直接跳转到首页并给出提示。

4.2 首页车辆列表与筛选器

首页就是车辆列表页,我在顶部的搜索栏放了品牌下拉框、座位数下拉框、租金区间输入和租期选择器。这里有一个体验上的细节:租期选择不是一个固定的开始日期和结束日期,而是“取车日期”和“还车日期”,用户选完后列表会自动过滤掉在这个时间段内不可租的车辆。

前端筛选器只是负责把参数传给后端,真正的过滤逻辑在后端接口里。组件数据用data里的carList数组承载,通过axios调用/api/car/list接口,拿到返回值后重新渲染。分页我采用的是简单的前端分页,后端返回全量列表,对几百条数据完全够用。如果数据量大了,再改成后端分页也不迟。

4.3 下单确认与订单详情页

用户点击“立即租车”后进入下单确认页,这个页面要展示车辆信息、租期、单价、总金额、押金和取车门店。用户点“提交订单”后向后端发送POST请求,成功后跳转到订单详情页,页面上显示订单号和状态,并提供“模拟支付”按钮。

订单详情页的状态展示是我做得比较细的地方。我用一个tag组件显示订单状态,并用一个步骤条展示整个流程:待支付、已支付待取车、用车中、已还车。当前状态高亮,之后的状态置灰。步骤条的当前值我用了computed计算属性,根据order.status动态映射。

4.4 管理后台页面

管理后台我做得比较精简,只保留车辆管理和订单管理两个核心页面。车辆管理页面是一张表格,每辆车后面提供“编辑”“下架”“上架”按钮。下架操作在后端做的是把car_status改成2,不代表删除记录,这样能保留历史数据。

订单管理页面主要有订单列表、订单详情查看、还车确认操作。还车确认这个功能要特别说一下,运营人员在用户归还车辆后,点击“确认还车”,后端会做两件事:把订单状态从“用车中”改为“已还车”,同时把车辆状态恢复为“可租”。还车时还可以录入还车里程和车损备注,这些字段我放在还车记录表里,避免污染订单主表。

4.5 axios封装与跨域配置

前端请求后端接口,最烦人的就是跨域问题。开发环境下我在Vue的vue.config.js里配置了devServer.proxy,把/api前缀的请求代理到http://localhost:8080,这样浏览器不会产生跨域报错。生产环境则是前端打包后由Nginx托管,Nginx配置location /api/ { proxy_pass http://localhost:8080; },同样解决跨域问题。

axios封装我单独放在utils/request.js里,主要做了三件事:统一加baseURL、请求拦截器里加Token、响应拦截器里处理401和业务错误码。这样在业务页面里只需要调用request.get('/car/list'),不用每个页面都写Token逻辑。

5. 联调、部署与典型踩坑记录

5.1 本地环境准备

我把项目交付给别人时,一定会先写清楚环境要求。后端需要JDK 1.8或以上、Maven 3.6、MySQL 8.0;前端需要Node.js 14以上、npm或yarn。启动顺序是:先执行SQL脚本初始化数据库,然后启动后端(直接运行CloudCarApplication),再启动前端(npm install然后npm run serve)。

如果发现端口冲突,后端在application.yml里改server.port,前端在vue.config.js里同步修改proxy的target地址。这里有个很多人忽略的问题:application.yml里的数据库密码不要用明文写死到代码里提交,可以通过环境变量注入。虽然项目演示阶段图省事写了明文,但文档里我会专门提醒这一点。

5.2 前端页面能打开但接口报404的排查链路

联调时遇到最典型的问题是:前端能打开、静态资源也正常,但接口全部404。第一次遇到这个情况,我先看了浏览器Network面板,发现请求地址是http://localhost:8081/api/car/list,而后端接口地址是/api/car/list,看起来没问题。接着看后端控制台,发现没有任何日志输出。

排查链路是这样的:先用curl -X GET http://localhost:8081/api/car/list手动请求接口,返回404。然后查看后端启动日志,发现端口是8081,确实启动了。最后检查Controller的RequestMapping,发现类上写的是@RestController,但方法上只标了@GetMapping("/list"),类上缺少@RequestMapping("/api/car")。加上之后,接口就通了。这是个很低级但很常见的错误,尤其是多模块项目里复制粘贴代码时特别容易丢注解。

5.3 日期数据差8小时的坑

订单时间在数据库里存的是2024-01-01 08:00:00,前端显示出来却变成2024-01-01 00:00:00,整整差了8小时。这个问题一看就是时区导致的。MySQL驱动连接串里如果没有指定serverTimezone=Asia/Shanghai,默认会使用UTC,而本地系统是东八区,于是出现时间偏移。

解决办法是在JDBC连接串里加serverTimezone=Asia/Shanghai,并在MySQL端执行set global time_zone = '+8:00'。同时后端接收前端传的日期参数时,我用@DateTimeFormat(pattern = "yyyy-MM-dd")解析,传输过程保持字符串,入库时再转成LocalDateTime,这样能最大限度避免时区干扰。

5.4 并发下单超卖:从复现到修复

前面提过超卖问题,这里把完整复现过程写一下。我用JMeter建了20个线程同时请求下单接口,参数都是同一辆车、同一个时间段,结果数据库里出现了两条租期重叠的有效订单,车辆状态也确实被改成了已出租。

定位过程分三步。第一步打印下单接口的SQL日志,发现每个线程都执行了select查询,全部查到的car_status都是0。第二步确认了根因:select和update之间不是原子操作。第三步修改方案:把“查询车辆状态并修改”合并成一条带条件的update语句,同时在事务开始前用SELECT ... FOR UPDATE给车辆记录加锁。最终效果是20个并发请求,只有1个成功下单,其余全部提示车辆已被占用。

这个问题的通用经验是:任何涉及“先查后改”的业务,都要考虑并发场景。用库存、余票、车位这类资源类数据,不要天真地相信查询结果。

5.5 部署上线流程

本地跑通后,部署上线我还踩过一些小坑。后端打包用mvn clean package,如果直接用java -jar启动,要知道jar包里的application.yml是同目录外部配置优先,还是内部配置优先。我建议外部配置文件放jar包同目录的config文件夹下,这样改配置不用重新打包。

前端构建用npm run build,产物在dist目录。把dist目录里的文件放到Nginx的html目录,然后Nginx配置里加一段location /api代理。还有一个小细节:前端用了BrowserRouter(也就是history模式路由),Nginx需要配置try_files $uri $uri/ /index.html,否则刷新页面会404。Vue Router如果用hash模式倒不用这个配置,但地址会带#号,不太好看,所以我选了history模式并补上了Nginx配置。

6. 源码结构、文档编写与二次开发建议

6.1 源码目录怎么组织更容易看懂

交付源码时,我把目录整理成了下面这个样子:

  • backend:Spring Boot后端工程
  • frontend:Vue前端工程
  • database:SQL脚本目录,包含init.sql和data.sql
  • docs:项目文档目录
  • README.md:项目说明、启动步骤、账号信息

后端代码里,我特意在serviceimpl里写了关键注释,尤其是订单状态流转和库存扣减的地方。很多开源项目代码写得很牛,但注释几乎为零,接手的人只能靠猜。做项目交付时,代码注释比技术本身更能体现专业度。

6.2 项目文档应该包含哪些内容

一套完整的项目文档,至少要有五块:一是项目简介和功能清单;二是环境准备和技术栈说明;三是数据库设计说明,包括ER图和表字段说明;四是部署运行文档,从克隆代码到启动成功每一步都要写清楚;五是接口文档,列出主要接口的请求方式、参数、返回示例。

接口文档我用的是一份Markdown文档,没有上Swagger。如果是一个人开发或课程设计项目,用Swagger会带来额外配置量,但如果你打算把这个项目放到GitHub上给大家用,集成Swagger其实是更好的选择,可以自动生成在线接口文档,别人调试起来会方便很多。

6.3 如果继续扩展,这个项目还能加什么

这个云租车平台目前已经能完整跑通租车闭环,但如果要商用或做更完整的毕设,还可以从几个方向扩展。一是增加会员体系和积分抵扣租金;二是接入真实的支付渠道,比如支付宝当面付或微信Native支付;三是增加地图API展示门店位置和车辆定位;四是引入消息队列处理订单超时未支付的自动取消。

从架构上看,这几个扩展点都不会破坏现有代码。订单超时未支付可以用Redis过期键加监听来实现,新增一个OrderTimeoutProcessor类就行。地图功能可以放在Vue组件里,通过引入第三方地图JavaScript SDK实现,后端只需要提供门店经纬度字段即可。

6.4 我对这套项目的几个实际使用心得

项目做完跑通之后,我最深的体会是:云租车这类业务系统,真正的技术难点往往不在某个单点功能有多炫,而在状态流转的严谨性和并发场景下的数据一致性。像订单状态机、库存扣减、租期冲突校验这些看似不起眼的设计,恰恰是决定系统能不能实际投入使用的关键。

另外一点心得是:做项目一定要留下完整的数据库脚本和清晰的启动文档。很多人拿到源码第一步不是看代码,而是先尝试把项目跑起来。如果数据库脚本缺失、启动步骤含糊,再好的功能代码也会劝退一大批人。我在database目录里放了两个SQL文件,README里从零开始写清楚了启动流程,这样无论是我自己二次开发,还是别人拿去做参考,都能少走弯路。

如果你也想基于Java和Vue做一套类似的管理系统,我建议先不要急着写代码,花两天时间把表结构和状态流转梳理清楚。表结构一乱,后面所有的接口逻辑都会跟着乱;状态流转不严谨,线上就会出现订单和车辆对不上账的情况。把这两件基础工作做扎实,整个项目开发过程会顺畅很多。

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

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

立即咨询