简介:这是一套基于微信公众平台的一物一码吸粉红包系统源码,版本V3.2.2全开源,面向需要做O2O营销、门店引流和粉丝裂变的运营者或开发者。通过批量生成二维码红包,可将其嵌入海报、传单、产品包装或活动现场,顾客扫码即领红包并自动关注公众号,实现真实粉丝沉淀与裂变传播。资源共23个文件,包含14个HTML页面、5个PHP后端逻辑文件、3张JPG预览图及1个manifest.xml配置文件,包体仅146KB,轻量易部署。目前已有1073人学习下载。代码结构清晰,覆盖二维码生成、红包发放、粉丝关注绑定等核心模块,并提供jssdk.php、receiver.php等关键接口文件,便于二次开发或直接部署;全开源无加密,商家可快速搭建吸粉红包活动,提升线上线下联动营销效果。
1. 一物一码吸粉红包:扫码领钱背后的公众号开发链路
线下商品包装上印一个二维码,消费者扫码后先关注公众号、再领到一笔现金红包,这个场景在快消品行业已经跑了很多年。V3.2.2 这类标着“全开源”的源码包,本质上是把码管理、公众号授权、微信支付现金红包、粉丝标签这几条链路打包成一套可部署的系统。它的业务闭环不复杂:商家生成一批带密钥的二维码贴在商品上,用户扫码后进入公众号网页授权,系统判断该码未被领取过,再调微信支付接口发钱。难点不在单一接口,而在状态一致性、防刷边界和对账兜底。
这套源码适合三类人研究:一是做私域运营的团队,需要把“扫码领红包”从纸面方案变成可落地的工具;二是接外包项目的开发者,想找一个现成的码管理模型来改;三是刚接触微信公众号开发的工程师,想完整看一遍授权、模板消息、支付回调之间的数据流转。V3.2.2 这个版本号对应的是微信支付 v2 现金红包接口时代的老方案,但它把一物一码的通用做法保留得很完整——码表设计、红包批次、领取幂等、对账报表,这些设计放到今天仍然能直接迁移到微信支付 v3 的商家转账接口上。
下面按一个可复现的路径来拆:先讲码与红包的核心链路设计,再讲服务器部署与公众号参数配置,接着深入红包发放的接口调用和防刷参数,最后给出一套本地联调与二次开发的验证方法。
2. 一物一码红包系统的核心设计:从发码到领取的表结构
要把“一物一码”落到工程上,第一件事是定义清楚“码”到底是什么。它不是一个随机字符串贴在包装上那么简单,而是一行有生命周期、有状态流转、有对账凭据的数据记录。一个完整的码记录,至少包含码号、批次号、关联的商品 ID、红包金额档位、状态(未激活/已激活/已领取/已退款)、首次扫码用户的 openid、领取时间、红包订单号。这套结构同时支撑两个核心动作:商家批量生成码时能低成本打印导出;用户扫码时能通过一个不透明的码值快速找到对应记录并原子化更新状态。
2.1 码的组成与生成策略:短码映射与防伪参数
常见的做法是数据库自增 ID 加一个不透明短码。自增 ID 用于内部关联和查询,短码用于对外传播和二维码内容。短码不能直接用自增 ID,否则商家导出码表后稍微对比就能推算总销量,码也容易被批量尝试领取。我一般会生成 16 位的不透明字符串,由前缀、随机段、校验位组成,生成后写入码表时加唯一索引。
码生成这一步可以用如下 PHP 脚本批量初始化,码表批量生成接口只接受管理员角色调用:
// 批量生成一物一码红包码 function generateCodes($batchNo, $count, $amount, $activityId) { $codes = []; $time = time(); $charSet = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // 去掉易混淆的 I O 0 1 for ($i = 0; $i < $count; $i++) { $randomStr = ''; $len = strlen($charSet) - 1; for ($j = 0; $j < 12; $j++) { $randomStr .= $charSet[random_int(0, $len)]; } $prefix = strtoupper(substr(md5($batchNo . $randomStr), 0, 2)); $check = strtoupper(substr(md5($randomStr . $amount . $activityId), 0, 2)); $code = $prefix . $randomStr . $check; // 16 位 $codes[] = [ 'code' => $code, 'batch_no' => $batchNo, 'amount' => $amount, 'status' => 0, // 0 未领取 'create_time'=> $time, ]; } // 批量插入,code 字段有唯一索引,重复入库失败时可以重试整个批次 return batchInsertCodeTable($codes); }这段代码里随机字符集故意去掉了易混淆的大写字母和数字,因为码最终要被印在瓶盖、包装袋或者吊牌上,OCR 识别和人工输入都可能出错。前缀来自批次号的摘要,便于码表快速定位对应批次;校验位参与金额和活动 ID 的摘要,避免有人把 A 商品的红包码换到 B 商品的活动里使用。批量插入把整批码一次性写入码表,配合唯一索引做兜底,天然屏蔽了重复生成的并发问题。
2.2 红包活动与批次关联:金额档如何落到领取逻辑
一套系统里往往同时跑多个活动,每个活动挂多个批次,每个批次对应不同的商品或渠道。这样设计的好处是财务核算时可以通过batch_no精准统计某个渠道花费了多少红包预算,而不是把账全记在活动头上。常见的做法是维护三张表:活动表、批次表、码表。活动表定义活动名称、有效期、预算上限;批次表定义码数量、单码金额、投放渠道和生效时间;码表每行存一个具体码的领取状态。
批次里的金额档位设计有一个容易被忽视的细节:红包金额是用户在扫码那一刻才确定的,而不是生成码时写死。原因是商家可能在一个活动中期的某个节点临时调低金额,已生成的码仍能按新档位发放。实现时一般只把活动 ID 写到码表里,领取时再查批次当前生效的金额配置。V3.2.2 源码里这个逻辑在红包活动服务层做得比较清晰,金额档位独立成表,支持按时间段切换。
-- 红包批次表,按渠道和时间段控制预算 CREATE TABLE `redpack_batch` ( `id` int(11) NOT NULL AUTO_INCREMENT, `batch_no` varchar(32) NOT NULL COMMENT '批次号', `activity_id` int(11) NOT NULL COMMENT '关联活动', `total_count` int(11) NOT NULL DEFAULT 0 COMMENT '码总数', `claimed_count` int(11) NOT NULL DEFAULT 0 COMMENT '已领取数', `per_amount` decimal(10,2) NOT NULL COMMENT '单码金额', `start_time` int(11) NOT NULL DEFAULT 0, `end_time` int(11) NOT NULL DEFAULT 0, `status` tinyint(4) NOT NULL DEFAULT 1 COMMENT '1启用 0停用', PRIMARY KEY (`id`), UNIQUE KEY `uk_batch_no` (`batch_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='红包批次表';把已领取数冗余到批次表里,执行领取时会先UPDATE redpack_batch SET claimed_count = claimed_count + 1 WHERE batch_no = ? AND claimed_count < total_count,这一行在事务里充当乐观锁。只有影响行数为 1 时才继续发放红包,从数据层面卡住超发。
2.3 关注公众号与领红包的先后顺序
一物一码吸粉红包的转化漏斗是“扫码 -> 关注 -> 领钱”,所以整个链路里最关键的中间态是“已关注但未领取”。用户扫码后进入的是公众号网页授权页面,拿到 openid,然后系统判断该用户是否已关注公众号,未关注就引导关注,关注完成后跳回领红包页面。这里最容易出问题的是网页授权回调时携带的 state 参数,它需要把码值原样带回,否则用户完成关注后无法定位到自己扫的是哪一个码。V3.2.2 里用 session 存码值和 openid,在回调里会话丢失会导致领取中断。更稳的办法是把码值加密后放进 state,回调时解密还原,不依赖 session。
3. 在 Linux 上部署开源源码包:环境、配置与上线要点
这套源码的部署形态很典型:LNMP 环境加一个可写的上传目录,再加一套队列任务处理红包发放。很多开源的公众号项目会把支付回调、红包发放做成同步接口,但放在高并发扫码场景下响应会很慢,而且微信对回调超时有严格的限制。V3.2.2 源码里红包发放是一个后台任务队列,前端扫码后只做状态预占,真正的打款动作由常驻进程异步执行。这个设计在源码包自带的部署文档里写得不详细,但看数据库里的任务表就能还原出它的队列结构。
3.1 Nginx 与 PHP 运行环境的坑:pathinfo 与伪静态
下载源码包解压到/data/www/redpack后,入口文件一般位于public/index.php,Nginx 需要把非真实文件的请求全部转发到这个入口。公众号消息接口和网页授权回调对 URL 有路径要求,配置不当会出现“接口配置失败”或“redirect_uri 参数错误”。
以下是一份可直接使用的 Nginx 虚拟主机配置,注意关闭pathinfo相关兼容,统一用 try_files:
server { listen 80; server_name redpack.example.com; root /data/www/redpack/public; index index.php index.html; access_log /var/log/nginx/redpack.access.log main; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } location ~* \.(jpg|png|gif|css|js|map|ico)$ { expires 30d; access_log off; } }这份配置有两个要点。一是try_files把不存在的路径交给index.php,确保公众号后台配置的菜单链接和扫码 URL 都能被前端控制器接管;二是静态资源的缓存策略,二维码图片是高频访问资源,加expires能显著降低后端压力。如果出现“页面能打开但接口请求 404”的情况,先检查 PHP-FPM 的security.limit_extensions是否包含.php,这是最常见的隐蔽故障。
3.2 公众号后台参数与支付商户配置对照
部署源码包时要手动把公众号后台、微信支付商户平台的参数填入系统管理后台。V3.2.2 版本保留了微信支付 v2 现金红包的完整接口,需要在商户平台下载 API 证书和密钥文件,放到application/extra/对应的证书目录。参数对应关系如下:
| 源码配置项 | 获取位置 | 注意点 |
|---|---|---|
| AppID | 公众号后台 -> 基本配置 | 与网页授权域名绑定 |
| AppSecret | 公众号后台 -> 基本配置 | 每次重置后需同步更新到源码 |
| Token | 公众号后台 -> 基本配置 | 用于服务器配置校验,与源码 env 一致 |
| EncodingAESKey | 公众号后台 -> 基本配置 | 消息加解密密钥,模式选安全模式时需要 |
| MchID | 微信支付商户平台 -> 账户中心 | 商户号,与 AppID 绑定关系需在商户平台确认 |
| APIv3 密钥 | 商户平台 -> API 安全 | v2 红包接口用证书 + 32 位 API 密钥 |
| 证书路径 | 商户平台 -> API 安全 -> 证书管理 | 分为 apiclient_cert.pem 和 apiclient_key.pem |
一个反复出现的问题是:网页授权域名与 JS 接口安全域名没有配置正确,导致扫码后跳转时出现“redirect_uri 参数错误”。公众号后台的网页授权域名只填域名不填协议和路径,例如填写redpack.example.com,但服务器配置里的 URL 却要求带完整路径https://redpack.example.com/index.php/api/wechat/callback,这两个位置经常被混淆。上线前先用微信官方的接口调试工具,拿一个真实的 code 换 openid,验证整个授权链路通了再放量。
3.3 初始化脚本与队列常驻进程
源码包根目录一般带一个install.sql或redpack.sql,导入后需要修改.env的数据库连接和 Redis 配置。V3.2.2 的红包发放任务用 Redis 列表做队列,推荐用supervisord托管消费进程。下面是对应的 supervisor 配置:
[program:redpack-worker] process_name=%(program_name)s_%(process_num)02d command=php /data/www/redpack/think queue:work --queue redpack --daemon directory=/data/www/redpack autostart=true autorestart=true stopasgroup=true killasgroup=true user=www numprocs=2 redirect_stderr=true stdout_logfile=/var/log/redpack_worker.log这里numprocs=2开两个消费进程,一个是领取消息的消费者,另一个是超时未发放任务的扫描器。如果这个过程没有正常跑起来,用户扫码后状态一直停在“领取中”,但公众号消息不会报错,排查时先看 Redis 队列长度和 worker 日志。上线顺序建议先启动 worker 再开放活动,避免用户扫码后红包进入死信队列。
4. 红包发放与提现对账:现金红包接口的参数与防刷策略
红包发放是这套系统里最敏感的资金操作,接口参数相比普通支付要多两倍,而且不同状态下的返回码语义含糊。V3.2.2 版本使用的是微信支付现金红包 v2 接口POST /mmpaymkttransfers/sendredpack。该接口要求客户端证书、双向 TLS 认证、32 位密钥签名,三个条件缺一不可。很多人在本地调试时鼓起勇气把证书路径写到配置里,却忽略了一个隐性条件:调用方 IP 必须在商户平台的白名单内,否则返回NO_AUTH。
4.1 现金红包接口调用:证书、签名与订单号约束
一次正常的红包发放请求包含以下几个关键参数,每个参数的取值都有严格的格式要求:
nonce_str 随机字符串,长度 32 位以内 mch_billno 商户订单号,格式 mch_id + yyyymmdd + 10 位数字,总长 28 位 mch_id 商户号 wxappid 公众号 AppID send_name 活动名称,展示给用户的商户名称 re_openid 领取红包的用户 openid,必须是 AppID 下的关注用户 total_amount 金额,单位分,且必须大于等于 100 分 total_num 1,单个红包 wishing 祝福语 client_ip 调用接口的服务器 IP act_name 活动名称 remark 备注信息其中总金额有下限约束,低于 1 元的红包会直接报错,这意味着“一物一码”的码面金额通常得在 1 元以上才能跑通这个接口。如果需要发几分钱的红包,通常的做法是换成微信支付代金券或商家转账接口,不能沿用现金红包。下面是基于 PHP 的发送实现,使用 v2 接口的 HMAC-SHA256 签名:
// 发送微信现金红包 v2 接口 function sendRedpack($openid, $amount, $billNo) { $params = [ 'nonce_str' => md5(uniqid()), 'mch_billno' => $billNo, 'mch_id' => '你的商户号', 'wxappid' => '公众号AppID', 'send_name' => '扫码领红包', 're_openid' => $openid, 'total_amount' => $amount, 'total_num' => 1, 'wishing' => '感谢您的关注', 'client_ip' => getServerIp(), 'act_name' => '一物一码活动', 'remark' => '扫码关注公众号领取红包', 'scene_id' => 'PRODUCT_2', // 商品营销场景 ]; // 生成签名,按 key 排序后拼接 ksort($params); $stringA = ''; foreach ($params as $k => $v) { $stringA .= $k . '=' . $v . '&'; } $stringA .= 'key=' . $this->apiKey; $params['sign'] = strtoupper(hash_hmac('sha256', $stringA, $this->apiKey)); // 使用证书发起 HTTPS 请求 $response = httpsPost( 'https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack', toXml($params), $this->certPath, $this->keyPath ); return parseXml($response); }签名是按照参数名 ASCII 码升序拼接后加 API 密钥做 HMAC-SHA256,再转大写。很多调不通的案例都出在mch_billno上:同一笔订单号当天重复使用会返回SYSTEMERROR,第二天再试却可能成功,这个歧义文档里没有说明。每笔发放前要用数据库红包记录表的主键做唯一约束,生成订单号时带上主键 ID,这样从源头杜绝了同一码重复发两次的可能。
4.2 领取流程里的幂等与防刷参数
防刷是一个系统工程,不是加一两个 if 就能解决的。扫码领红包的路径上至少有四个节点需要设防:码是否有效、用户是否已领过、IP 是否异常、批次是否超发。V3.2.2 里有一个值得保留的做法:领取请求里带上码号和 openid 的哈希值作为临时令牌,有效期为 5 分钟,用户扫码进入页面时下发,点击“领取”按钮时校验,此时用“页面令牌 + 码号 + openid”三者一起做缓存键。这能拦住批量脚本直接绕过页面刷接口。
另一个实用的防刷参数是时间窗口限制。同一个 openid 在 24 小时内最多领取 3 个码,同一个 IP 在 10 分钟内最多扫码 20 次,超限后在码表里跳过领取逻辑,直接返回“活动太火爆”。实现时把这两个规则放进 Redis 的 INCR + EXPIRE 里,成本极低但效果显著——大促期间真正的用户行为几乎不会触发这些阈值,而黄牛脚本很容易撞上。
// 领取前的频控检查 function checkFrequency($openid, $ip) { $dayKey = 'redpack:user:' . date('Ymd') . ':' . md5($openid); $ipKey = 'redpack:ip:' . date('Hi') . ':' . md5($ip); $dayCount = Redis::incr($dayKey); if ($dayCount == 1) { Redis::expire($dayKey, 86400); } $ipCount = Redis::incr($ipKey); if ($ipCount == 1) { Redis::expire($ipKey, 600); } if ($dayCount > 3 || $ipCount > 20) { return false; } return true; }这里的键设计把时间粒度放进键名里,天然实现窗口重置,不需要额外维护过期时间。用md5($openid)避免 openid 直接暴露在 Redis 键里,防止日志泄露用户身份。领取状态更新用数据库的UPDATE ... WHERE status=0做乐观锁保护。
4.3 对账任务与红包状态回查
红包发出去不等于结束,还需要定时回查微信侧的状态。常见场景是接口返回成功但用户没有收到红包,或者返回失败但用户实际已经收到钱。V3.2.2 源码里包含一个手写的对账脚本,每天凌晨扫描所有“发放中”状态的红包记录,调用查询接口获取最终状态并更新本地表。查询接口是POST /mmpaymkttransfers/gethbinfo,传入mch_billno和mch_id,返回包括STATUS(RECEIVED / SENDING / FAILED)和REFUND状态。如果发现订单状态为发放中超过 24 小时,需要人工介入查看。
5. 二次开发验证技巧:在本地打通码、关注与红包回调
源码包里的代码拿下来之后最头疼的问题是本地不好验证,因为微信公众平台的接口强制要求公网域名,本地环境很难模拟网页授权的完整链路。一个可行的替代方案是安装内网穿透工具把本地服务暴露到公网(注意不要用于生产环境),并在公众号后台临时把网页授权域名指向穿透域名。如果不想走穿透,也可以把扫码逻辑拆成两个部分:码表领取的代码逻辑写在本地测试,微信公众号接口内容用单元测试 mock 掉。
验证红包发放逻辑时有一种很干净的模拟方式:在服务层加一个“测试模式”开关,当配置开启时,不再真实调用微信现金红包接口,而是把total_amount写入本地日志表。测试模式下用户扫任何有效的码都能走完“关注 -> 领取 -> 记录”的完整流程,只是最后一步变成写一条fake_sent记录。上线前关闭该开关,恢复真实接口调用。这样做可以完整验证码状态流转、频控逻辑和订单号生成规则,只有最终打款行为被跳过。
二次开发时建议先改三个点:
- 活动有效期校验,在扫码入口处加一个
end_time判断,避免过期活动的码面金额被继续发放。 - 红包记录表增加
biz_no字段,承接商家自己的业务单号,未来做报表时能直接关联到对应订单。 - 把签名算法封装成独立类,目前 V3.2.2 的签名逻辑散布在控制器里,改造时抽取到 Extend 层。
验证完基础流程后,用下面的脚本模拟高并发领取,重点观察乐观锁和 Redis 频控是否生效:
# 并发 20 个请求模拟扫码领取,观察超发情况 ab -n 200 -c 20 -T 'application/x-www-form-urlencoded' \ -H 'X-Forwarded-For: 1.2.3.4' \ -p post_data.txt \ 'https://redpack.example.com/api/redpack/claim'post_data.txt里放code=某测试码,跑完检查请求响应中是否有超过 1 次成功的,再查码表看该码状态是否为已领取。如果出现两次成功领取同一个码的情况,问题通常出在码表的状态更新没有走UPDATE ... WHERE status=0而直接用了UPDATE ... WHERE code=xxx。这个并发验证脚本在本地十秒就能跑完,能提前暴露几乎所有核心数据一致性问题。
本文还有配套的精品资源,点击获取