简介:这套代驾小程序后端后台源码,基于ThinkPHP与Bootstrap构建,面向需要搭建或二次开发代驾管理平台、且已具备小程序前端的PHP开发者。系统内置扫码报单、客户主动下单、行驶中等待计费(按实际等待时长计费)等核心业务流程,支持线上线下支付,其中线上支付需在代码中开启;同时基于腾讯地图完成位置管理与路径规划,后端目录结构清晰,可直接部署于宝塔面板,运行环境为PHP7.1.3与MySQL5.7。压缩包约20.34MB,包含后台PHP源码、Bootstrap页面模板以及子目录中的小程序源码压缩包,但压缩包内不含uniapp小程序前端,前端源码需按说明通过独立链接下载。已有3728人学习这套代码,整体模块化程度较高,适合理解代驾后台的设计思路,也可作为自用商业项目的基础版本,但版权仅限自用,不可转售。 我最近接了个挺有意思的单子:代驾小程序的后端后台,注意,人家明确说不包小程序前端。这种需求在实际接单中非常常见,说明前端技术栈客户那边已经有谱了,或者找了别的团队并行开发。对我这种长期做微信生态开发的团队来说,这种活儿反而是最舒服的——不用纠结小程序UI交互,只需要把服务端API和运营后台做扎实,把业务逻辑跑通,把高并发场景扛住就行。
但这个项目远没有表面看起来那么简单。代驾业务的时间敏感性和位置敏感度极高,订单生命周期短、并发峰值集中在夜间,前后端接口联调、支付回调、实时位置推送、订单分配策略,每一个环节都有不少深坑。这篇文章我就把这个项目的完整实施过程、方案选型和踩坑记录写出来,给准备接代驾类项目的朋友做个参考。
提示:文中涉及的订单流程、计费模型、数据库设计、支付对接等内容,均基于我实际完成的代驾后端后台项目总结,部分细节为通用化处理,可直接迁移到同城出行、跑腿配送等LBS业务场景。
1. 项目定位与业务需求拆解
1.1 为什么会出现“不包小程序”的需求
先说说这种合作模式的背景。客户是三四线城市的一家本地生活服务商,手上已经有一批商家的微信生态资源,他们原本的计划是半年内上线代驾业务。小程序前端他们找了本地一个做UI设计的小团队,页面设计稿已经出了一半,但是后端这部分没人能做——前端团队只会写界面和调用现成API,遇到下单、派单、计费、账户体系这些自研业务就抓瞎了。
这其实折射出一个很现实的问题:很多小程序的开发瓶颈根本不在小程序端,而是在服务端。小程序端再复杂也就是一堆页面跳转和请求调用,但后端的订单状态机怎么设计、计价规则怎么配置、司机和用户的账户怎么管理、支付怎么对账,这些才是决定业务能不能跑通的关键。所以"不包小程序"不代表项目简单,反而意味着我这边要把整个业务底盘全部想清楚,给前端提供清晰、稳定、文档完整的API契约。
1.2 代驾核心业务链路与功能清单
代驾业务表面上看就是"用户叫车、司机接单、到达目的地、付钱走人",但深入拆解后,整个业务链路非常长。我梳理了一下核心流程:
用户端:登录鉴权 → 输入目的地 → 预估价格 → 确认下单 → 支付预付款/冻结押金 → 出发前通知 → 行程中查看路线 → 到达确认 → 评价。
司机端:上下线状态 → 听单 → 抢单/接单 → 联系用户 → 到达出发地 → 开始服务 → 行程进行 → 到达目的地 → 结算费用 → 提现。
平台端:司机审核入驻、订单监控、价格规则配置、优惠券管理、投诉仲裁、异常订单介入、数据看板。
听起来模块很多,但核心中最考验后端能力的是四个点:计价系统的准确性、订单状态机的严密性、派单策略的实时性、支付资金流的可追溯性。这四个点做扎实了,代驾后端就立住了一大半。
2. 后端架构与技术选型
2.1 技术栈选型与理由
技术选型这一步我其实纠结过一阵子。客户预算中等偏上,业务量预估前期一天几百单,但夜间高峰会出现短时间的单量脉冲,而且后续有向周边城市扩张的计划。考虑到团队的维护成本和后续扩展性,我最终定了这套组合:
- 后端框架:Spring Boot 2.7 + MyBatis Plus,Java系在这类传统业务系统里团队熟悉度最高,生态也最成熟,出了问题网上资料一抓一大把。
- 数据库:MySQL 8.0做主存储,业务初期阶段单库足够;Redis做会话缓存和热点数据,比如司机地理位置、在线状态、今日订单计数等都放在Redis里。
- 消息推送:使用WebSocket搭建实时通道,前端小程序通过WebSocket长连接接收订单状态变更和司机位置更新。这套方案比轮询省电省流量,而且代驾场景下位置更新频率不高,5秒一次足够。
- 文件存储:阿里云OSS,用于用户头像、车辆行驶证、驾驶证照片等资质文件的存储和审核。
- 地图服务:腾讯位置服务或高德地图API,涉及经纬度计算、路径规划、距离测算。
为什么不用微服务?代驾这个业务场景,哪怕单量做到一天几万单,单体的Spring Boot服务配合消息队列和Redis缓存也完全撑得住。盲目拆微服务只会增加运维负担,几十个服务实例部署、监控、链路追踪,对客户来说都是成本。我始终坚持一个原则——能用简单方案解决问题时,绝对不上复杂架构。
2.2 数据库模型设计要点
数据库模型设计是整个项目的骨架,这块如果设计不合理,后面写业务代码时处处是坑。代驾业务的核心表我拆成了以下几组:
订单域:order_main(订单主表)、order_price_detail(价格明细表)、order_cancel_record(订单取消记录表)。
司机域:driver_info(司机信息表)、driver_online_log(司机上下线流水表)、driver_wallet(司机钱包表)、driver_withdraw_record(提现记录表)。
用户域:user_account(用户主表)、user_coupon(用户优惠券表)、user_credit(用户信用分表)。
运营域:price_rule(计价规则表)、driver_audit_record(司机入驻审核表)、complaint_record(投诉处理表)。
订单主表是重头戏,我贴一下核心字段设计思路:
CREATE TABLE order_main ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL COMMENT '订单号,业务维度唯一', user_id BIGINT NOT NULL COMMENT '下单用户ID', driver_id BIGINT DEFAULT NULL COMMENT '接单司机ID', status TINYINT NOT NULL COMMENT '订单状态:0待支付 1待接单 2已接单 3司机已到达 4行程中 5待支付尾款 6已完成 7已取消 8申诉中', pickup_address VARCHAR(200) NOT NULL COMMENT '出发地详细地址', pickup_longitude DECIMAL(10,7) NOT NULL, pickup_latitude DECIMAL(10,7) NOT NULL, destination_address VARCHAR(200) COMMENT '目的地地址', dest_longitude DECIMAL(10,7) COMMENT '目的地经度', dest_latitude DECIMAL(10,7) COMMENT '目的地纬度', distance_estimate INT COMMENT '预估距离(米)', price_estimate DECIMAL(10,2) COMMENT '预估金额(元)', price_real DECIMAL(10,2) COMMENT '实际金额(元)', start_time DATETIME COMMENT '行程开始时间', reach_time DATETIME COMMENT '司机到达出发地时间', finish_time DATETIME COMMENT '行程结束时间', pay_status TINYINT COMMENT '支付状态:0未支付 1已支付 2退款中 3已退款', order_source TINYINT COMMENT '订单来源:1小程序 2电话代叫 3管理后台代下单', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_user_status (user_id, status), KEY idx_driver_status (driver_id, status), KEY idx_create_time (create_time) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='代驾订单主表';有两个很关键的设计细节。第一个是经纬度字段必须用DECIMAL(10,7)而不是FLOAT或DOUBLE,因为浮点类型存储经纬度会有精度误差,导致定位偏移几十米,这在代驾场景中是绝对无法接受的。第二个是订单状态字段索引的设计,针对用户维度查询和司机维度查询分别建了联合索引,因为运营后台查单量最大的场景就是"某个用户/某个司机的历史订单"。
3. 核心后端服务模块解析
3.1 订单状态机设计与流转
订单状态机是整个后端的大脑,也是我和前端联调时沟通最多的地方。前端每个页面的展示逻辑都依赖于订单当前的状态,所以状态定义必须清晰且完备。我设计的订单状态流转如下:
待支付(0) → 待接单(1):用户完成金额预冻结或预付 待接单(1) → 已接单(2):司机抢单成功或系统指派成功 已接单(2) → 司机已到达(3):司机点击"到达出发地" 司机已到达(3) → 行程中(4):司机确认接到用户,开始服务 行程中(4) → 待支付尾款(5):司机点击"到达目的地"结束行程 待支付尾款(5) → 已完成(6):用户支付尾款 任意状态 → 已取消(7):用户主动取消或超时未接单自动取消这个状态机表面上很直观,但真正落地时我遇到两个容易出问题的地方。
一是支付状态和订单状态是独立的。代驾场景下用户可能在车内支付,也可能下车后走远了才想起没付钱再做支付,所以"已完成"和"已支付"不能绑定死。我把订单状态和支付状态拆成两个字段分别管理,后台才能做到"订单已完成、费用未结清"这种真实业务状态。
二是取消订单时的退款处理逻辑。用户支付后取消订单,微信支付的退款是异步的,如果退款还没回调成功、订单状态就先改成已取消,用户那边看到"取消成功"但钱没到账,马上就会发起投诉。我的处理方式是:用户发起取消时先把订单标记为"取消中",然后调微信退款接口,退款回调成功后才把订单改为"已取消",同时把取消信息推到消息队列做短信通知。这样虽然取消了有延迟,但资金流永远是安全的。
3.2 实时计费系统的实现思路
计费是代驾业务里最容易出纠纷的模块。代驾的计费模型通常包括三个维度:起步价、里程费和等待费。起步价覆盖一定里程和时长,超出后按每公里计费,司机等待用户超过一定时间后按分钟计费。
我在后端实现的是规则引擎模式,把计费规则做成配置化。核心设计是一个规则执行器,接收订单实际数据,依次匹配起步价档位、里程费单价、等待费单价、夜间加价或恶劣天气调价因子,最终输出总费用。每次行程结束后,规则执行器触发计算,费用明细以JSON格式写入order_price_detail表。
public class PriceCalculator { public PriceResult calculate(OrderQuotationContext context) { // 1. 基础里程是否在起步价范围内 BigDecimal totalPrice = BigDecimal.ZERO; PriceRule rule = priceRuleService.getEffectiveRule(context.getCityCode(), context.getBusinessType()); // 2. 起步价判定 if (context.getDistance() <= rule.getBaseDistance() && context.getDuration() <= rule.getBaseMinutes()) { totalPrice = rule.getBasePrice(); } else { // 3. 超出部分分别计算里程费和时长费,高峰期加价因子 BigDecimal extraDistanceFee = rule.getPerKmPrice() .multiply(BigDecimal.valueOf((context.getDistance() - rule.getBaseDistance()) / 1000.0)); BigDecimal nightFactor = context.isNight() ? rule.getNightSurchargeFactor() : BigDecimal.ONE; totalPrice = rule.getBasePrice().add(extraDistanceFee).multiply(nightFactor); } // 4. 附加等待费、动态调价因子入明细 return buildResult(totalPrice, rule); } }这个实现里最主要的一个注意点就是精确计算。项目里跟金额相关的所有字段都是用BigDecimal,绝对不能用double或float,不然算出来106.78元实际存的是106.77999999这种结果,运维和财务能跟你急。另外计价规则在运营后台要支持按城市、按时间段配置多个版本,同一个城市可能白天和夜间是两套规则,接口查询时的时间条件非常关键。
3.3 派单策略:抢单模式还是指派模式
派单策略直接决定了订单能不能被快速消化。代驾这个场景里,司机数量不多,单量均匀,我对比了两种模式的特点后,选择了以抢单为主、超时自动转派为辅的混合策略。
抢单模式的核心是消息推送的实时性。用户下单后,系统把订单信息通过WebSocket推送给出发地周围3公里内正在听单的司机,司机端弹窗提醒,先点接单的司机获得订单。实现上用的是Redis的有序集合存储司机位置,查询附近司机直接用Redis的GEO命令,性能非常好。这里有一个设计细节:同一时刻禁止把一个订单推送给同一司机两次,防止司机重复点击接收产生并发冲突。
超时自动转派的逻辑是这样的:订单进入待接单状态后,如果90秒内没有被司机抢走,系统自动扩大范围为5公里再推一轮;如果再等60秒还是没人接,单子进入人工调度池,运营人员在后台手动指派。这样既确保了订单不会一直悬着,又给了平台运营人工干预的空间。
4. 微信支付V3对接与后台管理功能落地
4.1 微信支付V3整体对接步骤
这个项目里支付这块是硬指标,小程序端支付走的是微信支付V3接口。V3版本比起V2,最大的变化是API签名方式改成了商户私钥加解密,证书管理更严格,开发时要特别留意。我梳理一遍V3对接的完整步骤:
- 第一步是准备商户参数。确认商户号、APIv3密钥、商户API证书序列号、商户私钥文件。这些由客户在微信支付商户平台生成后我写入配置文件,同时要设置好支付回调域名和AppID绑定关系。
- 第二步是服务端下单接口。用户在小程序端点击支付后,小程序端把订单号传给后端,后端调微信支付V3的JSAPI下单接口,传入AppID、商户号、商品描述、订单号、金额(单位为分)、回调通知地址。接口返回prepay_id,后端再用prepay_id生成小程序端所需的支付参数,包括时间戳、随机串、签名等,返回给前端拉起支付。
- 第三步是回调处理。微信支付成功后会把结果POST到配置的回调地址,后端在回调里做验签、解密、更新订单支付状态,然后给前端推送支付成功消息。这里特别强调一个点:回调接口的处理逻辑必须幂等,因为微信服务器可能因为网络问题重复推送同一个支付结果的回调,如果处理逻辑没有做幂等控制,订单的支付状态可能被连续更新导致金额重复入账。
回到项目里,我额外做了一个支付对账定时任务。每半小时扫描一次本地订单表中支付状态为"处理中"的订单,调用微信支付的查单接口比对状态,如果微信侧显示已支付但本地未更新,就自动做状态修正。这个兜底机制极其重要,它能防止回调丢失时订单卡在死等状态。
4.2 后台管理系统的关键模块
后台管理系统我用的Vue3 + Element Plus独立部署,和后端通过JWT鉴权。虽然客户说不包小程序,但后台的完整度直接关系到他后续能不能自己运营,所以这个环节我做了不少精细打磨。
后台功能里最核心的是订单详情页。运营人员搜索订单后,需要在一个页面里完整体现:用户信息、司机信息、出发地目的地、里程时长费用明细、整个订单生命周期的时间线和操作记录、支付流水、退款状态。这要求后端在订单查询接口里做聚合查询,把订单、用户、司机、费用明细、操作日志一次返回,避免前端频繁多次请求。
另一个重要的后台模块是司机审核流程。代驾司机的准入管理很关键,司机需要在司机端上传身份证正反面、驾驶证、行驶证的照片,审核人员需要逐张查看并确认是否符合要求。我在审核流程里加了信息完整性自动校验——身份证号码和姓名是否匹配,驾驶证有效期是否过期,这些先用算法做初筛,再让人工确认。初筛逻辑虽然不复杂,但能帮运营省掉大量重复性工作。
5. 项目实战常见问题与排查记录
5.1 司机抢单高并发场景的处理
代驾业务的单量脉冲非常集中,尤其是周五周六晚上十点到凌晨两点,可能出现几十个司机同时抢一个订单的极端情况。我当时在这个场景上踩过一个很经典的坑。
最初版本我的抢单逻辑是:前端点击接单 → 后端查订单状态是否是待接单 → 如果是,则更新订单状态为已接单并写入司机ID。这个操作在并发下会出现严重的超卖问题——两个司机同时发起抢单,两个请求都查到订单是待接单状态,然后都执行了更新操作,导致一个订单有两个接单司机。排查后发现根因是查询和更新不是一个原子操作。
修复方案是改用数据库的乐观锁机制,在order_main表加一个version字段,更新时带上where条件version = 当前版本号且status = 1。如果更新影响行数为0,说明订单已经被其他司机抢走,接口直接返回"手慢了,订单已被抢"。Redis的分布式锁也能解决,但数据库乐观锁更简单也更可靠,在流量没有大到要单独做库存中心的阶段完全够用。
5.2 实时位置推送的方案演进
行程进行中,司机端每5秒上报一次当前位置,后端需要把这些位置实时推送给用户端的小程序。我最初的做法是司机上报位置后,后端立即通过WebSocket把位置数据推到用户通道。实测下来这个方案在司机少的时候很流畅,但司机数量一多,后端需要维护的连接数暴增,频繁的推送也占用了大量带宽。
优化方案是把位置推送改成了两级下发:司机端上报位置后,后端先在Redis里缓存最新位置并保留时间戳;用户端通过WebSocket订阅行程位置频道,后端每15秒批量把最新坐标推送到用户端一次,而不是来一个位置推一次。这个改动把推送频率降到原来的三分之一,用户端地图上的轨迹反而更平滑了,因为避免了频繁小幅度跳动。如果后续用户量更大,可以再引入MQ做削峰,但当前阶段这个方案已经足够稳定。
5.3 典型问题排查速查表
| 问题现象 | 可能原因 | 排查手段与解决方案 |
|---|---|---|
| 用户支付成功但订单未流转 | 回调被拦截或回调处理异常 | 查看应用日志确认回调请求是否到达;打开服务端的回调验签日志,确认商户证书序列号是否正确;核对APIv3密钥是否与商户平台配置一致 |
| 司机收不到新订单推送 | 司机端WebSocket断线未重连 | 检查WebSocket心跳机制,确认是否设置了合理的心跳间隔;司机端增加断线重连策略,应用切后台时降级为轮询方案 |
| 计价金额与后台配置不一致 | 计价规则匹配到错误城市或时段 | 在规则执行器里加调试日志输出命中的规则ID和生效时间;确认规则表当前生效时间是否覆盖了订单创建时间 |
| 订单状态与前端页面不一致 | 前后端状态枚举没有对齐 | 设计阶段就将状态枚举统一维护在后端,前端通过接口获取枚举描述,而不是前端自己写死中文文案 |
这个排查表部分内容是我这个项目实际处理过的案例,另外一些来自团队之前做过相似项目的经验积累,大家参考时结合自己的业务场景灵活对应。
6. 代驾后端项目的经验总结与扩展建议
做完这个项目以后,我自己对代驾类LBS后端项目的复杂度有了更清醒的认识。这类项目表面上看就是一个订单增删改查,但实际上涉及实时通信、资金安全、LBS检索、高并发下的数据一致性,每一个点都需要认真对待。资源调度、定位精度、订单状态流转、资金闭环,这几条线必须全理清了才能动手写代码,否则后面补窟窿的代价巨大。
如果后面有人想复刻这个项目,我的建议是:先从后台管理系统的数据模型入手,把核心业务实体和状态流转彻底搞清楚,再用后端接口打个底,让前端团队从第一天就开始跟着你的文档开发。接口文档的完善程度决定了前后端联调的顺畅程度,我在这个项目里坚持用Apifox管理所有接口文档,每次字段变动都同步更新文档和Mock数据,联调阶段几乎没有出现因为字段对不上而扯皮的情况。
这个项目后续有两个可以扩展的方向:一个是增加多城市自动分账逻辑,代驾平台如果有加盟商,订单收入和司机佣金需要按城市分账,那时候要对现有账户体系做扩展;另一个是接入地图ETA实时预估到达时间,通过历史轨迹数据训练模型,给用户更准确的等待时间提示。两个方向都是建立在现有订单和位置数据基础上的,扩展性本身是足够支持的。
本文还有配套的精品资源,点击获取