口红机H5在线游戏源码:服务端概率控制与微信生态适配要点
简介一套微信口红机H5在线游戏源码专为H5游戏运营者、独立开发者与中小团队站长设计无需接入公众号即可完整部署适用于门店活动、品牌推广、粉丝互动等场景。压缩包共4955个文件、约183MB以jpg1593个、png718个图片素材、php1265个后端业务逻辑、html574个静态页面、js288个前端交互、css131个样式为核心同时配套svg图标、gif动图、ttf/woff字体、mp3音效等资源目录按功能模块划分主题界面、接口、数据库与配置文件均可快速定位。包含数据库导入文件、后台管理入口及默认管理员账号可自行调整活动奖品、抽奖概率和界面文案还可替换图片素材与音效快速生成不同主题版本。部署教程明确说明SG11扩展安装、数据库连接与缓存清理步骤能帮助新手规避环境配置类典型报错。已有124人学习下载适合需要快速搭建可运营H5游戏并进行二次开发的读者。1. 微信口红机H5在线游戏源码一个链接就能开局的抽奖生意朋友圈里常见的口红机很多时候不是线下那台机器而是一个直接甩进微信群和朋友圈的H5链接点进来是一排整整齐齐的口红格子用户选中、支付或完成分享任务后端按权重抽奖中了填地址等发货“谢谢参与”则发一张优惠券。做这样一套微信口红机H5在线游戏源码难点不在前端动效而在“无需公众号也能跑”的部署路径——公众号网页授权和JS-SDK只有认证服务号能用但纯H5放在自己的域名上同样能在微信内置浏览器里正常打开、正常玩。支付和分享的弯绕明白这条路就通。本文适合美妆商家、做活动运营的产品以及买源码后想二次开发的个人开发者按步骤走慢则一天跑通。2. 口红机H5的三层骨架为什么概率必须控制在服务端先给一个反直觉的结论你在手机上看到的口红机页面前后端加起来往往不到一千行代码真正决定这个项目能不能长跑的是服务端那套概率、库存和订单的状态机。很多买来的源码喜欢把“剩余库存”“中奖结果”直接写在浏览器里用户用一遍就能抓到接口返回值甚至把中奖函数从控制台里调出来自己玩这种盘子上线第二天就得关。所以我每次评估一套口红机H5源码不是先看页面炫不炫而是先找“抽奖判定”到底在哪一层。2.1 前端交互层口红格子、动效和中奖飞屏前端这层负责的事不多渲染一排口红格子、受理用户点击、把结果用动效演出来。常见做法是一套移动端HTML页面加一个轻量交互层用不用Vue、uni-app都行——uni-app的H5产物也能用只是要记得把接口域名放到公共配置文件里避免换测试服时要重新打包。这就和常被问的“uniapp封装H5如何指向2个域名”是同一类问题域名别写死在源码里配置化才是正经做法。口红格子的布局就是宫格随便几张图片加CSS就能排出来div classboard idboard div classcell>import random def draw_by_weight(pool): pool 形如 [{prize_id:P1,weight:5}, {prize_id:P2,weight:50}] total sum(item[weight] for item in pool) r random.uniform(0, total) for item in pool: r - item[weight] if r 0: return item[prize_id] return pool[-1][prize_id]参数说明weight是相对权重不是绝对概率把特等奖权重设为1、谢谢参与权重设为100“中大奖”的直觉比例大概是1/101但运营常说的“中奖率”是含小奖的所以奖品表里要把“谢谢参与”也当作一档奖品单独列出来否则统计口径会打架。random.uniform(0, total)之后用递减法逐个命中比一次性生成权重数组再随机选择省内存也方便日后动态调整权重列表。边界情况要注意如果total为0函数会抛除零异常奖品池初始化时要兜一层if not pool or total 0: return none。第二套是带保底的动态加权。纯随机抽奖在样本量小的时候会出现“连抽五次全中”或者“大奖从没出现过”的极端分布用户会直接质疑概率造假。常见的做法是给每个用户累计“祝福值”def draw_with_blessing(pool, play_record): blessing play_record[blessing] need play_record[need_blessing] if blessing need: # 保底命中直接返回大奖并清零祝福值 play_record[blessing] 0 return BIG_PRIZE prize_id draw_by_weight(pool) if prize_id BIG_PRIZE: play_record[blessing] 0 else: play_record[blessing] blessing 1 return prize_id逻辑说明need_blessing就是保底门槛常见做法是大奖价值越高门槛越高比如“连续50次未中大奖第51次必中”。这样做不是为了讨好用户而是为了把运营风险控制在可预估范围如果没有保底大奖的发货成本在概率毛刺里会突然超标财务没法做预算。参数调优的原则是保底阈值乘上大奖成本要低于单用户预期毛利。比如大奖是一支市价300元的口红单次抽奖客单价10元保底阈值至少应设在60次以上否则每个用户在保底线前都能等到大奖成本就被打穿了。3.2 前端点击到出结果H5交互代码怎么写前一章贴了前端格子的监听代码这里补全一个更完整的前端抽奖流程。进入页面时先向后端请求/api/bootstrap拿到本轮gameToken和当日剩余次数点击格子后把gameToken随请求带给后端后端消费这个token并返回抽奖结果。async function bootstrap() { const res await fetch(/api/bootstrap, { method: POST }); const data await res.json(); localStorage.setItem(gameToken, data.token); renderBoard(data.prizes); renderLeftTimes(data.leftTimes); } async function doDraw(cellIndex) { const token localStorage.getItem(gameToken); const res await fetch(/api/lottery, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ cell: cellIndex, token }) }); const data await res.json(); if (data.code ! 0) { showToast(data.msg); // 常见返回次数不足、token已使用 return; } if (data.prize) { showWinFlyScreen(data.prize); requestAddress(data.prize); // 中奖后拉出填地址抽屉 } else { showLoseTip(data.coupon); } }参数说明gameToken是一次性令牌抽奖成功后应立即作废再次携带同一个token请求时后端要回一个“令牌已使用”的提示而不是再抽一次。这一条能同时挡掉两类问题用户手滑连点导致的重复抽奖以及恶意脚本刷接口。leftTimes是进入页面时的剩余次数它不是实时扣减的抽完后由响应里的最新剩余次数回填避免前端自己算着算着和服务器不一致。中奖后requestAddress内部会跳转到独立地址页不要把地址表单直接嵌在宫格页的fixed弹层里。这里要提醒一个常见的反模式有些源码为了图省事把“抽奖结果”直接放在请求参数里也就是把结果参数由客户端传上来服务端只做透传。这种接口拿到线上用户抓包改一个resultwin就全场通吃了。后端必须自己随机客户端传的cell只能用于记录用户点了第几格绝不能携带任何奖品信息或布尔结果。3.3 支付回调与对账凑齐“无需公众号”的最后一环如果你选择了现金抽奖模式微信支付回调就是整个源码里最容易出错的一段。回调的作用是用户付完钱后微信服务端会向你的回调地址POST一条通知告诉你这笔订单已经支付成功。这里的原则不是“接口收到通知就改状态”而是“验签通过且订单状态未被处理过才改状态”。# 伪代码结构具体字段以微信支付官方文档为准 def pay_callback(request): # 1. 校验请求来源与签名 if not verify_signature(request): return FAIL # 2. 解析订单号与支付结果 order_no request.get(out_trade_no) pay_state request.get(trade_state) # SUCCESS 才算支付成功 if pay_state ! SUCCESS: return SUCCESS # 非成功状态也回执避免重复推送 # 3. 幂等更新订单状态 updated update_order_if_created(order_no, statuspaid) return SUCCESS if updated else SUCCESS # 已处理过也回 SUCCESS逻辑说明verify_signature这一行是命门用商户号、证书序列号和回调报文算出的签名做比对验不过的通知一律当作没收到。update_order_if_created是用条件更新来实现幂等第一笔通知把订单从created改成paid微信因网络重试推来的第二笔通知条件更新影响行数为0函数依然返回SUCCESS但不会重复发货。为什么重复通知也要回SUCCESS因为微信支付会持续重试回调直到你明确返回成功如果因为“订单已处理”就回FAIL微信会一直推反而把你的日志刷爆。没有公众号时这条链路怎么走通页面在前端只是打开一个支付收银台或二维码后端通过商户接口生成code_url并不需要用户openid这是“无需公众号做支付”的常见可行路径。但这里有一个前置条件你必须拥有一个微信支付商户号并且把回调域名配到商户平台。没有商户号的话就按第2章说的方案走免费抽奖模式不引入资金流也就没有回调这回事。想清楚你做哪种再决定要不要把支付模块的代码放进部署清单。4. 适配微信内置浏览器的关键关卡从禁播音频到卡片分享一个页面在普通浏览器里跑得好好的一进微信就出现各种奇怪行为这几乎是每个做微信H5的人都经历过的玄学时刻。口红机这种强交互页面尤其不能忽略微信WebView的“小性子”这章把最容易踩的几个关卡逐个理清。4.1 识别微信环境UA判断和右上角引导第一件事是判断用户是不是在微信里打开的。常见的做法是用UA特征MicroMessenger来做环境识别function isWeChat() { return /MicroMessenger/i.test(navigator.userAgent); } if (isWeChat()) { // 在微信里显示“右上角分享给好友”的引导气泡 showShareGuide(); } else { // 在普通浏览器直接提示复制链接去微信打开 showBrowserTip(); }逻辑说明微信UA里固定带MicroMessenger字样版本号会跟着微信版本走所以用正则匹配特征值比匹配版本号可靠。这段代码放在页面入口处主要是为了做两件差异化的事情微信内引导用户点右上角“...”转发给朋友微信外提示复制链接去微信打开。需要注意电脑微信也带MicroMessenger但它的内核版本通常更旧某些CSS动画可能在电脑微信里掉帧验证时不能只看手机微信。区分环境不只是为了显示引导更重要的是决定拿不拿用户身份。如果后面有付费抽奖微信内环境配合公众号授权可以快速拿到openid无公众号时则拿不到普通浏览器拿到的用户标识和微信里可能不是同一个人统计口径要分开。我通常会给isWeChat()加一个开关允许后端通过配置强制切换“微信模式/浏览器模式”方便自己后期在电脑上调试。4.2 解锁微信端怪癖自动播放、长按存图、输入框上移微信内置浏览器有三个经典怪癖口红机一个都躲不开。第一个是自动播放被禁止。H5页面想放背景音乐或者点击格子播放音效直接调用audio.play()在微信里大概率被拒绝因为WebView在没有用户手势之前不允许播放。解决方法是让第一个触摸事件去解锁音频const bgm document.getElementById(bgm); document.addEventListener(touchstart, function unlockAudio() { bgm.play().catch(() {}); document.removeEventListener(touchstart, unlockAudio); }, { passive: true }); document.addEventListener(click, function unlockAudioClick() { bgm.play().catch(() {}); document.removeEventListener(click, unlockAudioClick); });参数说明passive: true告诉浏览器监听器不需要调用preventDefault触摸滚动时不会被破坏play().catch(() {})是因为用户首次触摸可能发生在异步准备好之前播放失败不能把错误抛到控制台吓到自己。解锁后可以销毁监听器避免每次触摸都触发一次play()调用。第二个是长按图片会弹出微信的“保存图片”菜单。口红机的奖品图和格子图都很适合长按保存但用户长按后页面可能出现菜单抖动体验很差。在图片区域禁用contextmenu和selectstart是常见做法document.querySelectorAll(.cell img).forEach(img { img.addEventListener(contextmenu, e e.preventDefault()); }); img.addEventListener(touchstart, () {}, { passive: true });注意不要全局禁用长按因为中奖后用户需要长按复制收货地址全局禁用会把复制功能也废掉。只针对纯装饰性的图片禁用就好了。第三个是iOS键盘把布局顶飞。填收货地址的页面里点击输入框弹出键盘时微信WebView的视口高度会变化position: fixed的底部按钮经常被顶到屏幕外或盖在键盘下面。常见规避是把底部按钮从fixed改成页面正常流输入框聚焦后按钮被键盘推上去而不是钉在某个绝对坐标。如果按钮必须吸底可以监听visualViewport的resize把按钮的位置跟随视口高度重新计算。口红机这种页面上我强烈建议地址提交做成独立页面而不是弹层这样键盘问题最简单。4.3 流量承接抽奖只是入口企业微信客服才是出口很多运营把口红机当成“抽奖小游戏”来投放抽完就结束这是一种浪费。口红机更合理的定位是“流量钩子”用户冲着口红来抽中或没抽中都需要一个出口去承接后续运营常见出口就是企业微信客服。H5接入企业微信客服的常见做法是在抽奖结果页和中奖确认页做一个“联系客服”的入口点击后打开一个由企业微信后台生成的客服链接或直接展示客服成员的二维码。这里有一个容易忽略的细节要把活动来源参数随链接带进客服侧比如sourcewx_lipstick_01这样客服后台能看到用户是从哪个活动来的话术可以提前分组。没有认证公众号也没有关系企业微信客服链接本身是独立H5不需要依附服务号授权。同时每一局抽奖的record_no最好不要只在后端日志里躺平中奖用户发起客服咨询时让用户把“单号页”的链接发过来客服点开就能看到这单是中了什么、发货到哪一步。这需要前端在结果页渲染一个只读的单号视图后端给一个带签名的一次性查询链接。这个功能不大但能把“用户中奖了找不到人”和“客服不知道用户中了什么”两个售后黑洞都堵上。一个口红机项目能走多远往往取决于这些细节是否在第一次上线前就安排好。5. 口红机H5落地避坑清单从丢单、重复支付到页面刷新买来的源码十有八九不会在文档里写清楚“哪里会炸”我把实际跑项目中反复踩过的五个坑按现象、原因、解决整理出来可以直接当成上线前的检查清单用。5.1 现象用户中了大奖后台却查不到这笔单用户截图说“我抽中了”后台翻遍流水表找不到记录第一反应是怀疑用户造假但查了接口日志会发现请求根本没到达抽奖逻辑。原因多半有两个一是前端在点击格子的瞬间就做了展示页面卡顿导致请求没发出二是后端先返回了中奖结果但写库失败后异常被吞掉了接口给用户的是“成功”库里却没有数据。解决后端抽奖接口必须把“写play_record成功”作为返回成功的前提代码顺序是“扣库存→写流水→提交事务→返回结果”。如果写库抛异常前面无论扣了多少库存都要回滚绝不能让用户看到中奖结果但库里没有单。额外再加一层兜底审计抽奖接口的入参、出参、耗时都打一条日志字段里带上record_no和token线上排查时用日志说话。5.2 现象支付成功后游戏又卡在“支付中”用户确实付了钱页面也跳转了但抽奖接口却说“订单未支付”用户心态直接爆炸。原因前端跳转后拿“支付成功”的页面展示但服务端订单状态只认微信回调回调没到订单就还停在created。回调没到的常见原因是回调地址配置成了http://或者回调域名没加到微信支付商户平台白名单微信无法访问。解决把订单状态判断全部放在服务端前端展示只读后端的查询结果。支付完成后前端不要立刻展示“支付成功”而是轮询/api/queryOrder后端在收到微信回调并更新订单为paid后才返回“可以抽奖”。轮询间隔取2秒连续10次没变再提示“正在确认支付结果”这能挡住大部分询问。检查清单里加一项上线前用微信支付的测试沙盒或小额单试跑一次回调确认回调URL不需要登录即可访问。5.3 现象iOS微信里页面反复加载刷新口红机页面在iOS微信里点进格子后白屏、闪一下又回到入口刷新一次又好了过一会儿又发作。这个问题在iPhone上尤其常见和“iOS微信H5公众号重复刷新”说的是同一类现象。原因一是页面用history.replaceState改写URL后iOS Safari对会话调度比较激进把WebView回收了二是入口页每次加载都去后端换新的gameToken用户一刷新令牌就变后端看到旧令牌失效又强制跳回入口形成循环。解决gameToken不要每次刷新都换进入页面时生成一次存入sessionStorage后端设置较长有效期如24小时抽奖时消费页面里不要用replaceState频繁改变历史记录路由切换用hash即可。还有一个干净的做法是让入口页只负责静默跳转真正的游戏页用独立URL承载这样WebView被回收后重新加载也能按URL直接回到游戏页。5.4 现象安卓手机键盘把按钮顶到屏幕外中奖后填地址的页面在安卓微信里点输入框键盘弹起来把底部“提交”按钮顶走点不到按钮只能先收键盘。原因低版本安卓WebView对visualViewport支持不完整页面高度在键盘弹出时没同步缩小position: fixed的按钮停留在被键盘截断的区域。解决地址页不要用吸底按钮改成表单末尾的普通按钮页面根容器去掉height: 100vh的写法改让内容自然撑高。键盘弹出时用visualViewport的resize事件把需要可见的控件滚进视口const vv window.visualViewport; if (vv) { vv.addEventListener(resize, () { document.getElementById(submitBtn).scrollIntoView({ block: nearest }); }); }这个监听只加在地址页游戏页不需要加避免抽奖动画被意外滚动干扰。5.5 现象分享出去的卡片没有标题和缩略图用户把口红机链接转发到群里卡片要么只有光秃秃的URL要么标题是一串默认域名。这个现象在“H5微信卡片分享”里被问得最多。原因微信在生成分享卡片时会优先读取页面头部里的title、description和og:image如果没有设置就退化成截取URL。认证公众号能通过JS-SDK强行指定分享卡片内容但“无需公众号”的模式下调用不了JS-SDK只能把页面头信息做对。解决在服务端按请求路径动态输出这些meta特别是缩略图地址要填一个带绝对域名的图片URL图片尺寸一般建议在300×300以上。分享出去的卡片不只是面子问题它直接决定口红机在群里的点击率。建议每次部署后先拿自己的微信转发一次看卡片效果是否符合预期再放量。6. 把口红机做成运营工具动态权重、抽取验证和复盘习惯最后一章不讲新功能讲一个让口红机从“一次性活动页”变成“长期运营工具”的习惯把概率和文案都做成可配置而不是藏在代码里。6.1 上线前的蒙特卡洛抽验权重抽奖算法写完第一件事不是联调页面而是跑十万次模拟验证真实概率符合业务预期。代码很简单from collections import Counter def simulate(pool, times100000): counter Counter() for _ in range(times): prize_id draw_by_weight(pool) counter[prize_id] 1 for prize_id, cnt in counter.most_common(): print(f{prize_id}: {cnt / times * 100:.2f}%)参数说明pool用线上同一份奖品表模拟次数越多结果越接近权重值的数学期望保底逻辑要单独写测试用例断言“连续N次未中、第N1次必中”。如果模拟出来大奖概率比预想的低回去查权重表不要在代码里写“如果随机数小于0.0001就中奖”这种特判否则运营想调参数就得改代码迟早翻车。6.2 把概率挪进配置里我自己做这类H5最深的教训是把大奖概率写死在源码里。上线第一天运营说“中奖率太高了预算超了”这边要改代码、重新打包、重新部署折腾半小时改成数据库权重配置后运营自己在后台把weight从5改成2一分钟生效不用碰代码。抽奖文案、按钮颜色、保底阈值凡是运营可能隔三差五想调的都放配置不要把运营的日常需求变成开发的发版任务。验证也要形成习惯每周看一眼play_record的分时中奖数出现连续一小时中奖率高于预设值先查是不是刷子用脚本撞接口再看保底逻辑是否被异常触发。概率游戏一旦让用户觉得“后台在暗箱操作”口碑就坏了反过来公开概率、限量保底、中奖可兑付用户反而会把你的链接转给更多朋友。这套复盘习惯不复杂但能让口红机H5从“接一个丢一个”变成可以长期运营的流量入口。希望帮到你。本文还有配套的精品资源点击获取