【Java项目-企悦抽】15-活动管理模块02-活动创建测试与活动列表实现

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

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

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

🔥 弹简特 个人主页

❄️ 个人专栏直通车:

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

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


🌟 博主简介:


文章目录


一、 前言

老铁们,上期我们完成活动创建后端,本期用Postman测试接口、解决Redis问题,实现前端创建活动与活动列表展示进一步结束我们的活动管理模块。

二、Postman测试创建活动的接口

1、首先先登录获取token

2、在请求头中带上token

3、异常测试数据:奖品数目<人员数据

json 复制代码
{
    "activityName": "2026年员工周年庆抽奖",
    "description": "公司五周年庆回馈员工专属抽奖活动",
    "activityPrizeList": [
        {
            "prizeId": 18,
            "prizeAmount": 1,
            "prizeTiers": "FIRST_PRIZE"
        },
        {
            "prizeId": 19,
            "prizeAmount": 3,
            "prizeTiers": "THIRD_PRIZE"
        }
    ],
    "activityUserList": [
        {
            "userId": 39,
            "userName": "李逍遥"
        },
        {
            "userId": 40,
            "userName": "赵灵儿"
        }
    ]
}

4、发送请求:报错

5、正确测试数据

json 复制代码
{
    "activityName": "2026年员工周年庆抽奖",
    "description": "公司五周年庆回馈员工专属抽奖活动",
    "activityPrizeList": [
        {
            "prizeId": 18,
            "prizeAmount": 1,
            "prizeTiers": "FIRST_PRIZE"
        },
        {
            "prizeId": 19,
            "prizeAmount": 1,
            "prizeTiers": "THIRD_PRIZE"
        }
    ],
    "activityUserList": [
        {
            "userId": 39,
            "userName": "李逍遥"
        },
        {
            "userId": 40,
            "userName": "赵灵儿"
        },
        {
            "userId": 41,
            "userName": "李四"
        }
    ]
}

6、发送请求成功

7、结果分析

7.1 存储mysql成功

7.2 存储Redis失败:Redis忘记连接了

8、解决Redis连接问题,再重试

将mysql中数据清除重来一次,刚刚由于我的服务器出了问题,Redis忘记连接了。

这次就成功了,数据也成功存到我们的Redis中


三、活动创建的前端实现

1、原型图

2、获取人员列表和获取奖品列表

从原型图中可以看出来,获取人员列表和获取奖品列表,我们得先从数据库中去查询数据。

那么值得开心的是这两个功能我们之前在人员管理和奖品管理中已经实现过了,我们可以复用之前的接口,因为返回的数据中包含有对应所需数据。

2.1 获取人员列表

回顾之前的获取人员列表接口:

请求

/base-user/find-list

参数:identity 用户身份信息

  • 传递ADMIN:返回管理员信息;
  • 传递的是NORMAL:返回的是普通人员的信息;
  • 如果不传递,那么就会将二者都返回

请求方式: GET

响应

json 复制代码
{
  "code": 200,
  "data": [
    {
      "userId": 15,
      "userName": "郭靖",
      "identity": "NORMAL"
    },
    {
      "userId": 14,
      "userName": "王五",
      "identity": "ADMIN"
    }
  ],
  "msg": ""
}

而分析我们前端需要的原型图,只要一个userName这个userName恰恰在我们的返回数据中,所以直接共用就行了

2.2 获取奖品列表

首先看原型图

由图可知,我们查询的时候只需要:奖品id和奖品名字就行了

但是我们之前获取奖品列表的时候是分页实现的如下接口

请求 /prize/find-list?currentPage=1&pageSize=10

请求方式:GET

响应 JSON 完整内容

json 复制代码
{
  "code": 200,
  "data": {
    "total": 3,
    "records": [
      {
        "prizeId": 17,
        "prizeName": "吹风机",
        "description": "吹风机",
        "price": 100,
        "imageUrl": "d11fa79c-9cfb-46b9-8fb6-3226ba1ff6d6.jpg"
      },
      {
        "prizeId": 13,
        "prizeName": "华为手机",
        "description": "华为手机",
        "price": 5000,
        "imageUrl": "5a85034b-91b7-48fe-953d-67aef2bdcc2d.jpg"
      }
    ]
  }
}

而我们本次中不选择使用分页,因此我们就自己再定义一个接口

2.2.1 接口文档

请求 /prize/find-listAll

请求方式:GET

响应 JSON 完整内容

json 复制代码
{
  "code": 200,
  "data": [
    {
      "prizeId": 17,
      "prizeName": "吹风机"
    },
    {
      "prizeId": 13,
      "prizeName": "华为手机"
    }
  ]
}
2.2.2 Result实体类
java 复制代码
@Data
public class FindPrizeListAllResult implements Serializable {
    /**
     * 奖品id
     */
    private Long prizeId;

    /**
     * 奖品名称
     */
    private String prizeName;
}
2.2.3 控制层的接口实现
java 复制代码
    /**
     * 查询奖品列表(查询总的数据)
     * @return 总的奖品列表
     */
    @RequestMapping("/prize/find-listAll")
    public CommonResult<List<FindPrizeListAllResult>> findPrizeListAll() {
        //没参数就不打印日志了
        List<PrizeListDTO> prizeListDTO = prizeService.findPrizeListAll();


        return CommonResult.success(convertToFindPrizeListAllResult(prizeListDTO));

    }


    private List<FindPrizeListAllResult> convertToFindPrizeListAllResult(List<PrizeListDTO> prizeListDTO) {
        // 如果参数为空,抛出异常
        if (null == prizeListDTO) {
            throw new ControllerException(ControllerErrorCodeConstants.FIND_PRIZE_LIST_ERROR);
        }
        // 创建一个结果对象
        List<FindPrizeListAllResult> findPrizeListAllResults = new ArrayList<>();
        for (PrizeListDTO plDto : prizeListDTO) {
            FindPrizeListAllResult result = new FindPrizeListAllResult();
            result.setPrizeId(plDto.getPrizeId());
            result.setPrizeName(plDto.getPrizeName());
            findPrizeListAllResults.add(result);
        }
        
        return findPrizeListAllResults;
    }
2.2.4 DTO实体类(服务层给控制层的)
java 复制代码
@Data
public class PrizeListDTO {
    /**
     * 奖品id
     */
    private Long prizeId;

    /**
     * 奖品名称
     */
    private String prizeName;
}
2.2.5 服务层和持久层的实现
java 复制代码
    /**
     * 查询所有奖品列表
     * @return
     */
    @Override
    public List<PrizeListDTO> findPrizeListAll() {
        return prizeMapper.selectPrizeListAll();
    }

	//持久层
    @Select("select id as prizeId, name as prizeName from prize")
    List<PrizeListDTO> selectPrizeListAll();
2.2.6 测试
2.2.7 前端交互实现
js 复制代码
 // 获取奖品列表的函数
    function fetchPrizes() {
        $.ajax({
            url: '/prize/find-listAll',
            type: 'GET',
            dataType: 'json',
            headers: {
                // jwt
                "user_token": userToken
            },
            success: function(result) {
                var prizes = result.data;
                var prizesContainer = $('#prizesContainer');
                prizesContainer.empty(); // 清空当前奖品列表
                prizes.forEach(function(prize) {
                    prizesContainer.append(
                        $('<div class="prize-item">').append(`
                            <input type="checkbox" id="prize-${prize.prizeId}" name="prize-${prize.prizeId}" value="${prize.prizeId}">
                            <label for="prize-${prize.prizeId}">${prize.prizeName}</label>
                            <input class="form-control" type="number" name="quantity-${prize.prizeId}" min="1" value="1">
                            <select class="form-control" name="level-${prize.prizeId}">
                                <option value="FIRST_PRIZE" selected>一等奖</option>
                                <option value="SECOND_PRIZE">二等奖</option>
                                <option value="THIRD_PRIZE">三等奖</option>
                            </select>
                        `)
                    );
                });
            },
            error: function(err){
                console.log(err);
                if(err!=null && err.status==401){
                    alert("用户未登录, 即将跳转到登录页!");
                    // 跳转登录页
                    window.location.href = "/blogin.html";
                    window.parent.location.href = "/blogin.html";//让父页面一起跳转
                }
            }
        });
    }

3、实现创建活动接口的调用

4、测试

两个奖品,一个人员肯定报错,因为人员的数必须大于等于奖品数。

java 复制代码
        /**
         * 等价于用户数量大于等于奖品数量
         */
        if (userAmount < prizeAmount) {
            throw new ServiceException(ServiceErrorCodeConstants.USER_PRIZE_AMOUNT_ERROR);
        }

修改奖品只有1份之后就创建成功并来到活动列表页


三、展示活动列表

1、原型图

2、时序图

3、前后端交互接口

请求 /activity/find-list?currentPage=1&pageSize=10

请求方式 :GET

响应

json 复制代码
{
  "code": 200,
  "data": {
    "total": 10,
    "records": [
      {
        "activityId": 23,
        "activityName": "抽奖测试",
        "description": "年会抽奖活动",
        "valid": true //表示活动是否有效
      },
      {
        "activityId": 22,
        "activityName": "抽奖测试",
        "description": "年会抽奖活动",
        "valid": true
      },
      {
        "activityId": 21,
        "activityName": "节日抽奖",
        "description": "xx班级年会抽奖活动",
        "valid": true
      }
    ]
  },
  "msg": ""
}

4、控制层实现01

4.1 前端提交的参数Param实体类

代码

java 复制代码
@Data
public class PageParam implements Serializable {
    /**
     * 当前页
     */
    private Integer currentPage = 1;//写一个默认值,如果没有这个值就默认第一页
    /**
     * 每页显示的记录数
     */
    private Integer pageSize = 10;//写一个默认值,如果没有这个值就默认每页显示10条

    /**
     * 获取偏移量
     *
     * @return
     */
    public Integer offset() {
        return (currentPage-1) * pageSize;
    }
}

4.2 控制层返回数据给前端的Result实体类

代码:

java 复制代码
@Data
public class FindActivityListResult implements Serializable {
    //总数:用于前端计算页数
    /**
     * 总量
     */
    private Integer total;


    //每一页要显示的记录数
    /**
     * 当前列表
     */
    private List<ActivityInfo> records;

    //内部类每一个记录存的值ActivityInfo
    @Data
    public static class ActivityInfo implements Serializable {

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

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

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

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

    }

}

5、服务层实现

5.1 DTO 实体

那首先这个实体类是从数据库中来的数据,所以你的属性得和数据库中DO实体保持一致,如下是我们的OD实体:

那么再给一个业务逻辑图看一下

代码

java 复制代码
@Data
public class ActivityDTO {
    /**
     * 活动id
     */
    private Long activityId;

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

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

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

    /**
     * 活动是否有效
     *
     * @return
     */
    public Boolean valid() {
        return status.equals(ActivityStatusEnum.RUNNING);
    }
}

5.1 服务层和持久层实现

服务层的业务逻辑怎么写呢?

思路:他核心就是封装分页数据给控制层

  1. 获取总数
  2. 从数据库中得到要显示的记录(每一个元素是DO)
  3. 将DO变为DTO
  4. 封装数据给控制层

那么接下来就是实现数据类型的转换换了:

思路:

代码

java 复制代码
    /**
     * 获取活动信息列表[分页]
     * @param pageParam 分页数据
     * @return
     */
    @Override
    public PageListDTO<ActivityDTO> findActivityList(PageParam pageParam) {
        //此处你写什么? -->封装分页数据给控制层
        //1.得到总数
        int total = activityMapper.count();

        //2.获取要显示的记录【得到的是持久层的】
        List<ActivityDO> activityDOList = activityMapper.selectActivityList(
                pageParam.offset(),
                pageParam.getPageSize()
        );

        //3.将持久层的DO变为服务层的DTO实体
        //首先你是一致DO变为DTO,所以得有DTO集合
        ArrayList<ActivityDTO> activityDTOList = new ArrayList<>();
        //然后就直接遍历我们的DO 加每一个元素取出来
        for (ActivityDO activityDO : activityDOList) {
            ActivityDTO activityDTO = new ActivityDTO();
            activityDTO.setActivityId(activityDO.getId());
            activityDTO.setActivityName(activityDO.getActivityName());
            activityDTO.setDescription(activityDO.getDescription());
            activityDTO.setStatus(ActivityStatusEnum.forName(activityDO.getStatus()));
            activityDTOList.add(activityDTO);

            //将值塞到集合中
            activityDTOList.add(activityDTO);
        }

        //4.封装分页数据给控制层

        return  new PageListDTO<>(total, activityDTOList);
    }

6、控制层实现02

主要就是基本代码+DTO实体变为Result实体

代码:

java 复制代码
    /**
     * 获取活动列表 (分页显示)
     * @param pageParam 分页参数(页号 和 当前页要显示的记录数)
     * @return
     */
     @RequestMapping("/activity/find-list")
    public CommonResult<FindActivityListResult> findActivityList(PageParam pageParam) {
        //1. 打印日志观察参数
        logger.info("ActivityController findActivityList, PageParam:{}",
                JackSonUtil.writeValueAsString(pageParam));

        //2. 调用服务层获取到活动列表 每一个元素是DTO
        //3. 将DTO转换为Result 并统一格式返回
        return CommonResult.success(
                convertToFindActivityListResult(
                        activityService.findActivityList(pageParam)));
    }

    private FindActivityListResult convertToFindActivityListResult(PageListDTO<ActivityDTO> activityList) {
        //判空
        if (null == activityList) {
            throw new ControllerException(ControllerErrorCodeConstants.FIND_ACTIVITY_LIST_ERROR);
        }

        //首先先有Result实体
        FindActivityListResult findActivityListResult = new FindActivityListResult();

        //先转换总数
        findActivityListResult.setTotal(activityList.getTotal());

        //然后转换集合:集合多个元素 得遍历 然后一个一个的转换
        List<FindActivityListResult.ActivityInfo> records = new ArrayList<>();

        for (ActivityDTO activityDTO : activityList.getRecords()) {
            FindActivityListResult.ActivityInfo activityInfo = new FindActivityListResult.ActivityInfo();

            activityInfo.setActivityId(activityDTO.getActivityId());
            activityInfo.setActivityName(activityDTO.getActivityName());
            activityInfo.setDescription(activityDTO.getDescription());
            activityInfo.setValid(activityDTO.valid());

            records.add(activityInfo);
        }
        findActivityListResult.setRecords(records);
        return findActivityListResult;
    }

7、Postman测试

这个其实就是一个简单的查询功能,基本都不会出错,只是类型转换麻烦一丢丢

但是你会发现,我们的数据每一条数据都返回了两个


五、活动列表前端实现

1、代码实现

js 复制代码
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="./js/toastr.min.js"></script>
<script>
    var currentPage = 1;
    var pageSize = 10;
    var totalPages;
    var userToken = localStorage.getItem("user_token");

    // 发送AJAX请求的函数
    function fetchActivities(page) {
      // 如果页码小于1,则重置为1
      if (page < 1) {
        page = 1;
      }
      // 更新当前页码
      currentPage = page;
      // 构建要发送的数据对象
      var dataToSend = {
        currentPage: currentPage,
        pageSize: pageSize
      };
      // 发送AJAX请求
      $.ajax({
          url: '/activity/find-list',
          type: 'GET',
          data: dataToSend, // 将分页参数作为请求数据发送
          dataType: 'json',
          headers: {
            // jwt
            "user_token": userToken
          },
          success: function(result) {
            if (result.code != 200) {
                alert("查询活动列表失败!" + result.msg);
            } else {
                var activities = result.data.records; // 假设返回的数据中活动列表字段为 'records'
                var activitiesHtml = '';
                var listContainer = document.getElementById('activities');
                // 在添加新内容前,先清空listContainer
                listContainer.innerHTML = '';
                activities.forEach(function(activity) {
                  var url = 'draw.html?activityName='+ encodeURIComponent(activity.activityName)
                          +'&activityId=' + encodeURIComponent(activity.activityId)
                          +'&valid=' + encodeURIComponent(activity.valid);
                  var linkTextActive = `<a href="${url}" target="_blank">活动进行中,去抽奖</a>`;
                  var linkTextInactive = `<a href="${url}" target="_blank">活动已完成,查看中奖名单</a>`;
                  var validClass = activity.valid ? 'active' : 'inactive';
                  var link = activity.valid ? linkTextActive : linkTextInactive;
                  activitiesHtml += `
                      <div class="activity-item">
                        <div class="activity-info">
                          <h4>${activity.activityName}</h4>
                          <p>${activity.description}</p>
                        </div>
                        <div class="${validClass}">
                          <p>${link}</p>
                        </div>
                      </div>
                    `;
                });
                $('#activities').html(activitiesHtml);
                // 更新分页控件的总页数
                totalPages = Math.ceil(result.data.total / pageSize);
                // 更新输入框的值
                $('#pageInput').val(currentPage);
              } // else end
          },
        error:function(err){
          console.log(err);
          if(err!=null && err.status==401){
            alert("用户未登录, 即将跳转到登录页!");
            // 跳转登录页
            window.location.href = "/blogin.html";
            window.parent.location.href = "/blogin.html";//让父页面一起跳转
          }
        }
      });
    }


    function previousPage() {
      if (currentPage > 1) {
        fetchActivities(currentPage - 1);
      } else {
        alert("已经是第一页");
      }
    }

    function nextPage() {
      if (currentPage < totalPages) {
        fetchActivities(currentPage + 1);
      } else {
        alert("已经是最后一页");
      }
    }

    $(document).ready(function() {
      fetchActivities(1);
    });

    // 绑定输入框回车事件
    $('#pageInput').on('keypress', function(e) {
      if (e.key === 'Enter') {
        var page = parseInt(this.value);
        if(page > totalPages){
          page = totalPages
          $('#pageInput').val(totalPages);
        }
        if (!isNaN(page) && page >= 1 && page <= totalPages) {
          fetchActivities(page);
        }
      }
    });

</script>

2、页面测试


最后~,兄弟么,咱们只有真的去写了,才会踩很多的坑,有些理论可能看不懂,但是我们实操就会恍然大悟,所以铁汁们,看到此处,说明咱是有耐心的,为你点赞,但是咱一定得动手练习哦

那么到这里,咱们项目就只剩下最后一个模块了,它也是整个项目的重头戏与核心------抽奖模块。下一篇博客,我们就正式进入这个重头戏,一起开启抽奖模块的实现,咱们下一篇见咯!

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

相关推荐
斯内普吖1 小时前
(开源)水果蔬菜商城实战指南 基于 Java + SSM + Vue + MySQL
java·vue.js·mysql·开源
wang_shu_mo_ran1 小时前
Spring IoC和DI概念篇
java·后端·spring
小狼154542 小时前
拼多多订单数据导出 CSV 实战:接口分页、字段平铺与 5 个数据坑,多多开票助手
java·前端·javascript
Joe_Wang52 小时前
【从0到1学习JVM · 25】同样都要停顿所有线程,Parallel比Serial到底强在哪
java·jvm·学习·垃圾回收
wuminyu2 小时前
JVM虚拟线程的底层实现原理分析
java·linux·c语言·jvm·c++
keyipatience2 小时前
跳表详细解析
开发语言·数据结构·c++·算法·跳表
喆星时瑜2 小时前
R语言基础语法入门
开发语言·r语言
想吃火锅10052 小时前
【leetcode】189.轮转数组js
开发语言·javascript·leetcode
郝学胜-神的一滴2 小时前
AI 编程智能体 02:AI智能体到底是什么
开发语言·人工智能·python·程序人生·pycharm