Golang|抽奖相关

文章目录

    • 抽奖核心算法
    • 生成抽奖大转盘
    • 抽奖接口实现

抽奖核心算法

  • 我们可以根据 单商品库存量/总商品库存量 得到每个商品被抽中的概率,可以想象这样一条 0-1 的数轴,数轴上的每一段相当于一种商品,概率之和为1

在这里插入图片描述

  • 抽奖时,我们会生成 U(0,1) 上的一个随机数,这个数位于哪个线段上就对应着抽中了对应的商品。
  • 构建线段,时间复杂度 O(n)
  • 用二分查找算法查找随机数位于哪一段,时间复杂度 O(logn),采集k个样本需要再乘以k

  • 接下来介绍二分查找区间算法:
  • N 个点把实数域分割成 N+1 段,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`)
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8 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`)
) ENGINE=InnoDB AUTO_INCREMENT=189549 DEFAULT CHARSET=utf8mb4 COMMENT="订单表";

在这里插入图片描述
在这里插入图片描述

  • 上面这一段,在数据量不大的时候还是可以的,但是数据量一大,在千万级以上大表的场景下就不行啦,会导致长时间的阻塞,而且读出来内存也不够

在这里插入图片描述

  • V2是对其优化,主要是一个分页查询思路,每次把一页的数据放入一个channel中,然后用一个go协程每次从channel中读出数据写入redis
    在这里插入图片描述

  • 对于每个商品的 count 字段,每次被抽中都应该 count 对应的值减1,但是在高并发的情况下mysql可能扛不住这么大并发量下的频繁写入,考虑先记录在redis里面,真正的减1操作是在redis里面实现的。

  • 在前端页面初始化的时候我们需要把整个大转盘的页面通过后端服务器返回的数据去渲染出整个大转盘出来,所以需要一开始通过 InitInventory 函数获得所有奖品的初始库存,存入redis。

在这里插入图片描述

  • 看看前端的代码:
<body><div class="center" id="my-lucky"></div><script>var 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 prizes=new 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(giftId=="0"){alert("抽奖结束")}else{myLucky.play();idx=giftMap[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:id"`Name 		string 	`gorm:"column:name"`Description string  `gorm:"column:description"`Picture 	string 	`gorm:"column:picture"`Price 		int 	`gorm:"column:price"`Count 		int		`gorm:"column:count"`
}

在这里插入图片描述

  • 上面这段代码就是从redis上获取所有奖品的库存量,其中商品id作为key的时候会统一加一个前缀prefix

抽奖接口实现

在这里插入图片描述

  • 当我们按下抽奖这个按钮后,前端会用js代码请求"/lucky"接口,由后端返回本次抽奖抽中了哪个商品id

在这里插入图片描述
在这里插入图片描述

  • ids和probs两个是一一对应的,一个存的是奖品id一个存的是奖品的库存量
  • 如果奖品已经count为0了,说明已经抽没了,不应该再参与抽奖
  • 为什么要给前端传一个0,因为这个0有特殊的意思,前端收到0后会告诉用户抽奖已经结束
  • 有可能同时对一个库存量为1的商品去执行减1操作,会导致库存量为负数,这个时候我们会执行新一轮的抽奖,重新再抽一遍,如果执行指定次数后还是失败,则会返回最后一行代码,谢谢参与

在这里插入图片描述

  • redis Decr 是支持原子性支持并发的

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/901860.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OpenCV 图形API(43)颜色空间转换-----将 BGR 图像转换为 LUV 色彩空间函数BGR2LUV()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 将图像从BGR色彩空间转换为LUV色彩空间。 该函数将输入图像从BGR色彩空间转换为LUV。B、G和R通道值的传统范围是0到255。 输出图像必须是8位无符…

【Python】用Python写一个俄罗斯方块玩玩

【Python】用Python写一个俄罗斯方块玩玩 一、引言1.成品效果展示 二、思考准备1.思考设计2.代码设计2.1 游戏页面2.2 控件设计2.2.1 方块生成2.2.2 方块碰撞2.2.3 方块消融2.2.4 游戏主循环2.2.5 游戏窗口 三、游戏完整版 一、引言 今日看到侄子在玩游戏&#xff0c;凑近一看…

维港首秀!沃飞长空AE200亮相香港特别行政区

4月13日-16日&#xff0c;第三届香港国际创科展在香港会议展览中心盛大举办。 作为国内领先、国际一流的eVTOL主机厂&#xff0c;沃飞长空携旗下AE200批产构型登陆国际舞台&#xff0c;以前瞻性的创新技术与商业化应用潜力&#xff0c;吸引了来自全球17个国家及地区的行业领袖…

Openfein实现远程调用的方法(实操)

文章目录 环境准备一、URL中接收参数二、接收一个参数三、接收多个参数四、传递对象五、传递JSON格式数据 环境准备 下面的配置&#xff0c;服务调用方加入即可。 依赖导入&#xff1a; <!-- openfeign依赖--><dependency><groupId>org.springframe…

Bright+Data网页解锁器:旅游行业数据革命的“隐形引擎”

在数字经济浪潮中&#xff0c;旅游行业正经历前所未有的变革。当消费者指尖滑动间完成跨国酒店预订&#xff0c;当航空公司每秒调整万次舱位价格&#xff0c;背后是一场无声的数据战争。而在这场战争中&#xff0c;BrightData网页解锁器正成为旅游企业破局的关键武器——它像一…

OpenCV 图形API(38)图像滤波-----Sobel 算子操作函数Sobel()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 cv::gapi::Sobel 函数是 OpenCV 的 G-API 模块中用于执行 Sobel 算子操作的一个函数&#xff0c;主要用于图像的边缘检测。Sobel 算子通过计算图…

CS5346 - Interactivity in Visualization 可视化中的交互

文章目录 Visualization representation interactionInteraction &#xff08;交互&#xff09;Benefits (好处)Typical Interaction Techniques&#xff08;交互技术&#xff09;SelectFilteringAbstract / Elaborate几何放缩&#xff08;Geometric zoom)语义放缩&#xff0…

第十六届蓝桥杯大赛软件赛省赛 C++ 大学 B 组 部分题解

赛时参加的是Python组&#xff0c;这是赛后写的题解&#xff0c;还有两题暂时还不会&#xff0c;待更新 题目链接题目列表 - 洛谷 | 计算机科学教育新生态 A 移动距离 答案&#xff1a;1576 C 可分解的正整数 Python3 import itertools from functools import cmp_to_ke…

Vue 解决 Error: please transfer a valid prop path to form item!

在 Vue.js 中使用表单验证库&#xff08;如 VeeValidate 或 Element UI 的表单组件时&#xff09;&#xff0c;遇到错误信息 "please transfer a valid prop path to form item!" 通常指的是在表单项的属性绑定中&#xff0c;路径&#xff08;prop path&#xff09;不…

在 Visual Studio Code 中安装通义灵码 - 智能编码助手

高效的编码工具对于提升开发效率和代码质量至关重要。 通义灵码作为一款智能编码助手&#xff0c;为开发者提供了全方位的支持。 本文将详细介绍如何在 Visual Studio Code&#xff08;简称 VSCode&#xff09;中安装通义灵码&#xff0c;以及如何进行相关配置以开启智能编码…

SQL 解析 with as dual sysdate level

目录 sql的运行顺序 with as EXTRACT ​编辑 dual sysdate level ​编辑 ​编辑 Oracle中的日期存储 核心部分 拆解字符串并计算最小值 关联子查询 NVL 函数 REGEXP_SUBSTR() sql的运行顺序 <select id="getTrendList" parameterType="java.uti…

快手OneRec 重构推荐系统:从检索排序到生成统一的跃迁

文章目录 1. 背景2. 方法2.1 OneRec框架2.2 Preliminary2.3 生成会话列表2.4 利用奖励模型进行迭代偏好对齐2.4.1 训练奖励模型2.4.2 迭代偏好对齐 3. 总结 昨天面试的时候聊到了OneRec&#xff0c;但是由于上次看这篇文章已经是一个月之前&#xff0c;忘得差不多了&#xff0c…

软考高级系统架构设计师-第11章 系统架构设计

【本章学习建议】 根据考试大纲&#xff0c;本章不仅考查系统架构设计师单选题&#xff0c;预计考12分左右&#xff0c;而且案例分析和论文写作也是必考&#xff0c;对应第二版教材第7章&#xff0c;属于重点学习的章节。 软考高级系统架构设计师VIP课程https://edu.csdn.net/…

selenium之文件下载

Selenium 自动化测试&#xff1a;轻松搞定文件下载 在 Web 自动化测试中&#xff0c;经常会遇到需要验证文件下载功能的场景。例如&#xff0c;测试报告的导出、用户上传文件的下载、PDF 文档的生成与下载等等。Selenium 本身并没有直接处理文件下载的内置方法&#xff0c;但我…

基于迁移学习实现肺炎X光片诊断分类

大家好&#xff0c;我是带我去滑雪&#xff01; 肺炎是全球范围内致死率较高的疾病之一&#xff0c;尤其是在老年人、免疫系统较弱的患者群体中&#xff0c;更容易引发严重并发症。传统上&#xff0c;肺炎的诊断依赖于医生的临床经验以及影像学检查&#xff0c;尤其是X光片&…

工业数据治理范式革新:时序数据库 TDengine虚拟表技术解析

小T导读&#xff1a;在工业数字化过程中&#xff0c;数据如何从设备采集顺利“爬坡”到上层应用&#xff0c;一直是个难题。传统“单列模型”虽贴合设备协议&#xff0c;却让上层分析举步维艰。TDengine 用一种更聪明的方法打通了这条数据通路&#xff1a;不强求建模、不手动转…

Redis面试——日志

一、RDB&#xff08;Redis DataBase&#xff09; RDB 全程是 Redis DataBase&#xff0c;它是一种将 Redis 在某一时刻内存中的数据以快照形式保存到磁盘的机制 &#xff0c;相当于给执行save/bgsave命令时刻的内存数据库数据拍了一张快照我们如果通过save命令来执行快照&…

【Android】常用参数实践 用户界面UI 布局文件XML

本文将系统总结 Android XML 布局的通用参数和常用布局类型的专属规则 一、通用布局参数 这些参数适用于所有 View 和 ViewGroup&#xff0c;是布局设计的基石。 1. 尺寸控制 android:layout_width 与 android:layout_height 定义视图的宽度和高度&#xff0c;可选值&#xf…

解决 VSCode 中 NVM 配置后无法识别 Node 和 NPM 的问题

在开发中&#xff0c;我们经常需要使用 Node.js 和 NPM 来管理 JavaScript 项目依赖&#xff0c;而 NVM&#xff08;Node Version Manager&#xff09;是开发者在本地环境中管理多个 Node.js 版本的得力工具。不过&#xff0c;有时候在 VSCode 中配置完 NVM 后&#xff0c;可能…

BGP分解实验·23——BGP选路原则之路由器标识

在选路原则需要用到Router-ID做选路决策时&#xff0c;其对等体Router-ID较小的路由将被优选&#xff1b;其中&#xff0c;当路由被反射时&#xff0c;包含起源器ID属性时&#xff0c;该属性将代替router-id做比较。 实验拓扑如下&#xff1a; 实验通过调整路由器R1和R2的rout…