【项目】【抽奖系统】抽奖 - 查询活动完整信息

2025博客之星年度评选已开启 10w+人浏览 888人参与


一、抽奖流程

  1. 参与者注册与奖品建⽴
    1.1. 参与者注册:管理员通过管理端新增⽤⼾, 填写必要的信息,如姓名、联系⽅式等。
    1.2. 奖品建⽴:奖品需要提前建⽴好
  2. 抽奖活动设置
    2.1. 活动创建:管理员在系统中创建抽奖活动,输⼊活动名称、描述、奖品列表等信息。
    2.2. 圈选⼈员: 关联该抽奖活动的参与者。
    2.3. 圈选奖品:圈选该抽奖活动的奖品,设置奖品等级、个数等。
    2.4. 活动发布:活动信息发布后,系统通过管理端界⾯展⽰活动列表。
  3. 抽奖请求处理(重要)
    3.1. 随机抽取:前端随机选择后端提供的参与者,确保每次抽取的结果是公平的。
    3.2. 请求提交:在活动进⾏时,管理员可发起抽奖请求。请求包含活动ID、奖品ID和中奖⼈员等附加
    信息。
    3.3. 消息队列通知:有效的抽奖请求被发送⾄MQ队列中,等待MQ消费者真正处理抽奖逻辑。
    请求返回:抽奖的请求处理接⼝将不再完成任何的事情,直接返回。
  4. 抽奖结果公布
    4.1. 前端展⽰:中奖名单通过前端随机抽取的⼈员,公布展⽰出来。
  5. 抽奖逻辑执⾏(重要)
    5.1. 消息消费:MQ消费者收到异步消息,系统开始执⾏以下抽奖逻辑。
  6. 中奖结果处理(重要)
    6.1. 请求验证:
    6.2. 系统验证抽奖请求的有效性,如是否满⾜系统根据设定的规则(如奖品数量、每⼈中奖次数限制等)等;
    6.3. 幂等性:若消息多发,已抽取的内容不能再次抽取
    6.4. 状态扭转:根据中奖结果扭转活动/奖品/参与者状态,如奖品是否已被抽取,⼈员是否已中奖等。
    6.5. 结果记录:中奖结果被记录在数据库中,并同步更新Redis缓存。
  7. 中奖者通知
    7.1. 通知中奖者:通知中奖者和其他相关系统(如邮件发送服务)。
    7.2. 奖品领取:中奖者根据通知中的指引领取奖品。
  8. 抽奖异常处理
    8.1. 回滚处理:当抽奖过程中发⽣异常,需要保证事务⼀致性。
    8.2. 补救措施:抽奖⾏为是⼀次性的,因此异步处理抽奖任务必须保证成功,若过程异常,需采取补救措施

技术实现细节

  • 异步处理:提⾼抽奖性能,不影响抽奖流程,将抽奖处理放⼊队列中进⾏异步处理,且保证了幂等性。
  • 活动状态扭转处理:状态扭转会涉及活动及奖品等多横向维度扭转,不能避免未来不会有其他内容牵扯进活动中,因此对于状态扭转处理,需要⾼扩展性(设计模式)与维护性。
  • 事务处理:在抽奖逻辑执⾏时,如若发⽣异常,需要确保数据库表原⼦性、事务⼀致性,因此要做好事务处理。

二、查询活动完整信息简介

时序图:

三、参数列表

参数名描述类型默认值条件
activityId活动idLong必须

四、接口规范

[请求] /activity-detail/find?activityId=24 GET

[响应] 
{
	 "code": 200,
	 "data": {
	 "activityId": 24,
	 "activityName": "测试抽奖活动",
	 "description": "测试抽奖活动",
	 "valid": true,
	 "prizes": [
	 	{
		 "prizeId": 18,
		 "name": "⼿机",
		 "description": "⼿机",
		 "price": 5000.00,
		 "imageUrl": "e606c8db-218a-40c2-8946-0d9f8570626d.jpg",
		 "prizeAmount": 1,
		 "prizeTierName": "⼀等奖",
		 "valid": true
	 	},
	 	{
		 "prizeId": 19,
		 "name": "吹⻛机",
		 "description": "吹⻛机",
		 "price": 200.00,
		 "imageUrl": "63404e12-26f7-4974-9a99-41993586093c.jpg",
		 "prizeAmount": 1,
		 "prizeTierName": "⼆等奖",
		 "valid": true
	 	}
	 ],
	 "users": [
	 	{
		 "userId": 44,
		 "userName": "郭靖",
		 "valid": true
	 	},
	 	{
		 "userId": 45,
		 "userName": "杨康",
		 "valid": true
	 	}
	 ]
	 },
 "msg": ""
}

五、controller层

com/yj/lottery_system/controller 包下 ActivityController 类中:

  • 非空校验
  • 调用service
  • GetActivityDetailResult:controller层返回类
  • convertTOGetActivityDetailResult:service返回结果 转换 controller返回结果 方法
    @RequestMapping("/activity-detail/find")
    public CommonResult<GetActivityDetailResult> getActivityDetailFind (Long activityId) {
        //日志打印
        log.info("getActivityDetailFind activityId: {}", JacksonUtil.writeValueAsString(activityId));
        //调用service服务
        ActivityDetailDTO activityDetailDTO = activityService.getActivityDetailFind(activityId);
        return CommonResult.success(
                convertTOGetActivityDetailResult(activityDetailDTO));
    }

5.1 GetActivityDetailResult:controller层返回类

com.yj.lottery_system.controller.result 包下:

  • 其实跟前面创建活动的service层返回的ActivityDetailDTO类差不多,只不过将状态表示直接换成Boolean,不需要提供单独方法
  • 也可以对照请求响应,一一填写
package com.yj.lottery_system.controller.result;

import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;

/**
 * @author: yibo
 */
@Data
public class GetActivityDetailResult implements Serializable {

    /**
     * 活动id
     */
    private Long activityId;

    /**
     * 活动名称
     */
    private String activityName;

    /**
     * 活动描述
     */
    private String description;

    /**
     * 活动是否有效
     */
    private Boolean valid;

    /**
     * 奖品信息(列表)
     */
    private List<Prize> prizes;

    /**
     * 人员信息(列表)
     */
    private List<User> users;


    @Data
    public static class Prize {
        /**
         * 奖品Id
         */
        private Long prizeId;
        /**
         * 奖品名
         */
        private String name;

        /**
         * 图片索引
         */
        private String imageUrl;

        /**
         * 价格
         */
        private BigDecimal price;

        /**
         * 描述
         */
        private String description;

        /**
         * 奖品等奖
         */
        private String prizeTierName;

        /**
         * 奖品数量
         */
        private Long prizeAmount;

        /**
         * 奖品是否有效
         */
        private Boolean valid;
    }

    @Data
    public static class User {
        /**
         * 用户id
         */
        private Long userId;
        /**
         * 姓名
         */
        private String userName;
        /**
         * 人员是否被抽取
         */
        private Boolean valid;
    }


}


5.2 convertTOGetActivityDetailResult:service返回结果 转换 controller返回结果 方法

com/yj/lottery_system/controller 包下 ActivityController 类中:

  • 对于类中的List,使用老方法流来赋值即可。
  • 在对奖品赋值的时候,我们将奖品按照等级排个序
private GetActivityDetailResult convertTOGetActivityDetailResult(ActivityDetailDTO activityDetailDTO) {

        if(null == activityDetailDTO) {
            throw new ControllerException(ControllerErrorCodeConstants.GET_ACTIVITY_DETAILS_ERROR);
        }
        GetActivityDetailResult getActivityDetailResult = new GetActivityDetailResult();
        getActivityDetailResult.setActivityId(activityDetailDTO.getActivityId());
        getActivityDetailResult.setActivityName(activityDetailDTO.getActivityName());
        getActivityDetailResult.setDescription(activityDetailDTO.getDesc());
        getActivityDetailResult.setValid(activityDetailDTO.valid());
        getActivityDetailResult.setPrizes(activityDetailDTO.getPrizeDTOList().stream()
                //按照奖品等级排序
                .sorted(Comparator.comparingInt(prizeDTO -> prizeDTO.getTires().getCode()))
                .map(prizeDTO -> {
                    GetActivityDetailResult.Prize prize = new GetActivityDetailResult.Prize();
                    prize.setPrizeId(prizeDTO.getPrizeId());
                    prize.setDescription(prizeDTO.getDescription());
                    prize.setImageUrl(prizeDTO.getImageUrl());
                    prize.setName(prizeDTO.getName());
                    prize.setPrice(prizeDTO.getPrice());
                    prize.setPrizeTierName(prizeDTO.getTires().getMessage());
                    prize.setPrizeAmount(prizeDTO.getPrizeAmount());
                    prize.setValid(prizeDTO.valid());
                    return prize;
                }).collect(Collectors.toList())
        );

        getActivityDetailResult.setUsers(activityDetailDTO.getUserDTOList().stream()
                .map(userDTO -> {
                    GetActivityDetailResult.User user = new GetActivityDetailResult.User();
                    user.setUserId(userDTO.getUserId());
                    user.setUserName(userDTO.getUserName());
                    user.setValid(userDTO.valid());

                    return user;
                }).collect(Collectors.toList())
        );

        return getActivityDetailResult;

    }

5.3 新增错误码

com/yj/lottery_system/common/errorcode 包下 ControllerErrorCodeConstants.java类

    ErrorCode GET_ACTIVITY_DETAILS_ERROR = new ErrorCode(302,"查询活动详情失败");

六、service层

6.1 创建接口

com/yj/lottery_system/service 包下 IActivityService接口类中:

    /**
     * 获取活动详情信息
     * @param activityId
     * @return
     */
    ActivityDetailDTO getActivityDetailFind(Long activityId);

6.2 实现接口

com/yj/lottery_system/service/impl 包下 ActivityServiceImpl 类中:

  • 非空校验
  • 先在 Redis 里面查找,方法创建活动时已写好
  • 有直接返回,没有进行下一步查表
  • 调 dao 查 活动表,拿到活动信息
  • 调 dao 查 活动奖品表,拿到活动关联奖品信息
  • 调 dao 查 活动人员表,拿到活动关联人员信息
  • 调 dao 查 奖品表,拿到活动关联奖品详细信息
  • 整合详细信息,方法创建活动时已写好
  • 存放Redis,方法创建活动时已写好
    /**
     * 获取活动详情信息
     * @param activityId
     * @return
     */
    @Override
    public ActivityDetailDTO getActivityDetailFind(Long activityId) {
        //查Redis
        if(null == activityId) {
            log.warn("查询活动详细信息失败 activityId不存在");
            return null;
        }
        ActivityDetailDTO activityFromCache = getActivityFromCache(activityId);
        if(activityFromCache != null) {
            log.warn("查询活动详细信息成功 activityFromCache:{}", JacksonUtil.writeValueAsString(activityFromCache));
            return activityFromCache;
        }
        //如果Redis没有,
        // 查表 活动表
        ActivityDO activityDO = activityMapper.selectById(activityId);
        // 活动奖品表
        List<ActivityPrizeDO> activityPrizeDOList = activityPrizeMapper.selectByActivityId(activityId);
        // 活动人员表
        List<ActivityUserDO> activityUserDOList = activityUserMapper.selectByActivityId(activityId);
        // 奖品表
        //先拿奖品id
        List<Long> prizeIdList = activityPrizeDOList.stream()
                .map(ActivityPrizeDO::getPrizeId)
                .collect(Collectors.toList());
        List<PrizeDO> prizeDOList = prizeMapper.batchSelectByIds(prizeIdList);
        
        //整合详细信息,存放Redis
        ActivityDetailDTO activityDetailDTO = convertActivityDetailDTO(activityDO, activityUserDOList, activityPrizeDOList, prizeDOList);
        //存放Redis
        cacheActivity(activityDetailDTO);
        return activityDetailDTO;
    }

七、dao层

com/yj/lottery_system/dao/mapper 包下 ActivityMapper.java 类

    /**
     * 根据活动id查活动信息
     * @param id
     * @return
     */
    @Select("select * from activity where id = #{id}")
    ActivityDO selectById(@Param("id") Long id);

com/yj/lottery_system/dao/mapper 包下 ActivityPrizeMapper.java 类

    /**
     * 根据活动id 查活动关联奖品信息
     * @param activityId
     * @return
     */
    @Select("select * from activity_prize where activity_id = #{activityId}")
    List<ActivityPrizeDO> selectByActivityId(@Param("activityId") Long activityId);

com/yj/lottery_system/dao/mapper 包下 ActivityUserMapper.java 类

    /**
     * 根据活动id 查活动关联人员信息
     * @param activityId
     * @return
     */
    @Select("select * from activity_user where activity_user activity_id = #{activityId}")
    List<ActivityUserDO> selectByActivityId(@Param("activityId") Long activityId);

八、测试

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鸽鸽程序猿

蟹蟹大哥

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值