
简介这是一套面向营销运营场景的H5幸运刮刮乐抽奖系统源码采用刮刮乐玩法支持免公众号直接运营并内置多级分佣功能适合企业、电商平台或地推团队快速部署会员拉新与促销抽奖活动。压缩包共2013个文件大小约42.01MB主体为PHP与JavaScript源码同时包含JSON数据配置、HTML页面、CSS样式、图片及GIF动效资源以及SQL数据库文件等覆盖前端交互、后端逻辑、数据存储与商户支付配置等完整环节。包内附带详细的搭建教程从系统环境MySQL5.6、PHP7.2及扩展安装到站点配置、数据库导入、环境变量修改均有说明并给出后台管理入口、管理员账号密码以及支付接口/回调地址的更换方法便于二次开发或直接上线。目前已有108人学习下载适合具备一定PHP开发基础、希望快速搭建抽奖分销体系的开发者参考使用。1. 免公众号的 H5 刮刮乐系统能直运营的关键这套 H5 幸运刮刮乐抽奖源码我拆过不止一遍。它表面上是 Canvas 刮奖动画真正把运营链路撑起来的是“免公众号 直运营 多级分佣”这三个词。免公众号意味着你不需要服务号、不需要 JSAPI 支付授权目录用户拿到 H5 链接或二维码就能直接参与直运营意味着部署一套 PHP 服务就能上线不依赖第三方平台多级分佣则把普通抽奖工具变成了能裂变的获客系统。适合做活动拉新、私域投放和代运营项目的团队读。下文按我从环境到上线的操作顺序讲重点放在易踩坑的配置和回调地址上。2. 从零部署PHP7.2、Swoole、Redis 与 ThinkPHP 伪静态2.1 环境版本为什么卡在 PHP7.2 和 MySQL5.6拿到源码先别急着建站先把环境版本对齐。摘要里明确写了 MySQL5.6、PHP7.2还要装 fileinfo、sg11、Swoole、redis 四个扩展并删除所有禁用函数。这不是随手写的而是和加密与运行库强相关。PHP 8.0 删掉了一批 PHP7 时代的函数而 sg11 即 SourceGuardian 加密的扩展加载器版本过旧或编译链不匹配时在 PHP 7.3 以上常常直接报 “Unable to load ... protected script” 或空白页。这里最低成本的就是锁定 PHP7.2。MySQL5.6 不是性能原因而是老项目的 SQL 习惯和字符集排序规则。如果你用 MySQL8 导数据默认 utf8mb4_0900_ai_ci 会导致部分老查询索引失效且 SQL 文件里若包含ENGINEMyISAM之类语句迁移时也会多出不少兼容问题。用 5.6 能少碰这些脏活。Swoole 在这个项目里一般用于异步回调或常驻内存的任务队列。刮刮乐活动在并发高时如果每一次刮奖都走 php-fpm 同步写库数据库和缓存压力都会堆起来有 Swoole 在可以把分销入账这类非核心逻辑丢到异步进程里。Redis 用来做抽奖频控多级分佣最怕被刷一台设备反复注册小号把一级二级佣金全拿走。没有 Redis你靠数据库count(*)也能查到但并发一高就是慢查询重灾区。组件版本/扩展作用缺失时现象MySQL5.6数据存储导入SQL报错或索引失效PHP7.2运行环境SG11扩展加载失败fileinfoPHP扩展MIME检测图片上传/二维码生成异常sg11PHP加载器解密源码页面空白或源码报错SwoolePHP扩展异步回调与队列高并发下回调解体延迟RedisPHP扩展服务频控和缓存抽奖次数限制失效2.2 扩展安装fileinfo、sg11、Swoole、Redis先确认当前环境命令如下php -v # 确认 PHP 版本 php -m | grep -iE fileinfo|swoole|redis php -m | grep -i SourceGuardian说明第一条看版本第二条如果输出三行中缺少项就需要用包管理工具或手动编译补装第三条必须能看到 SourceGuardian否则加密的入口文件根本不会被解析。sg11 的安装常见做法是把源码包里自带的ixed.7.2.lin复制到php -i | grep extension_dir显示的目录下然后在 php.ini 末尾加一行extensionixed.7.2.lin。Swoole 和 redis 扩展一般用 pecl 装pecl install redis-5.3.7 pecl install swoole-4.8.13注意Swoole 版本不要选 5.x因为 PHP7.2 的 ABI 对不上。装完后执行php -m确认再重启 php-fpm。删除禁用函数这里我一般处理proc_open、shell_exec和symlink其余保持系统默认因为旧代码的导出、压缩功能可能会用到全部删除会有风险。注意要在 PHP-FPM 的disable_functions配置里改改完重启再验证。2.3 站点、运行目录与伪静态创建站点的时候“运行目录”要指向public伪静态选thinkphp。如果你用 Nginx伪静态规则本质上就是所有非真实文件都重写到index.php。Nginx 配置参考server { listen 80; server_name draw.example.com; root /www/wwwroot/draw/public; index index.php index.html; location / { if (!-e $request_filename) { rewrite ^(.*)$ /index.php?s$1 last; } } location ~ \.php$ { include fastcgi_params; fastcgi_pass 127.0.0.1:9000; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; } }说明root直接指到public不是项目根目录。if (!-e $request_filename)是 ThinkPHP 常见的兼容写法把不存在的 URI 转给index.php的s参数。fastcgi_pass的地址要和 PHP-FPM 配置一致宝塔上通常是/tmp/php-cgi-72.sock要把它替换成你实际的 socket 或端口。Apache 环境则是在public目录放.htaccess内容就是 ThinkPHP 官方提供的 rewrite 规则。判断伪静态有没有生效最简单是随便访问一个不存在的路径如果返回 ThinkPHP 的 404 页面而不是 Apache/Nginx 的 404就说明 rewrite 正常。3. Public 入口、数据库导入与 .env / database.php 的替换3.1 为什么入口一定要指向 Public 目录ThinkPHP 5 的标准结构是 public 下只有 index.php、静态资源和其他公开文件而 application、config、runtime 都在更上层。如果把站点根目录设在项目根目录别人就能直接访问到/config/database.php的备份或缓存文件甚至某些不会解析 PHP 的中间件能直接把文件内容拉走。这套源码的敏感配置在 .env 和 config/database.php 两处根目录和 public 目录的层级关系一旦弄错调试时看到的报错里就可能直接带出数据库账号。所以第一步不要跳先在站点设置里把运行目录切到 public。3.2 导入数据库在新建站点时大部分面板会自动建一个空库但表结构需要手动导入。我一般先在命令行建库指定字符集再导入 SQLmysql -uroot -p -h127.0.0.1 -e CREATE DATABASE IF NOT EXISTS draw_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; mysql -uroot -p -h127.0.0.1 --default-character-setutf8mb4 draw_db sql/install.sql说明第一行是建库utf8mb4_unicode_ci对特殊昵称、表情符号更稳妥。第二行把 SQL 文件导入。如果你的源码包里的 SQL 文件名不是install.sql就用包内实际的.sql文件路径。导入完成后可以用mysql -uroot -p -e USE draw_db; SHOW TABLES;看表是否齐全。常见问题有两种一是 SQL 文件带有DROP TABLE IF EXISTS导入到已有业务库会清数据必须用独立库二是 SQL 文件头部有USE语句和命令行指定的库不一致打开 SQL 文件确认一下。3.3 .env 第5行域名、12-14行数据库连接这里的修改是整套源码能不能跑通的关键。.env默认内容大致是按源码包行号排列行号参数示例作用5APP_URLhttps://draw.example.com拼接 H5 分享链接和支付回调的基础域名12DB_HOST127.0.0.1数据库地址13DB_USERNAMEdraw_user数据库账号14DB_PASSWORDxxxxxx数据库密码修改成# 第5行只写域名不要带目录 APP_URLhttps://draw.example.com # 第12-14行写实际数据库信息 DB_HOST127.0.0.1 DB_USERNAMEdraw_user DB_PASSWORDyourpassword逻辑说明APP_URL不只给前端海报用很多支付回调、分享海报二维码的拼接都依赖这一项。常见错误是把https://draw.example.com/public填进去导致生成的海报地址变成 404。DB_*三项要数据库同步修改否则后面导入数据再改也没用。3.4 config/database.php 46-48行有些人不喜欢碰 .env直接把数据库写死在 config 里。实际项目里 .env 和 database.php 会互相覆盖Tp5 的惯例是 database.php 优先读取 env 函数如果没有就读取硬编码默认值。源码在config/database.php第 46-48 行附近长这样hostname env(DB_HOST, 127.0.0.1), database env(DB_NAME, draw_db), username env(DB_USERNAME, draw_user), password env(DB_PASSWORD, yourpassword),说明如果你只改了 .env 却不起作用大概率是这里的后缀写死成旧账号了。我在部署时两处会一起检查避免后面排查数据库连接错误浪费时间。改完配置还需要清掉 runtime 缓存常见做法是执行php think clear chmod -R 755 runtime注意chmod -R 755 runtime可以让 PHP-FPM 写入日志和缓存但不要给 777生产环境会留下安全隐患。这一步结束后浏览器打开https://draw.example.com能进入 H5 首页就说明环境基本通了。4. 多级分佣的分账实现从数据库字段到回调地址修改4.1 分佣数据怎么流转多级分佣系统的核心不是刮奖动画而是资金归属和状态流转。这套源码里常见的设计是用户表user有一个from_uid或parent_id字段订单表记录每次抽奖支付的金额分佣记录表rebate_log记录每一级应得多少钱和结算状态。它的流转顺序是用户通过分享链接带from_uid进入H5 落地页把该参数绑定到新用户账号。用户支付抽奖订单支付回调正常返回后订单状态改为已支付。分佣逻辑根据配置的比例向上逐级查出from_uid对应的上上级。每产生一条分佣记录先插入 pending 状态等结算任务或用户提现时再更新。如果某天分佣金额对不上一般先从订单的pay_status查起而不是直接查分佣。因为回调没到、重复回调都会造成状态错乱。排查时用的 SQL 大致是SELECT u.id, u.nickname, r.level, r.amount, r.status, r.created_at FROM rebate_log r LEFT JOIN user u ON r.user_id u.id WHERE r.order_sn 202501010001 ORDER BY r.level;说明level表示第几级status是分佣状态。只看支付成功且未发放的记录能快速判断是回调没触发还是佣金被卡在了二级审核上。4.2 回调地址app/Http/Middleware/Home.php 第71行摘要里专门点名这个文件可见它才是整套系统真正的中枢。第71行附近一般是在handle方法里拼回调地址源码大致长这样// app/Http/Middleware/Home.php 第71行附近 $callbackUrl https:// . $_SERVER[HTTP_HOST] . /api/pay/callback; if (config(app.env) demo) { $callbackUrl env(APP_URL) . /api/pay/callback; }说明这里改的是支付平台异步通知回来的地址必须是一个可以被外网直接访问的 URL不能用内网 IP。如果你用的是易支付这个地址就是易支付服务器回调你系统的入口如果你对接微信官方支付回调地址是在微信商户平台后台配置的不在这个文件里改所以摘要会说“对接微信官方的不需要改”。实际操作时我会先确认这个地址是否被中间件强制加了app/demo之类的前缀曾经遇到把env(APP_URL)配错导致回调地址变成https://xxx.com/public/api/pay/callback结果全部回调失败。4.3 支付配置SDK/epay.config.php支付参数在SDK/epay.config.php里行号很短内容也简单行号参数说明8partner易支付商户ID11key商户密钥27gateway对接的支付网关地址修改示例// 8行 $config[partner] 10086; // 11行 $config[key] abcdef1234567890; // 27行 $config[gateway] https://pay.epay.example.com/;说明这里的密钥是支付平台给你签名用的需要和易支付后台完全一致否则回调验签失败订单会一直停留在未支付状态。gateway是发请求的网关地址如果对接的是易支付第三方服务商填服务商提供的接口地址如果自建支付网关填你自己的网关域名。注意不要在这里写微信公众号的 AppID 和 Secret那是另一个位置。摘要里提到“公众号APP ID密钥后台更换”指的是后台设置不是这个配置文件。4.4 后台管理员密码与权限默认后台地址/admin/login账号 admin密码 123456。登录后找到“权限管理-管理列表”可以修改管理员密码。这个位置容易被忽略因为很多人习惯直接改数据库。修改管理员后后台的菜单权限和操作日志都会绑定 admin 这个 ID直接改库容易把密码 hash 改坏。建议在后台页面上操作同时把默认后台路径换掉常见做法是改路由别名或用 Nginx 加location限制内网 IP 访问。5. Canvas 刮奖层的实现及 H5 在微信与 App 内的兼容适配5.1 刮奖层globalCompositeOperation 与刮开面积刮刮乐前端实现并不复杂主要利用 Canvas 的图层叠加。先画一层银灰色覆盖在奖品图上监听手指或鼠标移动用destination-out把覆盖层擦成透明。核心代码const canvas document.getElementById(scratch-card); const ctx canvas.getContext(2d); const width canvas.width, height canvas.height; // 覆盖层 ctx.fillStyle #c0b8a0; ctx.fillRect(0, 0, width, height); ctx.font bold 24px sans-serif; ctx.textAlign center; ctx.fillStyle #ffffff; ctx.fillText(刮一刮, width / 2, height / 2); canvas.addEventListener(pointermove, function (e) { const rect canvas.getBoundingClientRect(); const x (e.clientX - rect.left) * (width / rect.width); const y (e.clientY - rect.top) * (height / rect.height); ctx.globalCompositeOperation destination-out; ctx.beginPath(); ctx.arc(x, y, 20, 0, Math.PI * 2); ctx.fill(); });说明destination-out会让新绘制的图形区域在目标 canvas 上变成透明所以刮到哪里哪里就露出下层奖品。坐标换算(e.clientX - rect.left) * (width / rect.width)是必须做的因为 canvas 的实际尺寸和 CSS 尺寸不一样不做换算会出现在高清屏上刮偏的经典问题。判断是否完全刮开可以在每次移动后对 canvas 像素做采样统计 alpha 小于某阈值的点占比例超过 40% 就自动弹窗显示奖品。常见做法是把“刮开比例阈值”放在前端配置里方便运营调整。5.2 免公众号场景下的用户识别与分享裂变免公众号意味着没有 openid那用户身份靠什么识别这套源码的处理方式是用手机号或邀请码绑定。新用户打开 H5先弹手机号授权或手动输入邀请码系统把from_uid写入 cookie / 本地缓存后续抽奖、下单都根据这个标识归属。链接长这样https://draw.example.com/?from_uid10086如果源码被封装成了 AppH5 页面还需要支持跳转 App 唤起。iOS 用 Universal LinksAndroid 用 URL Scheme例如drawapp://open?from_uid10086。前端可以先尝试跳转 scheme再用 2 秒定时器检测是否切到了后台如果没切后台说明用户没装 App继续留在 H5 页。这个技巧在做 H5 拉 App 场景时很常用。5.3 微信内嵌、小程序内嵌与 App 内嵌的差异同样是 H5在不同宿主里的坑不一样。微信内打开时如果页面用 uniapp 开发并嵌在微信公众号中需要定位的能力就必须引入公众号 JS-SDK走 wx.config 签名流程。需要注意这套源码默认免公众号所以不会主动注入 wx.config只有你要做“按照用户位置发放不同奖品”的活动时才需要接。签名接口要拿当前 url 的后半段url 必须是去除 hash 的完整地址否则会报 invalid signature。微信小程序内嵌 H5 时web-view 的左上角没有默认返回按钮这个坑很常见。常见做法是在 H5 里放一个“返回”按钮调用import wx from weixin-js-sdk; function back() { if (wx.miniProgram wx.miniProgram.navigateBack) { wx.miniProgram.navigateBack(); } else { history.back(); } }App 内嵌 H5 的情况则看 WebView 容器暴露了什么桥。一般安卓会在window上挂AndroidWebView对象iOS 用window.webkit.messageHandlers。确定宿主后优先走桥不要先调 history因为 App 的 webview 不维护完整的浏览历史栈。这里可以用一张表总结宿主用户识别关闭/返回定位微信直接打开手机号/邀请码浏览器返回需要 JS-SDK微信小程序 web-view手机号/邀请码wx.miniProgram.navigateBack小程序授权后传给 H5App webviewJSBridge 注入身份调原生关闭方法调原生定位接口6. 跑通后如何验证分佣入账与防刷奖支付回调不是说你点了“支付成功”就会走到分佣里建议用 curl 直接模拟支付平台的异步通知。比如易支付的回调格式是 form 表单项我一般这样打curl -X POST https://draw.example.com/api/pay/callback \ -d pid10086trade_noEPAY123out_trade_no202501010001typewxpaymoney100.00signtest说明这个请求大概率会验签失败因为 sign 是假的。但失败其实也是验证说明链路通到了签名校验这一层如果连失败日志都没有那就要检查回调地址的中间件是否拦截、nginx 是否把 POST 转成了 GET。分佣记录验证用一条 SQL 就够了SELECT order_sn, level, user_id, amount, status FROM rebate_log WHERE order_sn 202501010001 ORDER BY level;针对防刷奖推荐用 Redis 的 INCR 做设备维度的刮奖次数限制。常规代码里如果没有可以在进入刮奖接口前加$key draw:limit: . $userId; $times Redis::incr($key); if ($times 1) { Redis::expire($key, 3600); } if ($times 5) { return json([status 0, msg 今日刮奖次数已用完]); }最后再说一个实用技巧我会把回调请求原文写入 runtime/logs/callback_YYYYmmdd.log每次支付回调都记录$_POST和签名校验结果再配合上面的 SQL 对账基本几分钟就能定位是支付平台没通知还是分佣代码挂了。本文还有配套的精品资源点击获取