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

从零开始的抽奖系统创作(4)

目录

1.活动创建模块

Service:

1.校验活动信息

1.校验人员是否存在 

2.校验奖品是否存在 

3.校验奖品数量是否大于人员数量 

4.校验活动奖品等级有效性 

2.保存活动信息 

3.保存活动相关的奖品信息 

 4.保存活动相关的人员信息 

5.整合完整的活动信息 

6.存放Redis 

7.构造返回 

2.查询活动列表

1.查询活动总数

2.查询活动列表 

3.构造返回 

3.从Redis中获取数据

1.查询Redis 

2.如果Redis中不存在,查表

3.整合活动完整信息 

4.缓存到Redis中 

4.抽奖模块

1.从前端获取中奖信息

2. 成功接收到队列中的消息,放入消息队列

3.处理消息队列任务 

3.1校验抽奖信息是否有效


1.活动创建模块

Service:

其他模块较为简单不再概述

该模块开启一个事务,当活动创建错误时需要将数据库中的数据回滚

    @Transactional(rollbackFor = Exception.class) // 涉及多表

1.校验活动信息

        //1.校验活动信息checkActivityInfo(param);
1.校验人员是否存在 
//获取输入的人员idList<Long> userIds = param.getActivityUserList().stream().map(CreateUserByActivityParam::getUserId).distinct() //去重.collect(Collectors.toList());

掌握:

流式读取List数据,利用.distinct()进行数据去重

lambda表达式写法:CreateUserByActivityParam::getUserId直接提取ActivityUserList中的用户id

        //在数据库中找寻存在的人员List<Long> existsIds = userMapper.selectUserListByIds(userIds);

通过刚才在   传入的数据中获取的用户id  进行数据库查询前端 输入的人员中  在数据库中存在的人员

Mybatis:

    @Select("<script>" +" select id from user " +" where id in" +" <foreach item='item' collection='items' open='(' separator=',' close=')'>" +" #{item}" +" </foreach>" +" </script>")List<Long> selectUserListByIds(@Param("items") List<Long> ids);

然后校验数据库中获取的人员id是否为空 

if(existsIds == null || existsIds.isEmpty()) {throw new ServiceException(ServiceErrorCodeConstant.ACTIVITY_USER_ERROR);}

不为空后,循环遍历userIds        判断是否有人员不存在数据库中

userIds.forEach(id -> {if(!existsIds.contains(id)) {throw new ServiceException(ServiceErrorCodeConstant.ACTIVITY_USER_ERROR);}});
2.校验奖品是否存在 
//获取输入的奖品idList<Long> prizeIds = param.getActivityPrizeList().stream().map(CreatePrizeByActivityParam::getPrizeId).distinct() //去重.collect(Collectors.toList());

同理crud一下

        //在数据库中找寻存在的奖品List<Long> existsPrizeIds = prizeMapper.selectPrizeListByActivity(prizeIds);//判断是否存在prizeIds.forEach(id -> {if(!existsPrizeIds.contains(id)) {throw new ServiceException(ServiceErrorCodeConstant.ACTIVITY_PRIZE_ERROR);}});
3.校验奖品数量是否大于人员数量 
  long userCount = param.getActivityUserList().size();long prizeCount = param.getActivityPrizeList().stream().mapToLong(CreatePrizeByActivityParam::getPrizeAmount).sum();if(userCount > prizeCount) {throw new ServiceException(ServiceErrorCodeConstant.ACTIVITY_USER_PRIZE_ERROR);}

掌握:.mapToLong()可以将内部的数据转化为Long类型

.sum()  对数据进行求和(流中获取的)

4.校验活动奖品等级有效性 
  param.getActivityPrizeList().forEach(prize -> {if(ActivityPrizeTiersEnum.forName(prize.getPrizeTiers()) == null) {throw new ServiceException(ServiceErrorCodeConstant.ACTIVITY_PRIZE_TIERS_ERROR);}});

2.保存活动信息 

ActivityDO activityDO = new ActivityDO();activityDO.setActivityName(param.getActivityName());activityDO.setDescription(param.getDescription());activityDO.setStatus(ActivityStatusEnum.RUNNING.name());activityMapper.insert(activityDO);

直接将状态设置为  :活动进行中 

insert:

    @Insert("<script>" +" insert into activity (activity_name, description, status)" +" values (#{activityName}, #{description}, #{status})" +" </script>")@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")int insert(ActivityDO activityDO);

3.保存活动相关的奖品信息 

//先获取奖品信息List<CreatePrizeByActivityParam> prizeParams = param.getActivityPrizeList();List<ActivityPrizeDO> activityPrizeDOList = prizeParams.stream().map(prizeParam -> {ActivityPrizeDO activityPrizeDO = new ActivityPrizeDO();activityPrizeDO.setActivityId(activityDO.getId());activityPrizeDO.setPrizeId(prizeParam.getPrizeId());activityPrizeDO.setPrizeAmount(prizeParam.getPrizeAmount());activityPrizeDO.setPrizeTiers(prizeParam.getPrizeTiers());activityPrizeDO.setStatus(ActivityPrizeStatusEnum.INIT.name());return activityPrizeDO;}).collect(Collectors.toList());activityPrizeMapper.batchInsert(activityPrizeDOList);

@Mapper
public interface ActivityPrizeMapper {@Insert("<script>" +" insert into activity_prize (activity_id, prize_id, prize_amount, prize_tiers, status)" +" <foreach collection='items' item='item' index='index' separator=',' >" +" values (#{item.activityId}, #{item.prizeId},#{item.prizeAmount},#{item.prizeTiers}, #{item.status})" +" </foreach>" +" </script>" )@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")int batchInsert(@Param("items") List<ActivityPrizeDO> activityPrizeDOList);

 4.保存活动相关的人员信息 

//获取人员信息List<CreateUserByActivityParam> userParams = param.getActivityUserList();List<ActivityUserDO> activityUserDOS = userParams.stream().map(userParam -> {ActivityUserDO activityUserDO = new ActivityUserDO();activityUserDO.setActivityId(activityDO.getId());activityUserDO.setUserId(userParam.getUserId());activityUserDO.setUserName(userParam.getUserName());activityUserDO.setStatus(ActivityUserStatusEnum.INIT.name());return activityUserDO;}).collect(Collectors.toList());activityUserMapper.batchInsert(activityUserDOS);

5.整合完整的活动信息 

这一步要先获取奖品ids,用于从数据库中获取奖品的详细信息

  List<ActivityDetailDTO> activityDetailDTOList = new ArrayList<>();//先获取奖品ids  用于获取数据库中的奖品表基础属性List<Long> prizeIds = param.getActivityPrizeList().stream().map(CreatePrizeByActivityParam::getPrizeId).distinct().collect(Collectors.toList());//从数据库中获取List<PrizeDO> prizeDOList = prizeMapper.selectPrizeListByIds(prizeIds);//整合ActivityDetailDTO detailDTO = convertToActivityDetailDTO(activityDO,activityPrizeDOList, activityUserDOS, prizeDOList);

在整合全部数据这一步是很复杂的一步:

1.先将活动的信息放入整合数据中

        ActivityDetailDTO activityDetailDTO = new ActivityDetailDTO();activityDetailDTO.setActivityId(activityDO.getId());activityDetailDTO.setActivityName(activityDO.getActivityName());activityDetailDTO.setDesc(activityDO.getDescription());activityDetailDTO.setStatus(ActivityStatusEnum.forName(activityDO.getStatus()));

2.将用户数据放入 整合数据

        //构造一个UserDTOList<ActivityDetailDTO.ActivityUserDTO> auDTOs = activityUserDOS.stream().map(auDO -> {ActivityDetailDTO.ActivityUserDTO auDTO = new ActivityDetailDTO.ActivityUserDTO();auDTO.setActivityUserId(auDO.getUserId());auDTO.setActivityUserName(auDO.getUserName());auDTO.setStatus(ActivityUserStatusEnum.forName(auDO.getStatus()));return auDTO;}).collect(Collectors.toList());activityDetailDTO.setUserDTOList(auDTOs);

3.将奖品数据放入 整合数据

但是要注意奖品数据来源有两个对象:

List<ActivityPrizeDO>:活动与奖品关联--->可以得到奖品与活动关联的属性

List<PrizeDO>:奖品--->可以得到奖品的基本属性

        //构造一个pDTO  其中使用Optional进行遍历中遍历List<ActivityDetailDTO.PrizeDTO> aDTOs = activityPrizeDOList.stream().map(apDO -> {ActivityDetailDTO.PrizeDTO aDTO = new ActivityDetailDTO.PrizeDTO();aDTO.setId(apDO.getPrizeId());//从prizeDOList中循环遍历//运用 filter 方法筛选出 PrizeDO 对象,筛选条件为该对象的 id 与 apDO 的 prizeId 相等。//利用 findFirst 方法返回符合条件的第一个元素,将其封装到 Optional 对象中。// 若列表为空或者没有匹配项,Optional 对象为空。Optional<PrizeDO> optional = prizeDOList.stream().filter(pDO -> pDO.getId().equals(apDO.getPrizeId())).findFirst();//如果optional为空不执行该方法optional.ifPresent(pDO -> {aDTO.setName(pDO.getName());aDTO.setDescription(pDO.getDescription());aDTO.setPrice(pDO.getPrice());aDTO.setImageUrl(pDO.getImageUrl());});aDTO.setPrizeAmount(apDO.getPrizeAmount());aDTO.setPrizeTiers(ActivityPrizeTiersEnum.forName(apDO.getPrizeTiers()));aDTO.setStatus(ActivityPrizeStatusEnum.forName(apDO.getStatus()));return aDTO;}).collect(Collectors.toList());

我们选择流式遍历奖品活动关联数据,其中基本属性要从奖品数据中循环读取

所以有流中套流:

利用:Optional<PrizeDO>  过滤不要的数据(根据两者中的公共属性奖品id)

.filter()   :与map使用类似

findFirst():取满足.filter() 中的第一个数据(达到去重)

optional.ifPresent():如果内部数据为空就不会执行流式遍历(不用使用if判空了)

6.存放Redis 

cacheActivityDetailDTO(detailDTO);

为了事务在数据存入Redis中失败时不进行回滚 (校验时可以再从数据库中读取),

try捕获一下异常

    /*** 缓存活动信息到Redis中** @param detailDTO*/private void cacheActivityDetailDTO(ActivityDetailDTO detailDTO) {if(detailDTO == null || detailDTO.getActivityId() == null) {logger.warn("cacheActivityDetailDTO 缓存到Redis中失败!, detailDTO:{}",JacksonUtil.writeValueAsString(detailDTO));}//当缓存失败后但是sql没存储失败,不希望回滚try{//TODO 缓存到Redis中redisUtil.set(ACTIVITY_PREFIX + detailDTO.getActivityId(),JacksonUtil.writeValueAsString(detailDTO), ACTIVITY_OUT_TIME);}catch (Exception e) {logger.error("cacheActivityDetailDTO 缓存到Redis中失败!", e);}}

7.构造返回 

CreateActivityDTO createActivityDTO = new CreateActivityDTO();createActivityDTO.setActivityId(activityDO.getId());return createActivityDTO;

2.查询活动列表

向前端返回两个参数:

活动总数据量:便于用于计算页数

活动列表:前端展示的数据

@Data
public class FindActivityListResult implements Serializable {/*** 活动总数据量*/private Integer total;/*** 活动列表*/private List<ActivityInfo> records;@Datapublic static class ActivityInfo implements Serializable {/*** 活动id*/private Long activityId;/*** 活动名称*/private String activityName;/*** 活动描述*/private String description;/*** 活动是否有效*/private Boolean valid;}
}

业务: 

1.查询活动总数

Integer total = activityMapper.count();

2.查询活动列表 

List<ActivityDO> activityDOList = activityMapper.selectActivityList(param.offset(), param.getPageSize());

查找翻表数据(根据两个参数:起始页,一页展示的数据个数)

    /*** 获取翻页数据* @param offset  展示的起始索引* @param pageSize  一页展示个数* @return*/@Select("Select * from activity order by id desc limit #{offset}, #{pageSize}")List<ActivityDO> selectActivityList(@Param("offset") Integer offset, @Param("pageSize") Integer pageSize);

3.构造返回 

List<ActivityDTO> activityDTOList = activityDOList.stream().map(activityDO -> {ActivityDTO activityDTO = new ActivityDTO();activityDTO.setActivityId(activityDO.getId());activityDTO.setActivityName(activityDO.getActivityName());activityDTO.setDescription(activityDO.getDescription());activityDTO.setStatus(ActivityStatusEnum.forName(activityDO.getStatus()));return activityDTO;}).collect(Collectors.toList());

3.从Redis中获取数据

从Redis中获取活动信息,如果不存在,从数据库中获取并保存到Redis中

1.查询Redis 

        ActivityDetailDTO detailDTO = getActivityFromCache(activityId);if(detailDTO != null){logger.info("从Redis中获取活动信息成功!, activityId:{}", activityId);return detailDTO;}

 调用RedisUtil方法:

    /*** 通过活动id在Redis中获取活动信息* @param activityId* @return*/private ActivityDetailDTO getActivityFromCache(Long activityId) {if(activityId == null) {logger.warn("getActivityFromCache 获取Redis的activityId异常!, activityId:{}",activityId);}try{String activityJson = redisUtil.get(ACTIVITY_PREFIX + activityId);if(activityJson == null) {logger.info("getActivityFromCache 获取Redis中的活动信息失败!, key:{}",ACTIVITY_PREFIX + activityId);return null;}return JacksonUtil.readValue(activityJson, ActivityDetailDTO.class);} catch (Exception e) {logger.error("从Redis中获取活动信息异常, key:{}", ACTIVITY_PREFIX + activityId);return null;}}

2.如果Redis中不存在,查表

//活动表ActivityDO activityDO = activityMapper.selectById(activityId);//活动奖品表List<ActivityPrizeDO> activityPrizeDOList = activityPrizeMapper.selectAByActivityId(activityId);//活动人员表List<ActivityUserDO> activityUserDOS = activityUserMapper.selectByActivityId(activityId);//奖品表//先获取奖品ids  用于获取数据库中的奖品表基础属性List<Long> prizeIds = activityPrizeDOList.stream().distinct().map(ActivityPrizeDO::getPrizeId).collect(Collectors.toList());//从数据库中获取List<PrizeDO> prizeDOList = prizeMapper.selectPrizeListByIds(prizeIds);

与上一个模块有点像,先获取奖品id再查询数据库

3.整合活动完整信息 

detailDTO = convertToActivityDetailDTO(activityDO,activityPrizeDOList, activityUserDOS, prizeDOList);

4.缓存到Redis中 

cacheActivityDetailDTO(detailDTO);

4.抽奖模块

涉及到rabbitMQ的使用,这里不做介绍,以后会有更详细的记载

1.从前端获取中奖信息

直接返回前端信息,然后通过异步处理 中奖信息 将其放入消息队列中加快前端返回给用户的速度,提高用户体验。

    /*** 异步抽奖,接⼝只做奖品数校验即可返回。** @param param* @return*/@RequestMapping("/draw-prize")public CommonResult<Boolean> drawPrize(@Validated @RequestBody DrawPrizeParam param) {logger.info("DrawPrizeController drawPrize  param:{}", JacksonUtil.writeValueAsString(param));drawPrizeService.drawPrize(param);return CommonResult.success(true);}

2. 成功接收到队列中的消息,放入消息队列

    public void drawPrize(DrawPrizeParam param) {logger.info("DrawPrizeServiceImpl  drawPrize  param:{}",param);// 发送消息到消息队列  交换机,路由键(绑定的key),消息体Map<String, String> map = new HashMap<>();map.put("messageId", String.valueOf(UUID.randomUUID()));map.put("messageDate", JacksonUtil.writeValueAsString(param));rabbitTemplate.convertAndSend(EXCHANGE_NAME,ROUTING, map);logger.info("mq消息发送成功, map:{}", JacksonUtil.writeValueAsString(map));}

3.处理消息队列任务 

logger.info("Mq成功接收到消息  message:{}", JacksonUtil.writeValueAsString(message));String paramString = message.get("messageDate");DrawPrizeParam drawPrizeParam = JacksonUtil.readValue(paramString, DrawPrizeParam.class);

使用:

@RabbitListener(queues = "DirectQueue"):rabbitMQ绑定

@RabbitHandler:监听器

3.1校验抽奖信息是否有效

1.校验活动与奖品是否存在
2.活动是否有效  根据活动状态判断
3.奖品是否有效
4.获奖人数与奖品数量是否相等

    @Overridepublic void checkDrawPrizeParam(DrawPrizeParam param) {//1.校验活动与奖品是否存在// 根据活动ID查询活动信息ActivityDO activityDO = activityMapper.selectById(param.getActivityId());// 根据活动ID和奖品ID查询活动奖品信息ActivityPrizeDO activityPrizeDO = activityPrizeMapper.selectByAPId(param.getActivityId(), param.getPrizeId());if(activityDO == null || activityPrizeDO == null){throw new ServiceException(ServiceErrorCodeConstant.ACTIVITY_OR_PRIZE_EMPTY);}//2.活动是否有效  根据活动状态判断if(ActivityStatusEnum.COMPLETED.name().equalsIgnoreCase(activityDO.getStatus())){throw new ServiceException(ServiceErrorCodeConstant.ACTIVITY_NOT_RUNNING);}//3.奖品是否有效if(ActivityPrizeStatusEnum.COMPLETED.name().equalsIgnoreCase(activityPrizeDO.getStatus())){throw new ServiceException(ServiceErrorCodeConstant.ACTIVITY_PRIZE_NOT_RUNNING);}//4.获奖人数与奖品数量是否相等if(param.getWinnerList().size() != activityPrizeDO.getPrizeAmount()){throw new ServiceException(ServiceErrorCodeConstant.WINNER_PRIZE_AMOUNT_ERROR);}}

http://www.xdnf.cn/news/606547.html

相关文章:

  • FPGA 42 ,时序约束深度解析与实战应用指南( FPGA 时序约束 )
  • 分享|16个含源码和数据集的计算机视觉实战项目
  • VMware虚拟机突然无法ssh连接
  • Spring Boot WebFlux流式返回全攻略:从基础到企业级实践
  • PHP7内核剖析 学习笔记 第八章 命名空间
  • Python打卡DAY34
  • 亚马逊搜索代理: 终极指南
  • 线性回归中涉及的数学基础
  • 嵌入式学习笔记 - freeRTOS链表中pxIndex->pxPrevious 与pxIndex->pxPrevious->的区别
  • DB-GPT扩展自定义Agent配置说明
  • 微信小程序调用蓝牙API “wx.writeBLECharacteristicValue()“ 报 errCode: 10008 的解决方案
  • GMP模型入门
  • Lyra学习笔记1地图角色加载流程
  • 树莓派WiringPi库
  • 大模型「瘦身」指南:从LLaMA到MobileBERT的轻量化部署实战
  • php 根据另一个数组中 create_time 的时间顺序,对原始数组进行排序。
  • Neo4j入门第一期(Cypher入门)
  • RabbitMQ ⑥-集群 || Raft || 仲裁队列
  • CentOS 7.6 升级 Openssl 及 Openssh 方法文档
  • Unity EventCenter 消息中心的设计与实现
  • EasyExcel使用
  • GD32 IIC(I2C)通信(使用示例为SD2068)
  • 2.4g芯片引脚功能
  • 56 在standby待机打通uart调试的方法
  • 5.23本日总结
  • SDL2常用函数SDL事件处理:SDL_Event|SDL_PollEvent
  • Vue+css实现扫描动画效果(使用@keyframes scan)
  • RequestBody注解中Map
  • 为什么信号经过线束会有衰减?
  • AG32VH 系列应用指南