
简介微信拼团商城系统v6.1多商户版是一套基于 PHP5.3MySQL 的社交电商源码面向需要搭建微信拼团平台或进行二次开发的商家与开发者。系统以平台招商、多商户入驻为核心内置优惠券、团长免单、同楼购、秒杀、抽奖、广场和评价等粉丝营销模块适合社区团购、同楼拼单等场景。压缩包共 2000 个文件、22.49MB以 PHP 业务逻辑、HTML/HTM 页面、JavaScript/CSS 前端脚本、DWT/LBI 模板及 GIF/PNG 图片素材为主附带的 .bak 备份文件有助于理解源码结构与调试验证从内容预览可看出源码包含后台登录、公共函数等关键模块的备份便于本地部署与调试。运行环境上资源要求 PHP5.3 与 MySQL6PHP5.4 环境下可能出现兼容问题部署前需留意。已有 52 人学习/下载对于需要参考多商户拼团产品设计或研究 PHP 商城源码的读者可直接获得完整目录与部署素材节省从零搭建的时间若想二次开发也能从商家管理中心、订单处理、营销活动等模块中提取可复用的业务思路。1. 微信拼团商城 v6.1 多商户版先确认它解决的是社区团购的哪一环做社区团购最怕的不是没人下单而是订单、优惠、结算三条线在人多的时候各算各的账。微信拼团商城系统 v6.1 多商户版把微信小程序开发里常见的那套需求打包成了完整链路多商户入驻、拼团、优惠券、团长免单、同楼购、秒杀、抽奖、广场和评价。它不是单个拼团插件而是一个能跑社区团购业务的骨架适合手里有商户资源、想快速上线小程序团购的团队也适合想研究多商户营销系统数据模型的开发者。这篇文章按实际部署这类系统的顺序把数据模型、并发控制、部署避坑和上线自检一次讲完。2. 多商户分账与团长免单订单状态机和结算表要走在业务代码前面这类系统我一般劝人先别急着写接口先把表和状态机定下来。多商户版和单商户版的差别不在控制器代码量而在数据模型单商户版订单只要记 user_id 和 goods_id多商户版每一张订单都得知道钱该进谁的口袋、平台抽多少、什么时候结算。团长免单又给订单加了一条裂变线——成团判断、免单发放、失败退款全都要挂在同一个状态机上否则活动一多后台对账就成玄学。2.1 多商户数据模型商户、商品、订单、结算单四张表的依赖关系先看基础的两张表商户表和商品表。商户表里一定要带 settle_ratio 字段表示平台抽成后商户能拿到的比例0.90 就是平台抽 10%。很多系统把分账比例写死在代码里后来改活动规则只能发版非常被动。商品表通过 merchant_id 挂在商户下面订单表同样要带 merchant_id这样按商户维度统计销售额时一条 SQL 就能出结果不用去 join 商品表再反查商户。CREATE TABLE merchant ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL COMMENT 商户名称, contact_name varchar(50) NOT NULL DEFAULT , contact_mobile varchar(20) NOT NULL DEFAULT , settle_ratio decimal(5,2) NOT NULL DEFAULT 0.90 COMMENT 商户结算比例0.90平台抽10%, status tinyint(1) NOT NULL DEFAULT 1 COMMENT 1正常 0冻结, created_at datetime DEFAULT NULL, PRIMARY KEY (id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商户表; CREATE TABLE goods ( id int(11) NOT NULL AUTO_INCREMENT, merchant_id int(11) NOT NULL COMMENT 归属商户, title varchar(120) NOT NULL, price decimal(10,2) NOT NULL COMMENT 零售价, group_price decimal(10,2) NOT NULL COMMENT 拼团价, stock int(11) NOT NULL DEFAULT 0, sales int(11) NOT NULL DEFAULT 0, status tinyint(1) NOT NULL DEFAULT 1, PRIMARY KEY (id), KEY idx_merchant (merchant_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品表;金额字段一律用 decimal(10,2)不用 float否则累计分账时会出现 0.1 加 0.2 不等于 0.3 的经典翻车。订单的结算不能在订单表里直接打标因为同一天的订单可能持续到第二天还在退款。常见做法是单独建一张结算单按天汇总用 merchant_id 和周期做唯一键定时任务去 updateOrCreate。CREATE TABLE settlement ( id int(11) NOT NULL AUTO_INCREMENT, merchant_id int(11) NOT NULL, period_start date NOT NULL COMMENT 结算开始日期, period_end date NOT NULL COMMENT 结算结束日期, order_amount decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT 订单实付总额, platform_fee decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT 平台抽成, settle_amount decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT 应结算给商户的金额, status tinyint(1) NOT NULL DEFAULT 0 COMMENT 0待结算 1已结算, PRIMARY KEY (id), UNIQUE KEY uk_merchant_period (merchant_id,period_start,period_end) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商户结算单;// 每日凌晨汇总前一天订单到结算单settlement.php $start date(Y-m-d, strtotime(-1 day)) . 00:00:00; $end date(Y-m-d, strtotime(-1 day)) . 23:59:59; $rows OrderModel::where(paid_at, , $start) -where(paid_at, , $end) -whereIn(status, [1, 2, 3, 4]) // 已支付且未退款的订单 -groupBy(merchant_id) -selectRaw(merchant_id, SUM(pay_amount) as order_amount) -get(); foreach ($rows as $row) { $ratio MerchantModel::find($row-merchant_id)-settle_ratio; $fee round($row-order_amount * (1 - $ratio), 2); SettlementModel::updateOrCreate( [merchant_id $row-merchant_id, period_start $start, period_end $end], [order_amount $row-order_amount, platform_fee $fee, settle_amount round($row-order_amount - $fee, 2)] ); }这段脚本的逻辑是以支付时间 paid_at 为口径汇总前一天所有已支付的订单金额再按商户的 settle_ratio 拆出平台抽成和应结金额。updateOrCreate 配合唯一键 uk_merchant_period保证同一商户同一天的结算单只存在一条任务重复跑也不会产生重复数据。参数上值得注意两个点一是统计口径必须是 paid_at 而不是 created_at订单创建和支付可能跨天二是 status 列表要把已退款的 5 排除掉退款订单应该单独冲减结算单。2.2 团长免单的成团状态机成团、失败与补齐人数怎么处理团长免单不是简单的把团长的钱退回去。常见做法有三种达到成团人数后退款给团长、发一张等额免单券、直接给团长佣金。v6.1 这类系统里最常见的是发免单券因为微信支付的退款有频率和手续费限制频繁退款不划算。免单券还能锁住团长的下一次消费对平台更有利。订单状态建议用一张表定死后端代码里只认这个枚举status含义触发时机0待支付用户下单创建1已支付拼团中支付回调成功2已成团待发货团内人数达到目标3已发货商户发货4已完成用户确认收货可评价5已退款拼团失败或用户申请退款状态机的核心在支付回调到成团这一段代码要写成幂等的否则微信支付接口重复回调两次会把成团逻辑执行两遍// 支付回调处理onPaySuccess.php function onPaySuccess($orderId) { $order OrderModel::find($orderId); if ($order[status] ! 0) { return; // 幂等保护已处理过的订单直接跳过 } $order-status 1; $order-paid_at date(Y-m-d H:i:s); $order-save(); $group GroupBuyModel::find($order[group_id]); $group-current_num 1; if ($group-current_num $group-target_num) { // 成团整团订单进入待发货 $group-status 1; $group-save(); OrderModel::where(group_id, $group-id)-update([status 2]); // 给团长发等额免单券金额取自团长本人订单的实付 $leaderOrder OrderModel::where(group_id, $group-id) -where(user_id, $group-leader_id) -where(status, 1) -first(); if ($leaderOrder) { CouponService::issueLeaderFreeCoupon($group-leader_id, $leaderOrder[pay_amount]); } } else { $group-save(); } }这段代码有两个关键点。第一入口的 status ! 0 判断是幂等保护支付回调可能因为网络重试发多次没有这个判断就会出现 current_num 多加、免单券发两次的问题。第二成团后要把整个团的订单都置为待发货不只当前这一单因为免单针对的是成团动作所有参团成员的订单状态必须同步推进。团长免单券的金额也一定要查团长本人的订单不能拿当前回调的这一单金额去发。拼团失败的自动退款要交给定时任务不能放在用户点开页面时才触发否则没人访问就一直挂着// 定时任务每分钟执行一次处理超时未成团的订单 public function handleExpiredGroups() { $expired GroupBuyModel::where(status, 0) -where(end_time, , date(Y-m-d H:i:s)) -get(); foreach ($expired as $group) { $group-status 2; // 拼团失败 $group-save(); $orders OrderModel::where(group_id, $group-id) -where(status, 1) -get(); foreach ($orders as $order) { RefundService::refund($order); // 调用微信支付接口原路退回 $order-status 5; $order-save(); } } }注意这里的坑退款必须只处理 status1 的订单如果团里有人已经申请了部分退款状态不再是 1就不能再退第二次。另外退款接口调用失败时要记录日志并重试不能在循环里直接吞异常否则对账时钱和订单永远对不上。注意成团和退款的触发尽量收敛到定时任务和回调里不要放在用户端页面请求里否则同一个请求被多端触发状态会乱。3. 优惠券、秒杀与抽奖营销活动的并发控制和券量一致性营销模块是整个系统最容易被压垮的地方。优惠券超发、秒杀超卖、抽奖重复中奖这三个问题本质是同一个问题并发下先查后写。解决思路也统一把扣减动作前置到 Redis用原子操作挡住并发DB 只做最终落库和兜底校验。3.1 优惠券的领取与核销先扣 Redis 计数再落库核销必须条件更新优惠券要拆成两张表模板表存发行量和规则用户券表存每一张实际发出去的券。很多人只建一张表在模板表里减库存结果同一张券被两个用户同时领走原因就是 UPDATE 前没有校验剩余量。CREATE TABLE coupon_template ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(60) NOT NULL COMMENT 券名称, type tinyint(1) NOT NULL COMMENT 1满减 2折扣 3免邮 4免单, amount decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT 面额或折扣值, min_amount decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT 使用门槛, total int(11) NOT NULL DEFAULT 0 COMMENT 发行总量, status tinyint(1) NOT NULL DEFAULT 1, start_time datetime NOT NULL, end_time datetime NOT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT优惠券模板表; CREATE TABLE user_coupon ( id int(11) NOT NULL AUTO_INCREMENT, user_id int(11) NOT NULL, template_id int(11) NOT NULL, status tinyint(1) NOT NULL DEFAULT 0 COMMENT 0未使用 1已使用 2过期, order_id int(11) NOT NULL DEFAULT 0 COMMENT 核销订单ID, received_at datetime NOT NULL, used_at datetime DEFAULT NULL, PRIMARY KEY (id), KEY idx_user_status (user_id,status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户券表;领券接口的并发控制我一般用 Redis 的 incr 做预扣成功后再落库// receiveCoupon.php public function receiveCoupon($userId, $templateId) { $tpl CouponTemplateModel::find($templateId); if (!$tpl || $tpl[status] ! 1) { return 优惠券不存在或已下架; } if (time() strtotime($tpl[start_time]) || time() strtotime($tpl[end_time])) { return 不在领取时间范围内; } $redisKey coupon:issued:{$templateId}; $issued $redis-incr($redisKey); if ($issued $tpl[total]) { $redis-decr($redisKey); // 超发把计数回滚 return 已被抢完; } try { UserCouponModel::create([ user_id $userId, template_id $templateId, status 0, received_at date(Y-m-d H:i:s) ]); } catch (\Throwable $e) { $redis-decr($redisKey); // 落库失败也要回滚计数 return 领取失败请重试; } return 领取成功; }这里 Redis 的计数只是预扣真正的数据以 user_coupon 表为准。落库失败时必须 decr 回滚否则用户没领到券计数却被占用了。核销的时候相反要以 DB 的条件更新为准防止同一张券在并发下被两个订单核销// 核销couponId 和 orderId 一起锁状态必须是0 $affected UserCouponModel::where(id, $couponId) -where(status, 0) -where(order_id, 0) -update([ status 1, order_id $orderId, used_at date(Y-m-d H:i:s) ]); if ($affected 0) { throw new \Exception(优惠券已被使用或已过期); }affected 为 0 就代表这张券已经被别的订单抢先核销了直接抛异常让下单流程回滚。这是典型的乐观锁写法MySQL 的行锁会保证同一时刻只有一个 UPDATE 能成功。提示Redis 里的 issued 计数是流量阀门最终对账以 user_coupon 表为准两个数字要定期比对偏差超过 0 就查日志。3.2 秒杀与抽奖Redis 预扣库存 异步队列落库DB 仍要兜底秒杀的逻辑和领券类似但库存扣减的实时性要求更高。常见做法是开售前把商品库存预热到 Redis抢购请求直接打 Redis抢到了就把下单任务丢进队列由 worker 异步落库。用 Lua 脚本保证查库存和扣库存是原子的避免并发下两个请求同时读到库存为 1-- seckill_stock.lua -- KEYS[1]: seckill:stock:{goodsId} local stock tonumber(redis.call(GET, KEYS[1]) or 0) if stock 0 then return 0 end redis.call(DECRBY, KEYS[1], 1) return 1// seckillBuy.php $stockKey seckill:stock:{$goodsId}; $lua file_get_contents(base_path(scripts/seckill_stock.lua)); $ok $redis-eval($lua, [$stockKey], 1); if (!$ok) { return 手慢了已抢完; } // 抢到把下单请求写入队列立即返回排队中 $redis-lpush(seckill:order_queue, json_encode([ user_id $userId, goods_id $goodsId, seckill_price $price ])); return 排队成功请稍候刷新订单;eval 的第三个参数 1 表示 KEYS 数组里有 1 个 key。Lua 脚本里不要用字符串拼接 key统一走 KEYS 数组传递否则会有注入风险。worker 消费队列落库时还要在 DB 层再做一次条件更新兜底因为 Redis 的库存只能挡住并发流量挡不住队列重复消费// SeckillWorker.php 消费队列 while (($data $redis-rpop(seckill:order_queue))) { $info json_decode($data, true); DB::beginTransaction(); try { // DB 层兜底只有库存大于0才能扣减 $affected GoodsModel::where(id, $info[goods_id]) -where(stock, , 0) -decrement(stock); if (!$affected) { throw new \Exception(DB库存不足); } OrderModel::create([ user_id $info[user_id], goods_id $info[goods_id], pay_amount $info[seckill_price], status 0 ]); DB::commit(); } catch (\Throwable $e) { DB::rollBack(); // 落库失败要补偿 Redis 库存 $redis-incr(seckill:stock: . $info[goods_id]); Log::error(seckill order failed: . $e-getMessage()); } }参数上要注意 DB 层的 decrement 必须带 where(stock, , 0)这是最后一道防线防止 Redis 预热值和 DB 实际库存不一致时出现超卖。抽奖的处理方式和秒杀不同抽奖不扣库存扣的是奖池名额。每个奖项在 Redis 里放一个计数中奖后同样走预扣加异步落库加 DB 条件更新中奖记录表对 user_id 加唯一索引保证同一活动同一用户只能中一次。现金奖品涉及平台资质社区团购场景下建议用优惠券、积分、实物代替。4. 同楼购与广场评价位置校验、楼栋配送费和内容审核的配合同楼购和广场评价都偏社区属性一个负责把配送单元细化到楼栋一个负责把用户晒单变成内容流量。这两个模块单独看都不复杂但和主订单、积分系统串起来后容易出现定位不准和重复发奖的问题。4.1 同楼购的楼栋模型配送费按楼栋配定位必须服务端二次校验同楼购的核心是以楼栋为配送单元。前端先选小区再选楼栋楼栋表里直接存经纬度和配送费。配送费按楼栋配置比较灵活因为有些楼栋离小区门口远骑手配送成本不一样。CREATE TABLE building ( id int(11) NOT NULL AUTO_INCREMENT, community_id int(11) NOT NULL COMMENT 小区ID, name varchar(60) NOT NULL COMMENT 楼栋名如3栋2单元, lat decimal(10,6) NOT NULL COMMENT 楼栋纬度, lng decimal(10,6) NOT NULL COMMENT 楼栋经度, delivery_fee decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT 该楼栋配送费, status tinyint(1) NOT NULL DEFAULT 1, PRIMARY KEY (id), KEY idx_community (community_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT楼栋表;下单时前端会通过 wx.getLocation 拿到用户定位但微信小程序的定位在室内和地下车库经常漂移所以我坚持在前端选取楼栋之外服务端还要做一次距离校验。判定逻辑很简单用户上报的经纬度与所选楼栋的球面距离大于阈值时不允许下单提示用户重新选择// checkBuildingDistance.php function checkBuildingDistance($userLat, $userLng, $buildingId) { $building BuildingModel::find($buildingId); $dist haversine($userLat, $userLng, $building[lat], $building[lng]); return $dist 300; // 300米误差阈值普通小区规模够用 } function haversine($lat1, $lng1, $lat2, $lng2) { $radius 6371000; // 地球半径单位米 $rad M_PI / 180.0; $dLat ($lat2 - $lat1) * $rad; $dLng ($lng2 - $lng1) * $rad; $a sin($dLat / 2) * sin($dLat / 2) cos($lat1 * $rad) * cos($lat2 * $rad) * sin($dLng / 2) * sin($dLng / 2); return 2 * $radius * asin(sqrt($a)); }这里有个实际运营的细节300 米的阈值是按普通小区规模估的如果是大型社区或者城中村楼栋密集且定位偏差大建议放宽到 500 米同时在下单页面允许用户手动重选楼栋。只信前端定位不放服务端校验会出现用户明明在 A 小区订单却被算到 B 小区配送员跑错门的翻车现场。4.2 广场与评价一单一评、先审后发积分回馈要防重复广场本质是 UGC 内容流评价是下单完成后的闭环动作。这两个功能我用同一张评价表承载广场展示的是审核通过且标记为晒单的评价。评价表的关键约束是一单一评用 order_id 的唯一索引保证用户不能重复评价同一个订单CREATE TABLE review ( id int(11) NOT NULL AUTO_INCREMENT, order_id int(11) NOT NULL COMMENT 订单ID唯一, user_id int(11) NOT NULL, goods_id int(11) NOT NULL, content varchar(500) NOT NULL DEFAULT , images text COMMENT 图片URL的JSON数组最多9张, score tinyint(1) NOT NULL DEFAULT 5 COMMENT 1-5星, audit_status tinyint(1) NOT NULL DEFAULT 0 COMMENT 0待审核 1通过 2驳回, created_at datetime NOT NULL, PRIMARY KEY (id), UNIQUE KEY uk_order_id (order_id), KEY idx_goods (goods_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT评价表;评价流程建议走先审后发用户提交后状态是 0进入后台审核列表审核通过后状态置 1广场 feed 只查 audit_status1 的记录。图片不要直接存 base64先传到对象存储或微信云存储库里只存 URL 数组。如果要做内容审核照片可以对接内容安全接口文本做关键词过滤把涉政、辱骂、广告类的评价挡在后台。审核通过后的积分回馈是常见出错点。运营在后台手抖点两次通过积分就会发两次。我在项目里用条件更新把审核动作和发积分动作绑在同一把锁上// reviewAudit.php $affected ReviewModel::where(id, $reviewId) -where(audit_status, 0) // 只有待审核的记录能被更新 -update([audit_status 1]); if ($affected 1) { PointsService::grant($review[user_id], 10, 评价晒单奖励); // 同步把晒单内容推送到广场内容池 PlazaService::push($reviewId); }affected 1 才发积分第二次点审核时 where 条件已经不满足不会重复加分。积分发放本身也要做成单调的PointsService 里记录积分流水来源的单号来源单号加唯一索引从根上杜绝同一笔评价对应两条积分流水。5. 微信拼团商城部署避坑支付回调、超卖和审核驳回的 5 个典型问题这部分是血泪经验汇总。每次帮人接手这类系统故障排行前五名基本固定支付掉单、秒杀超卖、优惠券算错钱、定位错楼栋、小程序审核被拒。按现象、原因、解决逐个说。5.1 支付回调掉单补单任务必须独立跑现象用户在微信支付里付了钱小程序端订单却一直停在待支付。原因微信支付接口的回调是异步的服务端网络波动、回调 URL 被伪静态规则拦截、或者回调处理代码里有脏数据导致异常都可能让回调没触发或处理失败。只依赖回调更新订单一次失败就永久掉单。解决回调处理函数要幂等先验签再用 out_trade_no 查本地订单处理逻辑做成可重复执行的同时写一个补单定时任务每 5 分钟调用微信支付的查单接口把本地待支付但微信侧已支付的订单主动拉起来更新状态。线上系统跑下来补单任务处理的订单占比通常有 3% 到 5%这不是小概率事件。5.2 秒杀还是超卖库存扣减必须原子现象压测 200 并发抢 100 件商品订单创建了 130 条后台库存变成负数。原因秒杀接口直接走 DB先 SELECT 库存判断大于 0再 UPDATE 减库存。两个请求同时 SELECT 到库存为 1都认为可以买双双执行 UPDATE。复合操作没有原子性超卖就发生了。解决按第 3 章的方案Redis 预扣加 Lua 原子脚本加 DB 条件更新兜底。压测前把 Redis 库存预热成和 DB 一致压测后核对两边数字。如果不想引入 RedisDB 层必须用一条 UPDATE ... WHERE stock 0 判断影响行数绝不能先查后改。5.3 优惠券叠加把订单金额算成负数统一计价入口校验下限现象订单实付金额出现 -6.5 元用户账单异常财务对账直接崩。原因下单逻辑里逐段计算优惠满减券减完又叠加免单券或者秒杀价和优惠券叠加后低于 0。多个营销模块散落在不同文件里每个模块只知道自己减了多少不知道别的模块减了多少。解决所有下单计价收敛到一个统一入口 OrderPriceService先取商品最低价再按平台券、商户券、免单券的优先级依次计算每一步都校验当前金额是否低于 0.01 元低于就中断并回滚。免单券本质上是一种订单金额上限抵扣券不能和满减券同时生效这个规则要写进券模板的 exclude_type 字段里。5.4 同楼购定位漂移前端 getLocation 只能当参考现象用户站在 3 栋楼下下单后台记录显示他选了 8 栋配送员把货送错了门。原因wx.getLocation 在室内和楼栋密集区域误差可达几十到几百米H5 端定位偏差更大。只信前端传的经纬度就容易匹配到隔壁楼。解决服务端按第 4.1 节的 haversine 距离做二次校验超过阈值弹窗要求用户手动选择楼栋同时记录用户最近三次成功下单的楼栋 ID生成常用楼栋列表下次下单默认选中减少用户手动输入的次数。前端如果用 uniapp 开发iOS 和安卓的定位精度差异也要在联调时分别测一遍。5.5 小程序审核被驳回类目资质和虚拟支付是重灾区现象小程序提审被驳回理由是服务类目与小程序页面内容不符或涉及虚拟支付。原因拼团商城页面里有抽奖、充值、会员卡这类功能但后台选的类目是普通电商。微信小程序年审时对类目资质查得很严抽奖涉及实物奖品需要对应类目现金奖品直接属于违规。解决上线前先在微信公众平台把所有页面截图过一遍确认类目覆盖抽奖奖品一律用优惠券、积分、免单券或实物禁止现金和虚拟币涉及多商户入驻的类目要选电商平台而不是商家自营需要的资质文件提前准备。提审前用微信开发者工具自测支付、定位、授权三个最容易被审核员点到的入口。6. 上线前自检用并发压测和三条核对 SQL 验证拼团、优惠券与结算最后一个技巧是上线前的自检流程。我习惯在发布前夜跑一轮压测再用核对脚本验证三个关键数字库存与订单数守恒、优惠券核销数与订单对得上、商户结算金额与订单流水对得上。压测工具我用 ab 就够重点是压完后必须有一份可重复执行的核对脚本不能靠肉眼盯后台。# 预热 Redis 库存为 100 redis-cli set seckill:stock:1001 100 # 200 个请求50 并发打秒杀接口 ab -n 200 -c 50 http://your-domain.com/api/seckill/buy?goods_id1001tokendemo_token压测结束后跑核对脚本验证初始库存等于剩余库存加已支付订单数加已退款订单数// verify_stock.php 核对库存守恒 $init 100; // 初始库存 $stock GoodsModel::where(id, 1001)-value(stock); $paid OrderModel::where(goods_id, 1001) -whereIn(status, [1, 2, 3, 4]) // 已支付且未退款的订单 -count(); $refunded OrderModel::where(goods_id, 1001) -where(status, 5)-count(); if ($init ! $stock $paid $refunded) { exit(库存不一致: 剩余{$stock}, 已支付{$paid}, 已退款{$refunded}\n); } echo 库存守恒共创建订单 . ($paid $refunded) . 单\n;再核对另外两条 SQL优惠券核销数应该等于 orders 表里使用了优惠券的订单数商户结算单的 order_amount 应该等于对应时间区间内已支付订单 pay_amount 之和。三张表对上了再放开微信支付接口的正式流量。我个人的习惯是把这三条核对写成一个脚本挂 crontab上线后每 5 分钟跑一次连续半小时无告警才去睡。第一次做拼团系统时我在支付回调没做幂等上线当晚重复回调把两个团长的免单券各发了两张后台对账对了半宿。从那以后所有写接口都默认加幂等和唯一约束这个习惯救了我很多次。希望帮到你。本文还有配套的精品资源点击获取