简介一套可用于快速搭建外卖平台的开源源码包覆盖用户端小程序与App、商户端、配送端以及后端服务适合具备服务端或小程序开发基础的技术人员和创业团队进行二次开发与学习。资源包共2000个文件以JS、PHP、HTML等源码文件为主同时包含WXSS/WXML小程序页面、JSON配置、CSS样式以及PNG/JPG图片素材压缩包约135.72MB目录按多端模块划分方便定位前台页面、后台接口与相关资源。目前已有458人学习下载。商户端实现商品上架、库存调整、订单处理和营销活动配置配送端内置派单接单、地图导航、送达确认与配送员管理小程序端免安装即可完成浏览点餐、订单查询和在线支付后端提供规范的API接口用于数据交互。整套源码覆盖从下单到配送完成的完整业务闭环既可支持快速搭建可运营的外卖演示系统也为深入理解全栈项目架构提供了珍贵的实战参考尤其适合作为课程设计或服务端技术选型的阅读资料。1. 食刻外卖系统这个东西四端源码分别解决谁的问题想自建外卖平台的人多半在第一步就被成本劝退开发一个用户端 App 不算贵贵的是同时维护商户端、配送端和后台还要保证三端数据实时一致。食刻外卖系统源码的价值在于把这一整套摊开——用户小程序/App、商户端、配送端、运营后台全部开源意味着你不需要从零写订单流转、配送抢单、商户结算这些外卖行业通用的复杂逻辑。适合两类人一类是想快速上线本地外卖业务的创业者另一类是手里已有电商或 O2O 项目、想参考多端订单架构的技术负责人。这篇文章按我实际部署这类多端外卖系统的经验把从拉源码到能接真实订单的关键步骤和参数一次讲完。2. 本地跑通食刻外卖系统从导入数据库到多端联调2.1 四端架构的技术选型为什么后端不能只做一套 API拿到任何一份外卖系统源码第一件事不是急着启动而是先看目录结构确认每一端的技术栈。这类多端开源项目最常见的布局是后端一套 API 服务前端按业务角色拆成多个独立工程。食刻外卖系统源码里你大概率会看到这样的目录划分——用户端小程序和 App 共用一个接口层但打包产物不同商户端和配送端各自独立因为它们的操作频率、页面形态和权限模型差异太大。后端技术栈常见的是 Java Spring Boot 或 PHP Laravel 系。判断方法很简单看根目录有没有pom.xml或composer.json。我这边部署的版本是 Spring Boot 为主数据库用 MySQL缓存用 Redis消息推送走 WebSocket。选这个组合不是因为花哨而是外卖业务对订单状态的一致性要求极高——用户下单、商户接单、骑手取货这三个动作必须在秒级内同步到所有端。如果把商户端和用户端写在同一个 Web 工程里后期流量一上来数据库连接和接口超时会先拖垮你。四端共用一套 API 是对的但有一个前提接口层必须按角色做权限隔离。部署后第一件事就是检查后端的拦截器确认商户端接口带merchant_token、配送端接口带courier_token而不是一套user_token通吃。常见做法是改 Spring Boot 的拦截器注册类按 URL 前缀区分。这一步不做好后面上线就是安全事故。2.2 后端启动的最小配置数据库初始化与 Redis 连接在本地跑通这套系统核心是让后端服务先起来。先从源码包里的sql目录找到初始化脚本通常是一个init.sql或按模块拆分的多个.sql文件。这里有个容易翻车的细节不要直接用 Navicat 双击导入而是先建好数据库和账号再指定字符集导入否则中文字段很容易乱码。# 创建数据库utf8mb4 是外卖系统存储中文地址和备注的底线 CREATE DATABASE shike_order DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; # 导入初始化脚本 mysql -uroot -p shike_order /path/to/sql/init.sql # 确认核心表数量一般外卖系统至少有三四十张业务表 mysql -uroot -p -e USE shike_order; SHOW TABLES;导入完成后检查表里是否有基础数据。很多源码包为了演示效果会预置一个测试商户、几个测试商品和一个管理员账号。如果SHOW TABLES之后发现merchant表是空的说明这份源码需要你从后台手动入驻那就得先找到后台管理端的入口用预置的管理员账号创建商户。这一步决定了你后面登录商户端时有没有数据可看。接下来改后端配置。Spring Boot 项目的配置集中在src/main/resources/application.yml你需要改三处数据库连接、Redis 连接、文件存储路径。如果源码里带application-prod.yml或.env文件优先看环境配置文件开发环境的默认值往往指向localhost。spring: datasource: url: jdbc:mysql://127.0.0.1:3306/shike_order?useUnicodetruecharacterEncodingutf8mb4useSSLfalseserverTimezoneAsia/Shanghai username: root password: your_password redis: host: 127.0.0.1 port: 6379 database: 0 servlet: multipart: max-file-size: 10MB max-request-size: 20MB # 文件存储菜品图片和营业执照上传都走这里 file: upload-dir: ./uploads改完配置就可以启动后端。注意启动前先确认 Redis 已经跑起来否则 Spring Boot 启动时会因为连不上 Redis 直接报错退出。外卖系统里 Redis 不只是缓存还承担了购物车、验证码、骑手定位临时存储这些职责Redis 挂了等于整个系统不可用。# 常规 Spring Boot 启动方式 cd shike-server mvn clean package -DskipTests java -jar target/shike-server.jar --spring.profiles.activedev看到日志里出现Started ShikeApplication且没有报错说明后端起来了。你可以先请求一个健康检查接口验证这类多端系统通常会暴露/api/health或/actuator/health返回{status:UP}就说明数据库和 Redis 连接都正常。2.3 小程序和 App 的联调配置找到每个前端的请求入口后端跑通之后前端才是多数人卡住的地方。小程序端、App 端、商户端、配送端四个工程每个都有自己的接口地址配置。不要每个文件全局搜索http://去替换那样改完大概率漏掉 WebSocket 地址。常见做法是小程序和 App 工程里都会有一个config.js或request.js里面统一管理 API 域名。改这一处就够。我用 uni-app 打包的小程序工程举例配置长这样// src/config.js export default { // HTTP 接口地址本地联调用局域网 IP真机调试不能用 127.0.0.1 baseUrl: http://192.168.1.100:8080/api, // WebSocket 地址接收新订单通知、骑手位置更新 wsUrl: ws://192.168.1.100:8080/ws, // 七牛或阿里云 OSS 的图片地址前缀 imageUrl: http://192.168.1.100:8080/uploads }这里的头号坑是127.0.0.1。你在电脑上启动后端然后拿微信开发者工具跑小程序电脑上访问127.0.0.1没问题但你的手机如果做真机预览手机会把127.0.0.1理解为手机自己必然连不上。替换成局域网 IP比如192.168.1.100并在后端启动参数里加上--server.address0.0.0.0让后端服务监听所有网卡。改完配置文件在微信开发者工具里重新编译小程序。如果接口报request:fail先用电脑浏览器直接访问一下http://192.168.1.100:8080/api/health确认网络通不通。浏览器都打不开问题一定在网络或后端启动参数不在小程序代码。App 端和配送端如果是原生 Android 工程同样找build.gradle里的buildConfigField或strings.xml里的域名配置。如果是 uni-app 打的 App 包manifest.json里也有一个基础路径配置别忘了一起改。商户端如果是 Web 管理后台直接找.env.development文件里的VITE_API_BASE_URL或NODE_ENV对应的代理配置。3. 商户端与配送端源码拆解接单、派单、结算的三条业务主链路3.1 商户端核心模块营业状态、商品管理与订单处理商户端源码是这套系统里业务密度最高的一块。表面看是商品管理和订单列表实际背后是库存扣减逻辑、营业时间判断、自动接单开关三个联动模块。先看营业状态。商户端界面上有一个「营业中/已打烊」的切换按钮这个状态在数据库里是merchant表的一个字段但前端展示时不能只查这个字段——系统要根据当前时间判断是否在商户设置的营业时间内。源码里通常是一个定时任务或在查询时用 SQL 动态计算例如取餐时间设置在外卖平台上很严格修改营业时间会触发force_update标记。这一块调试时可以用商户后台的「预览店铺」功能直接看效果。商品管理的核心是库存和销量。开源系统里库存扣减常见做法是下单时扣减但这里有个并发问题如果用户下单后商户超时未接单系统自动取消订单库存要不要回补靠谱的源码会在订单取消的异步回调里做库存回补。部署后你要验证的就是这条回补链路做法是在商户后台手动取消一个已支付订单然后看商品库存是否加回来。如果不回补说明取消订单的队列消费者没注册成功。订单处理模块最需要仔细读。商户端收到新订单后通常有「接单」「拒单」「开始出餐」「出餐完成」四个动作每个动作对应一个接口。这里有个常见的设计差异接单是商户手动点还是系统根据auto_accept开关自动接单。自动接单逻辑一般在后端有一个定时轮询任务每几秒扫描一次待接单订单超过设定阈值自动确认。部署时如果不想要自动接单就把商户表里的auto_accept字段默认值改成0或者在后端配置文件里关闭定时任务。3.2 配送端工作流抢单池、订单指派与状态回传配送端源码解决的问题纯粹且聚焦骑手打开 App看到可抢订单抢单后按路线取货配送最后完成送达。这个流程在技术上有三个关键点抢单池的并发控制、订单状态回传的实时性、骑手轨迹的上报频率。抢单池是配送端最有意思的设计。多个骑手同时抢同一单只允许一个人成功。开源系统里常见做法是 Redis 分布式锁或数据库乐观锁。用 Redis 的做法是骑手点击抢单时后端执行SETNX命令给订单 ID 加锁抢到锁的返回成功没抢到的返回「订单已被抢」。这个方案在骑手数量少的时候没问题但如果骑手超过 200 人并发抢单Redis 锁的过期时间设置不当会造成「锁超时但业务还没处理完」的极端情况。部署后建议把锁的过期时间从默认的 3 秒改成 10 秒并在业务完成后主动释放锁。订单状态回传走的是 WebSocket。骑手点「已取货」后后端要推送给用户端「骑手已出发」推送通道异常是排查最多的地方。这里分享一个定位技巧先看后端的 WebSocket 连接数再用消息推送日志确认消费者是否收到消息。如果连接数正常但用户端收不到事件八成是前端没有监听对应的事件名——用户端小程序里监听的是order_status_changed而后端推送的是status_changed名字不匹配静默失败。骑手轨迹上报的逻辑很直接骑手客户端每隔几秒调用一次位置上报接口后端存入 Redis 列表用户在客户端看到的骑手位置就是从这个列表读的。要注意的是轨迹的清理策略如果只存不删Redis 内存会膨胀。很多源码用一个定时任务只保留最近 200 个坐标点超过就丢弃。3.3 订单状态机从用户下单到商户结算的完整流转三端源码都要引用同一套订单状态定义这是多端系统最容易混乱的地方。食刻外卖这类完整系统订单状态一般包含十几个节点但核心链路可以用一张表看明白状态触发方后续动作常见异常待支付用户提交订单创建支付单超时未支付自动取消已支付支付回调推送商户端回调重复通知需幂等商户已接单商户操作或自动接单推送配送端商户端超时未接单自动退款配送中骑手取货成功推送用户端骑手长时间不取货待收货骑手点击送达推送用户端误点送达需商户确认已完成用户确认收货或超时触发商户结算退款争议单冻结结算已取消用户/系统/商户退回库存和优惠卷退款延迟部署后务必先沿着「用户下单 → 支付 → 商户接单 → 骑手配送 → 完成」这条链路跑一遍用两个浏览器分别登录用户端和商户端观察状态变化。我遇到太多案例是状态机在「已支付」到「商户已接单」这段断掉原因是支付回调里更新订单状态的事务没有提交排查时要看后端日志里有没有Transaction rolled back的异常。商户结算是另一个容易踩坑的点。订单完成后系统要按平台抽成比例计算商户收入并把钱记入商户账户余额。这里的设计要分清「记账」和「打款」两个动作记账是结算流水表插入一条记录打款是调用微信商家转账接口把钱转给商户。大部分开源源码只做了记账打款功能留了接口但需要你自己配商户号。理解这一点很重要——上线运营前要把结算账单跑通否则商户看到订单完成了但余额不动客服电话会被打爆。4. 从「能跑」到「能运营」地图、支付、小程序登录三个必配位置4.1 地图服务的秘密配送费计算和骑手轨迹都依赖它外卖系统离不开地图服务。这套系统里有三个位置用到地图用户选择收货地址时定位、商户设置配送范围时绘制地理围栏、骑手端实时显示轨迹。三个位置用的是同一个地图服务商的 SDK但后台配置却是分开的。先说配送范围。商户后台通常会有一个「绘制配送范围」的页面用 JavaScript API 在地图上画多边形。这个多边形保存到数据库后用户下单时要判断收货地址是否在范围内。靠谱的实现是把多边形顶点坐标存成经纬度数组下单时用射线法判断点是否在多边形内。这里有一个隐蔽的兼容问题不同地图服务商对经纬度的坐标系定义不同。国内地图服务商默认用 GCJ-02 坐标俗称火星坐标但 GPS 设备原始的定位是 WGS-84。如果骑手端上报的是原始 GPS 坐标而地图是 GCJ-02轨迹会出现几十米的偏移看起来就像骑手在非机动车道上飞驰。解决做法是在后端做一个坐标转换的工具类统一转成 GCJ-02 再存储和展示。配送费的计算在源码里通常是一个可配置的策略类。常见逻辑是「基础配送费 距离加价」距离按照商户到用户收货点的直线距离算。这里要注意直线距离和实际骑行距离差异很大。跑通这个系统后建议去地图服务的 Web API 申请一个「骑行路径规划」的接口用真实道路距离来算配送费避免 3 公里内一口价导致亏损。地图服务商一般在控制台创建两个应用——一个 Web 端用于商户后台绘制配送范围一个移动端 SDK 用于用户 App 定位和骑手轨迹追踪。两个应用的 Key 不能混用Android 和 iOS 的 Key 也要分开申请这是新手最容易反复踩的配置坑。4.2 支付和退款微信支付商户号与回调地址的落地配置外卖是强支付场景支付配不通系统演示得再好看也没用。食刻外卖这套源码大概率接的是微信支付因为外卖场景里微信支付的用户习惯最成熟。部署支付需要准备三样东西微信支付商户号、API v3 密钥、商户证书。# 支付配置一般长这样位置在 application.yml 或独立的 pay-config.yml wechat: pay: # 商户号在微信支付商户平台申请不是开放平台的 AppID mch-id: 16xxxxxx # AppID小程序或 App 对应的凭据和商户号做绑定 app-id: wx1234567890abcdef # API v3 密钥32 位字符串在商户平台设置 api-v3-key: your-32-bit-api-v3-key # 商户证书序列号证书下载后可以在线查看 mch-serial-no: xxxxxxxxxxxxx # 证书私钥文件路径apiclient_key.pem private-key-path: /path/to/apiclient_key.pem # 支付回调地址微信服务器通知支付结果的入口 notify-url: https://your-domain.com/api/pay/wechat/notify回调地址这块有一个历史遗留认知需要纠正支付回调不推荐配置在公网 IP 上微信支付回调只接受 80 和 443 端口且要求域名已备案。本地联调阶段可以用内网穿透工具把本地端口映射到一个临时域名但上线前必须换成正式域名并且回调路径要和源码里WechatPayController的RequestMapping完全一致。很多人改了自己的回调地址却忘了源码里 controller 的路径前缀导致支付成功后订单一直是待支付状态。退款配置同样容易被忽略。商户端点「退款」按钮实际调用的是微信支付退款接口。退款有一个硬性要求退款金额不能超过原订单金额且订单如果已经部分退款再次退款时要传out_refund_no作为幂等键。源码里如果退款逻辑做得好会先查订单实付金额再调退款防止负数漏洞。小程序支付还有一个「调起支付」的环节需要后端先调用微信支付的「统一下单」接口拿到prepay_id再用prepay_id生成小程序端需要的签名参数。这里的签名算法容易出错因为参与签名的字段名单和顺序微信官方文档写得很死板必须严格按照文档顺序拼接。排查时用微信官方提供的签名校验工具不要自己肉眼比对。4.3 小程序登录和手机号授权经营里最常改的两个地方微信小程序登录是外卖系统用户体系的基础。用户第一次打开小程序时要走wx.login - 后端 code2Session - 拿到 openid - 静默注册用户这条链路。但外卖场景有个特殊需求用户必须绑定手机号才能下单不然骑手联系不上人。现在微信的规则变化比较大Android 和 iOS 上获取手机号的策略有差异新的审核规范倾向于要求用户主动点击「获取手机号」按钮而不是进入页面就弹窗。源码里通常写的是老逻辑你可能要手动改成新规范// 用户点击按钮后才触发手机号授权 button open-typegetPhoneNumber getphonenumberhandlePhoneNumber 一键登录 /button// 拿到加密数据后交给后端解密并绑定到用户账号 async handlePhoneNumber(e) { if (e.detail.errMsg getPhoneNumber:ok) { const { code } e.detail const res await request({ url: /api/user/bindPhone, method: POST, data: { code } }) // 后端拿到临时 code 后调微信接口换取真实手机号再绑定 if (res.data.success) { uni.showToast({ title: 登录成功 }) } } }这段逻辑的后端实现里注意临时code只能使用一次且有效期只有几分钟。如果用户多次点击按钮前端要防重后端也要对同一个code做幂等处理否则并发请求下会出现手机号绑定失败。除了手机号小程序端还经常要配「订阅消息」。外卖订单状态的每一次变化比如「商户已接单」「骑手已取货」都应该给用户推一条订阅消息。这个功能需要在小程序后台申请消息模板把模板 ID 填到源码的配置里。许多人忽略的是订阅消息必须由用户主动触发授权也就是下单的时候弹窗让用户勾选「允许通知」。如果用户没勾选后续订单状态推送就会静默失败这不是你代码的问题而是微信的产品机制限制。5. 多端联调与上线避坑五个高频问题的现象、原因、处理5.1 商户端能登录但用户端一直加载中各端环境变量不一致现象同一套后端服务商户端的管理后台访问正常但用户端小程序请求接口一直转圈过了十几秒才报超时。原因商户端如果是 Web 后台浏览器访问时走的是你本机网络后端地址自然能通。用户端小程序如果开了「不校验合法域名」的开关开发环境能跑但一旦关掉开关真机调试小程序只能请求 HTTPS 或已备案的合法域名。这是一条微信的硬规则你本地用 IP 端口访问没问题换成真机就立刻暴露。处理开发阶段在微信开发者工具里勾选「不校验合法域名、web-view业务域名、TLSHTTPS版本」并确认详情面板里项目配置的本地地址确实是局域网可达的 IP。上线前把小程序的 request 域名和后端 WebSocket 域名都加到微信公众平台的「服务器域名」里。注意 WebSocket 域名和 request 域名是分开配置的一个以wss://开头一个以https://开头各填各的。5.2 骑手位置在地图上飘忽不定坐标偏移与定位频率冲突现象骑手轨迹在商户端和用户端展示时位置一会儿在路东一会儿在路西甚至出现「穿墙」路径。原因前面提过的坐标系不统一是主因。骑手端如果直接使用 GPS 原生坐标上报而地图组件用的是国测局坐标GCJ-02两者之间存在约几十米到上百米的偏移。另一个原因是定位上报频率太高比如每 2 秒上报一次但地图 SDK 的轨迹平滑处理跟不上导致位置跳变。处理先确认位置上报接口的日志打印出 GPS 原始坐标WGS84和后端转换后的GCJ02坐标进行对比。如果差值超过 50 米说明转换函数没生效。再检查前端地图 SDK 是否开启了「高精度定位」模式有些地图组件为了省电默认用基站粗略定位误差能达到几百米。正确做法是骑手端配置为 GPS 优先且上报频率设置为每 5 秒一次连续行驶时根据上次坐标点距本次的距离做轨迹抽稀。5.3 用户支付成功但订单状态没变回调地址不可达或签名验签失败现象微信支付账单里能查到用户付款成功但商户端和用户端都没收到订单变化订单一直停在「待支付」状态。原因支付回调链路断掉。第一可能是回调地址在公网不可达用curl从外网访问回调 URL 看是否返回 200这是最常见的。第二是验签失败微信支付用 API v3 的证书签名如果你的服务器时间和真实时间差超过 5 分钟签名校验会失败微信会不断重试通知直到你的接口正确响应。处理打开后端日志搜索wechat notify相关的关键词。日志为空说明请求根本没到你的服务器检查服务器安全组是否放通了 443 端口。日志有报错但提示验签失败检查服务器时间执行date命令看时间是否准确安装并开启 NTP 时间同步。处理完这两步再在微信支付商户平台后台手动触发一次「订单状态查询」看能否推送到你的接口。5.4 商户端提示「有订单未处理」但列表里看不到WebSocket 连接断了但界面没刷新现象商户端在电脑上挂着新订单来了会响铃但点进订单列表是空的刷新页面后订单才出现。原因这是 WebSocket 推送和 HTTP 接口数据不同步的经典问题。响铃是 WebSocket 实时推过来的事件列表数据是 HTTP 接口查数据库查出来的。如果你的后端接口查询了 Redis 里的一个列表缓存而新订单写入数据库后没有同步更新缓存就会出现「提示有新单但查不到」的错位。处理订单写入数据库后必须同步更新 Redis 的结果缓存或者直接关闭订单列表的缓存查询改成实时查库。商户端订单量本身不大实时查库完全扛得住没必要为了缓存引入一致性负担。我处理这类问题时的排查路径是先看接口返回的数据结构再确认是数据库查不到还是缓存没更新大概率是后者。5.5 多商户结算金额出现分账差异按订单抽成和按时间段抽成逻辑冲突现象运营后台的结算报表里同一家商户一天的订单抽成金额和商户端自己看到的结算金额对不上差了几毛到几块钱不等。原因外卖系统的抽成逻辑如果是「按订单实时抽成」那每笔订单完成时就会记一条结算明细。但如果运营后台另有一套「按日/按月汇总抽成」的定时任务两套逻辑对「订单完成时间」的定义不同——一个按用户确认收货时间一个按骑手送达时间时间差落在跨天边界时同一个订单就被算进了不同天的结算里。处理统一结算口径。把结算明细表的生成逻辑改为只依赖订单的「完成时间」并且在后端配置里约定时区为GMT8。如果源码里两套逻辑并行关闭其中一套优先保留明细级别的结算流水因为月底对账查的是每一笔订单的来源而不是一个汇总数字。结算表设计要保留order_id和order_amount的冗余字段避免关联查询时原始订单被修改导致对不上。6. 上线前最后三天支付测试、备份回滚和日志监控从源码到真运营中间还差一个「灰度心态」。不要一上来就在全部商户上启用系统先用自己的一个测试商户跑三天真实订单。第一件事是「一分钱订单」验证。让测试商户上架一个 1 元商品你作为用户真实下单、微信支付、全程观察订单状态流转。这一步不是测功能而是测微信支付的回调和商户端的「接单—出餐—配送—完成」一条链路。用真实的微信支付环境而不是沙箱环境因为沙箱回调地址和正式环境不同验证结果不可靠。第二件事是备份与回滚脚本。部署完后立即用mysqldump全量备份一次数据库并把备份文件放到和网站服务器不同的机器上# 上线前执行一次全量备份压缩后保留至少一周 mysqldump -uroot -p shike_order | gzip ~/backup/shike_order_$(date %Y%m%d).sql.gz # 回滚时要能快速重建库 gunzip -c ~/backup/shike_order_*.sql.gz | mysql -uroot -p shike_order第三件事是日志监控。后端启动后把application.log的级别调到INFO以上并重点监控ERROR和WARN。排查定位时没有日志等同盲人摸象。我喜欢再加一条硬性习惯每天早上看一眼「支付回调失败」这个关键词的日志数量它是订单异常的先行指标如果超过 5 条就要立刻处理。这三板斧做完再谈推广和招商不迟。多端系统本身就容易让人手忙脚乱先把支付、结算、日志这三件事钉死后面碰到任何运营问题都有据可查。这套做法陪我好几个外卖项目走过了上线期希望帮到你。本文还有配套的精品资源点击获取