首页 / 资讯中心 / 文章详情

开源跑腿小程序智能派单系统:算法拆解与落地避坑

开源跑腿小程序智能派单系统:算法拆解与落地避坑 ★ FEATURED ARTICLE
简介一套基于Fastadmin、ThinkPHP与Uniapp框架开发的同城跑腿全开源系统完整覆盖用户端、骑手端和运营后台适合校园跑腿、同城配送、帮取帮送等场景支持预约取件与即时下单。系统源码无加密支持私有化部署后台可灵活切换抢单模式与系统派单模式并根据骑手距离、服务等级智能推送订单计价规则完善支持按距离、重量自动计算费用可针对夜间、恶劣天气临时加价同时内置跑腿小费、物品保价等功能帮助提升接单率并保障物品安全。用户端操作简洁可查看附近骑手、在线支付并实时跟踪订单进度骑手端提供地图精确选点、路线规划、一键抢单、弹窗及语音提醒、自由开工开关还支持兼职/全职模式便于团队差异化管理和佣金结算。运营后台则涵盖订单管理、骑手管理、计费规则配置、数据统计等模块便于日常维护与业务分析预约取件可设定具体上门时间系统自动派单时兼顾距离与忙闲状态。压缩包约64.82MB已有94人学习下载适合需要搭建或二次开发同城跑腿平台的技术团队与开发者。1. 跑腿小程序智能派单系统从抢单乱象到自动指派同城校园配送的开源样本校园里一到中午跑腿单子几分钟就涌进来几十单。骑手全凭手速抢单远的没人接、小的没人理用户等半小时才有人响应预约取件的单子更是到点了才临时抓人配送体验全靠运气。这套全开源的跑腿小程序智能派单系统把用户端、骑手端和后端派单引擎放进同一个工程交付核心不是“能下单、能接单”的表层功能而是那套按距离、负载、顺路度自动打分、指派骑手的派单逻辑以及预约取件需要的那条定时触发闭环。适合想在校园、园区、小城区快速起跑腿业务的团队也适合想研究双端小程序加派单服务完整实现的开发者。下文按我落地这类工程的习惯讲透拆解、跑通、调参和避坑。2. 先拆系统双端功能边界与智能派单的核心逻辑2.1 用户端和骑手端各管什么一张功能清单看清边界看这类源码工程第一步不是打开代码而是先看两个小程序端的页面目录和路由表把角色边界画出来。用户端和骑手端是两套独立的微信小程序工程它们共用同一个后端服务但页面、接口、数据权限完全隔离。角色核心页面关键接口数据落点用户端下单页、订单列表、订单详情、支付、评价POST /order/create、POST /order/pay、GET /order/list、POST /order/cancelorders 表、payments 表骑手端待接单列表、我的订单、取件确认、送达拍照、收入页POST /order/accept、POST /order/pickup、POST /order/deliver、GET /rider/incomeorders 表状态流转、rider_locations 表公共登录、手机号绑定、消息推送POST /auth/login、POST /auth/phoneusers 表、riders 表用户端关注的是下单路径最短选地址、选跑腿类型取件/送件/代办、勾选预约时间、支付。订单列表页要注意分页加载微信小程序页面列表加载更多是标配写法下拉触底用onReachBottom翻页不要一次把全量订单塞进 setData。骑手端关注的是接单和交付闭环新订单提醒、取件码、送达拍照、今日收入。小程序端本身不存业务状态所有状态都以后端 orders 表里的 status 字段为准。管理端在这个工程里通常是独立仓库或直接嵌在后端管理后台里核心功能是骑手审核、费率配置和派单参数调整。如果这份 zip 里只有用户端和骑手端两个小程序目录管理功能一般被做成了后端服务里的几个 admin 路由给运营同学开个浏览器页面就能用。2.2 为什么抢单模式会翻车智能派单要解决的三件事抢单模式看起来公平跑起来全是问题。第一骑手只挑顺路大单远单、小单、雨天单没人接用户等待时间完全不可控。第二新手骑手手速拼不过老骑手单量两极分化平台体验方差极大。第三预约取件订单无法提前锁定骑手到点了才在群里喊人用户预约了个寂寞。智能派单的意思不是“给骑手推几条通知”而是系统在订单产生时根据当前空闲骑手的位置、在途负载、行进方向自动算出一个分数把订单指派给分数最高的人。骑手端没有“抢”按钮只有“接受/拒绝”。校园场景特别适合这套逻辑地理范围小通常 3 到 5 公里订单密度高骑手数量有限不需要上复杂的路径规划引擎一个打分公式就够用。同城跑腿场景也能套只是要把距离权重和骑手数量阈值调一调。这套工程最值得读的代码就是派单服务。很多开源项目的派单模块只是“按距离最近分配”那叫就近分配不叫智能派单。真正的差异在负载均衡和顺路度判断上。2.3 派单打分模型距离、负载、顺路度三个系数怎么算派单引擎的核心是一个打分函数。常见实现是把三个维度归一化到 0 到 100 分再按权重相加。我见过多数开源工程默认权重是距离 0.4、负载 0.35、顺路度 0.25具体值通常在派单配置表里不用改代码就能调。// dispatchScore.js - 派单打分核心函数 function calcScore(order, rider) { // 1. 距离分骑手到取件点 500 米内满分每多 100 米扣 10 分下限 0 const dist getDistance(order.pickupLat, order.pickupLng, rider.lat, rider.lng); const distanceScore Math.max(0, 100 - Math.floor((dist - 500) / 100) * 10); // 2. 负载分在途订单 0 单1001 单702 单403 单以上直接排除 const loadScore [100, 70, 40, 0][Math.min(rider.activeOrders, 3)]; // 3. 顺路度分骑手当前行进方向与取件点的夹角余弦余弦值映射到 0~100 const cosAngle calcCosAngle(rider.heading, order.pickupLat, order.pickupLng, rider.lat, rider.lng); const directionScore Math.round((cosAngle 1) / 2 * 100); // 4. 加权总分低于阈值则不派 const score 0.4 * distanceScore 0.35 * loadScore 0.25 * directionScore; return score; }这段代码要注意三个参数。getDistance用的是 haversine 公式坐标统一用微信小程序wx.getLocation返回的 gcj02 坐标系不要混用 wgs84否则距离会偏几十米直接导致派给错误的骑手。activeOrders是骑手当前在途订单数需要在订单表里维护一个冗余计数每次接单和送达时更新不要现查现算。calcCosAngle需要骑手端定期上报位置和方向角一般 10 到 15 秒上报一次频率太低会导致顺路度判断滞后骑手已经转弯了系统还认为他顺路。派单引擎的触发方式有两种立即派单和定时扫单。新订单进来先走立即派单计算一次如果没找到超过阈值默认 60 分的骑手订单进入待派单池由定时任务每 30 秒扫一次直到有人接单或订单超时。这种兜底设计比单纯依赖 WebSocket 推送可靠得多。2.4 预约取件的实现思路定时任务不是闹钟是状态机预约取件是这个工程里最容易做砸的功能。常见错误是把“预约”做成闹钟到点了给用户和骑手发个通知至于骑手在哪、单子谁接全看运气。正确做法是把预约做成一个完整的订单状态机。订单状态流转待支付 → 已支付等待触发→ 待取件进入派单池→ 已接单 → 配送中 → 已完成。取消动作在任何非终态都可以触发已支付的预约单取消要走退款流程。后端需要一张预约任务表字段至少包括order_id、trigger_time、status、retry_count由一个扫描服务定期检查。-- appointment_task 表结构核心就是一个待触发的状态机 CREATE TABLE appointment_task ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, trigger_time DATETIME NOT NULL, status TINYINT NOT NULL DEFAULT 0, -- 0待触发 1已触发 2已取消 retry_count TINYINT NOT NULL DEFAULT 0, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_trigger (status, trigger_time) );扫描服务每分钟跑一次把status 0 AND trigger_time NOW()的订单捞出来先更新状态为“已触发”再调用派单接口。关键点是用服务端时间判断不要相信客户端传的预约时间戳客户端时间可以修改服务端时间才是权威。用户端在预约详情页可以用wx.setNavigationBarTitle动态把标题改成“等待配送中”或“骑手已接单”提升体验但页面显示的时间仍要以后端返回为准。3. 把开源工程跑起来后端服务与双端小程序的联调步骤3.1 目录结构先读懂server、user-mp、rider-mp 三层怎么协作这类全开源的跑腿工程目录组织通常遵循三层结构后端服务、用户端小程序、骑手端小程序。后端服务用 Node.js 的 Express 框架居多也有用 Java Spring Boot 的但 Node 在中小型校园项目里更常见因为上手快、依赖少、WebSocket 生态成熟。# 常见开源工程目录结构具体命名以你拿到的 zip 为准 project/ ├── server/ # 后端服务Node.js Express │ ├── app.js # 应用入口注册路由和 WebSocket │ ├── config/ │ │ └── index.js # 数据库连接、微信 AppID、端口配置 │ ├── routes/ # HTTP 路由订单、骑手、用户、支付 │ ├── services/ │ │ ├── dispatch.js # 派单引擎打分和指派逻辑 │ │ └── appointment.js # 预约取件扫描器 │ ├── socket/ │ │ └── index.js # 骑手端长连接处理 │ └── sql/ │ └── init.sql # 数据库初始化脚本 ├── user-mp/ # 用户端微信小程序工程 │ ├── pages/ │ ├── utils/ │ │ └── request.js # 封装的 wx.request 和 wx.connectSocket │ └── app.json └── rider-mp/ # 骑手端微信小程序工程 ├── pages/ └── utils/两个小程序端共用一个后端服务但登录鉴权要区分角色。用户端调/auth/login拿 user token骑手端调/auth/rider/login拿 rider token后端中间件校验两种 token 时路由权限要分开。最常见的翻车原因是骑手 token 拿去调用户接口后端没做角色校验导致骑手能看到用户的隐私订单数据。这个权限隔离要在一开始就确认清楚。3.2 本地跑通后端初始化数据库与启动派单服务先跑后端再跑小程序端顺序不能乱。后端起不来小程序端做什么都是白搭。# 进入 server 目录安装依赖 cd server npm install # 创建数据库导入初始化脚本 mysql -u root -p -e CREATE DATABASE IF NOT EXISTS paotui DEFAULT CHARSET utf8mb4 mysql -u root -p paotui sql/init.sql # 修改配置数据库账号、密码、端口、微信小程序 AppID/Secret # 打开 config/index.js把 database.password 改成你自己的密码 # 启动开发服务nodemon 热重载 npm run dev配置文件的必改项是数据库连接串和微信小程序 AppID。init.sql至少创建 users、riders、orders、order_status_log、rider_locations、appointment_task 这几张表订单状态变更要有 status_log 表做审计不然线上出了问题完全没法回溯是谁在什么时间改了订单状态。npm run dev跑起来后控制台会打印监听端口默认一般是 3000。接下来用curl http://localhost:3000/health或浏览器访问一下健康检查接口确认服务进程活着。如果npm install报错先看 Node 版本。这类老工程对 Node 版本有要求常见的坑是 Node 18 以上装不了旧版依赖需要用nvm切到 Node 14 或 16 再装。3.3 导入用户端和骑手端微信开发者工具里的四个必改项后端起来后用微信开发者工具分别导入 user-mp 和 rider-mp 目录。导入后不要急着预览先把四个配置项改掉。// user-mp/utils/config.js 或 rider-mp/utils/config.js // 两个端的配置结构类似改同一批字段 module.exports { baseUrl: http://localhost:3000, // 后端地址真机预览时改成局域网 IP appid: wx1234567890abcdef, // 你的小程序 AppID测试号也可以 mapKey: your-qq-map-key, // 腾讯地图 WebService Key骑手端导航用 pollInterval: 5000 // 用户端订单状态轮询间隔单位毫秒 };baseUrl在模拟器里可以填http://localhost:3000但真机预览时必须改成电脑的局域网 IP比如http://192.168.1.100:3000否则手机访问不到电脑上的后端服务。appid先用测试号也能跑起来但微信小程序登录获取手机号接口必须企业主体小程序才能用个人主体直接报错这点在下一节展开。mapKey用于骑手端导航和距离计算需要到腾讯地图开放平台申请类型选择 WebService API别选成 Web 端 JavaScript APIkey 的权限不对也会白屏。改完配置后在微信开发者工具的“详情 → 本地设置”里勾选“不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”。这个选项只对当前项目生效跳过域名校验才能让小程序请求http://localhost:3000。真机预览时手机打开小程序也要开启右上角菜单里的“开发调试”模式否则真机同样会被域名白名单拦住。3.4 双端联调真机预览、手机号登录与订单全链路验证后端和两个小程序端都准备好后按下面的顺序做端到端联调。我习惯把用户端和骑手端分别在两个微信开发者工具窗口里打开后端跑在一个终端里三块屏幕对照着看日志谁挂了立刻能看出来。用户端点“微信一键登录”后端日志里应该出现/auth/login请求。开发环境下如果手机号登录接口报错先在后端加一个POST /auth/dev_login路由接收任意手机号直接返回 token绕开微信接口限制先把链路走通。用户端下一笔“立即送”订单选择取件点和送达点支付成功。此时数据库 orders 表多一条记录status 为“已支付”。骑手端登录应该收到新订单派单推送。骑手端接单后用户端订单详情页应实时显示骑手位置。再下一笔“预约取件”订单触发时间设为 5 分钟后。到时间后预约扫描器会把订单推进派单池骑手端应收到通知。走完取件码验证、送达拍照、用户确认、评价的完整闭环。全程观察后端日志和 orders 表的 status 字段流转任何一步卡住都能定位到具体接口。联调阶段最常见的坑是骑手端收不到派单推送。先检查骑手端 WebSocket 是否连接成功看后端日志里有没有 rider 的连接记录再确认骑手端 app.js 的onShow里做了 socket 重连微信小程序切后台会回收 WebSocket不重连就永远离线。这个问题在第四节详细展开。4. 避坑把派单系统跑起来最容易踩的 5 个高频坑4.1 小程序请求本地后端一直被拦request 合法域名校验现象用户端和骑手端所有请求都失败控制台报错http://localhost:3000 不在以下 request 合法域名列表中。原因微信小程序出于安全策略默认禁止向非 HTTPS 域名发起请求。本地开发没有配置合法域名而且http://localhost本身也不在默认白名单里。解决在微信开发者工具“详情 → 本地设置”勾选“不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”。注意这个勾选只对当前项目生效换一台电脑重新导入工程要重新勾。真机预览时还要在手机上打开小程序右上角的“开发调试”开关否则真机照样拦。上线前必须把后端域名配置成备案过的 HTTPS 域名并在小程序管理后台把域名加进 request 合法域名列表。4.2 骑手端收不到派单推送WebSocket 被微信回收现象派单引擎日志显示已指派骑手但骑手端没有任何提示下拉刷新后才看到新订单。有的情况是骑手端切后台再回来就一直收不到通知直到杀掉小程序重新打开。原因两个原因叠加。第一骑手端只在app.js的onLaunch里创建了 WebSocket 连接微信小程序切后台后 WebSocket 会被系统回收回前台时没有重连第二后端没有做心跳检测连接断了也不知道一直往一个死连接里推消息。解决骑手端在app.js的onShow里检查连接状态断开则重连重连时携带登录 token 重新鉴权后端 WebSocket 服务加心跳机制每 30 秒检测一次连续三次心跳无响应就主动断开连接并清理内存里的连接对象。派单推送不能只依赖 WebSocket订单状态变更后把最新数据写进数据库骑手端下拉刷新或进入详情页时再拉一次双通道兜底。4.3 微信小程序登录获取手机号接口报错个人主体不能用现象点击“手机号一键登录”按钮后端回调报invalid code或该小程序暂无权限代码层面看不出问题。原因wx.getPhoneNumber是一次性 code而且只对企业主体小程序开放。个人主体小程序调用这个接口微信直接拒绝。很多开源工程默认代码是接正式手机号接口的但你自己的账号可能是个人主体跑不通。解决开发阶段在后端加一个POST /auth/dev_login路由接收手机号参数直接返回 token前端把登录方式做成可配置的开发环境走 dev 登录生产环境走微信手机号登录。上线前确认小程序主体是企业且已在后台申请并开通手机号快速验证组件权限。这个坑属于“不看账号类型就联调”的典型翻车点。4.4 预约订单到点不派单扫描器没启动或时间比较出错现象预约取件订单状态一直停在“已支付”到了预约时间没有任何变化骑手端也没收到通知。原因第一预约扫描服务没启动很多人只启动了 HTTP 服务忘了项目里还有一个定时任务服务第二扫描 SQL 里的时间字段比较用的是客户端传的triggerTime字符串不是服务端时间第三预约任务表的status没更新扫描器每次把同一个订单捞出来重复触发。解决启动后端时确认控制台有appointment scanner started之类的日志。扫描逻辑里先做状态抢占UPDATE appointment_task SET status 1 WHERE order_id ? AND status 0影响行数为 0 就跳过防止重复触发。触发时间判断统一用trigger_time NOW()服务端数据库时间为准客户端传的预约时间只作为展示参考。4.5 重复派单两个骑手被指派了同一笔订单现象两个骑手同时收到同一笔订单的派单通知后端的派单日志显示同一订单被指派两次。原因派单引擎存在并发。如果部署了多个实例或定时扫单和立即派单同时执行两个执行线程都查到同一笔待派单订单各自计算并指派了不同骑手数据库没有做状态约束。解决在 orders 表加version字段派单更新时用乐观锁UPDATE orders SET rider_id ?, status ASSIGNED, version version 1 WHERE id ? AND version ? AND status PENDING受影响行数为 0 说明被其他线程抢占了当前线程丢弃本次指派。如果工程部署了多个后端实例则需要在 Redis 里用SETNX做派单锁但校园场景单机部署用乐观锁就够了引入 Redis 反而增加运维负担。5. 派单参数调优与预约取件的进阶做法5.1 三个核心系数怎么调从“能跑”到“跑得聪明”派单打分模型的默认权重——距离 0.4、负载 0.35、顺路度 0.25——只是起步值直接上线运营会被现实教育。校园午高峰和晚高峰的订单特征完全不一样必须按时段调参。// server/config/dispatch.json - 分时段派单参数 { morning: { distanceW: 0.4, loadW: 0.35, directionW: 0.25, minScore: 60 }, noon: { distanceW: 0.3, loadW: 0.5, directionW: 0.2, minScore: 45 }, evening: { distanceW: 0.35, loadW: 0.25, directionW: 0.4, minScore: 50 } }午高峰订单密度大骑手手里很容易压三四个单子这时负载权重提到 0.5宁可让距离稍远一点的骑手接单也不能让单量最饱和的骑手继续被塞单。负载分三段式打分——0 单 100 分、1 单 70 分、2 单 40 分——在高峰期的区分度太低建议改成连续函数比如在途订单每多一单扣 35 分能更精细地体现负载差异。晚高峰和低峰期订单稀疏方向权重调到 0.4 的效果最好。骑手送完最后一单顺路回家的方向接单意愿最高系统把订单派给行进方向夹角小的骑手接单率明显上升。minScore阈值也不是越高越好校园骑手少阈值设在 60 分会导致高峰期大量订单进待派单池我实际运营中会把午高峰阈值降到 45 分保证单子有人接比派给最完美的人更重要。这些参数建议全部放到数据库配置表里配合一个热更新接口改完即时生效不需要重启服务。5.2 预约取件不丢单状态抢占、重试队列和退款分支预约取件从功能上线到稳定运行需要补三个细节。第一是状态抢占扫描器把订单从“待触发”改为“已触发”时用条件更新而不是先查后改避免多个扫描器实例重复处理同一笔订单。第二是触发失败要进重试队列指派骑手失败不能直接放弃按 1 分钟、5 分钟、15 分钟的间隔做指数退避重试三次仍然失败就标记为“派单失败”转人工处理。第三是用户取消预约单要走退款流程状态机里必须有对应的取消分支支付成功后取消、预约触发后取消、骑手接单后取消三个时间点退款逻辑完全不同。预约触发的准确率还取决于后端服务器时间。很多工程部署在云服务器上云服务器的系统时间可能和标准时间有几秒偏差挂号同步服务或者用云厂商自带的时间同步组件。预约单对分钟级偏差不敏感但如果扫描器每 60 秒跑一次再加上几秒偏差实际触发时间可能比预约时间晚了两分钟用户端体验就是“到点了没动静”差评就来了。5.3 骑手实时位置上报频率、坐标系和地图渲染的配合骑手端位置上报频率直接影响派单距离和顺路度计算的准确性。默认 15 秒一次能覆盖大多数场景但高峰期可以把频率提到 10 秒低峰期降到 20 秒。位置上报要走独立的轻量接口比如POST /rider/location只传 lat、lng、heading 三个字段别把整个订单列表一起传上来。后端收到后更新rider_locations表内存里保留一份最新坐标派单打分时直接读内存不要实时查数据库否则每次派单都要扛一次全表扫描。坐标系和地图渲染有个容易忽视的细节。微信小程序wx.getLocation返回type: gcj02的坐标腾讯地图组件默认也是 gcj02这条链路是一致的。但工程里如果后端的距离计算用了标准的 haversine 公式而骑手上报的坐标是 gcj02算出来的距离和地图上肉眼看到的距离会有几十米偏差。城市里几十米可能差一个路口校园里几十米可能就把订单派给了一个完全不顺路的人。解决方法是前后端统一用 gcj02如果从项目里看到有 wgs84 的转换函数先确认有没有被调用。6. 调参后怎么验证派单日志回放是最笨也最有效的方法调派单参数最怕的事是改完了不知道是变好了还是变坏了全凭感觉。我自己的习惯是在派单引擎里加一行日志每一轮派单决策都记录订单号、候选骑手列表、每个骑手的距离分、负载分、方向分、总分以及最终指派结果。这个日志写到独立的 dispatch_log 表只插不删保存 30 天。线上出问题时的排查路径就清晰了用户投诉订单没骑手接直接按订单号查 dispatch_log看当时候选骑手都有谁、为什么没人超过阈值是距离太远还是负载分全被扣光了。再配合抓包看小程序端请求用 charles 这类工具抓用户端下单和骑手端上报位置的请求参数确认入参经纬度有没有传反、坐标系对不对。小程序本身是个黑匣子不看请求和返回只看页面很多问题永远定位不了。调参后的验证周期至少一周。改完权重后对比调整前后平均接单时长、骑手空驶率、单均配送时长三个指标。我踩过最深的坑是只看平均接单时长——负载权重调高后接单时长确实变短了但骑手手里压单太多单均配送时长暴涨用户等待总时间反而更长。后来改成同时看接单时长和配送时长两个指标负荷权重才调到一个合理值。派单参数没有标准答案只有基于自己业务数据不断回放、对比、调整的循环。希望这些经验能帮你把这套开源的派单系统真正用起来少走几步弯路。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站