资讯详情

跑腿小程序全开源:智能派单与系统派单双模式源码解析

📅 2026/10/27 9:03:04 | 华诺云谱 👁 阅读
跑腿小程序全开源:智能派单与系统派单双模式源码解析
简介这是一套基于FastadminThinkPHP与Uniapp开发的全开源同城跑腿系统源码面向跑腿创业团队、校园配送运营者及需要二次开发的技术人员覆盖用户端、骑手端与运营后台三端支持帮取、帮送两种业务模式可私有化部署且无加密。功能上包含按距离与重量自动计价的规则引擎、夜间或恶劣天气临时加价、预约取件、跑腿小费、物品保价、地图选点与路线导航并同时提供一键抢单、接单大厅主动接单、自由开工听单以及依据骑手距离、送货地址与等级进行智能推送的系统派单模式兼职与全职骑手均可参与佣金结算。资源包共2000个文件以1079个js、265个html、235个vue、213个json为主另含142个md说明、58个css样式及sql建表脚本等压缩包约59.12MB前后端与数据库结构完整。目前已有247人学习下载适合作为同城配送项目的落地参考与二次开发基础。1. 跑腿小程序全开源一套能跑通智能派单的源码到底长什么样同城配送和校园跑腿这两年的需求一直在涨我身边不少做本地生活的朋友都在琢磨自己搭一套系统。但真去搜「小程序源码」翻出来的要么是只有用户端下单页面的空壳要么是派单逻辑写死在代码里的半成品骑手端和调度端根本对不上。这套全开源的跑腿小程序源码把用户端、骑手端和后台调度三块都放出来了核心是那套智能派单和系统派单的双模式调度逻辑。它适合谁想快速验证同城配送商业模式的小团队、需要给校园跑腿项目做二次开发的技术负责人以及想拆解派单算法怎么落地到实际订单流的后端工程师。预约取件这种非即时场景也覆盖了不是只能跑外卖那种即时单。2. 拆开源码包用户端、骑手端与调度后台的模块划分2.1 三端目录结构与技术栈对应关系拿到源码包先别急着往服务器上扔花十分钟把目录结构捋清楚后面改代码能省掉大量翻文件的时间。这套项目按端拆得很干净根目录下通常能看到user-app、rider-app、admin-server和dispatch-core四个主目录。用户端和骑手端都是小程序形态走的是原生小程序语法加组件化封装后台调度是独立服务负责订单池管理和派单计算。# 典型的目录结构不同版本可能有细微差异 ├── user-app/ # 用户端小程序下单、支付、订单追踪 │ ├── pages/ │ │ ├── order/ # 下单流程页 │ │ └── track/ # 骑手位置追踪页 │ └── utils/ │ └── request.js # 统一请求封装改 baseURL 就在这里 ├── rider-app/ # 骑手端小程序接单、抢单、状态上报 │ ├── pages/ │ │ ├── hall/ # 接单大厅 │ │ └── route/ # 配送路线页 │ └── utils/ │ └── location.js # 位置上报逻辑派单精度依赖它 ├── admin-server/ # 后台管理订单监控、骑手管理、财务对账 │ ├── controllers/ │ └── models/ └── dispatch-core/ # 派单核心订单池、骑手池、匹配算法 ├── strategy/ # 派单策略智能派单和系统派单在这里切换 └── config/ └── dispatch.yaml # 派单参数配置文件这个划分的好处是调度逻辑独立成模块你改派单规则不用动小程序端的代码。dispatch-core里的strategy目录是重点智能派单和系统派单的差异基本都在这几个文件里。dispatch.yaml是调参入口后面讲参数怎么设会专门说。2.2 数据库表设计与订单状态流转订单状态机是跑腿系统的骨架状态定义错了后面全是坑。这套源码的订单表设计得比较规范核心字段包括订单 ID、用户 ID、骑手 ID、订单类型即时单/预约单、取件地址、送达地址、预约时间、订单状态和派单模式。状态流转大致是待支付 → 待派单 → 已派单 → 骑手已接单 → 取件中 → 配送中 → 已送达 → 已完成。预约取件单在「待派单」之前会多一个「待生效」状态到预约时间前若干分钟才进入派单池。-- 订单表核心字段简化版实际字段更多 CREATE TABLE orders ( id bigint NOT NULL AUTO_INCREMENT, user_id bigint NOT NULL, rider_id bigint DEFAULT NULL COMMENT 派单后写入, order_type tinyint NOT NULL DEFAULT 1 COMMENT 1即时单 2预约单, dispatch_mode tinyint NOT NULL DEFAULT 1 COMMENT 1智能派单 2系统派单, status tinyint NOT NULL DEFAULT 0 COMMENT 0待支付 1待派单 2已派单..., appoint_time datetime DEFAULT NULL COMMENT 预约单生效时间, pickup_addr varchar(255) NOT NULL, deliver_addr varchar(255) NOT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_status_type (status, order_type), KEY idx_rider (rider_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;idx_status_type这个联合索引很关键派单服务轮询待派单订单时会频繁按状态和类型筛选没这个索引订单一多查询就拖慢整个调度周期。appoint_time字段只在预约单里有值即时单为 NULL派单逻辑里靠这个字段区分是否到了可派单时间。2.3 派单核心模块的入口与配置加载dispatch-core启动时会先加载dispatch.yaml把派单模式、骑手筛选半径、最大并行单量这些参数读进内存。入口文件一般叫dispatcher.js或dispatch_service.py里面有个定时轮询或者消息队列触发的调度循环。每次调度做三件事从订单池捞待派单订单、从骑手池筛可用骑手、按策略做匹配并写回结果。// dispatch-core/strategy/index.js 派单策略入口示意 const config require(../config/dispatch.yaml); // 加载派单参数 const smartStrategy require(./smart); // 智能派单多因子评分 const systemStrategy require(./system); // 系统派单规则轮询 async function dispatch(order) { // 根据订单的 dispatch_mode 字段选择策略 const strategy order.dispatchMode 1 ? smartStrategy : systemStrategy; const riders await getAvailableRiders(order, config.radius); // 按半径筛骑手 if (!riders.length) { await pushToRetryQueue(order); // 无可用骑手进重试队列 return; } const matched strategy.match(order, riders, config); await assignOrder(order.id, matched.riderId); // 写回骑手 ID }config.radius控制骑手筛选半径默认值通常在 3 到 5 公里校园场景可以调小到 1 公里因为骑手密度高。pushToRetryQueue是无骑手可用时的兜底避免订单卡在待派单状态一直空转。智能派单的match方法内部会算距离、骑手当前负载、历史准时率等因子系统派单则简单按轮询或先到先得分配。3. 智能派单与系统派单两种模式的参数配置与切换3.1 智能派单的多因子评分逻辑智能派单不是玄学本质是把「谁最合适送这单」拆成几个可量化的因子加权求和后取最高分。这套源码里常见的因子有四个骑手到取件点的距离、骑手当前在手单量、骑手历史准时率、订单方向与骑手当前行进方向的一致性。每个因子归一化后乘权重权重在dispatch.yaml里配。# dispatch-core/config/dispatch.yaml 派单参数配置 dispatch: mode: 1 # 1智能派单 2系统派单全局默认 radius: 3000 # 骑手筛选半径单位米 max_parallel_orders: 5 # 骑手最大并行单量 smart: weight_distance: 0.4 # 距离权重越高越倾向就近派单 weight_load: 0.3 # 负载权重越高越倾向给空闲骑手 weight_punctuality: 0.2 # 准时率权重 weight_direction: 0.1 # 方向一致性权重 system: assign_rule: round_robin # 系统派单规则round_robin 或 first_come retry: interval: 30 # 无骑手可用时重试间隔单位秒 max_retry: 10 # 最大重试次数超过转人工weight_distance设 0.4 意味着距离是主导因素这适合订单密度均匀的城区。校园跑腿场景骑手都在校内距离差异不大可以把weight_load提到 0.4 让单量分配更均匀。max_parallel_orders设 5 是经验值设太高骑手容易超时设太低运力浪费。retry.interval和max_retry配合用30 秒重试一次、最多 10 次超过就转人工调度避免订单无限期挂着。3.2 系统派单的规则轮询实现系统派单比智能派单简单适合骑手数量少、订单量稳定的场景比如固定几个骑手跑一个校园。它的逻辑是按规则轮流分配不计算复杂评分。round_robin模式维护一个骑手队列每来一单分配给队首骑手然后该骑手移到队尾first_come模式则是谁先上报空闲就派给谁。// dispatch-core/strategy/system.js 系统派单实现示意 let riderQueue []; // 轮询队列 function roundRobinMatch(order, riders) { // 过滤掉已达最大并行单量的骑手 const available riders.filter(r r.currentLoad config.max_parallel_orders); if (!available.length) return null; // 取队首骑手分配后移到队尾 const picked available.shift(); available.push(picked); riderQueue available; // 更新队列 return { riderId: picked.id }; } function firstComeMatch(order, riders) { // 按骑手最后空闲上报时间排序取最早的 const sorted riders .filter(r r.currentLoad config.max_parallel_orders) .sort((a, b) a.freeReportTime - b.freeReportTime); return sorted.length ? { riderId: sorted[0].id } : null; }currentLoad是骑手当前在手单量每次派单前都要从骑手端同步最新值否则会超派。freeReportTime依赖骑手端定时上报空闲状态上报间隔太长会导致first_come不准。系统派单的优点是公平、可预测缺点是没法应对骑手位置和订单方向的动态变化订单高峰期容易出现「近的骑手没单、远的骑手跑断腿」。3.3 两种模式的切换时机与混合使用实际运营中很少从头到尾只用一种模式。常见做法是高峰期用智能派单提效率平峰期用系统派单保公平。切换可以在dispatch.yaml里改全局mode也可以按订单类型或区域动态切。比如校园跑腿的预约取件单因为时间确定、骑手可提前规划用系统派单轮询反而更稳即时单用智能派单抢时效。// 按订单类型动态选择派单模式的示例 function selectStrategy(order) { // 预约单用系统派单即时单用智能派单 if (order.orderType 2) { return systemStrategy; } // 高峰期11:00-13:00, 17:00-19:00用智能派单 const hour new Date().getHours(); if ((hour 11 hour 13) || (hour 17 hour 19)) { return smartStrategy; } return systemStrategy; // 平峰期系统派单 }这个动态选择逻辑可以放在dispatch-core的入口处也可以做成配置项让运营在后台改。注意切换模式时骑手端的接单大厅展示逻辑要跟着变智能派单下骑手看到的是系统推来的单系统派单下可能需要骑手主动抢单两端逻辑要对齐不然会出现骑手看不到单的情况。4. 本地跑起来环境搭建、依赖安装与首单验证4.1 后端服务与数据库初始化先把后端跑通再碰小程序端这样接口调试有目标。后端一般是 Node.js 或 Java 服务依赖装完后要初始化数据库。源码包里通常带一个init.sql或者 migration 脚本导入后建表并插入测试数据。# 后端服务启动流程以 Node.js 为例 cd admin-server npm install # 安装依赖 cp .env.example .env # 复制环境变量模板 # 编辑 .env填入数据库连接、Redis 地址、小程序 AppID 等 mysql -u root -p running_errand init.sql # 导入建表脚本 npm run dev # 启动开发服务默认监听 3000 端口.env里几个必须改的项数据库 host/port/user/password、Redis 地址派单队列和骑手位置缓存依赖它、小程序 AppID 和 Secret用户端登录用。init.sql导入后检查orders、riders、users三张表是否都有数据测试数据能帮你快速验证派单流程。npm run dev启动后访问http://localhost:3000/health看服务是否正常返回 ok 再往下走。4.2 小程序端配置与真机调试用户端和骑手端都是小程序用开发者工具打开对应目录改request.js里的baseURL指向你本地或测试服务器的地址。本地调试时小程序开发者工具需要勾选「不校验合法域名」否则请求本地 IP 会被拦。// user-app/utils/request.js 请求配置 const BASE_URL http://192.168.1.100:3000/api; // 改成你的后端地址 // 真机调试时不能用 localhost要用局域网 IP function request(options) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL options.url, method: options.method || GET, data: options.data, header: { Authorization: wx.getStorageSync(token) }, success: res res.statusCode 200 ? resolve(res.data) : reject(res), fail: reject }); }); }BASE_URL真机调试必须用局域网 IPlocalhost在手机上指向手机自己。Authorization头带的是登录后存的 token没登录时为空后端要放行登录和注册接口。两个小程序端的BASE_URL都要改改完先跑用户端下单流程再跑骑手端接单流程。4.3 首单全流程验证与派单日志查看验证分四步用户端下单、后台看到待派单订单、派单服务触发、骑手端收到单。每步都有对应的日志或数据变化可以确认。# 查看派单服务日志确认派单是否触发 tail -f logs/dispatch.log # 正常派单成功会输出类似 # [dispatch] order1001 modesmart riders8 matchedrider_003 score0.87 # [dispatch] order1001 assigned to rider_003 # 直接查数据库确认订单状态和骑手绑定 mysql -u root -p -e SELECT id, status, rider_id, dispatch_mode FROM orders WHERE id1001;dispatch.log里matchedrider_003 score0.87说明智能派单算出了最高分骑手并分配成功。如果日志里只有riders0说明筛选半径内没骑手检查骑手端是否上报了位置、radius是否设得太小。数据库里status应该从 1待派单变成 2已派单rider_id从 NULL 变成具体骑手 ID。首单跑通后再测预约取件单确认appoint_time到了才进派单池。5. 派单不准、订单卡住、骑手收不到单排查清单5.1 订单一直停在待派单状态现象是用户下了单后台也能看到但状态一直不变成已派单骑手端没反应。原因通常是派单服务没启动、或者订单没进派单池。先看dispatch-core进程在不在再看订单的status是不是 1、appoint_time是不是已经过了。预约单如果appoint_time设成了未来时间派单服务会跳过它这是正常的但如果是即时单还卡着就是服务问题。解决方法是重启派单服务并检查dispatch.yaml里的retry配置max_retry设太小会导致重试几次就放弃。5.2 智能派单总是派给同一个骑手现象是明明有多个骑手在线订单却集中派给某一个人。原因是评分因子里weight_load权重太低或者骑手的currentLoad没实时更新。智能派单算分时如果负载因子不生效距离最近的骑手会一直赢。解决方法是把weight_load从 0.3 提到 0.4 以上并检查骑手端上报currentLoad的频率建议每完成一单或每 60 秒上报一次。另外确认max_parallel_orders没设得过大设成 10 的话骑手接满之前都不会被过滤。5.3 骑手端接单大厅空白现象是骑手登录后接单大厅没有订单展示。原因可能是派单模式与骑手端展示逻辑不匹配。智能派单下订单是系统推给指定骑手的骑手端应该展示「待接单」列表系统派单下如果是抢单模式骑手端展示的是「可抢单」池。如果后端配的是智能派单但骑手端按抢单池去拉数据自然拉不到。解决方法是检查dispatch.yaml的mode和骑手端请求的接口是否对应智能派单对应/api/rider/pushed-orders系统派单对应/api/rider/available-orders。5.4 预约取件单提前被派出去现象是用户约了明天上午取件结果今天骑手就收到单了。原因是appoint_time的判断逻辑没生效或者时区处理有问题。派单服务捞单时应该加条件appoint_time IS NULL OR appoint_time NOW() INTERVAL 30 MINUTE只提前半小时把预约单放进池子。如果服务器时区和数据库时区不一致NOW()算出来的时间会偏导致提前派单。解决方法是统一用 UTC 时间存储展示时再转本地时区并在派单查询里显式加时间条件。5.5 派单日志报骑手池为空但骑手在线现象是日志显示riders0但骑手端明明显示在线。原因是骑手位置上报的坐标格式或坐标系不匹配。小程序端wx.getLocation默认返回 gcj02 坐标系如果后端按 wgs84 去算距离或者骑手位置压根没写进 Redis筛选半径内就查不到人。解决方法是确认骑手端上报位置时带了坐标后端存 Redis 的 key 没过期以及距离计算用的坐标系和上报的一致。校园场景还要注意围栏设置骑手如果被围栏挡在外面也会筛不到。6. 派单权重调优用历史订单回放验证参数改得对不对参数改完不能直接上生产得先验证。我一般会拿历史订单做回放把过去一周的订单按时间顺序重新喂给派单服务看新参数下的派单结果和实际结果的差异。这套源码的dispatch-core支持回放模式在dispatch.yaml里把replay.enabled设成 true指定订单时间范围就行。# 回放模式配置 replay: enabled: true start_time: 2024-01-01 00:00:00 end_time: 2024-01-07 23:59:59 output: logs/replay_result.csv # 回放结果输出含每单派给谁、评分多少回放跑完后看replay_result.csv重点对比三个指标平均派单距离、骑手单量标准差、预估超时率。平均派单距离降了说明weight_distance调对了单量标准差降了说明weight_load起作用了预估超时率要结合骑手历史准时率算如果超时率升了说明权重调得太激进。# 分析回放结果的简单脚本 import pandas as pd df pd.read_csv(logs/replay_result.csv) # 平均派单距离米 avg_distance df[dispatch_distance].mean() # 骑手单量标准差越小越均衡 load_std df.groupby(rider_id)[order_id].count().std() # 预估超时率派单距离 / 骑手历史均速 承诺时效 算超时 df[est_time] df[dispatch_distance] / df[rider_avg_speed] timeout_rate (df[est_time] df[promise_minutes] * 60).mean() print(f平均派单距离: {avg_distance:.0f}m) print(f骑手单量标准差: {load_std:.2f}) print(f预估超时率: {timeout_rate:.2%})这个脚本跑完如果平均距离和超时率都降了、单量标准差没升太多参数就可以上。如果超时率降了但单量标准差飙了说明骑手之间分配太不均得把weight_load往回拉一点。调参是个来回试的过程别指望一次到位。血泪经验是回放用的骑手历史均速要按骑手分别算不能用全局平均值否则预估超时率会失真。另外回放时要把retry逻辑也打开不然无骑手可用的订单会被跳过统计出来的超时率偏低。从那以后我每次改派单参数都强制走一遍回放确认三个指标都没恶化才敢推到线上。希望这套源码和调参思路能帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑