
简介这份源码包是一个基于PHPMySQL的美团三合一系统整合了外卖点餐、到店团购等常用业务模块适合需要快速搭建本地生活服务平台的开发者或创业者使用。资源包共2011个文件整体约83.07MB主要包含817个JS交互脚本、308个PNG图片、271个HTML页面、172个CSS样式以及48个PHP后台逻辑文件前台展示与后台管理兼顾导入数据库并按说明配置环境后即可运行和二次开发。当前已有159人学习参考。包内附带了公众号授权、微信支付参数配置以及数据库连接等部署说明可帮助使用者完成从环境搭建到支付联调的整体流程。整体而言这套源码功能模块完整适合熟悉ThinkPHP或有PHP基础的技术人员直接研究使用也可作为本地生活类项目开发时的参考模板。1. 美团三合一系统源码下载先别急着跑这套代码到底值不值得下搜“美团三合一系统源码下载”的人多半和我当年一样手头有一个“做个本地生活平台”的需求想找一份带用户端、商家端、管理后台的完整工程直接跑起来看业务闭环。这个“三合一”在源码圈里通常指一套订单体系同时撑起三种角色或三类业务常见组合是用户点餐、商家接单、管理端看板还有一类是外卖、团购、跑腿三种单据共用一个后端。它的价值在于让你不用从零写多端接口适合全栈入门、接私活演示和课程设计参考。但这类包的水很深二手转发的压缩包经常缺库、缺 SQL 脚本前端 baseURL 写死成别人的域名甚至有人在源码里藏定时任务。动工之前先把下面这几件事捋清楚。2. 三合一到底是哪三合一源码里的三种组织方式与选型理由2.1 三端合一用户的 H5、商家的小程序、管理端的 PC打开一份正常的三合一工程最先看见的是三个前端目录加一个后端。常见做法是按端拆目录而不是按页面拆因为三端的发布节奏和权限边界完全不同。典型结构长这样meituan-like/ ├── admin/ # PC 管理后台Vue3 Element Plus ├── miniapp/ # 微信小程序端uni-app 或原生小程序 ├── mobile/ # 用户 H5 端Vue3 Vant ├── server/ # 后端 APIPHP Laravel 或 Java Spring Boot └── sql/ # 数据库初始化脚本这个目录结构决定了你后续改动的工作量。admin管平台数据miniapp和mobile管用户和商家三端大部分接口是重合的只在路由守卫上做区分。我一般会先读server/routes/api.php看它是不是按/api/mobile、/api/merchant、/api/admin分段这直接决定你新增接口是复用拦截器还是自己写权限判断。参数上有一个全项目最容易被忽略的点移动端的baseURL。很多源码默认写的是生产环境域名你在本地跑起来之后前端所有请求全打到别人的服务器上页面就会白屏或者回一个诡异的数据结构。拿到源码第一件事全局搜http://把mobile/src/config/index.js和admin/src/utils/request.js里的 baseURL 改成http://127.0.0.1:8000/。顺带说一句等跑通之后再读 Vue3 源码解析类的内容你会更清楚多端工程为什么要用环境变量管理接口地址而不是写死在配置里。2.2 三角色合一用户、商家、管理员如何共用一个登录态有一部分三合一源码的“三合一”指角色一套账号体系里同时存在用户、商家、管理员三种身份但不会为商家单建一张用户表。主流设计是共用一个users表加一个role字段区分登录鉴权用 JWT Redis。核心代码逻辑一般是这样的// server/app/Http/Middleware/JwtAuth.php public function handle($request, Closure $next) { $token $request-bearerToken(); if (!$token) { return response()-json([code 401, msg 未登录], 401); } try { $payload JWT::decode($token, env(JWT_SECRET), [HS256]); $request-attributes-set(user_id, $payload-uid); $request-attributes-set(role, $payload-role); } catch (\Exception $e) { return response()-json([code 401, msg 登录已过期], 401); } return $next($request); }这段中间件的逻辑很直白从请求头里取 bearer token解码后把uid和role塞进请求对象后续控制器直接读这两个属性做数据过滤和权限判断。关键参数是JWT_SECRET它一旦为空或者长度不够所有和登录相关的接口都会报签名错误后面第 5 章会专门讲这个坑。和 Java 课程设计里常见的 Session 拦截器组合不同这类三合一源码几乎都偏无状态 Token原因是 H5、小程序、PC 三端都要持久化登录态Cookie 在 App 容器里最不稳定。商家端和小程序端共用同一套/api/merchant接口前端拿到 token 后按角色跳转不同首页后端只认 payload 里的role这样你新增一个角色时不需要改中间件只需要改路由分组。2.3 三业务合一外卖、团购、跑腿的单据如何落表第三种“三合一”指业务层一份源码里同时跑外卖订单、团购券订单、跑腿任务。核心就在订单主表的设计上这类表的典型结构是CREATE TABLE orders ( id bigint unsigned NOT NULL AUTO_INCREMENT, order_sn varchar(32) NOT NULL COMMENT 订单号, order_type tinyint NOT NULL DEFAULT 1 COMMENT 1外卖 2团购 3跑腿, shop_id bigint unsigned NOT NULL COMMENT 门店ID, user_id bigint unsigned NOT NULL COMMENT 下单用户ID, status tinyint NOT NULL DEFAULT 0 COMMENT 0待付款 1已付款 2已接单 3配送中 4已完成 5已取消 6退款中, pay_status tinyint NOT NULL DEFAULT 0 COMMENT 0未支付 1已支付 2已退款, total_amount decimal(10,2) NOT NULL DEFAULT 0.00, pay_time datetime DEFAULT NULL, created_at timestamp NULL DEFAULT current_timestamp(), PRIMARY KEY (id), KEY idx_shop_status (shop_id, status), KEY idx_order_sn (order_sn) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT三合一订单主表;把三类业务塞进一张表换来的是对账简单、后台列表一次查出全部单据代价是订单明细必须按order_type去关联不同的子表外卖关联order_goods团购关联order_coupon跑腿关联delivery_task。新手改这种表最容易犯的错是把status当成全局统一状态实际上不同类型订单的状态流转并不同。比如团购券可以“未核销”外卖没有这个概念跑腿单没有“已接单”而是“骑手已取件”。所以判断一份源码设计得好不好就看状态字段是不是跟着order_type走的。字段上的两个默认值要记住status默认 0 表示待付款pay_status默认 0 表示未支付。排查订单问题时“待付款”是表里最干净的数据优先从这里开始查状态机是否被改坏。2.4 下载前先看这 4 个信号判断教学版还是可交付版先把边界说清楚标着“美团三合一”的源码九成是第三方开发者参照美团业务形态写的仿版或半成品不是美团内部工程。它值得学但直接拿去交付要谨慎。下载之前可以从四个信号判断它的成色。第一看环境要求写没写具体版本。“PHP 7.4 MySQL 5.7 Redis 6”是好的信号只写“前后端分离”的基本都是随口复制。第二看有没有数据库脚本和演示数据缺 SQL 的包可跑程度大打折扣说明发布者自己都没完整跑通过。第三看有没有加密痕迹常见的是 ionCube、Zend Guard、入口文件里塞 eval 混淆加密意味着你没法排查后门也没法改功能。第四看最近维护时间超过一年没动过的包前端依赖大概率停在 Node 12 时代装依赖都能折腾一晚上。教学版和可交付版之间最大的差别不是代码质量而是有人真的拿它跑完过一条完整订单链路。3. 从下载到跑通部署“三合一”源码的最小命令集3.1 下载源码后的第一件事校验完整性和加密情况解压之后别急着配环境先做三个检查。这三条命令能在十分钟内告诉你这个包能不能信任# 解压后先数一数文件正常的三合一工程前端加后端至少 500 个文件 find . -type f -not -path */node_modules/* | wc -l # 检查是否存在可疑编码eval base64 是 PHP 源码里最常见的混淆特征 grep -rn base64_decode server/app server/config 2/dev/null | head -n 20 # 检查是否带安装锁和数据库备份很多二手包会把这些一起打包进来 find . -name install.lock -o -name *.sql.bak 2/dev/null第一条命令帮你判断包是否完整。正常的工程文件数在 500 到 2000 之间如果排除 node_modules 后只有几十个文件大概率是阉割版。第二条命令最需要认真看base64_decode本身是合法函数正常代码里也会出现但它通常只在工具类里用。如果你在控制器入口文件里看到eval(base64_decode(...));这种一行代码完成整个文件工作的写法基本可以确定这文件被人动过手脚。第三条命令是帮你识别这是不是“二道贩子”的复制包install.lock存在说明系统已经在别的服务器上装过了里面的配置信息不一定适合你。如果发现有可疑文件先看上下文不要急于删。把匹配的文件名记下来去对应的干净版本里比对不少免费包里多出来的定时任务就藏在这种文件里。3.2 环境准备PHP 7.4/8.0 MySQL 5.7 Redis 的常见组合这类源码八成是 PHP 写的另外两成是 Java Spring Boot。PHP 系的运行成本最低非常适合“源码建站”式的快速交付。我一般用 Docker Compose 起一套标准环境而不是在本机一个个装中间件省得被系统自带的 PHP 版本带偏services: mysql: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: meituan3 ports: - 3306:3306 redis: image: redis:6-alpine ports: - 6379:6379 php: image: php:8.0-fpm volumes: - ./server:/var/www/html ports: - 9000:9000这里有一个必须补的环节PHP 镜像默认没装扩展。至少需要装pdo_mysql、redis、bcmath这三个否则后面一定会报Call to undefined function Redis::connect()。装扩展在你的 Dockerfile 里加两行docker-php-ext-install pdo_mysql bcmath和pecl install redis就行。提示遇到Call to undefined function Redis::connect()时先别找代码问题九成是 PHP 的 redis 扩展没装。三合一工程的中间件依赖比普通单页应用多一个队列服务。订单推送、微信通知、短信验证码全都走队列环境里少了 Redis后端能启动但业务跑不动。这也是为什么我不建议直接用 Apache 老 PHP 跑环境差异会把“代码问题”和“环境问题”搅在一起排错成本翻倍。3.3 初始化项目数据库迁移、密钥生成与本地域名配置环境就绪后进入源码的server目录开始初始化。以下命令按顺序执行每一条都对应一个常见的失败点cd server cp .env.example .env # .env 里必须改的参数 # DB_HOST127.0.0.1 # DB_DATABASEmeituan3 # DB_USERNAMEroot # DB_PASSWORDroot # QUEUE_CONNECTIONredis # REDIS_HOST127.0.0.1 php composer.phar install php artisan key:generate php artisan migrate --seed php artisan serve --host0.0.0.0 --port8000cp .env.example .env是为了让应用读取到环境变量。很多人直接改.env而没复制模板结果APP_KEY为空所有加密和签名功能全部报错。php artisan key:generate会把应用密钥写回.env这是 Laravel 系 PHP 源码的必经步骤。migrate --seed建表并写入测试商家、测试商品、测试用户种子数据决定了你登录时有没有东西可看。QUEUE_CONNECTIONredis是这类源码的默认值如果你不启动队列监听订单状态会永远停在“已下单”因为状态流转逻辑是通过事件触发队列去执行的。最后一行php artisan serve只是开发调试用真上线要交给 Nginx 做伪静态和反向代理。前端部分的启动命令相对固定cd mobile npm install npm run dev这里最常翻车的不是命令而是 Node 版本。源码发布时间早的话依赖里可能锁着 node-sassNode 20 环境直接编译不过。常见做法是装 Node 14 npm 6或者看package.json里的engines字段按它锁的版本走。3.4 订单推送不执行队列进程和 supervisor 守护三合一源码跑起来之后第一单会暴露最核心的问题订单消息推不到商家端。原因不是 WebSocket 没连上而是队列 worker 没启动。前端注册了事件后端把任务丢进 Redis但没人消费。开发环境手动启动一次就能验证php artisan queue:work redis --sleep3 --tries3--sleep3表示空闲时等 3 秒再取下一个任务--tries3表示任务失败最多重试 3 次超过就进入失败队列。上线环境不可能让进程一直在终端前台跑要用 supervisor 守护# /etc/supervisor/conf.d/queue.conf 参考配置 # [program:meituan3-worker] # commandphp /var/www/html/server/artisan queue:work redis --sleep3 --tries3 --timeout90 # numprocs2 # autorestarttruenumprocs2是消费者进程数小站点 2 个够用--timeout90防止某个慢任务卡死整个 worker。队列是排查订单问题时的第一道关卡订单没反应先看 Redis 里queues:default的长度确认任务是堆积了还是根本没进去。4. 源码的关键逻辑订单推送、支付回调与商家聚合的读写边界4.1 订单状态机从下单到完成的状态流转规则三合一工程的业务核心不在页面在订单状态机。先看一张状态定义表status含义可流转到0待付款1、51已付款2、62已接单3、43配送中4、64已完成无5已取消无6退款中4状态机在代码里的实现通常是一个校验数组 一个跳转方法// server/app/Services/OrderStateService.php private array $transitions [ pending [paid, cancel], paid [confirmed, refund], confirmed [delivering, completed], delivering [completed], completed [], cancel [], ]; public function move(Order $order, string $target): bool { if (!in_array($target, $this-transitions[$order-status], true)) { throw new OrderStatusException(非法状态跳转: {$order-status} - {$target}); } $order-status $target; return $order-save(); }这段代码的精髓在in_array(..., true)第三个参数是严格比较。PHP 里字符串1和整数1松散比较相等如果不加true用户传一个delivering可能被错误匹配到别的状态。很多翻车案例都是在这里栽的。我见过不少教学版源码根本没有状态机直接在控制器里$order-status 2;一写了事。修改这类代码时要小心如果哪一天加了“取消后重新支付”逻辑这种散装赋值会让你满项目找状态更新点。判断一份源码业务功底怎么样先看它有没有集中管理状态流转这是三合一系统的地基。4.2 支付回调的幂等同一笔通知处理两次会怎样支付回调是三合一系统里最容易出线上事故的地方。用户支付成功后微信或支付宝会请求你服务器的回调地址如果返回内容不对支付渠道会按重试节奏反复推送同一笔订单。处理方法也很标准// server/app/Http/Controllers/Api/PayNotifyController.php public function notify(Request $request) { $orderSn $request-input(order_sn); $order Order::where(order_sn, $orderSn)-first(); if (!$order) { return FAIL; } // 幂等判断已支付订单直接返回成功不再处理 if ($order-pay_status 1) { return SUCCESS; } DB::transaction(function () use ($order) { $order-pay_status 1; $order-status 1; $order-save(); // 触发后续事件发微信通知、创建配送单 event(new OrderPaid($order)); }); return SUCCESS; }这段代码的关键不是事务而是事务之前的pay_status 1判断。请求进来先查一次订单发现已经支付过就直接返回SUCCESS避免重复入账和重复通知。没有这个判断的源码用户付一笔商家端能播报三遍新订单语音因为支付渠道的重试请求最多能到十几次。幂等判断之后才是事务和事件。event(new OrderPaid($order))是在事务提交前触发的如果事件监听器里的消费者把微信通知发出去了事务又失败回滚会出现“通知发了但订单没改”的情况。稳妥做法是事务提交之后再用dispatch()-afterCommit()推事件这样能保证一致。这是后来改源码时最值得补的一层防护。4.3 商家聚合接口的读写边界列表查询别把全库捞出来三合一源码里被折腾得最多的是商家端订单列表。商家只应该看到自己店铺的单但很多新手改着改着就把shop_id过滤丢了结果商家 A 的账号能看到全平台所有订单。常见做法是这样的// server/app/Http/Controllers/Api/Merchant/OrderController.php public function index(Request $request) { $orders Order::query() -where(shop_id, $request-user()-shop_id) -with([goods, delivery]) -when($request-status, fn ($q, $status) $q-where(status, $status)) -orderBy(id, desc) -paginate($request-integer(pageSize, 10)); return response()-json($orders); }$request-user()-shop_id从中间件塞进去的用户对象里读当前店铺这是数据隔离的边界。with([goods, delivery])用来预加载关联数据没有它会出现经典的 N1 查询列表返回 10 条订单代码会再执行 10 次子查询取商品和配送信息接口耗时直接翻几倍。when()是条件查询status参数不存在时自动忽略避免前端传空值把 SQL 拼坏。paginate()默认读page和per_page参数这里用$request-integer(pageSize, 10)强制转成整型防止有人传pageSizeabc让数据库报错。数据量变大后这条 SQL 的瓶颈在orderBy(id, desc)主键排序比created_at更快更稳前提是订单表用自增主键。所以三合一系统的订单表都爱用bigint auto_increment不只是为了简单更是为了列表查询的性能。5. 美团三合一源码常见问题与避坑从白屏到后门的 5 个现场5.1 现象一前端白屏接口飞到别人的域名现象H5 页面能打开但页面空白打开浏览器 Network 面板请求的接口地址是https://xxx.com根本不是你的本地服务。原因源码里的baseURL是写死的生产环境地址没有走环境变量。发布者自己部署时用的是线上域名打包时把这个地址留在了所有前端资源里。你跑npm run dev请求照样往人家服务器发。解决全局搜前端目录里的http://和https://把配置里的固定域名替换为/api然后在 Nginx 里做代理location /api/ { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; }替换完之后清一遍浏览器缓存因为 Service Worker 可能已经把旧的接口地址缓下来了。这个坑几乎每一份二手三合一包都有排查优先级排第一。5.2 现象二登录接口一直报“签名错误”现象用户名密码都对但接口返回“签名错误”或“登录已过期”刷新页面重登也不管用。原因.env里的JWT_SECRET是源码里写死的默认值或者干脆就是JWT_SECRETsecret。代码里要求 32 位以上的随机字符串长度不够或都用默认值签出来的 token 在下次请求校验时对不上。解决在server目录下执行重新生成密钥并确认.env已更新php artisan jwt:secret生成后必须重启 PHP 服务才会生效只改.env不重启等于没改。这个坑的隐蔽之处在于首次登录可能成功因为签发和校验用的是同一个 secret问题出在换了环境变量之后旧 token 全失效表现为“登录状态不稳定”。5.3 现象三支付回调重复入账商家播报三遍现象用户支付成功商家端收到两条甚至三条新订单语音数据库里订单金额没变但通知重复执行。原因回调接口没有做幂等处理后端的通知事件被重复消费。支付渠道重试回调加上队列 worker 消费超时重试一次支付触发多次业务逻辑。解决两层防护。第一层在数据库给orders.order_sn加唯一索引从物理上防止同一笔订单被插入两次第二层在回调代码开头判断pay_status 1已支付订单直接返回SUCCESS不再处理。之前 4.2 节那段代码就是这个问题的标准答卷。5.4 现象四图片上传 500或换台机器图片全裂现象本地开发时图片能传部署到服务器后上传接口 500或者图片当时能打开重启环境后裂图。原因storage目录不可写或者public/storage符号链接没有建立。Laravel 系的 PHP 源码会把上传文件写到storage/app/public再通过软链映射到public/storage任何一步缺失都会导致图片读写失败。解决chmod -R 775 storage bootstrap/cache php artisan storage:link有些源码不按 storage 规范走直接把上传文件写到public/uploads这种目录要额外确认权限。还有一点下载包里如果带了public/storage那通常是一个真实目录而不是软链在 Linux 上部署时会表现怪异删掉重建才是对的。5.5 现象五压缩包里多了个定时任务部署后经常外连现象代码和数据库看起来都正常但服务器 CPU 偶尔飙高网络连接里频繁出现一个陌生的外网 IP几分钟连一次。原因二手源码被二次处理过发布者塞了定时任务定期拉取外部地址或上报访问量。这类脚本常用一串base64_decode藏在入口文件里也有的写在app/Console/Kernel.php的schedule里。解决先查系统定时任务再看项目里的调度定义crontab -l # 检查源码里的外联请求 grep -rE (file_get_contents|curl|wget).*(http) server/app server/public --include*.php发现有写死的陌生域名回源直接删掉对应文件。检查重点放在routes/web.php、public/index.php和app/Console/Kernel.php这三个位置这是藏调度任务最常见的地方。这个操作不是多疑免费二手源码里出现外联脚本的概率比你想的高得多。6. 把“三合一”源码改造成能交付的项目验证脚本与一个单号技巧6.1 三合一订单号生成规则一眼看出是哪笔业务订单号用自增 id 有个问题对账时看不出业务类型和店铺而且能直接推算平台单量。我习惯把订单号改成“时间 类型 店铺 随机位”的 20 位结构$orderSn date(YmdHis) . str_pad($orderType, 2, 0, STR_PAD_LEFT) . str_pad($shopId % 100, 2, 0, STR_PAD_LEFT) . str_pad(random_int(0, 9999), 4, 0, STR_PAD_LEFT);生成结果类似202507211430010103456拆开看20250721143001是下单时间01是外卖业务03是店铺尾号456是随机数。排障时扫一眼单号就知道是哪天的哪类单不用开 SQL 查。注意random_int是加密安全的随机函数比mt_rand更抗猜测。订单号拼接完后要查一次重数据库order_sn加唯一索引兜底。6.2 半小时跑完的回归链路用脚本替手点改完状态机或支付逻辑后最怕的就是手点页面点到手软还漏了一条链路。我常用 curl 直接打核心接口做回归# 模拟用户下单 curl -X POST http://127.0.0.1:8000/api/mobile/order \ -H Authorization: Bearer $USER_TOKEN \ -d goods_id1num1shop_id1 # 模拟支付平台回调 curl -X POST http://127.0.0.1:8000/api/pay/notify \ -d order_sn202507211430010103456pay_trade_noTEST001 # 模拟商家接单 curl -X POST http://127.0.0.1:8000/api/merchant/order/confirm \ -H Authorization: Bearer $MERCHANT_TOKEN \ -d order_sn202507211430010103456跑完后再查一次订单状态下单后pay_status0 status0回调后pay_status1 status1接单后status2。任何一步对不上说明状态机在改的过程中被破坏了。这套脚本我每改一次业务代码就跑一遍比什么都管用。我当年拿一套三合一源码直接上线跳过了支付回调的幂等验证结果用户付一笔门店播报三遍最后只能手动对账费了整整两天。后来不管接什么源码第一件事就是把订单链路脚本连跑十遍确认状态机没有重复消费和非法跳转。这个习惯帮我挡掉了至少五次线上事故。希望帮到你。本文还有配套的精品资源点击获取