☰
SpringBoot+Vue新能源汽车租赁管理系统:设计与实现全解析
2026/10/10 19:00:08 网站建设 项目流程

开头

做汽车租赁系统,过去大家第一反应就是传统燃油车那一套:车辆档案、订单、押金、违章押金。但这两年新能源汽车租赁的需求涨得非常快,尤其在一线城市,很多人通勤、周末出游都开始租电车。电车租赁和油车租赁看着差不多,实际做起来完全是另一个难度:电池续航要实时跟踪、充电桩要对接、电费和服务费怎么算、车辆还回来之后电量怎么考核、保险费用怎么按车型动态调整,这些全是新问题。

我最近正好完整做了一个“基于SpringBoot+Vue新能源汽车租赁管理系统”的项目,从数据库设计、接口开发到前端页面联调,前前后后迭代了三个版本。这篇文章我就把这个系统的设计思路、核心模块拆解、关键代码实现,以及我踩过的坑,一起整理出来。不管是毕业设计、个人练手项目,还是公司里真要落地一套租赁系统,这份内容都能给你一个比较完整的参考。

再说一下这个系统能干什么:用户端支持注册登录、实名认证、在线选车租车、订单支付、续租还车、充电记录查询;管理端支持车辆管理、订单管理、用户管理、计费规则配置、充电桩管理、数据统计。前后端分离,后端SpringBoot提供REST API,前端Vue负责交互展示,是目前企业级项目中很标准的组合。

1. 项目整体设计与思路拆解

1.1 新能源汽车租赁业务,到底和传统租车差在哪

先说业务建模。很多人拿到这个题目就照搬传统租车系统的表结构,这是第一个大坑。

传统租车系统的核心对象是“车—订单—用户”,计费维度基本就是日租价加超时费。但新能源车租赁多出了几个关键对象:

第一是电池与续航。车辆当前电量、续航里程、充电状态这些数据,会直接影响车辆是否能被租赁。你不可能把一辆只剩30公里续航的车租给要跑100公里的用户,这个规则如果靠人工判断,效率太低,系统必须自动约束。

第二是充电行为。用户租车期间可能在外面充了电,也可能直接开回网点充电,充电费用怎么结算、电量和里程如何核验,都要有单独的记录和计算逻辑。

第三是计费模型复杂化了。新能源租赁除了按天计费,还可以按时长+里程计费,再加电费、服务费、保险费,每一单的费用要根据车型、使用时长、行驶里程、充电记录动态计算。

所以我在设计这个系统时,第一件事就是把业务对象重新梳理了一遍。不是简单建一个vehicle表和order表就完事,而是增加了battery_record(电池记录)、charging_record(充电记录)、price_rule(计费规则)这三个核心维度。

这套设计带来的好处是:后续增加新业务,比如分时租赁、预约充电、电池换电,都有现成的数据基础,不用推翻重来。

1.2 为什么选SpringBoot+Vue,而不是微服务全家桶

技术选型这个问题,几乎每个做毕设或内部系统的开发者都会纠结一下。我最早也考虑过Spring Cloud Alibaba那套微服务方案,把用户、订单、车辆分别拆成独立服务。但后来仔细评估了一下项目规模和团队维护成本,果断放弃了。

原因有三点:

其一,业务复杂度还没到必须拆分的程度。车辆租赁系统虽然模块多,但模块之间的关联极强,订单模块要实时读取车辆状态,车辆模块要更新订单关联数据,拆成微服务之后光是处理分布式事务就够喝一壶的。用单体应用加模块化分包,开发效率反而更高。

其二,SpringBoot的生态足够成熟。Spring Data JPA或者MyBatis-Plus做数据持久层,Spring Security做认证授权,Redis做缓存和分布式锁,这些组件搭配起来,一个小团队两周就能把核心功能跑通。

其三,Vue作为前端框架,上手曲线平缓,组件化开发很适合这种后台管理系统。管理端我用的是Vue3 + Element Plus,用户端用的是Vue3 + Vant,两个端共用一套接口,前后端分离开发,各干各的,互不阻塞。

提示:如果项目后续确实要扩展到多城市、多网点、高并发抢单场景,再考虑微服务化也不迟。前期过度设计,是很多项目烂尾的根源。

1.3 单体架构下的模块划分,怎么分才合理

虽然用了单体应用,但不代表代码可以随便堆。我按照“纵向业务+横向能力”的方式把项目分成了几个模块:

  • system模块:用户、角色、权限、菜单管理,基于Spring Security + JWT实现
  • vehicle模块:车辆档案、车型管理、车辆状态流转
  • order模块:订单创建、支付、改期、取消、还车
  • charging模块:充电桩管理、充电记录、电量换算
  • price模块:计费规则引擎,支持按天/按小时/按里程组合计费
  • statistics模块:运营数据统计,用于管理端大屏展示

每个模块内部按照Controller → Service → Mapper三层结构组织,模块之间通过Service接口调用。这样既保留了单体应用快速开发的优点,又为将来拆分服务预留了清晰的边界。

还有一个容易被忽略的点:配置文件的环境隔离。我配置了application-dev.yml、application-test.yml、application-prod.yml三套环境,通过spring.profiles.active切换。数据库连接、Redis地址、日志级别都按环境分开,避免开发环境配置误上生产。

2. 系统核心模块拆分与数据库设计

2.1 六个核心模块,各自承担什么职责

整个系统拆成六个核心模块,每个模块的职责边界要清晰,不然写着写着就会变成一个大杂烩。

第一个是用户模块。除了常规的注册、登录、个人信息维护,续航租赁系统必须要做实名认证和驾照认证。租车不是买电影票,用户必须上传身份证和驾驶证,后台审核通过后才能下单。这个流程我在用户表里加了一个audit_status字段,0待审核、1通过、2拒绝,所有租车接口都会先校验这个状态。

第二个是车辆模块。车辆除了基本档案(品牌、型号、车牌、颜色、车辆VIN码),还要维护当前状态:0空闲、1已预订、2租赁中、3维修中、4下线。关键在设计一个车辆状态流转表,任何状态变更都要记录操作人、操作时间、变更原因,方便后续追溯。

第三个是订单模块。这是整个系统的核心,包含订单创建、支付、取车、还车、取消、退款全流程。订单表我单独讲,下面有详细设计。

第四个是计费模块。计费不是简单的单价乘时长,而是要支持规则配置。比如某车型工作日和周末价格不同,租3天以上有折扣,超时按小时另外计费,保险费按天固定扣。我把这些规则做成了一张price_rule表,规则内容用JSON存储,计算时由计费引擎解析执行。

第五个是充电模块。充电桩分为自营桩和合作桩。自营桩需要管理系统内桩的状态(空闲/充电中/故障),合作桩则通过接口对接。租车期间用户的充电行为,统一记录到charging_record表。

第六个是统计模块。管理端首页要展示今日订单量、营收金额、车辆利用率、热门车型排行。这些统计如果实时去查订单表,数据量大了会非常慢,所以我用了定时任务每小时聚合一次,结果存到statistics_daily表里。

2.2 数据表设计,这几张表最关键

数据库设计的质量,直接决定了开发时是“越写越顺”还是“到处补洞”。下面这几张表,是这套系统最核心的部分。

用户表(sys_user)

字段类型说明
idbigint主键
phonevarchar(11)手机号,唯一索引
passwordvarchar(100)BCrypt加密存储
id_cardvarchar(18)身份证号,加密存储
driver_licensevarchar(30)驾驶证号
audit_statusint实名认证状态
balancedecimal(10,2)账户余额
pointsint积分

为什么要余额字段?因为租车押金和订单支付都可以从余额里扣,用户充值一次,后续消费就不用每次走第三方支付,体验更好。同时我做了充值流水表,每笔充值和消费都有据可查。

车辆表(vehicle_info)

字段类型说明
idbigint主键
plate_novarchar(10)车牌号
model_idbigint车型ID
statusint车辆状态
current_batteryint当前电量百分比
current_mileagedecimal(10,1)当前里程数
longitude / latitudedecimal车辆当前位置
power_consumptiondecimal(4,1)百公里电耗

current_battery和current_mileage是关键字段,每次还车时必须更新,同时写入battery_record表作为历史记录。这两个字段还承担了一个功能:系统自动判断车辆是否可租。当电量低于配置的阈值(我设的是20%),车辆状态会自动变成“待充电”,不可下单。

订单表(rent_order)

字段类型说明
idbigint主键
order_novarchar(32)订单编号,唯一
user_idbigint用户ID
vehicle_idbigint车辆ID
start_timedatetime预计取车时间
end_timedatetime预计还车时间
actual_start_timedatetime实际取车时间
actual_end_timedatetime实际还车时间
order_statusint订单状态
total_amountdecimal(10,2)订单总金额
deposit_amountdecimal(10,2)押金金额
mileagedecimal(10,1)实际行驶里程
settle_statusint结算状态

订单状态我单独维护了一个状态机:待支付→已支付→待取车→租赁中→待结算→已完结。另外还有两个分支:已取消和已退款。每种状态变化的触发条件和操作权限都封装在订单Service里,不允许直接通过update语句改状态,这是为了避免并发下状态错乱。

计费规则表(price_rule)

这个表我采用的是“规则模板 + JSON配置”的模式:

字段类型说明
idbigint主键
model_idbigint车型ID,为空则全局规则
rule_typeint0按时长,1按里程,2组合
rule_contenttextJSON存储规则详情
effective_datedate生效日期
expire_datedate失效日期

比如某车型的规则内容如下:

{ "perHourPrice": 30, "perDayPrice": 180, "overHourPrice": 15, "perKmPrice": 1.2, "insurancePerDay": 20, "discount": { "day3to5": 0.9, "dayOver5": 0.8 } }

计算时,读取当前时间命中的规则,按规则计算费用,结果保留两位小数。

2.3 订单状态机怎么设计才不容易出乱子

订单状态是租赁系统最容易出Bug的地方,我见过不少项目在“用户取消订单后还能去取车”这种低级问题上翻车。要解决这个问题,状态机必须收敛。

我的订单状态流转是这样定义的:

  • PENDING_PAYMENT(待支付):用户提交订单后生成,此时锁定车辆,但不真正占用,超时30分钟未支付自动取消。
  • PAID(已支付):用户完成支付,车辆状态改为“已预订”,此时车辆不能再被其他用户下单。
  • PENDING_PICKUP(待取车):用户到达网点,工作人员确认身份并验车后,生成取车记录。
  • RENTING(租赁中):用户完成取车,车辆状态变为“租赁中”,订单计时正式开始。
  • PENDING_SETTLEMENT(待结算):用户还车,车辆状态更新为“待检查”,系统计算最终费用。
  • COMPLETED(已完结):押金退款、费用结算全部完成。

这个状态机里,最关键的是每一步操作都要有前置校验。举个例子,用户还车时,系统要先校验订单状态必须是RENTING,然后更新车辆电量、里程,生成充电记录和费用明细,最后把订单置为PENDING_SETTLEMENT。顺序错一步,后面的结算逻辑就会拿到脏数据。

我推荐用枚举类来定义状态,而不是用魔法数字散落在业务代码里。枚举自带校验逻辑,写起来也更直观。代码里我定义了一个OrderStatusEnum,里面包含了状态值、描述、以及该状态下允许执行的操作列表。

3. 关键功能实战与核心实现

3.1 车辆实时状态管理:查得到、锁得住、改得对

车辆状态是整个系统的“活数据”,所有模块都在读写它。如果处理不好并发,两台车同时被租出去的事故就会发生。

先说查询侧。前端用户选车时,系统需要筛选出当前状态为空闲、电量高于阈值、且没有被预约的车辆。我最初是直接SELECT * FROM vehicle_info WHERE status = 0,后来发现不行,因为订单表里还有已支付但未取车的订单,这部分车虽然车辆表status还是0,但实际已经被预定了。

正确的做法是:车辆表状态和订单表状态联合判断。查询可租车辆时排除两类:车辆表状态不等于空闲的,以及订单表里存在当前时间在start_time和end_time之间且状态为PAID或PENDING_PICKUP的车辆。

再说写入侧。用户下单时,会先把车辆状态从“空闲”改成“已预订”。这一步必须用乐观锁防止并发:

UPDATE vehicle_info SET status = 1, update_time = NOW() WHERE id = #{vehicleId} AND status = 0

这条SQL影响行数为1,说明抢锁成功;影响0行,说明车辆已被其他人抢走。配合订单创建放在同一个事务里,基本可以杜绝超卖问题。

还有一个要注意的点:管理员手动修改车辆状态的功能,我在实现时加了一个二次确认弹窗,必须输入操作备注才能保存。运营人员在后台误操作一次,可能比高并发Bug破坏力还大。

3.2 计费引擎:时长、里程、保险,一个都不能算错

计费是这种业务系统里最敏感的地方。算多了用户投诉,算少了公司亏钱。我把计费逻辑单独抽了一个PriceCalculator类,不揉在OrderService里,方便单独测试和后续扩展。

计费引擎的核心流程分三步:

第一步,确定计费规则。根据车型ID和当前日期,在price_rule表中找到命中的规则。这里注意规则的有效期判断,比如节假日规则、暑期规则,不能拿过期规则去计算。

第二步,计算基础费用。如果规则是按时长计费,先算实际用车时长。精确到分钟,超过半小时按一小时算,不满半小时按半小时算。如果是按里程计费,读取行驶里程,乘以单价。组合计费则两种都算,取较高值,这是租赁行业常见做法。

第三步,叠加附加费用。保险费按用车天数乘日单价,电费根据充电记录实报实销。最后算上优惠券抵扣和会员折扣。

这里有一个容易踩的坑:超时费的计算边界。比如用户计划3天还车,实际用了3天2小时,超出的2小时怎么算?有的系统直接按“超过1小时按全天收费”,用户会觉得被坑;有的系统按小时单价算,公司觉得亏。我的方案是:超出时间在半小时以内免收,超过半小时按小时单价加收,最多加收不超过一天租金。这个规则在用户下单页面提前展示清楚,能减少不少纠纷。

代码层面,我强烈建议把金额计算全部用BigDecimal而不是double。租车订单金额最多也就几百上千,但电费、保险费这种需要精确到分,double的浮点误差在累计对账时会很恶心。

3.3 充电桩对接与电量结算,被低估的复杂度

充电模块是新能源租赁系统特有的东西,也是最容易被低估的部分。

我在一期做的是自营充电桩管理,充电桩通过HTTP接口上报状态和充电数据。系统里有一个定时任务,每30秒轮询一次所有自营桩的状态,更新到charging_pile表。用户在小程序端可以看到哪些桩空闲、哪些在充电中。

用户还车时,车辆的电量要和取车时做对比。这里我的计算逻辑是:

  • 还车电量比取车电量低,说明用户用了电,按电费和里程费结算。
  • 还车电量比取车电量高,说明用户在外部充了电,这部分充电费用由系统按电费标准补偿给用户。
  • 如果低得太离谱,比如取车时80%电量,还车时只剩5%,系统会自动触发警告,人工介入核实行驶里程,防止用户私下拔掉充电记录。

实际做的时候,电量数据的采集精度是个问题。有的车机接口能读到精确电量,有的只能读仪表盘估算值。我的做法是允许手动修正,网点工作人员还车验车时复核电量,跟车机上报值偏差超过10%的,以人工复核为准。

充电记录表我记录了充电桩ID、充电开始时间、结束时间、充电度数、费用。这些数据和订单表关联,在用户订单详情页里能看到每笔充电明细,做到费用透明。

3.4 订单支付、押金与退款流程的设计

支付模块用第三方支付平台的SDK实现,微信和支付宝都接了。但比支付本身更麻烦的,是押金逻辑。

新能源汽车租赁的押金一般比传统油车高,因为电池的成本占比大。我的设计是:下单选押金方案(比如3000元),支付时押金和租金一起冻结。还车结算后,押金原路退回。

这里有个细节:押金不能真扣,否则退款流程会受到支付平台单笔限额限制。正确做法是使用支付平台的免密代扣或者预授权能力。预授权模式比较适合租车场景:下单时冻结金额,还车后解冻,费用单独扣款。如果产生违章、损坏,再通过代扣协议扣款。

不过预授权对商户资质要求比较高,个人开发者在测试阶段申请不下来。我的过渡方案是:押金用普通充值余额做冻结,即在用户账户余额中锁定一笔金额作为押金,订单完结后解锁。这样既模拟了押金逻辑,又不需要特殊支付资质。

退款流程的注意点:退款必须走原支付渠道,不能直接退到余额,否则用户会产生资金二清的感觉(如果走余额退款,则需要在退款记录里注明余额退款)。我在退款表里记录了退款类型(租金退款、押金退款、电费退款)、关联订单号、退款金额、操作人,方便财务对账。

3.5 Vue前端架构与关键页面实现

后端接口设计得再好,前端页面拉跨,系统照样没法用。这个项目的前端我分成管理端和用户端两个工程。

管理端用的是Vue3 + Element Plus + Pinia + Vue Router。页面骨架是左侧菜单加右侧内容区,顶部展示当前登录管理员信息和车辆实时状态提醒。核心页面有:

  • 车辆管理页:支持车型筛选、状态标签、车辆上下线操作、编辑电量里程。列表用的虚拟滚动,几百辆车也不会卡顿。
  • 订单管理页:订单列表分页查询、订单详情抽屉展示全流程时间线、手动改状态操作。
  • 计费规则页:规则配置表单,JSON编辑器支持语法校验,保存前先跑一遍试算接口。
  • 数据统计页:使用ECharts折线图展示近7日营收、订单量、车辆利用率。

用户端我用的是Vue3 + Vant移动端组件库,页面包括首页车辆列表、车辆详情预约页、订单列表、个人中心。移动端页面最重要的一点是支付流程的交互设计:选车→确认订单→支付→预约成功,每一步都要有明确的流转提示,防止用户卡在中间不知道该做什么。

接口封装方面,我统一用Axios,加了请求拦截器和响应拦截器。请求拦截器里带token,响应拦截器里统一处理401跳转登录、500弹错误提示。后端返回结构统一为{ code, message, data },前端不用管异常分支,逻辑清晰很多。

踩坑提醒:后端分页接口的返回结构里,total不要用long类型传给前端。大部分前端组件期望的是number类型,虽然JSON里数字类型没问题,但如果后端用了String包装,前端排序和分页会有隐性问题。

4. 开发部署中的常见问题与排查实录

4.1 并发抢单,同一辆车被租给两个人

这个问题我在联调阶段真实遇到过。A用户和B用户同时下单同一辆车,后台竟然生成了两笔订单,而且都显示支付成功。

排查过程:一开始怀疑是车辆状态更新没加锁,检查代码发现确实用了乐观锁,但锁的是vehicle_info表,而订单创建的入口有两个——小程序端和管理后台代客下单。管理后台的入口漏了同样的状态校验,导致走了另一条逻辑。

解决方案:把“创建订单”这个操作收敛到OrderService的createOrder()方法里,所有入口都走同一个方法。方法内部先开启事务,执行车辆状态乐观锁更新,再插入订单记录。同时加了一个Redis分布式锁,锁的key是vehicle:lock:{vehicleId},防止同一辆车同时被两个请求执行。

这个经验告诉我:系统有没有Bug,往往取决于代码路径是否收敛。写的时候图方便多开了几个入口,排查的时候就要多花几倍时间。

4.2 订单列表越查越慢,索引优化实战

系统上线后跑了一个月,订单表数据到十万级别时,管理端的订单列表接口开始明显变慢。原来只需要几百毫秒的查询,涨到了3秒多。

先看了慢查询日志,定位到是订单列表的模糊查询导致的。我的查询条件是用户手机号、订单号、车牌号三者选一,但车牌号和用户手机号在订单表里没有索引,每次查询都是全表扫描加回表。

优化方案分两步:

第一步,加索引。order_no本来就是唯一索引没问题,给user_id加普通索引,给vehicle_id加普通索引。车牌号这个条件,改为先从vehicle_info表查出vehicleId列表,再用vehicle_id IN (...)去订单表扫,避免订单表直接模糊匹配车牌号字符串。

第二步,查询条件优化。把原本的%关键字%模糊搜索改成前缀匹配,手机号筛选直接用等值查询。订单列表页的分页也从LIMIT offset改成了基于上次查询最后一条ID的游标分页,深分页性能问题直接消失。

改造之后,同样的数据量下查询时间降到了200毫秒以内。数据库优化这件事,很多时候不是技术多高深,而是你要知道数据是怎么存储和检索的。

4.3 定时任务重复执行,订单重复取消

订单超时自动取消功能,我用的是Spring @Scheduled定时任务,每30秒扫描一次超过30分钟未支付的订单,执行取消操作。有段时间发现线上很多订单状态异常,明明已经支付成功,却被标记成已取消。

查日志发现,问题出在应用部署了多实例。定时任务在每个实例上都会执行,两个实例同时扫描到同一批待取消订单,重复执行了取消逻辑。第一个实例取消了订单,第二个实例又取消了一次,状态被覆盖。

解决这个问题的办法很直接:引入分布式锁。我用Redis的SETNX命令实现了一个简易锁,定时任务执行前先尝试获取锁,拿到锁的实例才执行任务,锁加了60秒过期时间防止任务崩溃导致死锁。

String lockKey = "task:order:autoCancel"; Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 60, TimeUnit.SECONDS); if (Boolean.TRUE.equals(locked)) { try { cancelExpiredOrders(); } finally { redisTemplate.delete(lockKey); } }

除了定时任务,发消息通知、生成统计报表这类操作,只要部署了多实例,都要考虑重复执行问题。这是从单机到分布式必须跨过的一道坎。

4.4 计费精度问题:一分钱引发的对账烦恼

系统接了一个合作充电桩的接口,对方返回的充电费用是浮点数,比如27.86999999元。如果不做处理直接存数据库,对账的时候就会差一分钱。

这个问题的根源是浮点数精度误差。我的解决办法是:所有涉及金额和电量的数据,在接口入口处就统一转换成BigDecimal,精度设置两位小数或四位小数。转换逻辑封装成一个工具类,所有从外部接口拿到的数值都先过一遍BigDecimal.valueOf(doubleValue).setScale(2, RoundingMode.HALF_UP)再入库。

还有一个小点:前端展示金额时,不要做前端计算。每个页面需要显示的金额都由后端计算好返回,前端只负责渲染。比如订单详情页的“总计”字段,永远使用后端返回的totalAmount,而不是前端把单价乘以数量。这能避免很多因为JS浮点数导致的对账差异。

写在最后

这套系统从零到一落地,前前后后花了一个多月。最大的感受是:你以为的业务简单,和真正写代码时面对的复杂度,完全是两回事。新能源租赁系统看似只是一个订单管理,实际上牵涉到状态机设计、并发控制、精确计费、外部接口对接、定时任务可靠性等一堆问题。每一个坑,都是在联调和上线的过程中被逼着解决的。

最后再分享一个实操经验:开发这类系统,建议先把“还车结算”这个环节画清楚流程图再动代码。因为还车动作是计费、电量更新、充电记录、押金解冻、违章标记等多个模块的交汇点,也是最容易出数据不一致的地方。这个环节想清楚了,整个系统的主干就稳了。

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

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

立即咨询