资讯动态

跑腿小程序全开源:智能派单与同城配送系统架构实战

发布时间:2026/10/27 18:01:09 来源:尧图企业网站定制
简介这是一套基于Fastadmin、ThinkPHP与Uniapp开发的全开源同城跑腿系统源码面向跑腿创业团队、校园配送运营者及小程序开发者覆盖用户端、骑手端与运营后台三端可私有化部署无加密限制。系统支持帮取帮送、预约取件、地图选点与导航规划并内置按距离重量计价、临时加价、小费与物品保价等费用规则派单侧提供一键抢单、语音弹窗提醒、接单大厅距离排序、自由开工以及按骑手距离、送货地址与等级智能推送的系统派单模式兼顾兼职与全职骑手佣金结算。资源包共2000个文件以1079个js、265个html、235个vue、213个json为主辅以142个md说明、58个css样式及sql建表脚本压缩包约59.12MB前后端与数据库结构完整。目前已有247人学习下载适合需要快速搭建同城配送平台、研究派单算法与计价逻辑的开发者参考复用。1. 跑腿小程序全开源一套代码怎么同时撑起用户端和骑手端校园里最常见的场景晚上十点宿舍楼下一堆人等着取快递骑手在几个取件点之间来回跑用户在小程序里刷新状态。这套「跑腿小程序 智能派单 同城配送」的组合本质是把「下单—派单—取件—送达—结算」这条链路用一套后端串起来前端拆成用户端和骑手端两个入口。全开源意味着你能拿到完整代码自己部署、改派单规则、接自己的支付和地图而不是被某个 SaaS 按单抽成。它适合三类人想在学校或社区跑同城配送的小团队、需要给现有商户加跑腿能力的开发者、以及想拿一套真实业务系统练手全栈的人。核心难点不在页面而在智能派单——订单怎么分配给骑手直接决定履约成本和用户体验。下面从架构、派单算法、两端实现到踩坑一层层拆开讲。2. 先想清楚架构用户端、骑手端和调度服务怎么分2.1 三角色与数据流一套跑腿系统最少有三个角色用户下单方、骑手履约方、调度服务撮合方。用户端和骑手端通常都是小程序共用一套后端 API但权限和可见数据完全不同。用户端能看自己的订单和骑手位置骑手端能看分配给自己的订单池和取送地址调度服务在后台按规则把订单推给骑手。数据流是这样的用户下单写入订单表状态为「待派单」调度服务轮询或监听新订单按派单策略选出候选骑手推送通知骑手接单后状态变「已接单」开始取件取件完成变「配送中」送达后用户确认状态「已完成」触发结算。每一步状态变更都要落库并推送给相关方否则用户端会出现「状态不刷新」的经典问题。常见做法是订单表加一个status字段用整数枚举配合一张order_status_log记录每次变更方便排查「订单卡在某个状态」的玄学问题。2.2 技术选型为什么多数开源方案选小程序 Node/Java 后端跑腿业务的特点是「轻前端、重调度、强实时」。前端用微信小程序原生或 uni-app原因是用户和骑手都习惯在微信里操作不用装 App。后端主流是 Node.jsExpress/NestJS或 JavaSpring Boot数据库 MySQL 存订单和用户Redis 做骑手位置缓存和派单队列地图用高德或腾讯的 Web 服务 API 算距离和路径。选 Node 的理由是派单逻辑里大量 IO查骑手、算距离、发通知异步模型省事选 Java 的理由是团队已有生态、事务控制更稳。两者都能跑关键是 Redis 一定要上——骑手位置每秒都在变直接压 MySQL 会拖垮订单查询。提示地图 API 的「骑行距离」和「直线距离」差别很大派单算距离时用直线距离做初筛、骑行距离做精排能省大量配额。2.3 最小可运行骨架下面是一个订单状态流转的核心表结构MySQL 建表语句字段注释写清了每个状态的用途CREATE TABLE order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL COMMENT 下单用户, rider_id BIGINT DEFAULT NULL COMMENT 接单骑手未派单为 NULL, type TINYINT NOT NULL COMMENT 1取件 2代买 3其他, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待派单 1已接单 2取件中 3配送中 4已完成 5已取消, from_addr VARCHAR(255) NOT NULL COMMENT 取件地址, to_addr VARCHAR(255) NOT NULL COMMENT 送达地址, from_lng DECIMAL(10,6) COMMENT 取件经度, from_lat DECIMAL(10,6) COMMENT 取件纬度, to_lng DECIMAL(10,6) COMMENT 送达经度, to_lat DECIMAL(10,6) COMMENT 送达纬度, fee INT NOT NULL COMMENT 配送费单位分, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_status (status), KEY idx_rider (rider_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;逻辑说明status用整数而不是字符串是为了派单查询时能走索引、比较快经纬度单独存字段而不是只存地址文本因为派单要算距离解析地址文本太慢。fee用「分」存整数避免浮点误差。idx_status让调度服务能快速捞出所有「待派单」订单idx_rider让骑手端查自己的订单不扫全表。参数怎么改如果业务只做校园经纬度精度可以降到 5 位如果做全城from_addr建议拆成「楼栋 详细」两列方便骑手端导航。3. 智能派单算法从「抢单」到「系统派单」怎么落地3.1 三种派单模式的取舍跑腿系统常见的派单模式有三种抢单、系统派单、混合。抢单是订单进公共池骑手自己抢实现最简单但高峰期会出现「近单被抢光、远单没人接」。系统派单是后台按规则直接指派履约率稳但规则写不好会得罪骑手。混合模式是先系统派给最合适的骑手N 秒不接再放公共池。校园跑腿我一般推荐混合午晚饭点单量集中纯抢单会乱纯系统派单骑手觉得不自由。混合模式的关键参数是「指派等待时长」校园场景设 15 到 30 秒比较合适太短骑手来不及看太长用户等得急。3.2 派单打分函数怎么写系统派单的核心是一个打分函数对每个在线骑手算一个分数分高的优先。分数通常由三部分组成——距离、当前负载、历史履约。距离越近分越高手上订单越多分越低历史准时率高的加权。def score_rider(rider, order, now): # 距离分骑手到取件点的直线距离越近越高用 1/(1d) 归一化 d haversine(rider.lat, rider.lng, order.from_lat, order.from_lng) dist_score 1.0 / (1.0 d / 1000.0) # d 单位米除以 1000 转公里 # 负载分手上未完成订单越多分越低 load_score 1.0 / (1.0 rider.active_orders) # 履约分历史准时率0~1 perf_score rider.on_time_rate # 权重可按业务调校园场景距离权重最高 return 0.5 * dist_score 0.3 * load_score 0.2 * perf_score逻辑说明haversine算球面两点直线距离比调地图 API 快得多适合做初筛。三个分项都归一化到 0~1 再加权避免量纲不同导致某一项压倒其他项。权重 0.5/0.3/0.2 是校园场景的经验值距离最重要因为校园范围小、骑手密度高谁近谁去最划算。参数怎么改如果做的是全城配送距离权重要降、负载权重升因为远距离骑行时间长让一个骑手顺路带多单更划算。active_orders建议设上限比如超过 5 单就不再派新单防止骑手爆单。3.3 派单触发与并发控制派单不能等用户下单后慢慢算常见做法是订单入库后发一条消息到 Redis 队列调度服务消费队列、算分、指派。这里有个必踩的坑同一订单可能被两个调度实例同时处理派给两个骑手。解决办法是用 Redis 的SETNX给订单加锁锁的 key 是订单 ID过期时间设 10 秒。# 订单入库后投递到派单队列 redis-cli LPUSH dispatch_queue order:10086 # 调度服务消费前先抢锁抢到才处理 redis-cli SET dispatch_lock:10086 1 NX EX 10逻辑说明LPUSH把订单 ID 推进队列调度服务用BRPOP阻塞取避免空轮询。SET NX EX是原子操作只有第一个抢到的实例能拿到锁处理完删掉锁。过期时间 10 秒是兜底防止实例崩溃后锁不释放导致订单永远派不出去。参数怎么改队列积压严重时可以起多个调度实例并行消费锁机制保证不重复派单。锁过期时间要大于派单逻辑最长耗时否则会出现「锁过期了第一个实例还在跑」的重复派单。4. 用户端和骑手端两端共用一个后端怎么不打架4.1 接口权限隔离用户端和骑手端共用后端最容易出的问题是越权骑手能查到别人订单用户能改订单状态。做法是在 JWT 里带role字段中间件按角色拦截。用户只能访问user_id等于自己的订单骑手只能访问rider_id等于自己的订单调度相关接口只对内部服务开放。// Express 中间件按角色和归属校验订单访问权限 function canAccessOrder(req, res, next) { const { role, uid } req.user; // 从 JWT 解出 const order await OrderModel.findById(req.params.id); if (!order) return res.status(404).json({ msg: 订单不存在 }); if (role user order.user_id ! uid) { return res.status(403).json({ msg: 无权访问 }); } if (role rider order.rider_id ! uid) { return res.status(403).json({ msg: 无权访问 }); } req.order order; next(); }逻辑说明先查订单再比对归属比在 SQL 里拼WHERE user_id ?更灵活因为骑手端有些接口要查「待抢单」这种不属于自己的订单。req.order挂到请求上后续处理函数不用重复查库。参数怎么改如果要做「骑手查看附近待抢单」需要单独开一个接口只返回脱敏后的地址和距离不返回用户手机号。4.2 骑手位置上报与用户端展示用户端要看到骑手实时位置靠的是骑手端定时上报经纬度后端写 Redis用户端轮询或走 WebSocket。小程序后台定位有频率限制常见做法是骑手端在「配送中」状态每 10 到 15 秒上报一次Redis 里存rider:location:{rider_id}设 60 秒过期防止骑手下线后位置还显示。# 骑手端上报位置后端写入 Redis def report_location(rider_id, lng, lat): key frider:location:{rider_id} redis.hset(key, mapping{lng: lng, lat: lat, ts: time.time()}) redis.expire(key, 60) # 60 秒不更新视为离线逻辑说明用 Hash 存经纬度和时间戳方便一次取出。过期时间 60 秒是「心跳」机制骑手端崩溃或断网后位置自动失效用户端就不会看到一个不动的假位置。参数怎么改校园场景范围小上报间隔可以放宽到 20 秒省电全城配送建议 10 秒否则用户端地图上骑手会「跳格」。4.3 订单状态推送状态变更要主动推给两端否则用户会一直手动刷新。小程序里常用订阅消息骑手端可以用 WebSocket 长连接。关键点是推送失败要有兜底订阅消息用户可能没授权那就退化成用户端轮询。注意订阅消息是「一次性」的用户授权一次只能推一条要在下单、接单、送达等关键节点分别引导授权别指望一次授权推全程。5. 避坑与排查跑腿系统上线后最容易翻车的五件事5.1 订单卡在「待派单」不动现象用户下单后状态一直不变骑手端也看不到单。原因通常是调度服务没消费队列或者派单锁没释放导致后续订单全被跳过。排查先看 Redis 队列长度LLEN dispatch_queue如果一直涨说明消费者挂了再看有没有残留的dispatch_lock:*键。解决是给调度服务加健康检查锁的过期时间设合理消费失败要重新入队而不是丢弃。5.2 同一订单派给两个骑手现象两个骑手同时收到同一单都去取件。原因是派单没加锁或锁过期太短两个调度实例并发处理。解决是用SET NX EX抢锁锁过期时间大于派单逻辑最长耗时指派成功后立即把订单状态改成「已接单」用数据库行锁兜底。5.3 骑手位置在地图上乱跳现象用户端看到骑手位置忽远忽近。原因是骑手端上报的经纬度坐标系不统一高德用 GCJ-02GPS 原始是 WGS-84混用会偏移几百米。解决是统一坐标系上报前转换后端存一种坐标系前端展示时再按地图 SDK 要求转换。5.4 高峰期派单越来越慢现象单量一上来派单延迟从几百毫秒涨到几秒。原因是每次派单都全量查在线骑手并逐个算距离骑手多了就慢。解决是用 Redis GEO 存骑手位置先按半径筛出附近骑手再算分把候选集从几百降到几十。5.5 用户确认收货后钱没到骑手账现象订单完成了骑手余额没变。原因是结算逻辑和状态变更没放在同一个事务里状态改了但结算失败。解决是把「改状态 写结算流水 更新余额」放一个数据库事务或者用消息队列保证最终一致结算失败要能重试。6. 派单权重怎么调一个可复现的调参方法派单权重没有标准答案得按自己的业务数据调。我一般用一个离线回放的方法把过去一周的真实订单和骑手轨迹导出来用不同权重跑一遍派单模拟看平均接单时长和骑手空驶距离两个指标。具体做法是写一个模拟脚本读历史订单对每个订单用当前权重算一遍该派给谁记录「从下单到骑手接单」的模拟时长以及骑手从当前位置到取件点的距离。跑几组权重对比权重组合距离/负载/履约平均接单时长平均空驶距离0.5 / 0.3 / 0.242 秒380 米0.7 / 0.2 / 0.135 秒290 米0.3 / 0.5 / 0.258 秒520 米从这组模拟数据能看出距离权重越高接单越快、空驶越短但骑手负载会不均热门骑手容易爆单。校园场景我最后定在 0.6/0.25/0.15 附近兼顾速度和公平。调参时别只看平均值要看 P95——极端慢的单才是用户投诉的来源。验证方法上上线后加一个埋点每单记录「派单耗时」和「骑手接单耗时」按小时聚合。如果某个时段派单耗时突然涨多半是骑手在线数不够或队列积压这时候该做的是引导骑手在线而不是继续调权重。这套系统我踩过最深的一个坑是早期没做派单锁测试环境单实例跑得好好的一上生产双实例就重复派单两个骑手跑到同一个取件点面面相觑。后来养成习惯任何涉及「抢」的逻辑先想并发再写代码。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑