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

✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨

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

✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨

🔥 弹简特 个人主页

❄️ 个人专栏直通车:

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

✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨


🌟 博主简介:


文章目录


一、前言

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

那么话不多说我们开始吧

二、抽奖需求分析

1、原型图

抽奖流程如下:

2、需求

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

我们前端的功能就是:

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

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

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

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

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

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

1、业务逻辑

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

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

2、约定前后端接口

2.1 时序图

2.2 前后端交互接口

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

响应

json 复制代码
{
    "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中,回顾一下,如下所示:

java 复制代码
@Data
public class ActivityDetailDTO {
    // 活动信息

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

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

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

    /**
     * 活动状态
     */
    private ActivityStatusEnum status;

    public Boolean valid() {
        return status.equals(ActivityStatusEnum.RUNNING);
    }

    // 奖品信息(列表)

    private List<PrizeDTO> prizeDTOList;

    // 人员信息(列表)

    private List<UserDTO> userDTOList;


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

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

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

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

        /**
         * 奖品等级
         */
        private ActivityPrizeTiersEnum tiers;

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

        /**
         * 奖品状态
         */
        private ActivityPrizeStatusEnum status;

        public Boolean valid() {
            return status.equals(ActivityPrizeStatusEnum.INIT);
        }
    }

    @Data
    public static class UserDTO {
        /**
         * 用户id
         */
        private Long userId;
        /**
         * 姓名
         */
        private String userName;
        /**
         * 状态
         */
        private ActivityUserStatusEnum status;

        public Boolean valid() {
            return status.equals(ActivityUserStatusEnum.INIT);
        }
    }

}

分析一下他的结构:

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

java 复制代码
@Data
public class ActivityDetailResult 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;
    }


}

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


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

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


3.2 服务层和持久层

咱们服务层干什么呢?

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

代码

java 复制代码
    /**
     * 获取活动详细信息
     * @param activityId 活动ID
     * @return
     */
    @Override
    public ActivityDetailDTO getActivityDetail(Long activityId) {
        // 校验活动ID是否为空
        if (null == activityId) {
            logger.warn("查询活动详细信息失败,activityId为空!");
            return null;
        }

        // 1. 先从Redis缓存中查询活动详情:这个方法我们之前在创建活动信息的时候就实现过了
        ActivityDetailDTO detailDTO = getActivityFromCache(activityId);
        if (null != detailDTO) {
            logger.info("查询活动详细信息成功!detailDTO={}", JackSonUtil.writeValueAsString(detailDTO));
            return detailDTO;
        }

        // 2. 缓存不存在,查询数据库
        // 2.1 查询活动主表信息
        ActivityDO aDO = activityMapper.selectById(activityId);

        // 2.2 查询活动关联奖品表信息
        List<ActivityPrizeDO> apDOList = activityPrizeMapper.selectByActivityId(activityId);

        // 2.3 查询活动参与人员表信息
        List<ActivityUserDO> auDOList = activityUserMapper.selectByActivityId(activityId);

        // 2.4 遍历活动奖品关联表,收集所有奖品ID
        List<Long> prizeIds = new ArrayList<>();
        if (apDOList != null && !apDOList.isEmpty()) {
            for (ActivityPrizeDO activityPrizeDO : apDOList) {
                Long prizeId = activityPrizeDO.getPrizeId();
                prizeIds.add(prizeId);
            }
        }

        // 2.5 根据奖品ID集合,批量查询奖品详情表
        List<PrizeDO> pDOList = prizeMapper.batchSelectByIds(prizeIds);

        // 3. 数据组装:将多张表数据整合为活动详情DTO
        detailDTO = convertToActivityDetailDTO(aDO, auDOList, pDOList, apDOList);

        // 4. 存入Redis缓存,方便下次查询
        cacheActivity(detailDTO);

        // 5. 返回最终的活动详情信息
        return detailDTO;
    }

解释:


4、Postman测试


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

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


补充知识:幂等性

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

通俗例子

  1. 开灯

    按一次开灯 → 灯亮;

    再按十次 → 还是亮。

    开灯这个操作就是幂等的。

  2. 付款转账(核心业务场景)

    接口调用1次 :扣100元;

    网络超时重试,自动再调用3次 :还是只扣100,不会扣300 。

    这就是接口要做幂等的原因。

专业定义

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

常见非幂等 & 幂等对比

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

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

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

常用实现方式

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

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

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

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

相关推荐
言乐61 小时前
Python语音检索
开发语言·python·django·virtualenv·pygame
BD_Marathon2 小时前
多轮对话聊天机器人
开发语言·机器人·c#
泡茶喝茶写代码2 小时前
A股量化数据工程:从 REST 接口到策略信号(第 1 篇):指数列表与实时行情接入
java·python·股票数据api·股票数据·股票数据api接口·股票api数据接口·股票量化数据接口
Sarvartha2 小时前
接口基础知识
java·开发语言
沐晓时光2 小时前
C语言入门,深入理解指针(1)
c语言·开发语言
AC赳赳老秦2 小时前
OpenClaw 数据引用规范自动生成:为公开数据构建可信来源标注与标准引用体系
大数据·开发语言·汇编·数据库·人工智能·deepseek·openclaw
不才不才不不才2 小时前
Spring 源码系列(29): 20 道高频面试题源码级解析合集
java·后端·spring
索隆zoro2 小时前
Army 的可插拔架构:army-jdbc 与方言模块
java·后端
无忧.芙桃2 小时前
C++语言原理与实践(十):vector类的底层实现
开发语言·c++·算法