当前位置: 首页 > news >正文

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

目录

  • 一、抽奖流程
  • 二、查询活动完整信息简介
  • 三、参数列表
  • 四、接口规范
  • 五、controller层
    • 5.1 GetActivityDetailResult:controller层返回类
    • 5.2 convertTOGetActivityDetailResult:service返回结果 转换 controller返回结果 方法
    • 5.3 新增错误码
  • 六、service层
    • 6.1 创建接口
    • 6.2 实现接口
  • 七、dao层
  • 八、测试

一、抽奖流程

  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;@Datapublic 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;}@Datapublic 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);// 奖品表//先拿奖品idList<Long> prizeIdList = activityPrizeDOList.stream().map(ActivityPrizeDO::getPrizeId).collect(Collectors.toList());List<PrizeDO> prizeDOList = prizeMapper.batchSelectByIds(prizeIdList);//整合详细信息,存放RedisActivityDetailDTO activityDetailDTO = convertActivityDetailDTO(activityDO, activityUserDOList, activityPrizeDOList, prizeDOList);//存放RediscacheActivity(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);

八、测试

http://www.jsqmd.com/news/274412/

相关文章:

  • 高效抖音视频批量采集:打造个人专属内容资源库的终极方案
  • 2025双片钉箱机哪家强?国内口碑榜TOP5供应商揭晓!市场双片钉箱机优选实力品牌 - 品牌推荐师
  • 收藏备用!程序员转型大模型全攻略:从入门到职业落地无死角
  • 【实战指南】零基础极速搭建开源财务工具:5分钟拥有专属个人财务管理系统
  • 收藏!大模型应用开发:程序员突破内卷的百万年薪新赛道
  • 免费网易云音乐NCM格式转换终极教程:一键解锁加密音频文件
  • Edge TTS终极指南:跨平台语音合成完整解决方案
  • 眼调节训练灯:守护孩子视力,筑牢近视防控防线
  • 2026年正规的大连智能锁维修安装,大连密码锁维修安装,大连智能锁维修安装公司选购指南与推荐 - 品牌鉴赏师
  • Python扩散模型实战核心拆解:文本生成图像与视频全流程
  • ComfyUI Manager全面指南:轻松掌握AI工作流插件管理技巧
  • 3分钟快速上手:小红书无水印下载神器XHS-Downloader完整教程
  • 电子万能材料试验机哪个牌子好质量好?源头制造商生产商供应商盘点 - 品牌推荐大师1
  • 轻量级Alienware硬件控制工具:告别臃肿官方软件的终极替代指南
  • BT下载加速终极指南:如何通过Tracker优化实现下载速度翻倍
  • 条件编译控制
  • 番茄小说完整下载指南:高效构建个人数字图书馆
  • 实用指南:3步轻松解密网易云音乐,实现全平台播放自由
  • 5个实战技巧让Vue3树形选择器开发效率翻倍
  • 终极无配置远程游戏串流完整解决方案
  • 基于GA遗传优化的多边形拟合算法matlab仿真
  • 告别千篇一律!2026年最有创意的年会策划公司,方案看完就心动 - 速递信息
  • 冥想第一千七百六十九天(1769)
  • 2026年气体爆破工厂推荐榜:液氧爆破/二氧化碳气体爆破/ 气体膨胀爆破/ 空气能爆破/液氧露天爆破厂家精选
  • ncmdump终极解密指南:快速实现ncm转MP3完整教程
  • 2026年神仙豆腐/观音豆腐/臭黄荆树苗厂家推荐:湖北芝兰农业全品类供应,助力特色农业发展
  • 挑选优质磁混凝污水处理设备:实力厂家与行业十大品牌盘点 - 品牌推荐大师1
  • 基于STM32的两路PWM互补输出带死区:编程与仿真探索
  • 免费开源绿色版工具!纯本地运行,支持图片压缩,可批量压缩和转格式,美观且好用 LocalSqueeze图片压缩
  • 2026年乳液施胶剂厂家推荐榜:AKD施胶剂 /中性施胶剂 /表面施胶剂 /固体表面施胶剂/湿强解离剂厂家精选