☰
H5口红机源码实战:概率表驱动、权重配置与移动端活动页落地
2026/10/14 4:06:46 网站建设 项目流程

简介:H5口红机源码是一套基于HTML5的移动端互动游戏开发项目,面向具备一定前端或全栈基础的开发者,用于快速搭建类似线下口红机的线上抽奖挑战玩法。压缩包约30.52MB,内含docx安装配置说明、SQL数据库初始化脚本,以及包含HTML/CSS/JavaScript等文件的完整源码目录,方便开发者从环境部署到数据库导入一气呵成。项目支持自定义游戏规则、调整难度并扩展奖品逻辑,是理解Canvas绘图、JS交互流程和数据库表设计的实用范例。目前已有369人学习下载,适合希望通过真实项目练习H5开发、前后端联调与数据表规划的学习者二次开发或仿写。

1. H5口红机源码,是一套游戏,更是一次概率营销

我在门店做过一次活动,真正留得住人的不是那台机器,而是扫码后手机里那个 H5 口红机小游戏。H5口红机源码的本质,就是把线下口红机“光柱循环、点击停止、落位开奖”的互动搬到浏览器里,再套一层奖品概率接口。它解决的是商家最关心的三件事:怎么让用户玩起来、中奖率怎么控、参与次数怎么限。适合网页前端新手拿来做活动页练手,也适合门店运营的开发者把它改造成公众号或小程序里的引流工具。要理解这套源码,先记住一个反直觉结论:结果不是靠手气,而是在点击前已经被概率表算好,动画只是回放。

2. 拆开源码包:文件结构、核心循环与奖品权重

先把文件结构搞清楚,才知道哪些是 C 位,哪些是陪跑。常见的口红机 H5 项目会把代码分成前端游戏、静态资源和极简后端三大块。

h5-lipstick/ ├── index.html ├── css/ │ ├── base.css │ └── game.css ├── js/ │ ├── config.js │ ├── game.js │ ├── audio.js │ └── request.js ├── assets/ │ ├── images/ │ └── sounds/ └── server/ ├── prize.php └── log.php

这个结构不是每个源码都一样,但绝大多数能跑的 H5 口红机都跑不出这个骨架。js/config.js是全局参数,js/game.js是游戏主逻辑,server/prize.php决定你抽到什么。如果只是想在本地看效果,把server/log.php删掉不影响运行;但不要把config.js删了,因为game.js开局就会读window.GAME_CFG,读不到直接白屏。

我一般会先开一个本地静态服务器,把页面跑起来,然后打开浏览器调试工具,看 Network 面板里有没有红色请求。如果只有页面没有数据,问题通常出在request.js里请求地址是相对路径还是绝对路径。很多源码默认请求路径是/server/prize.php,本地预览根目录是项目目录时能通;部署到子目录时就要改成./server/prize.php。这个改动不影响逻辑,但影响能不能抽到奖。建议先备份一份原版再动路径,后面写概率验证脚本还要用到。

2.1 文件职责:删之前先看引用关系

把文件拆成三堆:必须保留的包括index.html、css/、js/、assets/images/;可以按需删的是audio.js、server/log.php;不要轻易删的是server/prize.php。判断依据很简单:index.html里<script>引了谁,谁就是必需品。比如audio.js被删掉后游戏依然能跑,但中奖时没声音,体验会打折扣。server/prize.php被删掉后,前端只能用本地随机函数,任何人都能打开控制台反复调start()刷奖品。

这块源码的第二个隐藏点藏在图片命名上。口红机面板每个格子对应一张图片,文件名通常是p1.png到p8.png,和config.js里的id一一对应。如果换成自己的图片,最好保持相同命名和尺寸,否则会出现口红图标被压缩变形。图片尺寸建议做成正方形,透明底 PNG,边上留 10% 安全距离,这样动画移动时不会穿帮。许多二次开发翻车,不是逻辑改错,而是图片路径写错。

2.2 游戏主流程:先定奖品,再演动画

这是整个 H5 口红机最核心的一点。很多新手拿到源码后,第一反应是找“碰撞检测”,其实口红机 H5 根本没有碰撞检测,它只有三段逻辑:请求结果、移动光柱、展示结果。我见过一个版本把随机写在动画完成之后,结果用户截屏时看到光柱已经压中奖品,却弹了个“谢谢参与”,这就是典型的时序错误。

正确的做法是先向服务端要结果,再让动画去“追”那个结果。核心代码简化后是这样:

// js/game.js 核心流程(简化版) function start() { if (playing) return; // 防止连点导致定时器叠加 playing = true; clearInterval(timer); request('/server/prize.php', { uid: currentUser }, function (res) { const prizeId = res.prizeId; // 服务端已经算好抽到什么 const targetIndex = prizeToIndex(prizeId); // 奖品 id 映射到面板下标 const extra = 2 * prizeList.length + targetIndex; // 至少先走两圈 let step = 0; timer = setInterval(() => { step++; moveLight(step % prizeList.length); if (step >= extra) { clearInterval(timer); showResult(prizeId); playing = false; } }, cfg.speed); }); }

参数含义:prizeList.length是面板格子数量,线下实体机一般是 8 格,H5 版常见 5 到 8 格;cfg.speed是每次移动的间隔毫秒数,太短看着像抽筋,太长用户会不耐烦,240ms 左右比较接近实体机的手感;extra里乘 2 是为了让用户至少看到两圈循环,避免刚点开始就停在面前,那种“秒停”会让人觉得有猫腻。整体思路是:动画循环次数可以变,但最终停的位置必须由targetIndex决定,其余时间只是在转圈。

有人会问:如果服务端挂了怎么办?正常做法是前端请求失败时直接提示“网络异常,请重试”,而不是继续本地随机。因为一旦放开本地随机,用户就能通过断网、重放请求等手段反推奖品,全部绕过服务端。所以哪怕只是 Demo,也建议保留请求失败分支,不要让游戏在没有服务端时“假装开奖”。

2.3 权重表怎么配:从固定概率到动态干预

权重表通常写在config.js里,然后由前端传给服务端做随机。也有的版本把权重写在 PHP 文件里。不管放在哪,理解权重而不是百分比是第一步。

// js/config.js window.GAME_CFG = { speed: 240, prizes: [ { id: 1, name: '正装口红', weight: 5, stock: 10 }, { id: 2, name: '小样口红', weight: 15, stock: 50 }, { id: 3, name: '优惠券', weight: 30, stock: 200 }, { id: 4, name: '谢谢参与', weight: 50, stock: 0 } ] };

这个配置中总权重是 100,所以 id=1 占 5%,id=4 占 50%。但不是所有源码总权重都是 100,有的写 1、2、3,此时实际中奖率是该项权重除以总权重:

奖品weight占比
正装口红55 / 100 = 5%
小样口红1515 / 100 = 15%
优惠券3030 / 100 = 30%
谢谢参与5050 / 100 = 50%

如果运营说“把正装口红概率压到 1%”,你要做的是把权重 5 改成 1,同时保持其他权重相对不变。但更清晰的做法是统一取总权重为 10000,每个权重是该奖品概率乘以 100。比如 5% 就是 500,1% 就是 100。这种写法能避免运营按百分比填结果算错。

动态干预的常见玩法是“连续中奖降权”。服务端在每次抽奖后把近 10 次结果存起来,如果某奖品连续出现 3 次以上,就临时把它的权重减半。这个逻辑会放在后端而不是前端,否则用户可以改 JS。我在模拟项目里看到的实现是:

// server/prize.php 动态降权片段 $recent = getRecentHits(10); // 近10次抽奖记录 if (count($recent) >= 3 && array_sum($recent) === 3 * $recent[0]) { // 前三次都是同一个奖品,权重减半 $prize = $prizes[$recent[0]] ?? null; if ($prize) $prize['weight'] = max(1, $prize['weight'] / 2); }

这里的getRecentHits可以从 Redis 或数据库读取,真实的线上环境更推荐 Redis 的列表结构。动态干预不是必须的,但能避免“头奖连出”导致商家亏本。设置权重时还有一条红线:stock为 0 的奖品必须在下发前过滤,否则会出现“抽中了正装口红,但库存为 0”的投诉。过滤逻辑最简单就是后端unset掉无货项。如果要在前端看到库存,可以展示“已售罄”状态,但真实可中奖的库存永远放在服务端。

3. 跑起来:从本地 HTTP 到微信内调试

H5口红机源码的好处是不需要安卓或 iOS 工程,一个浏览器就能跑。但要分清“能看页面”和“能完整开奖”是两码事。想跑完整流程,本地至少要能执行 PHP;如果只跑前端动画,Python 自带 HTTP 服务就够了。

3.1 本地环境:不装 Node 也能启动

先进入源码根目录,开一个静态服务器:

cd h5-lipstick # 方式一:纯前端预览,用 Python python3 -m http.server 8000 # 方式二:带后端接口,用 PHP 内建服务器 php -S 0.0.0.0:8000 -t .

方式一适合你看动画、调样式,但请求/server/prize.php会返回 404。方式二会把整个目录作为 Web 根目录,同时处理 PHP 文件,这样request.js里的接口才能通。用0.0.0.0监听是因为等会要让手机访问,如果只监听localhost,手机就进不来。如果你电脑上装了 PHP 但运行不到,检查一下 PATH 环境变量,Windows 下很多源码包自带的集成环境也能用,不一定要把 PHP 配到全局。

启动后在浏览器地址栏输入http://localhost:8000。如果页面显示一张口红机背景图但没有按钮,多半是css/game.css没加载,看看是不是项目名带空格导致路径解析错误。我在本地通常先开静态服务器,再在调试工具里把手机模拟器打开,把窗口宽度调到 375 像素,这个尺寸接近主流手机的逻辑宽度。纯前端看完后,再切到 PHP 方式跑一遍完整开奖,确认接口日志里有prize.php请求。

提示:在 Windows 下,如果php -S启动后页面能打开但接口 404,先检查是不是在项目根目录执行,而不是在server/子目录执行。

3.2 移动端适配:视口、触摸、全屏与防缩放

H5 口红机是单手操作,按钮通常就一个“开始/停止”,所以触摸反馈比桌面端更重要。index.html 头部至少要这样写:

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <meta name="format-detection" content="telephone=no">

maximum-scale=1和user-scalable=no在很多新浏览器里被忽略了,但微信内置 WebView 的老内核仍会遵守,保留它们可以减少双指缩放。format-detection是为了防止手机号或数字被识别成拨号链接,尤其是奖品文案里出现“888”这种数字时。CSS 上加一行* { -webkit-tap-highlight-color: transparent; }能让点击高亮消失,否则按钮每次触摸都会闪一下灰框。

移动端最容易忽略的是 300ms 点击延迟。现代浏览器一般没了,但旧 WebView 还在,解决办法是给按钮加touch-action: manipulation:

button { touch-action: manipulation; user-select: none; -webkit-user-select: none; }

这里的原理是告诉浏览器这个元素禁止双击缩放,系统可以提前触发点击,不用再等待双击判断。如果你在真机测试时发现“点了没反应”,先加这个样式,再检查 JS 绑定的是click还是touchstart。很多源码两者都绑,导致一次点击触发两次,按钮会出现“弹一下又弹一下”的效果。建议统一改成pointerup事件,兼容触摸和鼠标,同时避免在 PC 浏览器里因为触摸事件残留导致触发两次。

3.3 联调准备:日志验证与接口模拟

要保证 H5 在手机浏览器里能正常开奖,不只是页面显示对,还需要验证接口联通。常见做法是先在电脑上打开页面,F12 控制台手动执行:

// 模拟调试:在没有后端时快速验证前端流程 window.__MOCK__ = true; function request(url, params, callback) { if (window.__MOCK__) { setTimeout(() => callback({ prizeId: 4 }), 300); } }

如果源码里request是全局函数,这段代码可以在页面加载后覆盖。实际源码里request可能被定义为局部函数,那就需要直接打开js/request.js改几行代码。这样做的意义是把“服务端问题”和“前端动画问题”隔离开:如果模拟返回 id=4 时前端能正常落到“谢谢参与”格,说明动画没问题,剩下的就是接口联调。

真机联调时,手机和电脑要在同一局域网,电脑上跑php -S 0.0.0.0:8000 -t .,然后手机浏览器访问http://电脑IP:8000。Windows 查看 IP 可以用ipconfig,macOS 用ifconfig | grep inet。在手机上打开后,点“开始”如果接口跨域,页面会提示失败,这时看 Network 面板错误信息,是 403 还是 404。404 是路径不对,403 多半是跨域问题,需要到 PHP 文件里加响应头。我建议在本地联调时把接口返回的 JSON 在控制台打出来,确认prizeId是数字而不是字符串,因为有些源码用字符串和数字做映射判断,会在===时全挂。

4. 配置项与后端校验:把“运气”握在运营手里

把游戏跑通只是第一步。口红机不是玩具,它是营销工具,运营最关心的是“能不能调概率”“能不能看出谁中了”“能不能防止刷”。这一章讲配置和校验。

4.1 奖品配置:图片、名称、库存

在config.js中,奖品数组的每一项都决定面板上一个格子。常见的字段如下:

字段类型说明示例
idnumber奖品唯一标识,对应图片名1
namestring前端展示名称正装口红
weightnumber权重,越大越容易中5
stocknumber库存,0 表示不可抽10
imgstring图片路径assets/images/p1.png

配置改写步骤:先把assets/images/下图片换成自己品牌图,命名p1.png到pN.png;然后在config.js里同步 name 和 id。注意img字段不是所有源码都有,有的版本直接从id拼路径,这时不要加img,否则读不到。如果你的源码没有stock字段,建议在config.js里加一个stock,但不要用它做真实扣库存,只作为前端展示。真实库存要在服务端数据库里扣减,否则同一份奖品会超发。

4.2 后端校验:别让用户直接调用 JS 函数

新版浏览器开发者工具是开放的,任何人都可以打开控制台执行源码里的函数。如果不做后端校验,用户会反复调start(),把口红机变成刷单工具。所以上线时必须让prize.php不再无条件返回奖品,而是先做“身份校验、次数校验、库存校验”三层检查。代码骨架:

// server/prize.php <?php session_start(); // 1. 身份:生产环境用 openid,Demo 可以用 uid 参数模拟 $uid = $_POST['uid'] ?? 'guest'; // 2. 次数校验:这里用 session 演示,线上用 Redis 做 $today = date('Ymd'); $key = "draw_count_{$uid}_{$today}"; $count = (int)($_SESSION[$key] ?? 0); if ($count >= 3) { http_response_code(403); exit(json_encode(['error' => 'reach_limit'])); } // 3. 库存过滤 $prizes = loadPrizesFromDb(); // 从数据库读取 $pool = array_filter($prizes, function ($p) { return $p['stock'] > 0; }); if (count($pool) === 0) { http_response_code(403); exit(json_encode(['error' => 'sold_out'])); } // 4. 按权重抽奖 $total = array_sum(array_column($pool, 'weight')); $rand = mt_rand(1, $total * 100) / 100; $cursor = 0; foreach ($pool as $prize) { $cursor += $prize['weight']; if ($rand <= $cursor) { // 抽中后扣库存并记录日志 deductStock($prize['id'], $uid); echo json_encode(['prizeId' => $prize['id']]); break; } }

这里把$_SESSION当存储只是演示。真实环境里,多个用户共享内存,Session 文件方案会在并发稍微高一点时出现锁竞争。建议用 Redis 的 INCR 命令加过期时间,第 4.3 节会提到。loadPrizesFromDb和deductStock是占位函数,生产环境换成数据库操作。注意mt_rand是 PHP 的随机数生成器,比rand()更离散,不要用array_rand,因为array_rand是按数组下标均匀随机而不是按权重。

4.3 参与限制:次数、时间窗口

除了抽奖逻辑,还要把活动开关和参与限制做成配置项。有些源码把限制写死在game.js里,比如“每天 3 次”,但运营要改次数就得改代码发版,很蠢。后端接口可以给一个独立的配置接口,前端每次进入时拉取:

// 前端启动前拉活动配置 fetch('/server/config', { credentials: 'include' }) .then(res => res.json()) .then(data => { if (!data.active) { showToast('活动已结束'); return; } window.DAILY_LIMIT = data.dailyLimit; // 每天可抽次数 initGame(); })
// server/config.php 返回活动配置 header('Content-Type: application/json'); echo json_encode([ 'active' => true, 'dailyLimit' => 3, 'startTime' => '2025-01-01 09:00:00', 'endTime' => '2025-01-31 22:00:00' ]);

前端拿到dailyLimit之后再调用抽奖接口,这样运营只改配置,不用动前端。但注意次数判断永远在后端做,前端显示剩余次数只是一个“预估”,因为可能有多个页面标签同时打开。时间窗口同样要后端判断,不能只靠前端隐藏按钮,否则用户改时间还能抽。这里还能加一个“分享解锁”的玩法:抽奖前要求把页面分享到好友或群聊,服务端通过分享回调确认后再发放一次额外次数。分享解锁属于社交裂变,实现时不要让前端自己上报“我分享了”,否则会被脚本刷,正确做法是用短链参数带上分享者标识,服务端校验来源。

5. 避坑清单:真机白屏、概率失控与音效失效的四个典型坑

这里写踩过最多的高频坑。每条按现象、原因、解决展开。

5.1 真机白屏 / 点击没反应

现象:电脑上正常,手机浏览器打开后只看到背景图,点击开始按钮没有任何反应。

原因:十次里有七次是config.js没有被正确加载。game.js里第一行就读window.GAME_CFG,一旦config.js因为路径错误或加载顺序不对没执行,整段脚本抛异常,页面后面的按钮绑定全部失效。另外,按钮绑定了click但没处理 300ms 延迟,用户点了但系统还没把点击事件派发出来,加上没有视觉反馈,看起来就是没反应。

解决:先在桌面浏览器控制台看有没有GAME_CFG is not defined之类的报错,有就检查index.html中<script src="js/config.js">是否在game.js之前。然后给按钮样式加touch-action: manipulation,并把事件绑定改成pointerup。如果源码里用的是click,不要直接改成touchstart,因为touchstart会在手指按下瞬间触发,和用户视觉上的“按下去”不同步。最稳妥是pointerup,兼容鼠标和触摸。改完一定要到真机测一遍,模拟器不能完全复现触摸时序。

5.2 音效在 iOS 上静音、安卓爆音

现象:安卓手机第一次玩有声音,iOS 手机完全没有音效,或者点按钮后隔几秒才播放。

原因:iOS 浏览器对音频播放有严格限制,页面在用户没有触摸之前不能创建音频上下文,程序里一进页面就new AudioContext()会被静音。另一个原因是源码在每次调用play()时新建Audio对象,iOS 只允许同一个用户手势触发的音频实例播放。安卓爆音通常是音频文件本身的问题,比如采样率是 48kHz 但没有编码好,或者声音文件是立体声且超长,iOS 能解码但音量异常。

解决:在用户第一次点击“开始”时执行一次音频解锁函数:

function unlockAudio() { if (window.__unlocked) return; const ctx = new AudioContext(); ctx.resume(); window.__unlocked = true; } document.getElementById('startBtn').addEventListener('pointerup', unlockAudio);

播放音效时不要每次都new Audio(),而是在页面加载时预创建 2 到 3 个Audio实例循环使用。音效文件建议统一转成 44.1kHz、16bit、单声道的 MP3,时长控制在 1 秒以内。如果你只是做活动页,甚至可以把音乐素材全部删掉,避免兼容性麻烦。

5.3 概率失控——权重算错导致头奖爆率

现象:运营把weight从 5 改成 1,结果抽了 100 次,头奖出了 18 次,跟预期 1% 完全偏离。

原因:源码里实际用的是百分比数组还是权重表,没有先确认。如果代码用Math.floor(Math.random() * total),而total不是权重总和而是奖品数量,概率完全失真。还有一种常见错误是权重区间写成<=和<边界重叠,导致头奖区间被多占一个点。另外,如果stock过滤逻辑放在权重计算之后,无货项被抽空后权重仍参与计算,导致头奖实际占比被抬高。

解决:写一个独立模拟脚本,把权重表跑一万次,看分布是否等于“权重/总权重”。这个脚本不依赖游戏页面,直接在 Node 或浏览器控制台跑。如果偏差超过正负 2%,检查随机边界条件:

// 区间写法一定要左闭右开 let rand = Math.random() * total; // 0 <= rand < total for (const item of prizes) { if (rand < item.weight) return item.id; rand -= item.weight; }

这里用的是<而不是<=,同时rand是浮点数,不会出现整数化造成的区间重叠。如果源码里写Math.floor(rand),记得把总权重也放大,避免概率集中到尾部。概率验证后,还要确认stock为 0 的奖品在进入权重算法前就被排除,否则它会占着百分比。

5.4 安卓返回键直接退出页面 / 接口跨域 403

现象:用户在安卓微信里点完开始,看到结果后想返回,按了一次物理返回,整个页面直接退出,活动数据没上报。另一个常见现象是页面从主域名打开,接口却在另一个域名,请求被浏览器拦截,返回 403。

原因:H5 页面默认只有一个历史记录入口,安卓返回键行为等于浏览器后退,页面关闭。跨域则是因为前后端没有设置 CORS 响应头,有的源码只在部分接口写了Access-Control-Allow-Origin,漏掉了预检请求。

解决:对于返回键,在页面加载后主动压入一个历史栈,并监听popstate:

history.pushState(null, null, location.href); window.addEventListener('popstate', function () { history.pushState(null, null, location.href); });

这样用户按返回键时,会先触发popstate并重新压回一个空记录,页面不会关闭,哪怕连续按两次也只会回到页面初始状态。这个技巧对活动页尤其是抽奖结算非常有用,可以避免用户误关闭。对于跨域,在server/prize.php顶部手动加上:

header('Access-Control-Allow-Origin: ' . ($_SERVER['HTTP_ORIGIN'] ?? '*')); header('Access-Control-Allow-Credentials: true'); header('Access-Control-Allow-Headers: Content-Type'); if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(204); exit; }

安全做法是不要把HTTP_ORIGIN直接回显,而是做一个白名单判断:如果来源不在允许列表里,就不返回该头。但很多活动页不需要登录,可以用*配合credentials: 'omit'。如果接口必须带 session cookie,就必须回显具体来源,同时打开Allow-Credentials,否则浏览器会拦截。

6. 上线前最后一件事:先做一次千次模拟验证

这里的模拟验证不是把页面点一千遍,而是用脚本直接打接口或权重算法,把随机分布跑出来。很多源码在本地抽 10 次看不出问题,一旦并发上来,概率就歪了。

先写一个纯粹的权重模拟脚本,模拟 10000 次抽奖:

// simulate.js 在源码目录外层运行 const prizes = [ { id: 1, weight: 5 }, { id: 2, weight: 15 }, { id: 3, weight: 30 }, { id: 4, weight: 50 } ]; const total = prizes.reduce((sum, p) => sum + p.weight, 0); const result = {}; for (let i = 0; i < 10000; i++) { let rand = Math.random() * total; let chosen = prizes[prizes.length - 1].id; for (const p of prizes) { if (rand < p.weight) { chosen = p.id; break; } rand -= p.weight; } result[chosen] = (result[chosen] || 0) + 1; } console.log('权重分布:', result); // 预期 id=4 出现在 5000 次附近,误差超过 200 就要查代码

跑完看比例:id=4 应该在 50% 附近,id=1 在 5% 附近。如果 id=4 占比超过 52%,说明权重算法里存在取整偏差或者total被错误改成奖品数量。这个脚本独立于源码,不需要引入 config.js,因为它模拟的只是抽奖算法,不是整个页面。

接下来验证服务端接口。先用 curl 连续打接口 100 次,每次换一个 uid,避免被次数限制挡住:

for i in $(seq 1 100); do curl -s -X POST http://localhost:8000/server/prize.php -d "uid=test$i" echo done > result.log grep -o '"prizeId":[0-9]*' result.log | sort | uniq -c

注意uniq -c对 JSON 字符串去重,能看到每个prizeId出现的次数。这个方法的缺点是没有把库存扣减和写日志逻辑算进去,所以它只验证随机算法,不验证并发扣库存。并发扣库存要再用压测工具,或者用三条脚本同时请求同一个 uid,观察扣减后是否出现负库存。

最后,给页面加一个“落单不上报”的补救机制。很多手机上用户抽完奖立刻切后台,ajax请求可能没发出去。用navigator.sendBeacon在pagehide时补发结算数据:

window.addEventListener('pagehide', function () { navigator.sendBeacon('/server/log.php', JSON.stringify({ uid: currentUser, prizeId: lastPrizeId })); });

sendBeacon比普通 fetch 更可靠,它在页面进入后台时把数据交给系统发送,不阻塞页面卸载。要注意pagehide里的数据量不要太大,只传必要字段,后端再根据uid和时间戳去重。

我上一次上线前偷懒只测了 100 次,结果权重参数被运营改成“1、15、30、50”后,总权重从 100 变成了 96,头奖实际概率成了 1.04%,运营按 1% 配库存,表面看没问题,但因为有库存过滤和动态降权,上线第一天头奖就被抽走两箱。从那以后,我每次都会强制把千次模拟脚本和服务端分布核对跑一遍,改一次配置跑一次,上线前再跑一次,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询