抽奖算法的设计与实现

news2024/11/25 16:56:46

更多内容欢迎访问我的个人博客网站:www.zpf0000.com

在数据库中准备好以下数据表

lottery表

sql代码解读复制代码
DROP TABLE IF EXISTS `lottery`;
CREATE TABLE `lottery`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `user_id` int NOT NULL DEFAULT 0 COMMENT '发起抽奖用户ID',
  `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '默认取一等奖名称',
  `thumb` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '默认取一等经配图',
  `publish_time` datetime NULL DEFAULT NULL COMMENT '发布抽奖时间',
  `join_number` int NOT NULL DEFAULT 0 COMMENT '自动开奖人数',
  `introduce` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '抽奖说明',
  `award_deadline` datetime NOT NULL COMMENT '领奖截止时间',
  `is_selected` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否精选 1是 0否',
  `announce_type` tinyint(1) NOT NULL DEFAULT 0 COMMENT '开奖设置:1按时间开奖 2按人数开奖 3即抽即中',
  `announce_time` datetime NOT NULL DEFAULT NULL COMMENT '开奖时间',
  `del_state` tinyint NOT NULL DEFAULT '0',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `delete_time` timestamp NULL DEFAULT NULL COMMENT '删除时间',
  `is_announced` tinyint(1) NULL DEFAULT 0 COMMENT '是否开奖:0未开奖;1已经开奖',
  `sponsor_id` int NOT NULL DEFAULT 0 COMMENT '发起抽奖赞助商ID',
  `is_clocked` tinyint(1) NULL DEFAULT 0 COMMENT '是否开启打卡任务:0未开启;1已开启',
  `clock_task_id` int NOT NULL DEFAULT 0 COMMENT '打卡任务任务ID',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 111 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '抽奖表' ROW_FORMAT = DYNAMIC;
SET FOREIGN_KEY_CHECKS = 1;

奖品表

sql代码解读复制代码
DROP TABLE IF EXISTS `prize`;
CREATE TABLE `prize`  (
  `id` int(0) NOT NULL AUTO_INCREMENT,
  `lottery_id` int(0) NOT NULL DEFAULT 0 COMMENT '抽奖ID',
  `type` tinyint(1) NOT NULL DEFAULT 0 COMMENT '奖品类型:1奖品 2优惠券 3兑换码 4商城 5微信红包封面 6红包',
  `name` varchar(24) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '奖品名称',
  `level` int(0) NOT NULL DEFAULT 1 COMMENT '几等奖 默认1',
  `thumb` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '奖品图',
  `count` int(0) NOT NULL DEFAULT 0 COMMENT '奖品份数',
  `grant_type` tinyint(1) NOT NULL COMMENT '奖品发放方式:1快递邮寄 2让中奖者联系我 3中奖者填写信息 4跳转到其他小程序',
  `create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0),
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 25 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '奖品表' ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

参与抽奖表

sql代码解读复制代码
CREATE TABLE lottery_participation
(
    id         BIGINT AUTO_INCREMENT COMMENT '主键'
        PRIMARY KEY,
    lottery_id INT     NOT NULL COMMENT '参与的抽奖的id',
    user_id    INT     NOT NULL COMMENT '用户id',
    is_won     TINYINT NOT NULL COMMENT '中奖了吗?',
    prize_id   BIGINT  NOT NULL COMMENT '中奖id',
    del_state tinyint NOT NULL DEFAULT '0',
    create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
    update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    CONSTRAINT index_lottery_user UNIQUE (lottery_id, user_id)
)
    COMMENT '参与抽奖' COLLATE = utf8mb4_general_ci;

设计思路

我们的抽奖算法将基于两种策略:基于时间的抽奖策略和基于人数的抽奖策略。

在基于时间的抽奖策略中,抽奖的开奖时间将作为一个重要的参考因素;

而在基于人数的抽奖策略中,参与抽奖的人数将决定中奖的概率。

针对这些不同的策略,我们很容易联想到使用策略模式来实现不同开奖算法的调用,最终通过RPC调用与其他服务进行通信。

实现步骤

定义RPC服务

步骤一:定义RPC请求、回复、远程服务的函数名

image-20240510211006593

image-20240511000823217

可以看见,我们首先定义了Req和Resp

protobuf代码解读复制代码
message AnnounceLotteryReq {
    int64 AnnounceType = 1;
}

message AnnounceLotteryResp {
}

rpc AnnounceLottery(AnnounceLotteryReq) returns (AnnounceLotteryResp);

步骤二:生成RPC服务模板

sh代码解读复制代码
goctl rpc protoc lottery.proto --go_out=./ --go-grpc_out=./  --zrpc_out=./ --style=goZero

策略模式

策略模式是一种行为设计模式,它允许在运行时选择算法的行为。它将一组算法封装在独立的类中,并使它们可以互换使用,从而使算法的变化独立于使用算法的客户端。

在策略模式中,通常有三个核心角色:

  1. 环境(Context):环境对象是使用策略的主体,它持有一个策略对象的引用,并在需要执行特定行为时将请求委派给策略对象。
  2. 策略(Strategy):策略是定义算法接口的共同接口或抽象类,它封装了具体的算法实现。
  3. 具体策略(Concrete Strategy):具体策略是策略的具体实现类,它实现了策略接口中定义的算法。

策略模式的核心思想是将可变的行为封装在独立的策略类中,使得这些策略类可以互换使用。这样,在需要变更行为时,只需要替换相应的策略对象,而不需要修改环境对象或其他客户端代码。

步骤一:定义抽奖策略接口

首先,在代码中定义了一个抽奖策略接口 LotteryStrategy,该接口声明了一个 Run() 方法用于执行抽奖策略。

go代码解读复制代码
type LotteryStrategy interface {
	Run() error
}

步骤二:定义具体抽奖策略类型

接下来,代码实现了两个基于不同策略的具体抽奖策略类型:TimeLotteryStrategyPeopleLotteryStrategy

TimeLotteryStrategy 结构体实现了基于时间的抽奖策略。它包含了 AnnounceLotteryLogic 的引用,以及当前时间信息。

go代码解读复制代码
type TimeLotteryStrategy struct {
	*AnnounceLotteryLogic
	CurrentTime time.Time
}

PeopleLotteryStrategy 结构体实现了基于人数的抽奖策略。它也包含了 AnnounceLotteryLogic 的引用,以及当前时间信息。

go代码解读复制代码
type PeopleLotteryStrategy struct {
	*AnnounceLotteryLogic
	CurrentTime time.Time
}

步骤三:根据传入的抽奖类型选择相应的策略进行抽奖

AnnounceLotteryLogic 结构体中,根据传入的抽奖类型选择相应的策略进行抽奖。 AnnounceLotteryLogic 结构体拥有一个 AnnounceLottery() 方法,该方法接收一个 pb.AnnounceLotteryReq 参数,并返回一个 *pb.AnnounceLotteryResp 和一个错误。

go代码解读复制代码
func (l *AnnounceLotteryLogic) AnnounceLottery(in *pb.AnnounceLotteryReq) (*pb.AnnounceLotteryResp, error) {
	var strategy LotteryStrategy
	switch in.AnnounceType {
	case constants.AnnounceTypeTimeLottery:
		strategy = &TimeLotteryStrategy{
			AnnounceLotteryLogic: l,
			CurrentTime:          time.Now(),
		}
	case constants.AnnounceTypePeopleLottery:
		strategy = &PeopleLotteryStrategy{
			AnnounceLotteryLogic: l,
			CurrentTime:          time.Now(),
		}
	}
	err := strategy.Run()
	if err != nil {
		return nil, errors.Wrapf(xerr.NewErrCode(xerr.AnnounceLottery_ERROR), "AnnounceStrategy run error: %v", err)
	}
	return &pb.AnnounceLotteryResp{}, nil
}

通过使用策略模式,可以根据不同的抽奖类型选择不同的策略进行开奖操作,而不需要在主逻辑中编写大量的条件语句来处理不同的策略。这样可以使代码结构更加清晰和可扩展,方便添加新的抽奖策略类型。

抽奖算法实现

步骤一:定义抽奖策略实现方法

现在,我们可以分别实现基于时间的抽奖策略和基于人数的抽奖策略的具体逻辑。在这些策略中,我们可以根据业务需求编写相应的代码,例如根据时间计算开奖结果或根据参与人数计算中奖概率。以下是部分代码示例:

go代码解读复制代码
type TimeLotteryStrategy struct {
	*AnnounceLotteryLogic
	CurrentTime time.Time
}

func (s *TimeLotteryStrategy) Run() error {
	// 基于时间的抽奖逻辑实现
	// TODO: 根据时间计算开奖结果
	return nil
}

type PeopleLotteryStrategy struct {
	*AnnounceLotteryLogic
	CurrentTime time.Time
}

func (s *PeopleLotteryStrategy) Run() error {
	// 基于人数的抽奖逻辑实现
	// TODO: 根据参与人数计算中奖概率
	return nil
}

步骤二:定义Winner结构体

​ 目前我们的开奖业务有三个结构体,分别是AnnounceLotteryLogic、TimeLotteryStrategy、PeopleLotteryStrategy。分别表示业务逻辑结构体,基于时间的抽奖逻辑,基于人数的抽奖逻辑;我们将之后所有通用的业务方法挂名在AnnounceLotteryLogic下,方便后续调用。

​ 定义Winner结构体用于存储中奖者的信息,包括抽奖id、用户id、奖品id。

go代码解读复制代码
type Winner struct {
	LotteryId int64
	UserId    int64
	PrizeId   int64
}

步骤三:从参与者中随机选择中奖者,并分配奖品

​ 该方法是一个公用方法,挂名在AnnounceLotteryLogic。输入参数包括抽奖id、奖品列表、参与者列表,输出参数包括中奖者列表。该方法首先计算每个参与者的中奖概率,然后根据中奖概率随机选择中奖者,最后分配奖品给中奖者。

go代码解读复制代码
func (l *AnnounceLotteryLogic) DrawLottery(ctx context.Context, lotteryId int64, prizes []*model.Prize, participantor []int64) ([]Winner, error) {
	// 随机选择中奖者
	rand.New(rand.NewSource(time.Now().UnixNano()))

	// 获取奖品总数 = 中奖人数
	var WinnersNum int64
	for _, p := range prizes {
		WinnersNum += p.Count
	}

	winners := make([]Winner, 0)

	records, err := l.svcCtx.ClockTaskRecordModel.GetClockTaskRecordByLotteryIdAndUserIds(lotteryId, participantor)
	if err != nil {
		return nil, err
	}

	// 查出来可能有多条记录 每条记录就是完成的一次任务 increase_multiple就是那一次任务所增加的概率,一个用户可能有多条记录,我这边在业务里面再进行统计一次
	// 所以用一个map来存储每个用户的中奖倍率
	RationsMap := make(map[int64]int64)
	for _, participant := range participantor {
		RationsMap[participant] = 1
	}

	for _, record := range records {
		RationsMap[record.UserId] += record.IncreaseMultiple
	}

	Ratios := make([]int64, len(participantor))

	for i, participant := range participantor {
		Ratios[i] = RationsMap[participant]
	}
    
	// 计算总的中奖概率
	totalRatio := int64(0)
	for _, ratio := range Ratios {
		totalRatio += ratio
	}
	// 计算每个用户的最终中奖概率
	FinalRatios := make([]float64, len(participantor))
	for idx := range Ratios {
		FinalRatios[idx] = float64(Ratios[idx]) / float64(totalRatio)
	}

	// 根据中奖总数量进行开奖
	for i := 0; i < int(WinnersNum); i++ { // 中奖人数
		var randomWinnerIndex int
		var winnerUserId int64

		//如果参与者少于预计中奖人数,就结束开奖。(参与人数 < 中奖人数)
		if len(participantor) == 0 {
			break
		}

		//生成一个0到1之间的随机数
		randomProbability := rand.Float64()

		// 根据随机数确定中奖用户
		probabilitySum := 0.0
		for idx := range participantor {
			// 逐个累加中奖概率,直到大于随机数
			probabilitySum += FinalRatios[idx]
			// 如果随机数小于等于累加的概率,说明中奖
			if randomProbability <= probabilitySum {
				// 中奖者的uid
				winnerUserId = participantor[idx]
				// 中奖者的索引
				randomWinnerIndex = idx
				break
			}
		}
		//fmt.Println("winnerUserId:", winnerUserId)
		//如果没有中奖用户,则第一个参与者中奖
		if winnerUserId == 0 {
			winnerUserId = participantor[0]
			//fmt.Println("没有中奖用户,默认第一个参与者中奖", winnerUserId)
		}

		// 对所有prizes按照type排序 // todo 获取的时候能保证type有序吗?有序则可以不用排序了
		sort.Slice(prizes, func(i, j int) bool {
			return prizes[i].Type < prizes[j].Type
		})

		// 如果当前等级的奖品的剩余数量都为0,去掉,获取下一等级的奖品。
		if prizes[0].Count == 0 {
			prizes = prizes[1:]
		}
		prizes[0].Count--
		prizeId := prizes[0].Id

		// 创建中奖者对象
		winner := Winner{
			LotteryId: lotteryId,
			UserId:    winnerUserId,
			PrizeId:   prizeId, // 使用选中的奖品名称
		}

		winners = append(winners, winner)

		// 从参与者列表中移除已中奖的用户以及对应的中奖概率
		participantor = append(participantor[:randomWinnerIndex], participantor[randomWinnerIndex+1:]...)
		FinalRatios = append(FinalRatios[:randomWinnerIndex], FinalRatios[randomWinnerIndex+1:]...)
	}

	return winners, nil
}

步骤四:实现按时间开奖业务逻辑

  • 首先,通过调用s.svcCtx.LotteryModel.GetLotterysByLessThanCurrentTime方法查询满足条件的抽奖活动。该方法返回了一组满足条件的抽奖活动列表,存储在lotteries变量中。
  • 对于每一个抽奖活动,进行以下操作:
  • a. 创建一个空的participators数组,用于存储参与者的ID。
  • b. 开启一个数据库事务,通过s.svcCtx.LotteryModel.Trans方法进行事务处理。
  • c. 根据抽奖活动ID(lotteryId),调用s.svcCtx.PrizeModel.FindByLotteryId方法获取该抽奖活动对应的所有奖品列表,存储在prizes变量中。
  • d. 调用s.svcCtx.LotteryParticipationModel.GetParticipationUserIdsByLotteryId方法获取参与该抽奖活动的用户ID列表,存储在participators变量中。
  • e. 调用s.DrawLottery方法进行抽奖操作,传入抽奖活动ID、奖品列表、参与者ID列表,并返回中奖者列表,存储在winners变量中。
  • f. 调用s.svcCtx.LotteryModel.UpdateLotteryStatus方法更新该抽奖活动的状态为"已开奖"。
  • g. 调用s.WriteWinnersToLotteryParticipation方法将中奖者信息写入数据库。
  • h. 提交事务,如果有任何错误发生,则回滚事务。
  • i. 调用s.NotifyParticipators方法执行开奖结果通知任务,传入参与者ID列表和抽奖活动ID。
  • 返回错误(如果有)。

go代码解读复制代码
// Run 按时间开奖业务逻辑
func (s *TimeLotteryStrategy) Run() error {
	// 查询满足条件的抽奖
	lotteries, err := s.svcCtx.LotteryModel.GetLotterysByLessThanCurrentTime(s.ctx, s.CurrentTime, constants.AnnounceTypeTimeLottery)
	if err != nil {
		return err
	}

	// 遍历每一个抽奖
	for _, lotteryId := range lotteries {
		var participators []int64
		// 事务处理
		err = s.svcCtx.LotteryModel.Trans(s.ctx, func(context context.Context, session sqlx.Session) error {
			//根据抽奖id得到对应的所有奖品
			prizes, err := s.svcCtx.PrizeModel.FindByLotteryId(s.ctx, lotteryId)
			if err != nil {
				return err
			}

			// 获取该lotteryId对应的所有参与者
			participators, err = s.svcCtx.LotteryParticipationModel.GetParticipationUserIdsByLotteryId(s.ctx, lotteryId)
			if err != nil {
				return err
			}

			winners, err := s.DrawLottery(s.ctx, lotteryId, prizes, participators)
			if err != nil {
				return errors.Wrapf(xerr.NewErrCode(xerr.AnnounceLottery_ERROR), "DrawLottery,lotteryId:%v,prizes:%v,participators:%v error: %v", lotteryId, prizes, participators, err)
			}

			//更新抽奖状态为"已开奖"
			err = s.svcCtx.LotteryModel.UpdateLotteryStatus(s.ctx, lotteryId)
			if err != nil {
				return err
			}

			// 将得到的中奖信息,写入数据库participants
			err = s.WriteWinnersToLotteryParticipation(winners)
			if err != nil {
				return err
			}

			return nil
		})
		if err != nil {
			return errors.Wrapf(xerr.NewErrCode(xerr.AnnounceLottery_ERROR), "AnnounceLotteryTrans error: %v", err)
		}

		// 执行开奖结果通知任务
		err := s.NotifyParticipators(participators, lotteryId)
		if err != nil {
			return err
		}
	}
	return err
}

步骤五:实现按人数开奖业务逻辑

  • 首先,通过调用s.svcCtx.LotteryModel.GetTypeIs2AndIsNotAnnounceLotterys方法查询开奖类型为2且尚未开奖的抽奖活动。该方法返回了一组满足条件的抽奖活动列表,存储在lotteries变量中。
  • 对查询到的抽奖活动列表进行检查,通过调用s.CheckLottery方法,传入抽奖活动列表,返回一个经过检查的抽奖活动列表CheckedLottery,该列表可能会剔除一些不符合条件的抽奖活动。
  • 对于每一个抽奖活动,进行以下操作:
  • a. 创建一个空的participators数组,用于存储参与者的ID。
  • b. 开启一个数据库事务,通过s.svcCtx.LotteryModel.Trans方法进行事务处理。
  • c. 根据抽奖活动ID(lottery.Id),调用s.svcCtx.PrizeModel.FindByLotteryId方法获取该抽奖活动对应的所有奖品列表,存储在prizes变量中。
  • d. 调用s.svcCtx.LotteryParticipationModel.GetParticipationUserIdsByLotteryId方法获取参与该抽奖活动的用户ID列表,存储在participators变量中。
  • e. 调用s.DrawLottery方法进行抽奖操作,传入抽奖活动ID、奖品列表、参与者ID列表,并返回中奖者列表,存储在winners变量中。
  • f. 调用s.svcCtx.LotteryModel.UpdateLotteryStatus方法更新该抽奖活动的状态为"已开奖"。
  • g. 调用s.WriteWinnersToLotteryParticipation方法将中奖者信息写入数据库。
  • h. 提交事务,如果有任何错误发生,则回滚事务。
  • i. 调用s.NotifyParticipators方法执行开奖结果通知任务,传入参与者ID列表和抽奖活动ID。
  • 返回错误(如果有)。

go代码解读复制代码
// Run 按人数开奖策略
func (s *PeopleLotteryStrategy) Run() error {

	// 查询开奖类型为2并且没有开奖的所有抽奖
	lotteries, err := s.svcCtx.LotteryModel.GetTypeIs2AndIsNotAnnounceLotterys(s.ctx, constants.AnnounceTypePeopleLottery)
	if err != nil {
		return err
	}

	CheckedLottery, err := s.CheckLottery(lotteries)
	if err != nil {
		return err
	}
	// 遍历每一个抽奖
	for _, lottery := range CheckedLottery {
		var participators []int64
		// 事务处理
		err = s.svcCtx.LotteryModel.Trans(s.ctx, func(context context.Context, session sqlx.Session) error {
			//根据抽奖id得到对应的所有奖品
			prizes, err := s.svcCtx.PrizeModel.FindByLotteryId(s.ctx, lottery.Id)
			if err != nil {
				return err
			}

			// 获取该lotteryId对应的所有参与者
			participators, err = s.svcCtx.LotteryParticipationModel.GetParticipationUserIdsByLotteryId(s.ctx, lottery.Id)
			if err != nil {
				return err
			}

			winners, err := s.DrawLottery(s.ctx, lottery.Id, prizes, participators)
			if err != nil {
				return errors.Wrapf(xerr.NewErrCode(xerr.AnnounceLottery_ERROR), "DrawLottery,lotteryId:%v,prizes:%v,participators:%v, error: %v", lottery.Id, prizes, participators, err)
			}
            
			//更新抽奖状态为"已开奖"
			err = s.svcCtx.LotteryModel.UpdateLotteryStatus(s.ctx, lottery.Id)
			if err != nil {
				return err
			}

			// 将得到的中奖信息,写入数据库participants
			err = s.WriteWinnersToLotteryParticipation(winners)
			if err != nil {
				return err
			}

			return nil
		})
		if err != nil {
			return errors.Wrapf(xerr.NewErrCode(xerr.AnnounceLottery_ERROR), "AnnounceLotteryTrans error: %v", err)
		}

		// 执行开奖结果通知任务
		err := s.NotifyParticipators(participators, lottery.Id)
		if err != nil {
			return err
		}
	}
	return nil
}

go代码解读复制代码
func (s *PeopleLotteryStrategy) CheckLottery(lotteries []*model.Lottery) (CheckedLotterys []*model.Lottery, err error) {
	// 筛选满足条件的抽奖
	// 1. 超过当前时间的,即使没有满足人数条件也需要进行开奖
	// 2. 当参与人数 >= 开奖人数,进行开奖

	for _, l := range lotteries {
		// l.AnnounceTime 小于等于 s.CurrentTime,即超过当前时间,需要开奖
		if l.AnnounceTime.Before(s.CurrentTime) || l.AnnounceTime.Equal(s.CurrentTime) {
			CheckedLotterys = append(CheckedLotterys, l)
		} else {
			ParticipatorCount, err := s.svcCtx.LotteryParticipationModel.GetParticipatorsCountByLotteryId(s.ctx, l.Id)
			if err != nil {
				return nil, err
			}
			// 检查参与人数是否达到指定人数
			if ParticipatorCount >= l.JoinNumber {
				CheckedLotterys = append(CheckedLotterys, l)
			}
		}
	}
	return
}

步骤六:实现根据中奖者列表,通知中奖者中奖的结果

该方法的输入参数包括中奖者列表、抽奖id,输出参数为空。通过调用notice服务的NoticeLotteryDraw方法,通知中奖者中奖的结果。

go代码解读复制代码
func (l *AnnounceLotteryLogic) NotifyParticipators(participators []int64, lotteryId int64) error {
	fmt.Println("NotifyParticipators", participators, lotteryId)
	_, err := l.svcCtx.NoticeRpc.NoticeLotteryDraw(l.ctx, &notice.NoticeLotteryDrawReq{
		LotteryId: lotteryId,
		UserIds:   participators,
	})
	if err != nil {
		return err
	}
	return nil
}

步骤七:更新参与抽奖表

该方法是更新中奖者的核心逻辑,根据中奖者列表,更新中奖者的中奖信息。该方法输入参数包括中奖者列表,输出参数为空。首先遍历中奖者列表,然后调用LotteryParticipationModel的UpdateWinners方法,更新中奖者的中奖信息。

go代码解读复制代码
func (l *AnnounceLotteryLogic) WriteWinnersToLotteryParticipation(winners []Winner) error {
    for _, w := range winners {
       err := l.svcCtx.LotteryParticipationModel.UpdateWinners(l.ctx, w.LotteryId, w.UserId, w.PrizeId)
       if err != nil {
          return err
       }
    }
    return nil
}

总结

通过以上步骤,我们成功地使用Go-Zero框架实现了一个抽奖算法。

在这个过程中,我们利用了模板定制化功能来快速生成代码,并灵活地设计了抽奖策略接口和具体实现。我们还结合RPC调用和其他服务进行数据交互,使得抽奖算法更加完善和可扩展。

希望本文对你理解Go-Zero框架的使用和抽奖算法的设计有所帮助。通过这个实战案例,你可

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

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

相关文章

【MySQL】:对库和表的基本操作方法

数据库使用的介绍 什么是SQL 学习数据库的使用——>基于 SQL编程语言 来对数据库进行操作 重点表述的是“需求”&#xff0c;期望得到什么结果。&#xff08;至于结果是如何得到的&#xff0c;并不关键&#xff0c;都是数据库服务器在背后做好了&#xff09; 重点表述的是…

DEGAS:将临床属性转移到细胞

DEGAS&#xff08;单细胞诊断证据量表&#xff0c;Diagnostic Evidence GAuge of Single cells&#xff09;是一种迁移学习框架&#xff0c;用于将疾病信息从患者转移到细胞。作者将这种可转移信息称为“印象-impressions”&#xff0c;它允许单细胞与疾病属性相关联&#xff0…

【Python】使用库 -- 详解

库就是别人已经写好了的代码&#xff0c;可以让我们直接拿来用。 一个编程语言能不能流行起来&#xff0c;一方面取决于语法是否简单方便容易学习&#xff0c;一方面取决于生态是否完备。所谓的 “生态” 指的就是语言是否有足够丰富的库&#xff0c;来应对各种各样的场景。在…

动态路由协议 —— EIGRP 与 OSPF 的区别

EIGRP&#xff08;增强内部网关路由协议&#xff09;和 OSPF&#xff08;开放式最短路径优先&#xff09;是两种最常见的动态路由协议&#xff0c;主要是用来指定路由器或交换机之间如何通信。将其应用于不同的情况下&#xff0c;可提高速率、延迟等方面的性能。那么它们之间到…

【Python系列】Python 缓存机制

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

【排序数组】python刷题记录

润到排序算法了。 顺便复习一下排序算法 easy work class Solution:def sortArray(self, nums: List[int]) -> List[int]:nums.sort()return nums 11种排序算法 # 选择排序 def selectsort(s):for i in range(0,len(s)-1):curmininfcuridx-1for j in range(i,len(s)):if …

【运维】软件运维方案(2024word完整版)

1. 文档介绍 2. 人员与责任 3. 运维过程内容 4. 运维资源 5. 运维服务规划保障 6. 事件处置 7. 质量改进 8. 运维边界及内容 获取方式&#xff1a; 本文末个人名片直接获取。

Leetcode1688. 比赛中的配对次数

问题描述&#xff1a; 给你一个整数 n &#xff0c;表示比赛中的队伍数。比赛遵循一种独特的赛制&#xff1a; 如果当前队伍数是 偶数 &#xff0c;那么每支队伍都会与另一支队伍配对。总共进行 n / 2 场比赛&#xff0c;且产生 n / 2 支队伍进入下一轮。如果当前队伍数为 奇…

【计算机视觉】siamfc论文复现实现目标追踪

什么是目标跟踪 使用视频序列第一帧的图像(包括bounding box的位置)&#xff0c;来找出目标出现在后序帧位置的一种方法。 什么是孪生网络结构 孪生网络结构其思想是将一个训练样本(已知类别)和一个测试样本(未知类别)输入到两个CNN(这两个CNN往往是权值共享的)中&#xff0…

【SRC】小程序抓包巨详细配置,一个Burp就够了,但是可以更优雅!

小程序抓包配置 文章目录 小程序抓包配置0x00 前言0x01 直接使用BurpSuite抓包0x02 配合Proxifier 0x00 前言 其实在PC端抓微信小程序的包&#xff0c;只需要一个BurpSuite就足够了&#xff0c;但是为了避免抓一些没用的包&#xff0c;减少对小程序抓包测试过程中的干扰&#…

学生处分类型管理

在智慧校园学工管理系统中&#xff0c;"处分类型"功能扮演着至关重要的角色&#xff0c;它如同一座桥梁&#xff0c;连接着校园秩序与学生行为规范的两端。这一模块的核心精髓&#xff0c;在于它以精准的违规行为界定和适当的处分措施&#xff0c;巧妙地平衡了纪律的…

Qmi8658a姿态传感器使用心得(4)linux

1.FIFO 结构与大小 FIFO 数据可以包含陀螺仪和加速度计数据&#xff0c;通过 SPI/I2C/I3C 接口以突发读模式读取。FIFO 大小可配置为 16 样本、32 样本、64 样本或 128 样本&#xff08;每个样本为 6 字节&#xff09;。 2.FIFO 模式 Bypass 模式&#xff1a;禁用 FIFO 功能。…

SpringCloud03_loadbalancer的概述、负载均衡解析、切换、原理

文章目录 ①. Ribbon进入维护模式②. loadbalancer的概述③. loadbalancer负载均衡解析④. 负载均衡案例总结⑤. 负载均衡算法原理 ①. Ribbon进入维护模式 ①. Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。 ②. 维护模式不再介绍,了解即可 ③.…

大语言模型-Transformer-Attention Is All You Need

一、背景信息&#xff1a; Transformer是一种由谷歌在2017年提出的深度学习模型。 主要用于自然语言处理&#xff08;NLP&#xff09;任务&#xff0c;特别是序列到序列&#xff08;Sequence-to-Sequence&#xff09;的学习问题&#xff0c;如机器翻译、文本生成等。Transfor…

【python】Numpy运行报错分析:ValueError - 数组维度不一致

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

java中多态的用法

思维导图&#xff1a; 1. 多态的概念 多态通俗的讲就是多种形态&#xff0c;同一个动作&#xff0c;作用在不同对象上&#xff0c;所产生不同的形态。 例如下图&#xff1a; 2. 多态的实现条件 Java中&#xff0c;多态的实现必须满足以下几个条件&#xff1a; 1. 必须在继承…

动画革命:Lottie如何改变我们对移动应用交互的认知

在数字世界的浩瀚星空中&#xff0c;每一个像素都跃动着无限创意与想象的火花。当静态的界面遇上动态的魔法&#xff0c;一场视觉盛宴便悄然开启。今天&#xff0c;让我们一同揭开一位幕后英雄的神秘面纱——Lottie&#xff0c;这个在UI/UX设计界掀起波澜的动画利器&#xff0c…

[trick]使用生成器打破嵌套循环

原文 break用于结束循环。但是&#xff0c;如果有嵌套循环&#xff0c;如何跳出外层循环&#xff1f; def this_is_the_one(x):return x 3my_list [[1, 2], [3, 4], [5, 6]] for sublist in my_list:for element in sublist:print(f"Checking {element}")if this_…

农场驿站平台小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;农场资讯管理&#xff0c;卖家管理&#xff0c;用户分享管理&#xff0c;分享类型管理&#xff0c;商品信息管理&#xff0c;商品分类管理&#xff0c;系统管理&#xff0c;订单管…

天舟飞船可视化:直观体验太空任务全过程

利用图扑先进的 3D 可视化技术&#xff0c;实时展示天舟飞船的发射、对接和任务执行&#xff0c;为观众提供身临其境的太空探索体验。