拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

【Java项目-企悦抽】16-抽奖模块01-获取活动完整信息接口实现

【Java项目-企悦抽】16-抽奖模块01-获取活动完整信息接口实现

✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
🎯你正在阅读「Java项目-企悦抽」系列文章🎯
✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨

🔥弹简特 个人主页

❄️个人专栏直通车:

  • 🔌接口测试从入门到跑路
  • ☕一个后端的 JavaEE 续命指南
  • 🛜网络原理续命手册
  • ☕Java项目-轻聊

✨靠热爱去书写自己,靠勇敢去书写生活!
✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨


🌟 博主简介:



文章目录

  • 一、前言
  • 二、抽奖需求分析
    • 1、原型图
    • 2、需求
  • 三、获取活动详细信息接口实现
    • 1、业务逻辑
    • 2、约定前后端接口
      • 2.1 时序图
      • 2.2 前后端交互接口
    • 3、代码实现
      • 3.1 控制层
      • 3.2 服务层和持久层
    • 4、Postman测试
  • 补充知识:幂等性
    • 通俗例子
    • 专业定义
    • 常见非幂等 & 幂等对比
    • 为什么后端/接口一定要做幂等?
    • 常用实现方式

一、前言

铁汁们,咱们企悦抽项目已经开始进入尾声了,从本期开始,我们将实现我们本项目的最后一个模块:抽奖模块,也是核心模块 同时 是一个难点,我们会分三期将我们的抽奖模块进行实现,分别是:1、提供完整活动信息、2、保存中奖信息、扭转状态、邮箱通知用户、3、返回中奖信息
那么话不多说我们开始吧

二、抽奖需求分析

1、原型图

抽奖流程如下:

2、需求

根据我们的原型图,可以知道,前端的功能是什么?
我们前端的功能就是:

  • 前端控制我们的抽奖流程
  • 确定中奖人:比如有10个人,3个奖品,那么在开始抽奖,点击“点我确定”的时候,前端需要在10个人里面每一次挑选一个人去中奖。

对于后端来说有三个任务(三个核心接口):

  • 提供完整活动信息:既然要在抽奖页查询抽奖那么要将活动、活动关联的奖品、活动关联的人员 等等活动的完整信息展示给前端才行。本篇博客实现
  • 保存中奖信息、扭转状态、邮箱通知用户:前端点击“点我确定”得出中奖人员之后,需要将中奖人员、中什么奖、是几等奖等发给后端存起来,用于到时候前端点击“已抽完、下一步”查看中奖名单的时候显示对应的信息。(此时注意需要扭转活动、奖品、人员状态,因为他们是有状态的,比如活动是否结束?奖品是否抽完?人员是否中过奖?中过奖就不能参与)(与此同时需要完成通知行为:将中奖信息通过邮箱返回给用户)下一期博客实现
  • 返回中奖信息:在第2步中,存储完中奖信息之后,我们提供一个返回中奖信息的接口。下下一期博客实现

那么接下来我们就先实现第一个任务:

三、获取活动详细信息接口实现

根据需求 我们需要将活动的详细信息返回,用于到时候的前端抽奖显示

1、业务逻辑


如图所示:咱们正是有了这样的一个业务逻辑,所以对于存储到Redis中是可能出现失败的,此时你查询的时候就得先查询数据库了

OK,那么我们知道了业务逻辑之后,接下里就是实现代码了,代码的实现也是很简单的,你只要搞清楚每一层需要做什么即可,我们会直接上手,遇到比较繁琐的处理我们会画图解释:

2、约定前后端接口

2.1 时序图

2.2 前后端交互接口

请求/activity-detail/find?activityId=24GET
响应

{"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":""}

3、代码实现

3.1 控制层

做什么?

  1. 有一个接口和前端打交道:需要前端传递一个活动ID
    • 首先有参数,就打印日志分析该参数
    • 其次就是调用服务层
    • 将服务层返回的DTO变为前端需要的Result实体
  2. 写一个方法就是类型转换:将服务层返回一点DTO变为前端需要的Result实体

首先你要知道,我们查询的数据是活动的完整数据,那么活动的完整数据我们在之前创建活动的时候,已经整合在了一个DTO中,回顾一下,如下所示:

@DatapublicclassActivityDetailDTO{// 活动信息/** * 活动id */privateLongactivityId;/** * 活动名称 */privateStringactivityName;/** * 活动描述 */privateStringdesc;/** * 活动状态 */privateActivityStatusEnumstatus;publicBooleanvalid(){returnstatus.equals(ActivityStatusEnum.RUNNING);}// 奖品信息(列表)privateList<PrizeDTO>prizeDTOList;// 人员信息(列表)privateList<UserDTO>userDTOList;@DatapublicstaticclassPrizeDTO{/** * 奖品Id */privateLongprizeId;/** * 奖品名 */privateStringname;/** * 图片索引 */privateStringimageUrl;/** * 价格 */privateBigDecimalprice;/** * 描述 */privateStringdescription;/** * 奖品等级 */privateActivityPrizeTiersEnumtiers;/** * 奖品数量 */privateLongprizeAmount;/** * 奖品状态 */privateActivityPrizeStatusEnumstatus;publicBooleanvalid(){returnstatus.equals(ActivityPrizeStatusEnum.INIT);}}@DatapublicstaticclassUserDTO{/** * 用户id */privateLonguserId;/** * 姓名 */privateStringuserName;/** * 状态 */privateActivityUserStatusEnumstatus;publicBooleanvalid(){returnstatus.equals(ActivityUserStatusEnum.INIT);}}}

分析一下他的结构:

那么现在我们控制层需要调用服务层得到这个DTO,那么最终将你这个DTO变为前端需要的Result实体类,此时我们就得定义这个Result实体,如下所示:

@DatapublicclassActivityDetailResultimplementsSerializable{/** * 活动id */privateLongactivityId;/** * 活动名称 */privateStringactivityName;/** * 活动描述 */privateStringdescription;/** * 活动是否有效 */privateBooleanvalid;/** * 奖品信息(列表) */privateList<Prize>prizes;/** * 人员信息(列表) */privateList<User>users;@DatapublicstaticclassPrize{/** * 奖品Id */privateLongprizeId;/** * 奖品名 */privateStringname;/** * 图片索引 */privateStringimageUrl;/** * 价格 */privateBigDecimalprice;/** * 描述 */privateStringdescription;/** * 奖品等奖 */privateStringprizeTierName;/** * 奖品数量 */privateLongprizeAmount;/** * 奖品是否有效 */privateBooleanvalid;}@DatapublicstaticclassUser{/** * 用户id */privateLonguserId;/** * 姓名 */privateStringuserName;/** * 人员是否被抽取 */privateBooleanvalid;}}


需要注意的一点:就是DTO中和我们的Result中的对于状态的处理:


OK,那么接下来就是解释一下控制层的代码了(代码很简单,其实核心重要的是业务要理清楚,如果不懂业务,那么代码是不知道怎么写的哦~)



其实我们项目做到现在,每一次类型转换,代码都会稍微多一点,但是整体真的不难,核心思路都是一样,一定要多练,光看是不行滴~!


3.2 服务层和持久层

咱们服务层干什么呢?

  1. 去Redis中查询数据,有我们就返回给前端
  2. 没有就去mysql中查询数据:活动基本表、活动关联的奖品表、活动关联的人员表、奖品表,所以我们后续写四条sql
  3. 将得到的mysql中数据整合,这个整合我们之前已经实现了
  4. 将整合的数据存到Redis中,然后再返回给前端

代码

/** * 获取活动详细信息 * @param activityId 活动ID * @return */@OverridepublicActivityDetailDTOgetActivityDetail(LongactivityId){// 校验活动ID是否为空if(null==activityId){logger.warn("查询活动详细信息失败,activityId为空!");returnnull;}// 1. 先从Redis缓存中查询活动详情:这个方法我们之前在创建活动信息的时候就实现过了ActivityDetailDTOdetailDTO=getActivityFromCache(activityId);if(null!=detailDTO){logger.info("查询活动详细信息成功!detailDTO={}",JackSonUtil.writeValueAsString(detailDTO));returndetailDTO;}// 2. 缓存不存在,查询数据库// 2.1 查询活动主表信息ActivityDOaDO=activityMapper.selectById(activityId);// 2.2 查询活动关联奖品表信息List<ActivityPrizeDO>apDOList=activityPrizeMapper.selectByActivityId(activityId);// 2.3 查询活动参与人员表信息List<ActivityUserDO>auDOList=activityUserMapper.selectByActivityId(activityId);// 2.4 遍历活动奖品关联表,收集所有奖品IDList<Long>prizeIds=newArrayList<>();if(apDOList!=null&&!apDOList.isEmpty()){for(ActivityPrizeDOactivityPrizeDO:apDOList){LongprizeId=activityPrizeDO.getPrizeId();prizeIds.add(prizeId);}}// 2.5 根据奖品ID集合,批量查询奖品详情表List<PrizeDO>pDOList=prizeMapper.batchSelectByIds(prizeIds);// 3. 数据组装:将多张表数据整合为活动详情DTOdetailDTO=convertToActivityDetailDTO(aDO,auDOList,pDOList,apDOList);// 4. 存入Redis缓存,方便下次查询cacheActivity(detailDTO);// 5. 返回最终的活动详情信息returndetailDTO;}

解释:



4、Postman测试


接下来我们实现第二个接口:抽奖相关的,那么该接口我们将在下一篇博客中完成~

咱们下一篇博客见:抽奖接口相关的


补充知识:幂等性

一句话:同一个操作,执行一次 和 重复执行很多次,结果完全一样,不会出问题。

通俗例子

  1. 开灯
    按一次开灯 → 灯亮;
    再按十次 → 还是亮。
    开灯这个操作就是幂等的。

  2. 付款转账(核心业务场景)
    接口调用1次:扣100元;
    网络超时重试,自动再调用3次:还是只扣100,不会扣300。
    这就是接口要做幂等的原因。

专业定义

一个接口/操作,执行任意多次,对系统产生的最终效果和执行一次完全相同。

常见非幂等 & 幂等对比

操作是否幂等说明
新增订单否调一次生成1单,调3次生成3单
查询数据是查1次、查10次,数据不变
修改状态为已完成是改一次完成,再改多少次还是完成
删除一条数据是删一次没了,再删还是没变化

为什么后端/接口一定要做幂等?

解决网络超时、重复提交、前端重复点击、消息重复消费带来的重复下单、重复扣款、重复创建数据问题。

常用实现方式

  • 唯一订单号/业务主键去重
  • 数据库唯一索引
  • 分布式锁
  • 状态机判断(已处理就不再处理)
  • Token防重提交

OK,到这里咱们抽奖模块的第一个接口——获取活动详细信息就实现完毕啦!核心思路就是:先查 Redis,没有再查 MySQL,最后整合活动、奖品、人员数据并回写缓存,为后续抽奖做好数据准备。

下一篇,我们就正式进入抽奖接口的实现,这里面会涉及状态扭转、中奖信息保存、邮箱通知等硬核内容,咱们下一篇见!

老铁们,如果本篇内容对你有帮助,不妨点赞、收藏,也欢迎在评论区留言交流,你的每一份支持都是我持续创作的最大动力~👋

返回列表