✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
🎯 你正在阅读「Java项目-企悦抽」系列文章 🎯
✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
🔥 弹简特 个人主页
❄️ 个人专栏直通车:
- 🔌 接口测试从入门到跑路
- ☕ 一个后端的 JavaEE 续命指南
- 🛜 网络原理续命手册
- ☕ Java项目-轻聊
✨ 靠热爱去书写自己,靠勇敢去书写生活!
✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
🌟 博主简介:

文章目录
- 一、前言
- 二、活动创建需求分析
-
- [1. 原型图](#1. 原型图)
- [2. 这次创建活动涉及到哪些数据库表](#2. 这次创建活动涉及到哪些数据库表)
-
- [2.1 活动表](#2.1 活动表)
- [2.2 活动奖品关联表](#2.2 活动奖品关联表)
- [2.3 活动人员关联表](#2.3 活动人员关联表)
- 3、创建活动时序图+实现思路
- 三、活动创建后端实现
-
- 1、约定前后端交互接口
-
- [请求体(Request Body)](#请求体(Request Body))
- [响应体(Response Body)](#响应体(Response Body))
- 字段说明
-
- [a. 活动基础信息](#a. 活动基础信息)
- [b. 奖品列表 activityPrizeList](#b. 奖品列表 activityPrizeList)
- [c. 参与用户列表 activityUserList](#c. 参与用户列表 activityUserList)
- 2、实现控制层01
-
- [2.1 返回的Result实体](#2.1 返回的Result实体)
- [2.2 请求的Param实体](#2.2 请求的Param实体)
-
- [2.2.1 活动基本信息+奖品信息+人员信息的Param实体](#2.2.1 活动基本信息+奖品信息+人员信息的Param实体)
- [2.2.2 奖品信息Param实体](#2.2.2 奖品信息Param实体)
- [2,2.3 人员信息Param实体](#2,2.3 人员信息Param实体)
- [2.3 校验参数](#2.3 校验参数)
- [2.4 控制层基本代码](#2.4 控制层基本代码)
- 3、实现服务层01
-
- [3.1 活动服务层接口](#3.1 活动服务层接口)
- [3.2 创建活动服务层返回给控制层的DTO实体](#3.2 创建活动服务层返回给控制层的DTO实体)
- 4、实现控制层02
-
- [4.1 将服务层返回的DTO转为控制层返回的Result](#4.1 将服务层返回的DTO转为控制层返回的Result)
- 5、实现服务层02
-
- [5.1 实现服务层的实现类](#5.1 实现服务层的实现类)
-
- [5.1.1 使用回滚](#5.1.1 使用回滚)
- [5.1.2 校验活动的有效性](#5.1.2 校验活动的有效性)
- [5.1.3 活动入库](#5.1.3 活动入库)
一、前言
老铁们,上一期我们结束了我们的奖品管理模块,从本期开始我们将进入我们为项目最核心的两个模块模块:活动管理模块和抽奖模块。
二、活动创建需求分析
1. 原型图

注意:参与抽奖的人只有普通用户,管理员是不参加抽奖的,管理员只负责,操作这个软件,点击开始抽奖看他的哪一个员工得奖了~
2. 这次创建活动涉及到哪些数据库表
2.1 活动表
首先你看我们当时设计的活动表

那么这个表用于抽象哪一些数据?用于存储哪些信息?👇

那么此时还有status这个状态字段有什么作用呢?他为什么用来描述活动的状态?理由如下图👇

2.2 活动奖品关联表
老铁要知道,咱们的需求使用活动奖品关联表来存什么东西,首先看:
第一个点:存圈选奖品这样的一个关联关系
你看,咱的奖品基本信息存在奖品表中

咱活动的基本信息存在活动表中

但是我们的活动和奖品是有关系的,多对多的关系,就是你创建活动的时候,你需要去圈选奖品的,那么这样的关系我们需要使用一个中间表来维护, 说白了,我们将这种关联关系存到中间表中哦!👇

第二个点:为什么需要存奖品数量和奖品等级(需求场景来的)

答案是:不可以的,你为什么要把奖品数量和奖品等级存到奖品比中呢?就因为带有奖品两个字?不不不,不能这样想,他是根据我们的需求来的哇,你奖品表如果存这两个字段在奖品管理那里是没有意义的,同时还增加了奖品表的压力,就是说,你打在点上,我们的奖品数量和奖品等级 只有在创建活动区圈选奖品的时候才有存在的意义,所以我们才将他存到我们的中间表中。
第三个点:为什么活动奖品关联表还要存一个状态(需求场景来的)
我们在抽奖的需求中:开始抽奖过程中,如果一个奖品已经抽完了,那么我们就展示这个奖品的获奖名单,而不是继续抽奖,此时这个参考参数就是来源于status。

2.3 活动人员关联表
咱们活动表和人员表之间是多对多的关系,那么需要使用一张中间表来维护这样的一种关系,你需要注意以下几点:
第一个点:活动人员关联表的基本信息

第二个点:中间表用来维护活动和人员之间的关系
首先你看:活动表存的是活动基本信息

人员表用于存储人员的基本信息

那么活动和人员之间是多对多的,所以我们需要使用中间表来存储他们的这种关系,那么中间表中存什么?
最基本的存用户ID+活动ID,那么到时候某个活动有哪些人就可以根据这个中间表去查询了

第三个点:你看咱们中间表中为什么有一个用户名字段?
这个也是一种设计哲学,我们说将来你这个活动有哪些用户,那么你得通过我们的中间表中的用户ID,拿到用户ID去查询用户表得到用户信息,对吧,但是我们的用户名是一个热门常用的字段,我们为了避免多次查询所以就索性将用户名这种经常查询的字段存到我们的中间表中。

第四个点:为什么中间表中有状态这个字段?
这个字段咱们是有需求的:我们有需求规定,每一个用户只能参与中奖一次(注意是只能中奖一次,不是只能抽奖一次),如果中奖了,那么你后续就不能去参见抽奖咯。
此时你怎么去判断该活动的该用户已经中过奖了呢?我们就是使用这个字段来维护的。

那么请问:为什么这个字段你要放在这种中间表中?
你看,你是判断该活动的那个人只能中奖一次,注意是某个活动的某一个人,所以我们理所当然放在这个中间表的字段中。
3、创建活动时序图+实现思路

1、 存活动基本信息到活动表
2、 存活动人员关系信息到活动人员表
3、 存活动奖品关系信息到活动奖品表
4、拿到奖品ID去奖品表中查询奖品信息(这一步是为什么?因为一个完整的活动需要:活动信息+人员信息+奖品信息
那么我们现在有活动信息了,而人员信息我们在活动人员表中有用户名字段了,现在唯一欠缺的就是奖品信息。)
5、得到奖品信息之后,我们将活动信息+人员信息+奖品信息整合成一个完整的完整活动信息
6、最后再将这个完整的活动基本信息+人员信息+奖品信息存到Redis中,因为他们是常用的数据,到时候我们经常查询的,就是说将来我们目的是为了抽奖,那么抽奖你必须得有活动ID(得告诉他你抽哪一个活动),那么我们拿到活动ID之后,需要查询活动的哪一些数据呢?我们需要返回活动数据+人员完整数据+奖品完整数据,那么返回这些数据干什么?目的就是为了抽奖,你抽奖你得告诉他有哪些奖品吧,所以要有奖品信息,那么你抽奖,得告诉他哪些人参与吧,所以得有人员信息。
我们把这些完整信息存到内存中,然后它的键值我们就设置为活动id,所以我们只需要通过活动ID就能查询到完整的信息。
三、活动创建后端实现
1、约定前后端交互接口
接口地址 :/activity/create
请求方式 :POST
Content-Type :application/json
请求体(Request Body)
json
{
"activityName": "抽奖测试", // 活动名称(必填)
"description": "年会抽奖活动", // 活动描述(必填)
"activityPrizeList": [ // 奖品列表(必填,非空)
{
"prizeId": 13, // 奖品ID(必填)
"prizeAmount": 1, // 奖品数量(必填)
"prizeTiers": "FIRST_PRIZE" // 奖品等级:一等奖/二等奖(枚举)
},
{
"prizeId": 12,
"prizeAmount": 1,
"prizeTiers": "SECOND_PRIZE"
}
],
"activityUserList": [ // 参与用户列表(必填,非空)
{
"userId": 25, // 用户ID(必填)
"userName": "郭靖" // 用户名(必填)
},
{
"userId": 23,
"userName": "杨康"
}
]
}
响应体(Response Body)
json
{
"code": 200, // 状态码:200=成功
"data": {
"activityId": 23 // 创建成功返回的活动ID
},
"msg": "" // 提示信息,成功为空
}
字段说明
a. 活动基础信息
| 字段名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| activityName | string | 是 | 活动名称 |
| description | string | 是 | 活动描述 |
b. 奖品列表 activityPrizeList
| 字段名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| prizeId | int | 是 | 奖品ID |
| prizeAmount | int | 是 | 奖品数量 |
| prizeTiers | string | 是 | 奖品等级: FIRST_PRIZE=一等奖 SECOND_PRIZE=二等奖 |
c. 参与用户列表 activityUserList
| 字段名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| userId | int | 是 | 用户ID |
| userName | string | 是 | 用户名称 |
2、实现控制层01
创建一个ActivityController类

2.1 返回的Result实体

代码解释

代码
java
@Data
public class CreateActivityResult implements Serializable {
/**
* 创建的活动id
*/
private Long activityId;
}
2.2 请求的Param实体
2.2.1 活动基本信息+奖品信息+人员信息的Param实体

代码
java
@Data
public class CreateActivityParam implements Serializable {
/**
* 活动名称
*/
private String activityName;
/**
* 描述
*/
private String description;
/**
* 活动关联奖品列表
*/
private List<CreatePrizeByActivityParam> activityPrizeList;
/**
* 活动关联人员列表
*/
private List<CreateUserByActivityParam> activityUserList;
}
2.2.2 奖品信息Param实体

代码
java
@Data
public class CreatePrizeByActivityParam implements Serializable {
/**
* 活动关联的奖品id
*/
private Long prizeId;
/**
* 奖品数量
*/
private Long prizeAmount;
/**
* 奖品等级
*/
private String prizeTiers;
}
2,2.3 人员信息Param实体

代码
java
@Data
public class CreateUserByActivityParam implements Serializable {
/**
* 活动关联的人员id
*/
private Long userId;
/**
* 姓名
*/
private String userName;
}
2.3 校验参数
我们使用注解 对我们的参数进行校验一下

| 注解 | 适用对象 | 核心作用 |
|---|---|---|
@NotBlank |
仅用于 字符串(String)类型 | 校验字符串非null + 非空串 + 非空白字符(空格/制表符/换行) |
@NotEmpty |
用于字符串、集合(List/Set)、数组、Map等容器类型 | 校验对象非null + 容器大小/字符串长度 > 0(不校验纯空白字符串) |

第一步:给集合加@Valid注解告诉他里面元素的属性也要校验

集合中元素属性具体如何校验?

流程图:

代码
java
//创建活动参数类
@Data
public class CreateActivityParam implements Serializable {
/**
* 活动名称
*/
@NotBlank(message = "活动名称不能为空")
private String activityName;
/**
* 描述
*/
@NotBlank(message = "活动描述不能为空")
private String description;
/**
* 活动关联奖品列表
*/
@NotEmpty(message = "活动关联奖品列表不能为空")
@Valid
private List<CreatePrizeByActivityParam> activityPrizeList;
/**
* 活动关联人员列表
*/
@NotEmpty(message = "活动关联人员列表不能为空")
@Valid
private List<CreateUserByActivityParam> activityUserList;
}
//活动关联的奖品表
@Data
public class CreatePrizeByActivityParam implements Serializable {
/**
* 活动关联的奖品id
*/
@NotNull(message = "活动关联的奖品id不能为空")
private Long prizeId;
/**
* 奖品数量
*/
@NotNull(message = "奖品数量不能为空")
private Long prizeAmount;
/**
* 奖品等级
*/
@NotNull(message = "奖品等级不能为空")
private String prizeTiers;
}
//活动关联人员列表
@Data
public class CreateUserByActivityParam implements Serializable {
/**
* 活动关联的人员id
*/
@NotNull(message = "活动关联的人员id不能为空")
private Long userId;
/**
* 姓名
*/
@NotNull(message = "姓名不能为空")
private String userName;
}
2.4 控制层基本代码
代码解释

代码:
java
@RestController
public class ActivityController {
//日志
private static final Logger logger = LoggerFactory.getLogger(ActivityController.class);
@RequestMapping("/activity/create")
public CommonResult<CreateActivityResult> createActivity(
@Validated @RequestBody CreateActivityParam param) {
//首先先打印日志,日志的格式我们使用 类型化+方法名+参数
logger.info("ActivityController createActivity, CreateActivityParam:{}",
JackSonUtil.writeValueAsString(param));//使用我们自定义的序列化方法将对象变为字符串展示出来
return CommonResult.success(
convertToCreateActivityResult(activityService.createActivity(param)));
}
}
3、实现服务层01
3.1 活动服务层接口

代码:
java
public interface ActivityService {
/**
* 创建活动
* @param param
* @return
*/
CreateActivityDTO createActivity(CreateActivityParam param);
}
3.2 创建活动服务层返回给控制层的DTO实体


java
@Data
public class CreateActivityDTO {
/**
* 活动id
*/
private Long activityId;
}
4、实现控制层02
4.1 将服务层返回的DTO转为控制层返回的Result

java
/**
* 将服务层返回的创建活动成功DTO信息 转为 前端需要的创建活动成功Result信息
* @param activity
* @return
*/
private CreateActivityResult convertToCreateActivityResult(CreateActivityDTO activity) {
if (activity == null) {
throw new ControllerException(ControllerErrorCodeConstants.CREATE_ACTIVITY_ERROR);
}
CreateActivityResult createActivityResult = new CreateActivityResult();
createActivityResult.setActivityId(activity.getActivityId());
return createActivityResult;
}
5、实现服务层02
5.1 实现服务层的实现类

5.1.1 使用回滚
由于涉及到多表的操作,那么我们就使用到了事务。
就是说你要么全部做,要么就不全做
事务的相关知识参考博客👉SpringBoot事务

5.1.2 校验活动的有效性
有老铁说,我们不是在控制层使用注解校验过了吗?是这样的,我们为了解耦,所以咱每一层各自做各自的控制层咱就做控制层的服务层就做服务层的。而且你需要校验参数的有效性的时候注意了,我们得看你选择的这个活动所关联的奖品数据和人员数据是否在我们的数据库中存着呀。
校验什么?
-
人员id在我们的人员列表中要存在:根据前端传递的人员id,去数据库中查询,看是否有这个id,如果有我再将这个人员的id数据传递回来
-
奖品id在我们的人员列表中要存在
-
人员数量要大于奖品的数量(如果你的奖品都比人多,那么你还抽什么奖呢?)
1、人员id在我们的人员列表中要存在和奖品id在我们的人员列表中要存在
图解思路

那么,对于奖品列表是否存在也是一样的原理,我就简单画一个简图了:

2、人员数量要大于奖品的数量

代码
java
/**
* 校验参数的有效性
* @param param
*/
private void checkActivityInfo(CreateActivityParam param) {
if (param == null) {
throw new ServiceException(ServiceErrorCodeConstants.CREATE_ACTIVITY_INFO_IS_EMPTY);
}
//1.人员id在我们的人员列表中要存在
List<Long> userIds = new ArrayList<>();
List<CreateUserByActivityParam> activityUserList = param.getActivityUserList();
for (CreateUserByActivityParam activityParam : activityUserList) {
Long userId = activityParam.getUserId();
// 去重:不存在才添加
if (!userIds.contains(userId)) {
userIds.add(userId);
}
}
List<Long> existUserIds = userMapper.selectExistByIds(userIds);
if (CollectionUtils.isEmpty(existUserIds)) {
throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_USER_ERROR);
}
for (Long userId : userIds) {
if (!existUserIds.contains(userId)) {
throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_USER_ERROR);
}
}
//2.奖品id在我们的人员列表中要存在
// 奖品id在奖品表中是否存在
List<Long> prizeIds = new ArrayList<>();
List<CreatePrizeByActivityParam> activityPrizeList = param.getActivityPrizeList();
for (CreatePrizeByActivityParam activityParam : activityPrizeList) {
Long prizeId = activityParam.getPrizeId();
if (!prizeIds.contains(prizeId)) {
prizeIds.add(prizeId);
}
}
List<Long> existPrizeIds = prizeMapper.selectExistByIds(prizeIds);
if (CollectionUtils.isEmpty(existPrizeIds)) {
throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_PRIZE_ERROR);
}
for (Long prizeId : prizeIds) {
if (!existPrizeIds.contains(prizeId)) {
throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_PRIZE_ERROR);
}
}
//3.人员数量要大于奖品的数量
int userAmount = param.getActivityUserList().size();
long prizeAmount = 0;
List<CreatePrizeByActivityParam> activityPrizeListNum = param.getActivityPrizeList();
for (CreatePrizeByActivityParam prizeParam : activityPrizeListNum) {
prizeAmount += prizeParam.getPrizeAmount();
}
if (userAmount < prizeAmount) {
throw new ServiceException(ServiceErrorCodeConstants.USER_PRIZE_AMOUNT_ERROR);
}
}
5.1.3 活动入库
(1)保存活动基本信息
1)数据库表和Java实体类的映射:活动DO



java
@Data
@EqualsAndHashCode(callSuper = true)
public class ActivityDO extends BaseDO {
/**
* 活动名称
*/
private String activityName;
/**
* 活动描述
*/
private String description;
/**
* 活动状态
*/
private String status;
}
2)持久层的sql


java
public interface ActivityMapper {
@Insert("insert into activity (activity_name, description, status)" +
" values (#{activityName}, #{description}, #{status})")
@Options(useGeneratedKeys = true, keyProperty ="id", keyColumn ="id")
int insert(ActivityDO activityDO);
}
3)活动的状态需要设置为枚举(要么进行中,要么就是已完成)


java
public enum ActivityStatusEnum {
RUNNING(1, "活动进行中"),
COMPLETED(2, "活动已完成");
private final Integer code;
private final String message;
/**
* 全参构造
* @param code
* @param message
*/
ActivityStatusEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
public static ActivityStatusEnum forName(String name) {
for (ActivityStatusEnum activityStatusEnum : ActivityStatusEnum.values()) {
if (activityStatusEnum.name().equalsIgnoreCase(name)) {
return activityStatusEnum;
}
}
return null;
}
public String getMessage() {
return message;
}
public Integer getCode() {
return code;
}
}
4)活动关联的奖品也是有状态的
java
public enum ActivityPrizeStatusEnum {
INIT(1, "初始化"),
COMPLETED(2, "已被抽取");
private final Integer code;
private final String message;
ActivityPrizeStatusEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
public static ActivityPrizeStatusEnum forName(String name) {
for (ActivityPrizeStatusEnum activityPrizeStatusEnum : ActivityPrizeStatusEnum.values()) {
if (activityPrizeStatusEnum.name().equalsIgnoreCase(name)) {
return activityPrizeStatusEnum;
}
}
return null;
}
public Integer getCode() {
return code;
}
public String getMessage() {
return message;
}
}
5)活动关联的人员也是有状态的
java
public enum ActivityUserStatusEnum {
INIT(1, "初始化"),
COMPLETED(2, "已被抽取");
private final Integer code;
private final String message;
ActivityUserStatusEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
public static ActivityUserStatusEnum forName(String name) {
for (ActivityUserStatusEnum activityUserStatusEnum : ActivityUserStatusEnum.values()) {
if (activityUserStatusEnum.name().equalsIgnoreCase(name)) {
return activityUserStatusEnum;
}
}
return null;
}
public Integer getCode() {
return code;
}
public String getMessage() {
return message;
}
}
6)服务层的实现逻辑

java
//2、将活动的基本信息保存到我们活动表里面去
ActivityDO activityDO = new ActivityDO();
activityDO.setActivityName(param.getActivityName());
activityDO.setDescription(param.getDescription());
activityDO.setStatus(ActivityStatusEnum.RUNNING.name());
activityMapper.insert(activityDO);
(2)保存活动关联的奖品信息
1)创建数据库实体类:活动关联的奖品信息的DO

java
@Data
@EqualsAndHashCode(callSuper = true)
public class ActivityPrizeDO extends BaseDO {
/**
* 关联的活动id
*/
private Long activityId;
/**
* 关联的奖品id
*/
private Long prizeId;
/**
* 奖品数量
*/
private Long prizeAmount;
/**
* 奖品等奖
*/
private String prizeTiers;
/**
* 奖品状态
*/
private String status;
}
2)奖品等级需要设置为枚举类

代码
java
public enum ActivityPrizeTiersEnum {
FIRST_PRIZE(1, "一等奖"),
SECOND_PRIZE(2, "二等奖"),
THIRD_PRIZE(3, "三等奖");
private final Integer code;
private final String message;
ActivityPrizeTiersEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
public static ActivityPrizeTiersEnum forName(String name) {
for (ActivityPrizeTiersEnum activityPrizeTiersEnum : ActivityPrizeTiersEnum.values()) {
if (activityPrizeTiersEnum.name().equalsIgnoreCase(name)) {
return activityPrizeTiersEnum;
}
}
return null;
}
public String getMessage() {
return message;
}
public Integer getCode() {
return code;
}
}
3)持久层sql编写
首先,要清楚,我们要存的是这个活动所关联的所有奖品,所以一定就是批量插入。

java
@Mapper
public interface ActivityPrizeMapper {
@Insert("<script>" +
" insert into activity_prize (activity_id, prize_id, prize_amount, prize_tiers, status)" +
" values <foreach collection = 'items' item='item' separator=','>" +
" (#{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)实现服务层的业务逻辑
基本思路就是:从前端参数中获取奖品集合,将奖品集合变为我们的DO实体,最后存到数据库中去即可。


代码:
java
//3、将活动关联的奖品信息保存到数据库活动奖品表里面去
List<CreatePrizeByActivityParam> prizeParams = param.getActivityPrizeList();
List<ActivityPrizeDO> activityPrizeDOList = new ArrayList<>();
for (CreatePrizeByActivityParam prizeParam : prizeParams) {
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());
// 添加到集合
activityPrizeDOList.add(activityPrizeDO);
}
activityPrizeMapper.batchInsert(activityPrizeDOList);
(3)保存活动关联的人员列表信息
这个逻辑跟保存活动关联的奖品列表信息一模一样,所以此处我们就不必过多的解释啦
1)创建数据库实体类:活动关联的用户信息DO

java
@Data
@EqualsAndHashCode(callSuper = true)
public class ActivityUserDO extends BaseDO {
/**
* 关联的活动id
*/
private Long activityId;
/**
* 关联的人员id
*/
private Long userId;
/**
* 姓名
*/
private String userName;
/**
* 关联的人员状态
*/
private String status;
}
2)持久层sql编写
java
@Mapper
public interface ActivityUserMapper {
@Insert("<script>" +
" insert into activity_user (activity_id, user_id, user_name, status)" +
" values <foreach collection = 'items' item='item' separator=','>" +
" (#{item.activityId}, #{item.userId}, #{item.userName}, #{item.status})" +
" </foreach>" +
" </script>")
@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
int batchInsert(@Param("items") List<ActivityUserDO> activityUserDOList);
}
3)服务层实现逻辑
逻辑跟上述的存活动关联的奖品信息是一样的

(4)整合活动信息
整合哪些活动信息?-->活动基本信息+活动关联的人员信息+活动关联的奖品信息
为什么需要整合?整合之后放在哪里?
为了将完整的活动信息存到redis中,为将来的抽奖做准备。将来抽奖的时候,我们需要查询到对应的活动完整数据。而且这些完整数据是我们的热点数据给存储到Redis中。
1)活动完整信息包括哪一些
那我们的需求,将来抽奖的时候得到活动的完整数据是哪些?(需求而来)

抽象页面需要的活动信息包括:活动名称、奖品等级、奖品份数、奖品图片
抽奖过程中:

抽象过程中需要的活动信息包括:活动名称、奖品等级、奖品份数、人员姓名
所以,根据需求,需要整合的完整活动信息包括:活动id、活动名称、活动描述 、活动状态 、奖品id、奖品名称 、奖品价格 、奖品描述 、奖品状态 、奖品等级、奖品图片、奖品份数、用户id、人员姓名、用户状态。其中奖品部分的奖品图片需要根据奖品id去查询奖品表得到。
2)存储的逻辑思路示意简图

3)存什么数据到Redis?

4)存到Redis中的完整活动信息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);
}
}
}
代码详细解释:
活动基本信息

奖品详细信息:

人员详细信息:

定义好实体类之后,我们需要分析清楚活动的基本信息来自于哪里?奖品的详细信息又来自于哪里?人员的详细信息又来自于哪里?👇
5)活动详细信息实体类中的数据来自于哪里?

6)图解实现业务逻辑

代码:
java
/*****************转换类型:将已经存储到数据库的DO数据变为DTO存到Redis中*************/
/**
* 根据基本DO整合完整的活动信息ActivityDetailDTO
*
* @param activityDO 活动基本信息
* @param activityUserDOList 活动关联用户列表
* @param prizeDOList 奖品详细信息列表
* @param activityPrizeDOList 活动关联奖品列表
* @return 完整的活动详情DTO
*/
private ActivityDetailDTO convertToActivityDetailDTO(ActivityDO activityDO,
List<ActivityUserDO> activityUserDOList,
List<PrizeDO> prizeDOList,
List<ActivityPrizeDO> activityPrizeDOList) {
// 1. 初始化活动详情DTO,设置基础信息
ActivityDetailDTO detailDTO = new ActivityDetailDTO();
detailDTO.setActivityId(activityDO.getId());
detailDTO.setActivityName(activityDO.getActivityName());
detailDTO.setDesc(activityDO.getDescription());
detailDTO.setStatus(ActivityStatusEnum.forName(activityDO.getStatus()));
// 2. 转换 活动奖品信息 → PrizeDTO列表
List<ActivityDetailDTO.PrizeDTO> prizeDTOList = new ArrayList<>();
// 遍历所有活动关联奖品
for (ActivityPrizeDO apDO : activityPrizeDOList) {
ActivityDetailDTO.PrizeDTO prizeDTO = new ActivityDetailDTO.PrizeDTO();
prizeDTO.setPrizeId(apDO.getPrizeId());
// 遍历奖品详细信息,根据prizeId匹配对应的PrizeDO
for (PrizeDO prizeDO : prizeDOList) {
if (prizeDO.getId().equals(apDO.getPrizeId())) {
// 匹配成功,设置奖品基础信息
prizeDTO.setName(prizeDO.getName());
prizeDTO.setImageUrl(prizeDO.getImageUrl());
prizeDTO.setPrice(prizeDO.getPrice());
prizeDTO.setDescription(prizeDO.getDescription());
break; // 找到即退出循环,提升效率
}
}
// 设置奖品等级、数量、状态
prizeDTO.setTiers(ActivityPrizeTiersEnum.forName(apDO.getPrizeTiers()));
prizeDTO.setPrizeAmount(apDO.getPrizeAmount());
prizeDTO.setStatus(ActivityPrizeStatusEnum.forName(apDO.getStatus()));
// 添加到结果集合
prizeDTOList.add(prizeDTO);
}
// 把奖品列表设置到DTO
detailDTO.setPrizeDTOList(prizeDTOList);
// 3. 转换 活动用户信息 → UserDTO列表
List<ActivityDetailDTO.UserDTO> userDTOList = new ArrayList<>();
// 遍历所有活动关联用户
for (ActivityUserDO auDO : activityUserDOList) {
ActivityDetailDTO.UserDTO userDTO = new ActivityDetailDTO.UserDTO();
userDTO.setUserId(auDO.getUserId());
userDTO.setUserName(auDO.getUserName());
userDTO.setStatus(ActivityUserStatusEnum.forName(auDO.getStatus()));
// 添加到结果集合
userDTOList.add(userDTO);
}
// 把用户列表设置到DTO
detailDTO.setUserDTOList(userDTOList);
return detailDTO;
}
/***************************************************************/
(5)缓存活动信息以及获取缓存中的活动数据
首先咱们缓存的代码
java
private void cacheActivity(ActivityDetailDTO detailDTO) {
if (null == detailDTO || null == detailDTO.getActivityId()) {
logger.warn("要缓存的活动信息不存在!");
return;
}
try {
redisUtil.set(ACTIVITY_PREFIX + detailDTO.getActivityId(),
JackSonUtil.writeValueAsString(detailDTO),
ACTIVITY_TIMEOUT);
} catch (Exception e) {
logger.error("缓存活动异常,ActivityDetailDTO={}",
JackSonUtil.writeValueAsString(detailDTO),
e);
}
}
代码解释:

那这里的处理异常,我们为什么直接处理不抛出改给CreateActivityDTO方法呢?
你看:

我们CreateActivityDTO这个方法干了存储活动基本信息,存储活动相关联的奖品信息,存储活动相关联的人员信息以及缓存完整信息到Redis中这4件事:

从缓存中获取我们的完整活动信息:
java
/**
* 根据活动id从缓存中获取活动详细信息
*
* @param activityId
* @return
*/
private ActivityDetailDTO getActivityFromCache(Long activityId) {
if (null == activityId) {
logger.warn("获取缓存活动数据的activityId为空!");
return null;
}
try {
String str = redisUtil.get(ACTIVITY_PREFIX + activityId);
if (!StringUtils.hasText(str)) {
logger.info("获取的缓存活动数据为空!key={}", ACTIVITY_PREFIX + activityId);
return null;
}
return JackSonUtil.readValue(str, ActivityDetailDTO.class);
} catch (Exception e) {
logger.error("从缓存中获取活动信息异常,key={}", ACTIVITY_PREFIX + activityId, e);
return null;
}
}

构造返回

OK,本期我们先将活动管理中的创建活动给实现完成,下期我们将继续实现活动管理模块中的剩下内容:测试、活动列表、活动管理模块的前端相关~
老铁们如果本篇内容对你有帮助,不妨点赞收藏,也欢迎在评论区留言交流,您的每一份支持都是我持续创作的最大动力~👋 咱们下期见😊
