简介这是一套面向潮玩盲盒电商创业者的移动端商城系统源码基于ThinkPHP框架开发覆盖盲盒抽奖、一番赏、抽盒机等核心玩法可同时部署为H5手机站与公众号商城适合具备PHP基础、希望快速搭建盲盒类电商平台的开发者或运营团队。压缩包共约2000个文件整体215.93MB其中1353个js与137个css构成前后台交互与样式213个html承载页面模板另有98个json配置、8个sql数据库脚本及少量md说明文档结构完整、便于二次开发。部署环境为Linux7.6Nginx1.18PHP7.2MySQL5.6需安装fileinfo、sg11、redis扩展后台入口与数据库配置集中在.env文件支付商户可在平台设置中调整。目前已有338人学习下载读者可借此获得一套可直接运行的盲盒商城系统快速理解抽奖逻辑、订单流程与前后台目录组织节省从零搭建的时间成本。1. 盲盒抽奖移动端系统从一番赏玩法到H5商城的落地拆解盲盒抽奖移动端系统这两年从潮玩圈一路烧到电商技术圈核心诉求就一个把线下抽盒机的随机快感搬到手机屏幕上同时让运营方能把库存、概率、订单、支付全部管起来。一套典型的潮玩盲盒系统商城通常要同时跑通APP、公众号H5、小程序三端背后是同一套抽奖引擎和商品中台。很多人第一次接触这类项目以为难点在动画效果实际做下来会发现真正的坑集中在概率配置、库存扣减和订单状态机这三块。这篇笔记面向想自己搭一套盲盒手机站源码的开发者或者正在评估一番赏盲盒源码能不能直接用的技术负责人把从选型到跑通的最小路径拆开讲。我不会假装手里有一份现成的完整源码包而是按这类系统最常见的架构把每个环节该怎么做、参数怎么设、哪里容易翻车说清楚。2. 抽奖引擎与概率模型一番赏的赏级分配怎么落到代码里一番赏的玩法本质是「每套奖品有固定数量抽完即止」这和普通概率抽奖有本质区别。普通抽奖是每次独立概率一番赏是有限库存下的不放回抽样。很多盲盒手机站源码在这块翻车就是因为用独立概率去模拟一番赏导致最后几个大奖永远抽不出来或者提前被抽空。2.1 赏级配置表的结构设计一套盲盒系统的奖品配置通常分三层套系Set、赏级Tier、具体奖品Prize。套系代表一批货比如「某系列第一弹」共 80 个盒子赏级代表 A赏、B赏、Last赏这种等级具体奖品挂在赏级下面。数据库设计上我一般会这样建-- 套系表一批盲盒的总体信息 CREATE TABLE blind_box_set ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(128) NOT NULL COMMENT 套系名称, total_count INT NOT NULL DEFAULT 0 COMMENT 总盒数, remaining_count INT NOT NULL DEFAULT 0 COMMENT 剩余盒数, status TINYINT DEFAULT 1 COMMENT 1上架 0下架, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 赏级表A赏/B赏/Last赏的库存与排序 CREATE TABLE prize_tier ( id BIGINT PRIMARY KEY AUTO_INCREMENT, set_id BIGINT NOT NULL, tier_name VARCHAR(32) NOT NULL COMMENT A赏/B赏/Last赏, tier_order INT DEFAULT 0 COMMENT 展示排序Last赏排最后, total_qty INT NOT NULL COMMENT 该赏级总数量, remaining_qty INT NOT NULL COMMENT 剩余数量, INDEX idx_set (set_id) ); -- 奖品明细表每个具体奖品挂在哪一赏 CREATE TABLE prize_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, tier_id BIGINT NOT NULL, name VARCHAR(128) NOT NULL, image_url VARCHAR(512), weight INT DEFAULT 1 COMMENT 同赏级内权重一般相等, INDEX idx_tier (tier_id) );逻辑说明套系表控制整体剩余盒数赏级表控制每个等级还剩多少奖品明细表只负责展示。抽奖时先判断套系剩余再按赏级剩余数量做不放回抽取。参数上total_count和remaining_count必须严格同步扣减tier_order用来保证 Last赏永远显示在最后一位这是还原一番赏体验的关键细节。2.2 不放回抽奖的核心算法一番赏的抽奖逻辑不能简单用rand()加权正确做法是「按剩余赏级数量做区间映射」。假设当前套系剩余 80 盒A赏剩 2、B赏剩 8、C赏剩 20、Last赏剩 1其余是普通款那么总权重就是 80随机一个 1 到 80 的数落在哪个区间就出哪个赏。import random def draw_prize(set_id): # 取出该套系下所有赏级的剩余数量 tiers query_tiers(set_id) # 返回 [{tier_id, tier_name, remaining_qty}, ...] total_remaining sum(t[remaining_qty] for t in tiers) if total_remaining 0: raise Exception(该套系已抽完) # 构造区间映射 rand_num random.randint(1, total_remaining) cumulative 0 for tier in tiers: cumulative tier[remaining_qty] if rand_num cumulative: # 命中该赏级扣减库存 deduct_tier_stock(tier[tier_id]) deduct_set_stock(set_id) return tier # 理论上不会走到这里兜底返回最后一个 return tiers[-1]逻辑说明rand_num的范围是 1 到总剩余数每个赏级占据的区间长度等于它的剩余数量这样保证每个赏级被抽中的概率严格正比于剩余数量。参数上要注意deduct_tier_stock和deduct_set_stock必须在同一个事务里否则并发抽奖时会出现超卖。我一般会在扣减时加WHERE remaining_qty 0条件用数据库的行锁兜底。2.3 并发抽奖的库存扣减方案移动端盲盒抽奖的并发量在活动期会很高尤其是整点开抢。常见做法是用 Redis 预扣减加数据库最终一致。Redis 里存每个赏级的剩余数量抽奖时用DECR原子操作扣减扣到负数就回滚并返回「已抽完」。数据库层面再用消息队列异步落单。# Redis 预扣减示例key 格式 tier:stock:{tier_id} # 先判断再扣减用 Lua 脚本保证原子性 EVAL local stock redis.call(GET, KEYS[1]); if not stock or tonumber(stock) 0 then return -1; end; return redis.call(DECR, KEYS[1]) 1 tier:stock:1001参数说明KEYS[1]是赏级库存 key返回 -1 表示库存不足返回扣减后的值表示成功。这个 Lua 脚本的好处是判断和扣减在一个原子操作里完成避免先 GET 再 DECR 之间的竞态。数据库落单时如果失败需要把 Redis 库存加回去这个补偿逻辑一定要写否则会出现 Redis 和数据库库存不一致的玄学问题。3. 三端适配与支付回调H5、公众号、APP 怎么共用一套抽奖逻辑盲盒手机站源码通常要求一套后端同时服务 H5、公众号和小程序前端可能用 Vue 或 React 各写一套但抽奖接口必须统一。这里的关键是把用户身份、支付方式、订单来源做成可配置的适配层而不是在每个端里复制抽奖代码。3.1 统一抽奖接口的参数设计后端暴露一个/api/draw接口所有端都调它。请求参数里带platform字段区分来源带openid或user_id做身份识别。返回结构统一前端各自渲染。// 请求体 { set_id: 1001, platform: h5, // h5 / mp / app user_token: xxxxx, draw_count: 1 // 支持连抽 } // 响应体 { code: 0, data: { prize: { tier_name: A赏, name: 某限定手办, image_url: https://cdn.example.com/a.jpg }, remaining_count: 79, order_no: BB202501011200001 } }逻辑说明platform字段不参与抽奖逻辑只用于后续统计和支付路由。draw_count支持连抽时后端要循环调用抽奖算法但库存扣减要合并成一次批量操作减少数据库压力。返回的order_no是预生成订单号支付成功后才真正写入订单表。3.2 支付回调的幂等处理支付回调是盲盒系统最容易出问题的地方。用户支付成功后支付平台会回调你的服务器但回调可能重复发送。如果没做幂等用户抽一次奖可能扣两次库存或者发两次货。def handle_payment_callback(order_no, transaction_id): # 先查订单是否已处理 order query_order(order_no) if order is None: return FAIL, 订单不存在 if order[status] PAID: # 已处理过直接返回成功避免重复发货 return SUCCESS, 已处理 # 用数据库唯一索引兜底transaction_id 建唯一索引 try: update_order_status(order_no, PAID, transaction_id) except DuplicateKeyError: return SUCCESS, 重复回调 # 执行抽奖和发货逻辑 execute_draw(order_no) return SUCCESS, OK逻辑说明幂等的核心是「先查状态已处理直接返回成功」同时用transaction_id的唯一索引做数据库层面的兜底。参数上order_no是本地订单号transaction_id是支付平台流水号两者都要存。注意回调接口必须返回支付平台要求的格式否则平台会一直重试。3.3 公众号 H5 的授权与支付差异公众号 H5 和小程序在支付上有区别H5 走的是 JSAPI 支付需要先通过公众号授权拿到openid小程序走的是小程序支付openid通过wx.login获取。后端要封装一个支付路由层根据platform字段选择不同的支付参数组装方式。端类型授权方式支付方式openid 来源H5公众号网页授权JSAPI授权回调小程序wx.login小程序支付code 换 openidAPP自有登录APP支付自有用户体系参数说明H5 授权时scope用snsapi_base静默授权即可不需要用户手动确认小程序的code只能用一次换完openid要缓存。APP 端如果接的是自有账号体系需要把user_id和支付平台的openid做映射表。4. 避坑与排查盲盒系统上线后最容易翻车的五个点这一章是我自己踩过的坑也是周围做潮玩盲盒系统的朋友反馈最多的问题。每个都按「现象 → 原因 → 解决」写方便对照排查。4.1 抽奖概率看起来不对大奖出得太快或太慢现象运营反馈 A赏在开服第一天就被抽完了或者最后 10 个盒子全是普通款。原因用了独立概率而不是不放回抽样或者 Redis 预扣减和数据库库存没同步。解决检查抽奖算法是否按剩余数量做区间映射检查 Redis 库存和数据库库存是否一致写一个对账脚本定时比对。4.2 支付成功但订单显示未支付现象用户付了钱抽奖页面没反应订单状态还是待支付。原因支付回调地址配置错误或者回调处理超时被支付平台重试但重试时订单已处理导致返回格式不对。解决检查回调地址是否公网可达检查回调接口是否在 5 秒内返回检查幂等逻辑是否返回了支付平台要求的成功标识。4.3 连抽时库存扣减出现负数现象数据库里某个赏级的remaining_qty变成负数。原因并发扣减时没有加行锁或者 Redis 预扣减后数据库扣减失败没有回滚。解决数据库扣减语句加WHERE remaining_qty 1Redis 扣减用 Lua 脚本保证原子性失败补偿逻辑必须写。4.4 H5 页面在部分安卓机型上抽奖动画卡顿现象抽盒动画在低端安卓机上掉帧严重。原因动画用了大量 DOM 操作或者高分辨率图片没有压缩。解决动画改用 CSS transform 和 opacity图片用 WebP 格式并做 CDN 压缩复杂动画考虑用 Canvas 替代 DOM。4.5 公众号 H5 支付提示「商家参数格式有误」现象调起支付时直接报错无法进入支付页面。原因openid和appid不匹配或者支付授权目录没配置。解决确认公众号的appid和支付商户号绑定关系确认支付授权目录包含当前页面路径确认openid是通过当前公众号授权获取的。5. 从跑通到上线一套盲盒系统的验证清单与压测技巧把抽奖逻辑跑通只是第一步真正上线前还需要做几件事库存对账、并发压测、支付回归、降级预案。我一般会按下面这个顺序来验证。先写一个库存对账脚本每 5 分钟跑一次比对 Redis 和数据库的库存差异。差异超过阈值就告警同时自动以数据库为准修复 Redis。这个脚本能帮你提前发现大部分库存不一致问题。def reconcile_stock(): tiers query_all_tiers() for tier in tiers: redis_stock redis.get(ftier:stock:{tier[id]}) db_stock tier[remaining_qty] if redis_stock is None: redis.set(ftier:stock:{tier[id]}, db_stock) continue if int(redis_stock) ! db_stock: # 以数据库为准修复 redis.set(ftier:stock:{tier[id]}, db_stock) log_warning(f库存不一致 tier{tier[id]} redis{redis_stock} db{db_stock})压测方面用 JMeter 或 wrk 模拟 500 并发同时抽奖观察三个指标接口响应时间、库存扣减是否准确、订单是否重复。我一般会压到 1000 并发看系统在极限情况下的表现。如果响应时间超过 2 秒就要考虑加缓存或者限流。支付回归测试要覆盖四种场景正常支付、重复回调、支付超时、支付失败。每种场景都要验证订单状态和库存变化是否符合预期。特别是支付超时要确保库存能正确回滚。降级预案方面如果 Redis 挂了系统要能自动切到数据库直接扣减虽然性能会下降但至少能保证活动不中断。这个开关我一般做成配置项出问题时手动切换。最后说一个我自己的习惯每次上线新活动前先在小范围灰度用 10% 的流量跑 30 分钟观察库存和订单数据。没问题再全量。这个习惯帮我避免了好几次大事故。希望帮到你。本文还有配套的精品资源点击获取