网站seo百度百科,网站上投放广告,温州 网站,网站流量不够文章目录 抽奖核心算法生成抽奖大转盘抽奖接口实现 抽奖核心算法
我们可以根据 单商品库存量/总商品库存量 得到每个商品被抽中的概率#xff0c;可以想象这样一条 0-1 的数轴#xff0c;数轴上的每一段相当于一种商品#xff0c;概率之和为1。 抽奖时#xff0c;我们会生… 文章目录 抽奖核心算法生成抽奖大转盘抽奖接口实现 抽奖核心算法
我们可以根据 单商品库存量/总商品库存量 得到每个商品被抽中的概率可以想象这样一条 0-1 的数轴数轴上的每一段相当于一种商品概率之和为1。 抽奖时我们会生成 U(0,1) 上的一个随机数这个数位于哪个线段上就对应着抽中了对应的商品。构建线段时间复杂度 O(n)。用二分查找算法查找随机数位于哪一段时间复杂度 O(logn)采集k个样本需要再乘以k。 接下来介绍二分查找区间算法N 个点把实数域分割成 N1 段target 是随机生成的实数target 应该落在哪一段上定义 array[i-1] target array[i] 为落在第 i 条线段上代表第 i 个奖品被抽中
// BinarySearch 查找 target 的最小元素下标arr单调递增不能存在重复元素
// 如果target比arr的最后一个元素还大返回最后一个元素下标
func BinarySearch(arr []float64, target float64) int {if len(arr) 0 {return -1}left : 0right : len(arr)for left right {// 通用条件if target arr[left] {return left}if target arr[right-1] {return right}// len(arr) 2, mid在left和right之间, 选择left的概率值if left right-1 {return right}// len(arr) 3mid : (left right) / 2if target arr[mid] {right mid} else if target arr[mid] {return mid} else {left mid // NOTE: 这里不是找直接数值而是区间}}return -1
}生成抽奖大转盘
首先看看我们对于抽奖大转盘所设计的 mysql 数据库表结构
-- ----------------------------
-- DataBase
-- ----------------------------
CREATE DATABASE lottery;use lottery;-- ----------------------------
-- Table structure for inventory
-- ----------------------------
DROP TABLE IF EXISTS inventory;
CREATE TABLE inventory (id int(11) NOT NULL AUTO_INCREMENT COMMENT 奖品id, 自增,created_at DATETIME(3) NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间,updated_at DATETIME(3) NULL DEFAULT NULL COMMENT 更新时间,deleted_at DATETIME(3) NULL DEFAULT NULL COMMENT 删除时间,name varchar(20) NOT NULL COMMENT 奖品名称,description varchar(100) NOT NULL DEFAULT COMMENT 奖品描述,picture varchar(200) NOT NULL DEFAULT 0 COMMENT 奖品图片,price int(11) NOT NULL DEFAULT 0 COMMENT 价值,count int(11) NOT NULL DEFAULT 0 COMMENT 库存量,PRIMARY KEY (id)
) ENGINEInnoDB AUTO_INCREMENT20 DEFAULT CHARSETutf8 COMMENT奖品库存表;insert into inventory (id,name,picture,price,count) values (1,谢谢参与,img/face.png,0,0);
insert into inventory (name,picture,price,count) values (篮球,img/ball.jpeg,100,1000),(水杯,img/cup.jpeg,80,1000),(电脑,img/laptop.jpeg,6000,200),(平板,img/pad.jpg,4000,300),(手机,img/phone.jpeg,5000,400),(锅,img/pot.jpeg,120,1000),(茶叶,img/tea.jpeg,90,1000),(无人机,img/uav.jpeg,400,100),(酒,img/wine.jpeg,160,500);-- ----------------------------
-- Table structure for order
-- ----------------------------
DROP TABLE IF EXISTS order;
CREATE TABLE order (id int(11) NOT NULL AUTO_INCREMENT COMMENT 订单id, 自增,created_at DATETIME(3) NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间,updated_at DATETIME(3) NULL DEFAULT NULL COMMENT 更新时间,deleted_at DATETIME(3) NULL DEFAULT NULL COMMENT 删除时间,gift_id int(11) NOT NULL COMMENT 商品id,user_id int(11) NOT NULL COMMENT 用户id,count int(11) NOT NULL DEFAULT 1 COMMENT 购买数量,PRIMARY KEY (id)
) ENGINEInnoDB AUTO_INCREMENT189549 DEFAULT CHARSETutf8mb4 COMMENT订单表;上面这一段在数据量不大的时候还是可以的但是数据量一大在千万级以上大表的场景下就不行啦会导致长时间的阻塞而且读出来内存也不够 V2是对其优化主要是一个分页查询思路每次把一页的数据放入一个channel中然后用一个go协程每次从channel中读出数据写入redis 对于每个商品的 count 字段每次被抽中都应该 count 对应的值减1但是在高并发的情况下mysql可能扛不住这么大并发量下的频繁写入考虑先记录在redis里面真正的减1操作是在redis里面实现的。 在前端页面初始化的时候我们需要把整个大转盘的页面通过后端服务器返回的数据去渲染出整个大转盘出来所以需要一开始通过 InitInventory 函数获得所有奖品的初始库存存入redis。 看看前端的代码
bodydiv classcenter idmy-lucky/divscriptvar giftMap new Map(); //维护奖品ID和转盘里奖品index的对应关系$(document).ready(function () {$.ajax({type: GET,url: api/v1/gifts,success: function (data) {console.log(data)let gifts data[data]var prizesnew Array();$.each(gifts,function(index,gift){giftMap[gift.Id]index;prizes[index] { background: #e9e8fe, fonts: [{ text: gift.Name }], imgs:[{src:gift.Picture,top:30,width:80,height:80}] };})// 直接使用luch-canvas抽奖插件 https://100px.net/usage/js.htmlconst myLucky new LuckyCanvas.LuckyWheel(#my-lucky, {width: 600px,height: 600px,blocks: [{ padding: 10px, background: #869cfa }],prizes: prizes,buttons: [{ radius: 40%, background: #617df2 },{ radius: 35%, background: #afc8ff },{radius: 30%, background: #869cfa,pointer: true,fonts: [{ text: 抽奖, top: -10px }]},],start: function() {$.ajax({type: GET,url: api/v1/lucky,success: function (giftId) {if(giftId0){alert(抽奖结束)}else{myLucky.play();idxgiftMap[giftId];myLucky.stop(idx);}}}).fail(function (result, result1, result2) {alert(出错了);});},end: function(prize) { // 游戏停止时触发alert(恭喜中奖: prize.fonts[0].text)}})}}).fail(function (result, result1, result2) {$(#my-lucky).html(数据加载失败);});});/script
/body我们可以看到前端的代码逻辑是先放一个空的div然后页面加载好之后通过js代码发起一个请求去请求/gifts这个接口获得数据渲染生成大转盘。这里有个小细节我们后端返回给前端gifts数据的时候要记得抹掉敏感信息也就是说我们的抽奖概率是通过商品的库存量来决定的我们不希望前端拿到json字符串后看到库存量所以我们的gifts返回给前端的时候记得把所有的库存量都置为0。 type Inventory struct {ID uint gorm:column:idName string gorm:column:nameDescription string gorm:column:descriptionPicture string gorm:column:picturePrice int gorm:column:priceCount int gorm:column:count
}上面这段代码就是从redis上获取所有奖品的库存量其中商品id作为key的时候会统一加一个前缀prefix
抽奖接口实现 当我们按下抽奖这个按钮后前端会用js代码请求/lucky接口由后端返回本次抽奖抽中了哪个商品id ids和probs两个是一一对应的一个存的是奖品id一个存的是奖品的库存量如果奖品已经count为0了说明已经抽没了不应该再参与抽奖为什么要给前端传一个0因为这个0有特殊的意思前端收到0后会告诉用户抽奖已经结束有可能同时对一个库存量为1的商品去执行减1操作会导致库存量为负数这个时候我们会执行新一轮的抽奖重新再抽一遍如果执行指定次数后还是失败则会返回最后一行代码谢谢参与 redis Decr 是支持原子性支持并发的