Golang|抽奖相关

news2025/5/14 2:23:02

文章目录

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

抽奖核心算法

  • 我们可以根据 单商品库存量/总商品库存量 得到每个商品被抽中的概率,可以想象这样一条 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 := 0
	right := 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) >= 3
		mid := (left + right) / 2
		if 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.html
                    const 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.coloradmin.cn/o/2338295.html

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

相关文章

idea maven 命令后控制台乱码

首先在idea中查看maven的编码方式 执行mvn -v命令 查看编码语言是GBK C:\Users\13488>mvn -v Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f) Maven home: D:\maven\apache-maven-3.6.3\bin\.. Java version: 1.8.0_202, vendor: Oracle Corporation, runt…

C# dll 打包进exe

Framework4.x推荐使用 Costura.Fody 1. 安装 NuGet 包 Install-Package Costura.Fody工程自动生成packages文件夹&#xff0c;300M左右。生成FodyWeavers.xml、FodyWeavers.xsd文件。 2. 自动嵌入 编译后&#xff0c;所有依赖的 DLL 会被自动嵌入到 EXE 中。 运行时自动解压…

【数据融合实战手册·实战篇】二维赋能三维的5种高阶玩法:手把手教你用Mapmost打造智慧城市标杆案例

在当今数字化时代&#xff0c;二三维数据融合技术的重要性不言而喻。二三维数据融合通过整合二维数据的结构化优势与三维数据的直观性&#xff0c;打破了传统数据在表达和分析上的局限&#xff0c;为各行业提供了更全面、精准的数据分析手段。从智慧城市建设到工业智能制造&…

ValueError: model.embed_tokens.weight doesn‘t have any device set

ValueError: model.embed_tokens.weight doesn’t have any device set model.embed_tokens.weight 通常在深度学习框架(如 PyTorch)中使用,一般是在处理自然语言处理(NLP)任务时,用于指代模型中词嵌入层(Embedding layer)的权重参数。下面详细解释: 词嵌入层的作用 …

解决:QTcpSocket: No such file or directory

项目场景&#xff1a; 使用QTcpSocket进行网络编程&#xff1a; 调用connectToHost连接服务器&#xff0c;调用waitForConnected判断是否连接成功&#xff0c;连接信号readyRead槽函数&#xff0c;异步读取数据&#xff0c;调用waitForReadyRead,阻塞读取数据。 问题描述 找不…

支付宝商家转账到账户余额,支持多商户管理

大家好&#xff0c;我是小悟 转账到支付宝账户是一种通过 API 完成单笔转账的功能&#xff0c;支付宝商家可以向其他支付宝账户进行单笔转账。 商家只需输入另一个正确的支付宝账号&#xff0c;即可将资金从本企业支付宝账户转账至另一个支付宝账户。 该产品适用行业较广&am…

3.Chromium指纹浏览器开发教程之chromium119版本源码拉取

获取Chromium最新版源码 Git是一个分布式版本控制系统&#xff0c;用于管理代码的版本和协作开发&#xff0c;它是目前最流行和广泛使用的版本控制系统之一。在Chromium项目中&#xff0c;通常使用gclient来获取Chromium的源代码&#xff0c;并使用Git来对代码进行版本控制和管…

【计算机视觉】OpenCV项目实战- Artificial-Eyeliner 人脸眼线检测

Artificial-Eyeliner 人脸眼线检测 项目介绍运行方式运行步骤常见问题及解决方法1. dlib 安装失败其他注意事项 2. 缺少 make / gcc3. **依赖库安装问题**&#xff1a;4. *人脸关键点检测失败&#xff1a;5. 眼线效果不理想&#xff1a;6. 实时处理延迟&#xff1a;7. 保存文件…

工作总结(十二)——迁移svn单项目到gitlab上,保留历史提交记录

文章目录 前言一、目的二、操作步骤1.创建项目库2.复制历史提交者账号3.复制待迁移项目以及历史记录4.push到gitlab远程仓库 总结 前言 本系列文章主要记录工作中一些需要记录的内容 一、目的 因为一些原因&#xff0c;我需要将svn库上的某个项目迁移到公司的gitlab库管理平台…

Git Flow

Git Flow深度解析&#xff1a;企业级分支管理实战指南 前言 在持续交付时代&#xff0c;分支策略决定团队协作效率。Git Flow作为经典的分支管理模型&#xff0c;被Apache、Spring等知名项目采用。2023年JetBrains开发者调查报告显示&#xff0c;Git Flow仍是中大型项目最常用…

Flink介绍——实时计算核心论文之Kafka论文详解

引入 我们通过S4和Storm论文的以下文章&#xff0c;已经对S4和Storm有了不错的认识&#xff1a; S4论文详解S4论文总结Storm论文详解Storm论文总结 不过&#xff0c;在讲解这两篇论文的时候&#xff0c;我们其实没有去搞清楚对应的流式数据是从哪里来的。虽然S4里有Keyless …

AI Agents系列之构建多智能体系统

&#x1f9e0; 向所有学习者致敬&#xff01; “学习不是装满一桶水&#xff0c;而是点燃一把火。” —— 叶芝 我的博客主页&#xff1a; https://lizheng.blog.csdn.net &#x1f310; 欢迎点击加入AI人工智能社区&#xff01; &#x1f680; 让我们一起努力&#xff0c;共创…

OJ笔试强训_1至24天

OJ笔试强训 Day01 [NOIP2010]数字统计_牛客题霸_牛客网 点击消除_牛客题霸_牛客网 两个数组的交集_牛客题霸_牛客网 Day02 牛牛的快递_牛客题霸_牛客网 最小花费爬楼梯_牛客题霸_牛客网 数组中两个字符串的最小距离__牛客网 Day03 简写单词_牛客题霸_牛客网 dd爱框框_…

3款顶流云电脑与传统电脑性能PK战:START云游戏/无影云/ToDesk云电脑谁更流畅?

这里写目录标题 一、前言二、本地机器配置环境三、START云游戏/无影云/ToDesk云电脑配置对比3.1 START云游戏3.2 无影云个人版3.3 ToDesk云电脑 四、本地电脑与云电脑性能实战4.1 游戏场景体验4.1.1 本地电脑测试4.1.2 云电脑测试英雄联盟黑神话悟空其他游戏 4.2 主流设计场景体…

java IO/NIO/AIO

(✪▽✪)曼波~~~~&#xff01;让曼波用最可爱的赛马娘方式给你讲解吧&#xff01;(⁄ ⁄•⁄ω⁄•⁄ ⁄) &#x1f3a0;曼波思维导图大冲刺&#xff08;先看框架再看细节哦&#xff09;&#xff1a; &#x1f4da; 解释 Java 中 IO、NIO、AIO 的区别和适用场景&#xff1a; …

java输出、输入语句

先创建一个用于测试的java 编写程序 #java.util使java标准库的一个包&#xff0c;这里拉取Scanner类 import java.util.Scanner;public class VariableTest {public static void main(String[] args) {#创建一个 Scanner 对象Scanner scanner new Scanner(System.in);System.…

宏基因组产品升级——抗菌肽数据库APD

抗菌肽&#xff08;Antimicrobial Peptides&#xff0c;简称AMPs&#xff09;是一类存在于多种生物体中的天然分子。它们在抵御微生物感染中扮演着重要角色&#xff0c;发挥着先天免疫反应的作用。抗菌肽功能分类广泛&#xff0c;包括&#xff1a;抗菌&#xff0c;抗生物膜&…

线程池七个参数的含义

Java中的线程池里七个参数的以及其各自的含义 面试题&#xff1a;说一下线程池七个参数的含义&#xff1f; 所谓的线程池的 7 大参数是指&#xff0c;在使用 ThreadPoolExecutor 创建线程池时所设置的 7 个参数&#xff0c;如以下源码所示&#xff1a; public ThreadPoolExe…

Windows suwellofd 阅读器-v5.0.25.0320

Windows suwellofd 阅读器 链接&#xff1a;https://pan.xunlei.com/s/VOO7tUkTHHTTjSe39CeVkUHbA1?pwd3ibx# OFD(Open Fixed-layout Document) &#xff0c; 数科OFD阅读器支持国标版式、可信阅读、是电子发票、电子证照&#xff0c;电子病历等电子文件理想阅读工具。 多格…

三大等待和三大切换

三大等待 1、三大等待&#xff1a;等待的方式有三种&#xff1a;强制等待&#xff0c;隐性等待&#xff0c;显性等待。 1、强制等待&#xff1a;time.sleep(2)&#xff0c;秒 优点&#xff1a;使用简单缺点&#xff1a;等待时间把握不准&#xff0c;容易造成时间浪费或者等待时…