Go-Zero实战:抽奖算法的设计与实现
本文将继续探索 Go-Zero 的实践应用,并介绍如何使用 Go-Zero 框架实现一个抽奖算法。通过实例代码,详细演示如何设计与实现一个高效、可靠的抽奖算法,并探讨相关实现方法和最佳实践。
抽奖算法是许多应用中常见的功能,通过规则和概率从参与者中选出中奖者,并分配奖品。本文将介绍如何在 Go-Zero 中构建这一功能,并涵盖策略模式的运用、RPC 服务的定义和数据表的设置。
实战前准备
数据库表结构
在实现抽奖算法前,需要准备一些数据库表来存储抽奖活动、奖品以及参与者信息。
lottery
表
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) NOT NULL DEFAULT '' COMMENT '默认取一等奖名称',
`thumb` varchar(255) NOT NULL DEFAULT '' COMMENT '默认取一等奖配图',
`publish_time` datetime NULL DEFAULT NULL COMMENT '发布抽奖时间',
`join_number` int NOT NULL DEFAULT 0 COMMENT '自动开奖人数',
`introduce` varchar(255) 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 '开奖时间',
`is_announced` tinyint(1) NULL DEFAULT 0 COMMENT '是否开奖:0未开奖;1已经开奖',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=111 DEFAULT CHARSET=utf8mb4;
prize
表
DROP TABLE IF EXISTS `prize`;
CREATE TABLE `prize` (
`id` int NOT NULL AUTO_INCREMENT,
`lottery_id` int NOT NULL DEFAULT 0 COMMENT '抽奖ID',
`type` tinyint(1) NOT NULL DEFAULT 0 COMMENT '奖品类型',
`name` varchar(24) NOT NULL DEFAULT '' COMMENT '奖品名称',
`level` int NOT NULL DEFAULT 1 COMMENT '几等奖',
`thumb` varchar(255) NOT NULL DEFAULT '' COMMENT '奖品图片',
`count` int NOT NULL DEFAULT 0 COMMENT '奖品份数',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=25 DEFAULT CHARSET=utf8mb4;
lottery_participation
表
CREATE TABLE lottery_participation (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键',
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',
create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) COMMENT '参与抽奖表';
抽奖算法设计思路
抽奖策略
我们将实现两种抽奖策略:
- 基于时间的抽奖策略:根据设定的开奖时间进行抽奖。
- 基于人数的抽奖策略:根据参与人数达到一定数量后进行开奖。
使用策略模式
策略模式是一种行为设计模式,允许在运行时选择不同的算法进行处理。在抽奖算法中,我们可以通过策略模式来选择不同的开奖规则,从而提高代码的灵活性和可扩展性。
实现步骤
步骤一:定义抽奖策略接口
type LotteryStrategy interface {
Run() error
}
步骤二:定义具体策略
我们定义了两个策略:
TimeLotteryStrategy
: 基于时间的抽奖策略。PeopleLotteryStrategy
: 基于人数的抽奖策略。
type TimeLotteryStrategy struct {
*AnnounceLotteryLogic
CurrentTime time.Time
}
type PeopleLotteryStrategy struct {
*AnnounceLotteryLogic
CurrentTime time.Time
}
步骤三:选择策略并执行
在主业务逻辑中根据传入的抽奖类型选择对应的策略。
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), "Strategy run error: %v", err)
}
return &pb.AnnounceLotteryResp{}, nil
}
步骤四:实现抽奖逻辑
随机选择中奖者并分配奖品
func (l *AnnounceLotteryLogic) DrawLottery(lotteryId int64, prizes []*model.Prize, participants []int64) ([]Winner, error) {
rand.New(rand.NewSource(time.Now().UnixNano()))
// 计算中奖人数
var totalWinners int64
for _, p := range prizes {
totalWinners += p.Count
}
winners := make([]Winner, 0)
// 计算每个参与者的中奖概率并随机选择中奖者
for i := 0; i < int(totalWinners); i++ {
// 抽奖逻辑...
winner := Winner{
LotteryId: lotteryId,
UserId: participants[randomIndex],
PrizeId: prizeId,
}
winners = append(winners, winner)
}
return winners, nil
}
基于时间的抽奖逻辑
func (s *TimeLotteryStrategy) Run() error {
lotteries, err := s.svcCtx.LotteryModel.GetLotteriesByTime(s.ctx, s.CurrentTime)
if err != nil {
return err
}
for _, lottery := range lotteries {
err := s.announceLottery(lottery)
if err != nil {
return err
}
}
return nil
}
基于人数的抽奖逻辑
func (s *PeopleLotteryStrategy) Run() error {
lotteries, err := s.svcCtx.LotteryModel.GetLotteriesByParticipants(s.ctx)
if err != nil {
return err
}
for _, lottery := range lotteries {
err := s.announceLottery(lottery)
if err != nil {
return err
}
}
return nil
}
步骤五:RPC 服务定义
定义 RPC 请求和响应消息。
message AnnounceLotteryReq {
int64 AnnounceType = 1;
}
message AnnounceLotteryResp {}
service LotteryService {
rpc AnnounceLottery (AnnounceLotteryReq) returns (AnnounceLotteryResp);
}
生成 RPC 代码:
goctl rpc protoc lottery.proto --go_out=./ --go-grpc_out=./ --zrpc_out=./ --style=goZero
步骤六:通知中奖者
我们可以通过 NotifyParticipators
方法通知中奖者结果。
func (l *AnnounceLotteryLogic) NotifyParticipators(participants []int64, lotteryId int64) error {
_, err := l.svcCtx.NoticeRpc.NoticeLotteryDraw(l.ctx, &pb.NoticeLotteryDrawReq{
LotteryId: lotteryId,
UserIds: participants,
})
return err
}
总结
本文详细介绍了如何使用 Go-Zero 框架实现一个抽奖算法,通过使用策略模式解决不同抽奖逻辑的实现,并结合 RPC 服务实现了抽奖结果的通知。希望通过这一实战,你能够更好地理解 Go-Zero 的使用,并将其应用到其他项目中。