砸金蛋活动页高并发抽奖与库存幂等实践

发布时间:2026/10/29 1:40:04
砸金蛋活动页高并发抽奖与库存幂等实践 简介面向微信平台互动营销开发者的“微信开发砸金蛋”实例包适合具备HTML5、JavaScript与PHP基础、希望快速理解公众号小游戏完整链路的初中级开发者参考。内容围绕砸金蛋游戏的页面呈现、交互逻辑与后端数据处理展开可用于品牌推广、节日促销或用户互动等活动场景的原型搭建。压缩包共6个文件以4张PNG图片、1个PHP脚本和1个HTML页面组成分别承担界面素材、服务端请求处理与前端结构展示整体约91KB体量轻便便于查阅与二次修改。已有218人学习说明其在微信小游戏入门与营销活动实现方面具有一定参考价值。通过学习可了解前端动画与点击反馈的组织方式、奖品与用户数据的接口衔接思路以及前后端分离的简易实现结构为自行搭建抽奖互动页面提供可复用的代码骨架和调试切入点。1. 砸金蛋活动页为什么很多团队把它做成一次性的抽奖壳砸金蛋活动页在社交平台里看起来只是三个蛋加一段动画真正上线后却要同时处理活动入口、用户身份、库存、概率和发奖。某开发者把第一版做成纯前端随机数活动开始十分钟就被重复请求打穿这种翻车在节日活动里并不少见。这个项目适合两类人手里有源码包、想把活动跑起来的开发者以及准备在现有活动框架上二次开发的人。它要解决的不是动画好不好看而是从点击金蛋到奖品落袋的链路能不能扛住并发、能不能查得清。2. 技术选型与页面结构一个 H5 活动从入口到发奖的骨架砸金蛋这种活动页前端看起来是动画后端看起来是抽奖接口真正决定能不能复用的是中间的库存、记录和发奖链路。常见做法是把项目拆成三块H5 活动页、抽奖服务、数据与缓存。前端负责展示金蛋、锤子动画和结果弹层后端负责身份校验、概率计算、库存扣减和中奖记录MySQL 存活动配置和流水Redis 扛高频库存与限流。不要一上来就盯着页面皮肤改先把这条链路对清楚后面换奖品、换活动时间才不会牵连代码。层常见选型在砸金蛋场景里的职责H5 页面Vue 组合式写法或原生 JS CSS/Canvas 动画金蛋点击、锤子动画、结果弹层、分享参数接口服务Spring Boot / Node.js / PHP 均可活动校验、抽奖、记录、发奖补偿关系库MySQL活动、奖品、抽奖记录、发奖状态缓存Redis库存预扣、日限、幂等键、接口限流部署Nginx 静态资源活动页缓存、接口反向代理、灰度入口这张表不是让所有人都换技术栈而是让你拿到源码后知道该看哪几层。前端目录里如果只有页面和图片没有api、utils这类拆分大概率抽奖逻辑也堆在页面里后端如果只有 Controller 没有 Service 和库存层后面加保底、加黑名单会很痛苦。常见做法是让活动配置和奖品配置走数据库页面只拿活动 ID 和展示字段不要把概率写死在前端 JS 里。2.1 前端页面与动画层请求和动画要并行砸金蛋的动画通常分三段点击锁定、锤子落下、金蛋裂开并弹出结果。很多新手会写成“动画播完再发请求”结果网络一慢用户以为没点上连续点五六次后端如果没有幂等就会多抽。更稳的写法是点击后立刻锁定按钮同时发抽奖请求动画只负责过渡结果以接口返回为准。前端不需要知道权重和中奖概率它只拿prizeId、prizeName、status这类展示字段。// web/src/components/EggGrid/index.js export function bindEggEvents(root, onDraw) { root.addEventListener(click, async (event) { const egg event.target.closest(.egg); if (!egg || egg.dataset.locked 1) return; // 先锁住当前金蛋避免连点造成重复请求 egg.dataset.locked 1; egg.classList.add(is-breaking); const requestId crypto.randomUUID(); try { const result await onDraw({ eggId: egg.dataset.eggId, requestId }); // 动画至少播 600ms避免结果弹得太突兀 await new Promise((resolve) setTimeout(resolve, 600)); showResult(result); } catch (error) { egg.dataset.locked 0; egg.classList.remove(is-breaking); showToast(网络开小差了请重试); } }); }这段代码的关键点是requestId和dataset.locked。requestId由前端生成后端拿它做幂等键同一个用户同一个请求 ID 重复提交只返回第一次的结果locked只解决当前页面连点挡不住脚本重放所以不能当前端防刷的全部。onDraw是抽奖接口的封装参数里带eggId是为了让后端知道用户点的是哪个蛋但奖品仍然由后端权重决定前端传的蛋 ID 不能直接映射奖品。2.2 后端服务与数据表四张表撑起活动后端不要把所有字段塞进一张大表。常见做法是活动表、奖品表、抽奖记录表、发奖记录表分开。活动表管时间、状态、日限奖品表管库存、权重、奖品类型抽奖记录表管用户每次点击的结果和幂等键发奖记录表管异步发奖状态。这样活动结束后对账能直接按状态筛出“发放中”和“发放失败”的记录不用翻应用日志。-- sql/schema.sql CREATE TABLE activity ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(80) NOT NULL, start_time DATETIME NOT NULL, end_time DATETIME NOT NULL, daily_limit INT NOT NULL DEFAULT 3, status TINYINT NOT NULL DEFAULT 1, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE prize ( id BIGINT PRIMARY KEY AUTO_INCREMENT, activity_id BIGINT NOT NULL, name VARCHAR(80) NOT NULL, weight INT NOT NULL DEFAULT 0, stock INT NOT NULL DEFAULT 0, daily_stock INT NOT NULL DEFAULT 0, prize_type TINYINT NOT NULL DEFAULT 1, status TINYINT NOT NULL DEFAULT 1, INDEX idx_activity (activity_id) ); CREATE TABLE draw_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, activity_id BIGINT NOT NULL, user_id VARCHAR(64) NOT NULL, prize_id BIGINT NOT NULL, request_id VARCHAR(64) NOT NULL, status TINYINT NOT NULL DEFAULT 0, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_request (activity_id, user_id, request_id), INDEX idx_user_time (user_id, created_at) ); CREATE TABLE award_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, draw_id BIGINT NOT NULL, user_id VARCHAR(64) NOT NULL, prize_id BIGINT NOT NULL, status TINYINT NOT NULL DEFAULT 0, retry_count INT NOT NULL DEFAULT 0, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_draw (draw_id) );activity.daily_limit控制每个用户每天能砸几次prize.weight是权重不是百分比总和可以是 100 也可以是 1000后端按区间累加即可。draw_record上的uk_request是防重复插入的兜底应用层 Redis 幂等失效时数据库还能挡一次。award_record.status我一般用 0 待发放、1 发放中、2 成功、3 失败定时任务只扫 0 和 3避免把成功记录反复推给发奖渠道。字段长度要按实际用户标识调整不要照抄 64 位就上线。2.3 目录结构按活动域拆而不是按技术层拆拿到源码包先看目录。活动类项目最容易出现两种结构一种按controller、service、mapper平铺找个抽奖逻辑要跳五个文件夹另一种按活动域拆activity、prize、draw、award各自成包。砸金蛋这种带库存和发奖的活动我更倾向第二种至少后面加“翻牌”“刮刮卡”时能把公共抽奖逻辑抽出去。egg-smash/ ├── web/ # H5 活动页 │ ├── src/ │ │ ├── pages/activity/ # 活动入口页 │ │ ├── components/EggGrid/ # 金蛋网格与动画 │ │ ├── api/draw.js # 抽奖接口封装 │ │ └── utils/auth.js # 平台用户标识获取 │ └── vite.config.js ├── server/ │ ├── src/main/java/.../draw/ # 抽奖领域 │ ├── prize/ # 奖品与库存 │ ├── award/ # 发奖与补偿 │ └── config/ # Redis、MQ、拦截器 ├── sql/ │ ├── schema.sql # 表结构 │ └── init_prize.sql # 活动与奖品初始化 └── deploy/ └── nginx.conf # 静态资源与接口代理这个目录树里web只关心展示和接口调用server按领域分包sql负责初始化deploy放反向代理。跑起来的第一步不是npm install而是先把sql/schema.sql和init_prize.sql灌进 MySQL再确认server的配置文件里 Redis、数据库、消息队列地址。下面这组命令按常见 Java Vite 结构写如果你的源码包是 Node 或 PHP把启动命令换成对应脚本即可。# 1. 初始化数据库库名按配置文件里的实际名称改 mysql -u root -p egg_smash sql/schema.sql mysql -u root -p egg_smash sql/init_prize.sql # 2. 启动缓存活动库存和日限都依赖它 redis-server --port 6379 # 3. 启动后端看控制台有没有端口冲突和数据库连接失败 cd server mvn spring-boot:run # 4. 启动前端活动页通常带活动 ID 参数 cd web npm install npm run dev -- --host 0.0.0.0命令里的egg_smash是示例库名实际以application.yml或.env为准。--host 0.0.0.0是为了让手机或另一台机器访问开发页活动页入口一般要带activityId比如/activity?activityId1001。如果页面能打开但点金蛋报 401先看平台用户标识有没有换到如果报 500先看 Redis 和 MySQL 是否连上如果一直转圈看 Nginx 或 Vite 代理有没有把/api转到后端端口。3. 核心链路拆解从点击金蛋到库存扣减的完整请求一条完整的砸金蛋请求会经过活动页加载、平台身份识别、点击锁、抽奖接口、活动校验、幂等校验、日限校验、权重抽奖、库存预扣、写抽奖记录、异步发奖。任何一步顺序错了后面都可能出现“用户中奖了但库存没扣”“库存扣了但记录没写”“记录写了但奖品没发”。这一章按请求顺序拆代码只保留关键段参数和失败分支在代码后说明。3.1 活动入口与身份识别先拿到稳定的用户标识活动页通常从平台内嵌浏览器进入链接里可能带一次性授权码。前端不要每次点击都去换用户标识常见做法是进入页面先换一次换到后存本地后续接口只带用户标识或会话令牌。授权码是一次性的页面刷新后如果还拿旧码去换后端会报失效所以回跳地址要把活动参数保留住。// web/src/utils/auth.js export async function ensureUserId() { const cached localStorage.getItem(platform_user_id); if (cached) return cached; const params new URLSearchParams(location.search); const code params.get(code); if (!code) { // 没有授权码时跳平台授权页return 参数保留活动 ID const returnUrl encodeURIComponent(location.href); location.href /api/auth/redirect?return${returnUrl}; return null; } const res await fetch(/api/auth/login?code${encodeURIComponent(code)}).then((r) r.json()); if (res.code ! 0) throw new Error(res.msg || 登录失败); localStorage.setItem(platform_user_id, res.data.userId); return res.data.userId; }code只用于换userId换完就不要再拼到业务接口里。returnUrl里保留activityId否则授权回跳后活动页不知道展示哪个活动。localStorage只存用户标识不存奖品概率和库存前端存这些字段等于把配置送给用户改。后端登录接口拿到code后调用平台服务换用户标识再把会话写进 Redis 或签发短令牌业务接口从会话里取用户不信任前端直接传的userId。3.2 砸蛋接口幂等、日限、抽奖三件事抽奖接口是整条链路的核心参数一般只有活动 ID、金蛋 ID 和请求 ID用户身份从会话取。下面这段是常见的 Controller 写法先做活动校验再用 Redis 做幂等最后做日限和抽奖。注意幂等键要带活动、用户和请求 ID不能只带用户否则用户第二次正常抽奖会被当成重复请求。PostMapping(/api/egg/smash) public ResultDrawRecord smash(RequestBody SmashRequest req) { // 1. 活动状态与时间窗口校验 Activity activity activityCache.get(req.getActivityId()); checkActivity(activity); // 2. 幂等同一个请求 ID 只处理一次重复请求直接返回旧结果 String idemKey egg:idem: req.getActivityId() : req.getUserId() : req.getRequestId(); Boolean first redis.opsForValue().setIfAbsent(idemKey, 1, Duration.ofMinutes(10)); if (Boolean.FALSE.equals(first)) { return Result.ok(drawRecordService.findByRequestId(req.getRequestId())); } // 3. 日限先自增再判断避免并发下先查后加导致超限 String dayKey egg:day: req.getUserId() : LocalDate.now(); Long count redis.opsForValue().increment(dayKey); if (count ! null count 1) { redis.expire(dayKey, Duration.ofDays(1)); } if (count ! null count activity.getDailyLimit()) { return Result.fail(今日次数已用完); } // 4. 权重抽奖与库存预扣由 drawService 完成 Prize prize drawService.draw(req.getActivityId()); DrawRecord record drawRecordService.save(req, prize); // 5. 事务提交后异步发奖失败由定时任务补偿 mq.send(egg_award, record.getId()); return Result.ok(record); }setIfAbsent的过期时间不能太短至少覆盖用户重试窗口十分钟是常见值太长会占内存活动结束后可以按活动 ID 清理。dayKey的日期用服务器时区不要用用户手机时区否则跨时区用户能多抽。drawService.draw返回的奖品必须已经完成库存预扣如果库存扣失败要返回兜底奖品不能让接口抛异常后用户看到“中奖”但记录没落库。mq.send放在事务提交之后如果项目里没有消息队列可以先用award_record表加定时任务扫表代替。3.3 权重与库存先算区间再扣库存权重抽奖不要用一堆if (random 0.01)写死奖品一多就难维护。常见做法是把奖品按权重累加随机数落在哪个区间就命中哪个奖品命中后再尝试扣库存扣失败就走兜底奖品。兜底奖品一般是“谢谢参与”或低价值券库存要足够大避免用户点了没结果。public Prize draw(Long activityId) { ListPrize prizes prizeCache.list(activityId); int totalWeight prizes.stream().mapToInt(Prize::getWeight).sum(); int hit ThreadLocalRandom.current().nextInt(totalWeight); int cursor 0; for (Prize prize : prizes) { cursor prize.getWeight(); if (hit cursor) { // 库存预扣成功才返回该奖品失败则走兜底 if (stockService.tryDeduct(prize.getId())) { return prize; } return fallbackPrize(activityId); } } return fallbackPrize(activityId); }totalWeight是所有启用奖品的权重和hit的范围是[0, totalWeight)cursor累加后判断命中区间。tryDeduct必须原子执行不能先get再set否则并发下会超卖。fallbackPrize也要有库存或标记为无穷不能兜底奖品本身扣减失败。这段代码只负责抽奖和预扣写记录、发奖、回滚库存要放在外层事务或补偿逻辑里顺序建议是“预扣库存 → 写记录 → 发消息”任何一步失败都要能按记录回滚。3.4 前端结果反馈动画结束不等于请求结束前端拿到结果后不要立刻弹窗也不要等动画完全结束才发请求。比较稳的做法是请求和动画并行动画至少播一段固定时长结果回来就缓存等动画结束再展示。这样网络快的时候不会闪一下网络慢的时候用户也能看到锤子落下不会以为按钮坏了。// web/src/pages/activity/index.js async function handleDraw({ eggId, requestId }) { const res await fetch(/api/egg/smash, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ activityId: activityId, eggId, requestId }) }).then((r) r.json()); if (res.code ! 0) { throw new Error(res.msg || 抽奖失败); } return { prizeName: res.data.prizeName, prizeType: res.data.prizeType, recordId: res.data.id, status: res.data.status }; }activityId从页面参数或后端配置取不要前端写死。requestId由点击时生成并一路带到接口重试时复用同一个才能让后端幂等生效。返回值只取展示需要的字段库存、权重、用户手机号这类信息不要回传。结果弹层里如果带“去领奖”按钮按钮跳转地址也要从后端拿前端拼接容易在换发奖渠道时漏改。4. 避坑与排查砸金蛋活动上线前后最容易翻车的 5 个点砸金蛋活动的坑大多不在动画而在并发和状态。活动开始前十分钟、奖品快抽完、用户疯狂连点这三个场景最容易翻车。下面五条是血泪经验里出现频率最高的每条按现象、原因、解决写方便你上线前对照排查。4.1 五个高频现象从连点到超卖现象一用户点一下金蛋结果弹了三次“未中奖”。原因通常是前端按钮没有锁或者锁了但请求失败后没有及时解锁用户以为没点上继续点后端如果没有幂等三次请求会生成三条记录。解决分两层前端点击后立刻加locked标记请求结束无论成功失败都要在finally里解锁后端用activityId userId requestId做唯一键Redis 幂等和数据库唯一索引都加上重复请求直接返回第一条记录。现象二奖品库存显示还有但用户抽中后提示“库存不足”。常见原因是库存扣减走的是“先查再减”并发下两个请求都查到有库存最后减成负数或者 Redis 预扣成功了数据库扣减失败没有回滚缓存。解决是把库存扣减改成原子操作Redis 用 Lua 或decrby判断返回值数据库用update prize set stock stock - 1 where id ? and stock 0看影响行数任何一层失败都要把另一层补回去。活动结束后按draw_record和prize.stock对账差一条都要查。现象三活动开始后改了奖品概率线上不生效。原因多半是奖品配置在应用启动时加载到本地缓存后面只读本地缓存没有刷新机制也可能前端把概率写死在 JS 里后端改了前端没改。解决是奖品配置放数据库或配置中心后端缓存加版本号或定时刷新活动进行中改概率要走“新版本生效”而不是直接改内存。前端只展示奖品名称和图片不参与概率计算。改完概率先用测试活动 ID 跑一百次看记录分布是否接近权重。现象四中奖记录一直是“发放中”用户来催。常见原因是抽奖事务提交后发消息失败或者消息消费失败没有重试也有可能是发奖接口超时但本地状态没更新。解决是加award_record本地消息表抽奖记录落库时同时写一条待发放记录定时任务扫status in (0,3)的记录重试重试次数超过阈值转人工。发奖接口要支持按drawId查结果不能只返回成功失败否则重复调用无法判断上次到底发没发。现象五活动页在部分手机打开白屏但开发机正常。原因可能是静态资源路径写成了绝对路径部署到子目录后 404也可能是用了较新的 JS 语法内嵌浏览器内核不支持还可能是接口跨域开发环境配了代理线上 Nginx 没配。解决是先看浏览器控制台和 Nginx 访问日志静态资源 404 就改publicPath或base语法报错就加构建降级接口跨域就统一走同域反向代理。活动上线前至少在一台低版本内嵌浏览器和一台普通手机上各点一遍。4.2 排查顺序和兜底开关线上出问题不要先翻代码先按“活动状态 → 缓存库存 → 数据库记录 → 发奖队列”的顺序看。活动状态不对后面都白查缓存库存对不上先看预扣和回滚日志数据库记录看draw_record的status分布发奖队列看积压和失败重试。下面几条命令是常用的排查入口库名和 key 前缀按实际项目改。# 看活动库存 key 的剩余值和过期时间 redis-cli get egg:stock:1001:3 redis-cli ttl egg:stock:1001:3 # 看某个用户当天的抽奖次数 redis-cli get egg:day:user_123:2025-01-01 # 看抽奖记录状态分布定位是未发奖还是发奖失败 mysql -u root -p -e select status, count(*) from egg_smash.draw_record where activity_id1001 group by status;egg:stock:1001:3里的1001是活动 ID3是奖品 ID实际 key 设计要跟代码里的stockKey一致。ttl返回-1表示没有过期时间活动库存 key 最好在活动结束后统一清理不要永久占内存。记录状态分布里如果大量status0看发奖消费者是不是没启动如果大量status3看发奖接口返回和重试次数。兜底开关要有两个一个关活动入口一个把抽奖降级为“只展示不扣库存”出问题时先止血再定位。注意库存预扣和数据库扣减必须能回滚活动结束后对账会暴露所有没回滚的差额不要等用户投诉才发现。5. 数据与风控奖品库存、抽奖概率和防刷的落地细节砸金蛋活动能不能稳住取决于库存扣得准不准、概率配得清不清、刷子挡不挡得住。库存方案没有绝对好坏活动量小可以直接用数据库乐观锁活动量大就用 Redis 预扣加数据库落账。概率配置要能解释不能只写一个随机数。防刷也别一上来就上复杂设备指纹先把用户维度、接口维度的频率限制做扎实。5.1 库存扣减数据库乐观锁和缓存预扣怎么选数据库乐观锁适合库存少、并发不高的活动写法简单事务内就能保证一致。缓存预扣适合瞬时流量大的活动Redis 扛住第一波数据库异步落账但必须补回滚和对账。两种方案可以组合Redis 做第一道预扣数据库做最终扣减任何一边失败都按记录补偿。方案适合场景优点风险点数据库乐观锁库存少、并发低、对账要求高实现简单事务一致热点行竞争大容易锁等待Redis 预扣瞬时流量大、奖品多抗并发响应快缓存与库不一致要回滚和对账两者组合正式活动、需要兜底兼顾性能和最终一致代码复杂补偿逻辑要全Redis 预扣的 Lua 脚本要保证“判断库存 扣减”在一个原子操作里完成不能拆成两条命令。下面这段脚本返回 1 表示扣减成功返回 0 表示库存不足调用方根据返回值决定走兜底还是回滚。-- KEYS[1] 库存 key例如 egg:stock:1001:3 -- ARGV[1] 扣减数量砸金蛋通常传 1 local stock tonumber(redis.call(get, KEYS[1]) or 0) if stock tonumber(ARGV[1]) then return 0 end redis.call(decrby, KEYS[1], ARGV[1]) return 1KEYS[1]要带活动和奖品维度不能只按奖品 ID否则两个活动共用同一个奖品会互相扣库存。ARGV[1]一般固定为 1如果业务支持一次抽多个奖才传大于 1 的值。脚本执行成功后还要写一条预扣流水记录drawId、prizeId、扣减数量数据库扣减失败时按流水回滚。不要只把库存放 Redis 不落库缓存重启后库存就丢了。5.2 概率配置权重表比写死百分比好维护权重表的好处是加奖品不用改代码运营改一行配置就能生效。权重不要求总和为 100后端按总和算区间即可。但权重表要加状态字段下架奖品不要直接删改成status0否则历史记录关联不上。保底策略可以单独做用户连续多次未中奖时提高下一次命中低价值奖品的权重但保底次数要配置化不能写死。-- 初始化奖品weight 是相对权重stock 是总库存 INSERT INTO prize (activity_id, name, weight, stock, daily_stock, prize_type, status) VALUES (1001, 一等奖, 1, 10, 1, 1, 1), (1001, 二等奖, 10, 100, 10, 1, 1), (1001, 谢谢参与, 89, 999999, 999999, 0, 1);weight1不代表 1%它和10、89一起按总和 100 计算命中概率分别约 1%、10%、89%。daily_stock是每日库存上限适合“每天放量”的活动如果活动不分天可以把它设成和总库存一样。prize_type0表示未中奖或兜底奖品发奖逻辑要跳过这类记录。改权重时先备份旧配置活动进行中不要直接改正在使用的行新增一条新版本或走配置中心发布。5.3 防刷限流、幂等、黑名单三层就够用防刷先做接口限流再做用户维度日限最后才是黑名单。限流按“用户 活动 分钟”维度单用户一分钟超过二三十次就拒绝日限按活动配置走不要所有活动都写死三次。设备指纹可以作为辅助但不要采集敏感信息前端生成一个随机标识存本地即可后端结合用户标识和 IP 做风控评分。黑名单要能手工加也要能自动加比如同一 IP 下大量用户短时间中奖先限流再人工看。// 用户维度分钟限流返回 true 表示放行 public boolean allow(Long activityId, String userId) { String minute LocalDateTime.now().format(DateTimeFormatter.ofPattern(yyyyMMddHHmm)); String key egg:limit: activityId : userId : minute; Long count redis.opsForValue().increment(key); if (count ! null count 1) { redis.expire(key, Duration.ofMinutes(2)); } return count ! null count 30; }minute精确到分钟key 过期时间设两分钟避免临界分钟计数丢失。阈值 30 是示例实际按活动力度调奖品价值越高阈值越低。这段限流只挡单用户挡不住批量账号所以还要有 IP 维度和活动维度的总限流。黑名单命中后直接返回“活动太火爆”不要返回“你被风控了”避免刷子调整策略。所有拒绝都要记日志日志里带活动 ID、用户标识、拒绝原因方便事后看是不是误伤。6. 二次开发把抽奖壳改成可复用的活动模板这份源码包最适合的二次开发方向不是换一套金蛋皮肤而是把抽奖、库存、发奖抽成活动模板。我一般先改配置不动核心代码活动时间、日限、奖品权重、库存、兜底文案走数据库或 JSON 配置前端只读展示字段。这样下一次做“翻牌”或“刮刮卡”只需要换前端交互层后端抽奖接口和发奖补偿可以复用。改造点改哪里验证方法换活动时间activity.start_time/end_time改到未来时间确认入口提示未开始调奖品概率prize.weight跑一百次抽奖看记录分布是否接近权重换发奖渠道award模块的发送实现用测试用户发一笔查award_record状态换页面皮肤web静态资源和组件样式手机和内嵌浏览器各点一遍加保底drawService外层加保底规则模拟连续未中奖看下一次是否触发配置建议单独放一个 JSON 或数据库行不要散落在代码里。下面这份配置是活动维度的最小集合前端启动时拉一次后端抽奖时以数据库为准前端配置只用于展示。{ activityId: 1001, dailyLimit: 3, prizes: [ { id: 1, name: 一等奖, weight: 1, stock: 10 }, { id: 2, name: 二等奖, weight: 10, stock: 100 }, { id: 3, name: 谢谢参与, weight: 89, stock: 999999 } ] }dailyLimit是每个用户每天可抽次数weight是相对权重stock是总库存。改完配置先跑幂等重放同一个requestId连发三次确认只产生一条记录再把库存改成 1两个用户并发抽确认只有一个中奖最后把活动状态改成结束确认入口和接口都拒绝。从那以后我每次改概率配置都强制走一遍库存回滚和幂等重放先看日志再开入口。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询