欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 科技 > IT业 > 抽奖算法的设计与实现

抽奖算法的设计与实现

2025/4/18 2:04:28 来源:https://blog.csdn.net/2301_77174919/article/details/140585373  浏览:    关键词:抽奖算法的设计与实现

更多内容欢迎访问我的个人博客网站: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 {*AnnounceLotteryLogicCurrentTime time.Time
}

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

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

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

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

go代码解读复制代码
func (l *AnnounceLotteryLogic) AnnounceLottery(in *pb.AnnounceLotteryReq) (*pb.AnnounceLotteryResp, error) {var strategy LotteryStrategyswitch 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 {*AnnounceLotteryLogicCurrentTime time.Time
}func (s *TimeLotteryStrategy) Run() error {// 基于时间的抽奖逻辑实现// TODO: 根据时间计算开奖结果return nil
}type PeopleLotteryStrategy struct {*AnnounceLotteryLogicCurrentTime time.Time
}func (s *PeopleLotteryStrategy) Run() error {// 基于人数的抽奖逻辑实现// TODO: 根据参与人数计算中奖概率return nil
}

步骤二:定义Winner结构体

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

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

go代码解读复制代码
type Winner struct {LotteryId int64UserId    int64PrizeId   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 int64for _, 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 intvar winnerUserId int64//如果参与者少于预计中奖人数,就结束开奖。(参与人数 < 中奖人数)if len(participantor) == 0 {break}//生成一个0到1之间的随机数randomProbability := rand.Float64()// 根据随机数确定中奖用户probabilitySum := 0.0for idx := range participantor {// 逐个累加中奖概率,直到大于随机数probabilitySum += FinalRatios[idx]// 如果随机数小于等于累加的概率,说明中奖if randomProbability <= probabilitySum {// 中奖者的uidwinnerUserId = participantor[idx]// 中奖者的索引randomWinnerIndex = idxbreak}}//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}// 将得到的中奖信息,写入数据库participantserr = 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}// 将得到的中奖信息,写入数据库participantserr = 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框架的使用和抽奖算法的设计有所帮助。通过这个实战案例,你可

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词