简介:一套面向代驾业务场景的后端管理后台源码包,适合需要快速搭建代驾平台服务端或进行二次开发的PHP开发者及技术负责人。整个后台采用ThinkPHP+Bootstrap开发,基于腾讯地图接入,支持线上线下支付(线上支付需在代码中开启)、扫码报单下单、客户主动下单、行驶中途等待计费等功能;业务代码结构清晰,便于二开,但版权仅限自用,不可转售。需要留意的是,资源本身不包含uniapp小程序端,小程序源码需按作者说明通过另附链接获取。压缩包为RAR格式,约20.34MB,文件总数与内部文件明细暂未在资源页展示;后台、接口逻辑、报单与订单相关模块均可直接部署到宝塔环境(PHP 7.1.3 + MySQL 5.7)使用。目前已有3728人浏览学习,更适合熟悉ThinkPHP、想快速落地代驾平台后端的开发者借鉴参考。 接了个“代驾小程序后端后台,不包小程序”的单子。刚看到需求描述的时候,我一度还挺轻松:小程序都不做,那不就是写一套接口加一个管理后台吗,工作量直接砍半啊。真正开工之后才发现,这个“不包小程序”恰恰是整个项目里最容易被低估的部分——小程序前端可以不做,但它调用的所有接口、所有订单状态流转、所有支付回调,全都压在后端和后台这一侧。这篇文章我就把这次项目从需求梳理、技术选型、核心模块实现到上线排错的完整过程整理一遍,尤其把那些只有真正落过地才看得见的坑讲清楚,给准备接同类需求的后端同学一个参考。
1. 先别急着写代码:把“不包小程序”的需求边界拆透
1.1 一份需求背后其实藏着四个端
客户说“不包小程序”,很多人第一反应是只做一个后台管理系统就够了,这是最大的误解。小程序虽然不做,但小程序的服务端还是你的,乘客在小程序上下单、支付、查订单,前端页面是别人写,接口协议却要你来定。换句话说,“不包小程序”只是砍掉了前端页面的工作量,后端接口一个都少不了。
拆到具体角色,这个项目实际包含四块:
- 乘客端小程序:别人负责开发,但登录、下单、取消、支付、订单详情所有接口都依赖后端提供;
- 司机端:我这边按H5页面做的,司机要能接单、上报位置、开始服务、结束服务、查看当日收入;
- 管理后台:自己开发,用来做司机入驻审核、订单查询、计费规则配置、优惠券管理、数据看板;
- 后端服务本体:包括业务接口、定时任务、消息推送、微信支付回调处理。
先把这四块列出来,再和客户对需求,你就不容易漏接口。实际项目里不少后期扯皮,都是因为前期没把“哪些端要接进来”讲清楚,做到一半客户突然说“司机端里应该有个评价页面”,你又不能不去补接口。
1.2 接口范围清单是第一个要交付的东西
需求评审阶段,我做了一张接口范围清单发给客户确认,这一步非常值得。清单大概长这样:
| 端 | 核心接口/功能 |
|---|---|
| 乘客端 | 微信登录授权、一键叫车、取消订单、下单预支付、订单详情、优惠券列表 |
| 司机端 | 司机登录、抢单/接单、位置上报、开始服务、结束服务、收入流水 |
| 管理后台 | 司机实名认证审核、订单搜索、计价规则配置、活动优惠配置、运营看板 |
| 公共服务 | OSS文件上传、微信支付回调、消息通知推送、内部定时任务调度 |
这张表的作用不只是给客户看,更是给自己建边界。有了清单之后,客户口头提的“要不在乘客端加个分享得优惠券”,你可以马上判断这个需求落在哪个端、影响哪些接口,而不是含糊地说“加到排期里”。独立开发者最怕的就是需求无边界,文档越早锁定,后面越省心。
1.3 代驾业务和普通订单系统的关键差异
代驾和普通外卖、跑腿系统看着都是派单,业务模型差别其实很大。外卖是商家出餐、骑手配送,整个链路有清晰的物理节点;代驾是线上发单、线下服务,从乘客下单到司机接单、到司机开车送乘客到达目的地,核心服务过程全在线下完成。
这意味着两个问题。第一,订单状态的推进大量依赖司机端主动上报,后端要容忍司机端网络不稳定、上报延迟,状态变更必须设计超时兜底;第二,计费不是简单的固定价格,代驾起步价、超出公里数、等待时间、夜间时段加价,这些规则不同城市还可能不一样。计价规则要是写死在代码里,后面改价就得发版,运营根本没法接受。
所以我们后来的设计原则是:凡是可能变化的业务规则,一律配置化,后端只负责执行计算;凡是涉及状态推进的操作,必须有明确的触发源和超时补偿。
2. 技术底座:为什么选 RuoYi + Spring Boot + Vue3
2.1 接单项目讲效率,权限和菜单不需要从零写
技术选型的时候,我没有从零手工搭一套 Spring Boot + Vue3 的前后端分离项目。原因很简单:接这种业务单,交付时间摆在那里,而像用户登录、菜单权限、角色管理、操作日志这类通用能力,自己写一遍至少要一到两周,还很容易写出漏洞。
最终选的是 RuoYi 框架做后端底座,Spring Boot 为主,管理后台前端用 Vue3 + Element Plus。RuoYi 这类框架最大的价值在于把后台管理系统里那些“绕不开但又不产生业务价值”的部分都准备好了:登录认证、权限拦截、菜单路由、代码生成器。代码生成器可以快速产出单表的增删改查接口,对后台里纯粹的配置管理页面(比如城市管理、车型管理、优惠券维护)效率提升非常明显。
管理后台用 Vue3 + Element Plus,组件生态成熟,表格、表单、弹窗这些高频操作都能直接拿现成组件拼。你可能觉得 Vue2 的资料更多,但新项目直接上 Vue3 完全没毛病,组合式 API 写起来更顺手,生态也早就跟上了。
2.2 从通用后台改造成代驾系统,最费劲的是状态管理
RuoYi 解决了通用后台,但代驾系统的业务核心它帮不上忙,需要重点改造。
第一,代码生成器默认生成的是单表 CRUD,但订单表绝不能是简单的增删改查。订单状态有严格的流转方向:待支付 → 待接单 → 已接单 → 司机已到达 → 服务中 → 待支付尾款 → 已完成,还有用户取消、超时取消、司机取消、异常取消这些分支。这类状态逻辑,必须写统一的订单状态机服务,所有状态变更走同一个入口,不能让每个接口自己拿着 update 语句去改 status。不然等订单量上来,状态数据一乱,对账和客服查单都无从下手。
第二,订单表不能物理删除。代驾订单涉及资金和售后,数据必须可追溯,设计上只用状态标记控制可见性。
第三,做了数据权限隔离。代驾业务在不同城市可能由不同运营人员管理,后台登录用户只能看自己城市范围内的订单和司机,不能看到全量数据。
2.3 密钥、盐、校验规则全部下沉到后端
前后端分离项目最容易犯的一个错误,是把不该让前端知道的东西放到前端项目里。代驾系统涉及支付,必须把安全边界彻底下沉到服务端。
我这里的处理方式是:接口签名密钥、AES加解密盐、微信支付商户私钥、API证书,全部只放在后端配置中心,前端只拿展示数据。像计价这样的核心逻辑,后端只接收订单的基础参数(起点、终点、车型),金额全部由后端计算后返回,前端不传 totalAmount 这样的字段,即使传了后端也直接忽略。
这个习惯在这次项目中直接避免了一次大事故,后面第 5 部分会详细讲。总之,涉及钱、涉及状态、涉及权限的字段,一律以服务端算出来的为准,前端传过来的都是参考值,甚至可以直接不信任。
3. 订单、派单与轨迹:代驾后端最容易失守的三个点
3.1 订单状态机怎么设计才兜得住取消和异常
订单状态是整个代驾系统的主干,我把它理解为一张“合同履约进度表”,每一步状态变更都相当于一次签字盖章,必须有触发方、有记录、有校验。
实际落地时,我定义了一个订单状态枚举,主要有这些状态:
- PENDING_PAY:乘客已下单,等待支付起步价;
- PENDING_DISPATCH:支付成功,等待派单;
- DISPATCHED:已派给司机,等待司机接单;
- ACCEPTED:司机已接单,正在前往乘客位置;
- ARRIVED:司机已到达上车点,等待乘客上车;
- IN_SERVICE:服务进行中,按里程计时;
- PENDING_FINAL_PAY:服务结束,等待支付尾款;
- COMPLETED:订单完成;
- CANCELED:订单取消(保存取消原因和操作人)。
所有状态变更通过一个 OrderStateService 统一处理,里面维护一张允许流转的映射表,不合法流转直接抛异常。举个例子,一个订单还在待支付状态,司机端是无论如何不能调“开始服务”接口的。
超时兜底也非常关键。车上乘客等久了司机迟迟不接单,总不能一直挂着。我做了两个定时任务:超过 5 分钟没有被司机接单的订单自动取消并退款;司机接单后超过 10 分钟没有点击“到达”,系统给司机端发提醒,同时升级告警给运营人员人工介入。
3.2 自动派单的并发控制和超时补偿
派单是代驾系统的核心动作,也是最容易出并发问题的地方。我第一版写的是:乘客下单后,遍历在线司机列表,把订单推给距离最近的司机。听起来没问题,但一上线就发现,多个订单同时创建时,同一个司机可能同时收到好几个订单推送,司机一接单,其他订单就悬空了。
后来改成用 Redis 做分布式锁控制。每个订单创建派单任务时,先生成任务ID,再用这个ID作为锁的 key 去 Redis 加锁,锁过期时间设为 3 秒。只有拿到锁的任务才允许把订单推送给指定司机,同一时间一个订单只可能推送一次,司机端也不会收到重复订单。
派单推荐逻辑也做了排序:在乘客周围 5 公里范围内找在线司机,按距离从近到远、司机评分从高到低、当日接单量从少到多排序,选出前 5 位候选司机,按顺序推送。司机端通过 WebSocket 收到新订单提醒,倒计时 30 秒内点击接单,超时未操作则自动轮询下一位候选司机。
WebSocket 在单机环境没什么问题,但部署到多节点之后,同一个司机的连接可能落在不同节点上,直接推送会找不到 session。我用 Redis Pub/Sub 做了一次中转,把推送消息广播到所有节点,由对应节点找到本地 session 再下发,就解决了跨节点推送的问题。
3.3 司机位置上报与真实里程计算
计费里程是代驾项目里最容易引起客诉的点,直接原因就是“司机端上报里程”和“乘客认可里程”对不上。
我不能盲目相信司机端传上来的里程数,位置坐标也一样。司机端每 5 秒上报一次经纬度,服务端接收后先做抽稀,只有“距离上一次记录点超过 50 米且时间间隔超过 10 秒”的坐标才真正落库,避免轨迹文件里堆积大量原地抖动数据。
乘客和司机之间的距离判断,我用 Haversine 公式计算球面两点间距离,用于实现“司机是否到达上车点”的判断逻辑。但车辆实际行驶的计费里程,不能直接用这个直线距离算,这样误差太大,对乘客也不公平。
我的做法是:服务开始时记录起点坐标,服务结束时记录终点坐标,调用地图服务的路径规划接口,获取实际的驾车路线里程,再结合等待时间和夜间时段计算总费用。司机端上报坐标和距离只用于展示和轨迹回放,不计入结算;结算金额永远是服务端基于第三方地图路径规划计算出来的结果。这一点虽然让技术实现复杂了不少,但上线后客诉率明显低很多。
4. 钱和后台:计价规则、微信支付v3对接与运营需求
4.1 计价规则做成配置表,别写死在代码里
代驾计费不是一口价,通常由起步价、超公里费、等待费、夜间服务费叠加组成。以我这次接的规则为例:起步价包含 5 公里,超出部分按每公里 2.5 元计算;等待时间超过 5 分钟之后,按每分钟 0.5 元计费;晚上 22 点到次日 6 点加收 20% 夜间服务费。不同城市还可能单独配置,规则完全不一样。
所以我把计价规则做成了配置表,后台运营可以直接改:
- base_price:起步价;
- base_distance:起步包含公里数;
- per_km_price:超公里单价;
- waiting_fee_per_min:等待费每分钟单价;
- night_surcharge_rate:夜间加价比例;
- effective_time:规则生效时间段。
下单时,服务端先从缓存里读取当前城市、当前时间对应的计价配置,再结合地图路径规划的里程数算出最终金额。计价配置修改后立即生效,不需要发版。这个表格看起来简单,但帮我省掉了至少三次“临时调价”的沟通成本。
4.2 微信支付v3回调的几个大坑
代驾项目涉及两次支付:下单时预付起步价,服务结束时支付尾款。支付流程对接微信支付 v3,里面坑不少。
第一个坑是金额单位。微信支付以“分”为单位,但后台展示、订单列表都以“元”为单位。我统一在数据库和接口层用 Long 类型存分,只有展示层才转换成元,避免 float/double 精度问题。
第二个坑是回调验签。微信支付 v3 的回调,不能直接把请求体拿来当普通 HTTP POST 处理,必须用官方 SDK 验证签名、解密报文。我见过有人为了省事跳过验签,结果被伪造回调刷了一晚上订单,这是血泪教训。SDK 的解析方法很成熟,照着官方文档接就行,但一定要确保生产环境的签名校验是开着的。
第三个坑是幂等。同一笔支付成功,微信可能会回调多次。如果回调里直接更新订单状态,第二次回调就可能导致状态错乱。解决方案是在 order_payment 表里对 transaction_id + order_id 建唯一索引,重复回调直接忽略。
第四个坑是掉单。如果用户支付成功但回调网络超时,订单会一直停在“待支付”状态。我加了一个定时补偿任务,每分钟扫描一次超过 3 分钟仍未收到回调的订单,主动到微信查询支付状态,查到了就补更新订单,查不到就标记为支付失败,把状态修正回来。
4.3 司机结算、对账以及管理后台的真正需求
司机收入结算是运营每天都要盯的事。我的方案是订单完成后生成一条司机收入流水,金额等于订单总费用减去平台服务费,T+1 结算给司机。平台服务费率不是代码里写死的,后台可以按城市配置,但任何修改都会记录操作日志,防止运营误操作或者内部问题。
管理后台如果只做增删改查页面,运营用起来会很难受。真正刚需是这些:
- 客服查单:能按手机号、订单号、时间段、司机姓名组合搜索订单,还要支持导出 Excel;
- 司机审核:司机提交身份证、驾驶证、行驶证照片,后台走“OCR识别 + 人工复核”流程,审核记录必须留痕;
- 数据看板:当日订单量、完单率、客单价、取消率、司机在线数。聚合查询不能实时去打订单表,否则报表页面能把库拖垮,我是每天凌晨跑任务汇总到统计表;
- 后台账号安全:管理后台登录要强密码校验,登录失败次数多了要锁账号,关键操作(改计价规则、审核司机、调分成比例)必须审计留痕,定期清理不再使用的管理员账号。后台权限错乱引发的风险,往往比业务漏洞更难排查。
5. 上线后第一场硬仗:刷单漏洞的完整排查过程
5.1 凌晨的异常订单,问题先出在接口参数上
上线第二周,某天早上运营群里炸了:凌晨 3 点到 5 点之间,系统产生了几百个订单,支付金额几乎全是 0.01 元,而且都来自同一 IP 段。更离谱的是,这些订单全部能正常进入派单流程,有几十个司机还真去接了单。
我第一反应是数据库被注入了,打开订单表一看,数据格式完全正常,没有拼接字符串的痕迹,但 payload 里都带着一个可疑字段:totalAmount=1。这个字段单位是分,对应 0.01 元。
这是典型的刷单攻击——攻击者直接构造创建订单的接口请求,把金额参数改成了一个极小值,后端没有校验金额合法性,直接落库了。
5.2 顺着日志追根因:被抓包的样例为什么成了突破口
接着往后查。我翻出创建订单接口的代码,发现计价逻辑居然是前端传一个 totalAmount,后端校验一下大于 0 就用它去落库。这等于把定价权交给了客户端,攻击者改个参数就能以一分钱价格下单。
关键问题是,攻击者是怎么知道这个参数的?答案更让人头大。团队两周前调试小程序支付时,用抓包工具保存过一份完整的请求样例,里面包含了接口地址和所有字段示例,后来贴到了内部文档库,没说清楚是测试数据,大家默认这是某个外部依赖的对接文档,没有设置阅读权限,这份样例就流出了。前端字段命名不规范也给了可乘之机,别人顺着文档一猜就猜出了接口结构。
这里我必须反思一点:抓包工具本身没有错,项目联调时我也会顺手看一眼请求报文,但测试环境抓到的报文绝对不能直接原样丢进生产项目的文档库。正式接口文档里必须隐去真实密钥、真实 openid、真实请求参数,架不住有心人拿去扫接口。
5.3 修复方案与事后加固
问题定位清楚之后,我分几步处理:
第一,紧急修复业务漏洞。创建订单接口不再接收 totalAmount 字段,即使传了也直接忽略,订单金额完全由服务端计价引擎计算,参数校验逻辑加严,低于正常价格范围的金额直接拒绝并触发告警。
第二,加固接口安全。给所有涉及创建的接口加了签名验证机制,前端请求时用约定密钥对参数生成签名,后端验签通过才处理;同时加 timestamp 参数,超过 3 分钟的旧请求直接丢弃,防止重放攻击。
第三,做频率控制。对同一个手机号、同一个微信 openid、同一个 IP 维度做限制,1 分钟最多创建 3 个订单,超过直接拦截,并返回风控提示。
第四,清理存量数据。把凌晨产生的异常订单全部标记为风控取消,已支付的订单走原路退回。司机端已经接单的,按平台规则做了补偿。
整个排查过程复盘下来,核心原因其实就是一句话:后端把本该自己掌握的钱相关参数,交给了客户端去定义。这个教训太深刻了,从那以后,所有计价、优惠、状态流转相关的接口,凡是钱和状态有关的字段,我一律不信任前端。
说点题外话,如果让我重新接一次这个需求,我会在第一周就拉着客户把订单状态机和计价规则白纸黑字确认清楚,再开始写代码;所有涉及金额、状态、权限的字段,一律以服务端计算为准,前端只做展示。这两条做到了,代驾后台项目基本能稳一大半。剩下的,就是在每一行代码里记住:系统的边界不在需求文档的边界,而在它背后真实的业务和真实的钱。
本文还有配套的精品资源,点击获取