目录
[FindActivityListResult 查询结果:](#FindActivityListResult 查询结果:)
附加:本项目的抽奖系统测试计划测试计划(抽奖系统)-CSDN博客
创建活动
创建活动的信息包含:
- 活动名称
- 活动描述
- 圈选奖品:勾选对应奖品、并设置奖品等级(一二三等奖)、以及奖品数量
- 圈选人员:勾选参与抽奖人员
时序图:

将完整的活动信息放入到redis目的:
- 为了抽奖的时候确定抽奖的活动是哪一个。
- 活动+关联奖品+关联人员的完整信息,就是为了抽奖,根据关联的人员去进行抽奖。为了达到高效的目的,将完整的信息放入到缓存中
前后端交互接口:

Controller层接口设计:
java
@RestController
public class ActivityController {
private static final Logger logger = LoggerFactory.getLogger(ActivityController.class);
@Autowired
private ActivityService activityService;
/**
* 创建活动
* @param param
* @return
*/
@RequestMapping("/activity/create")
public CommonResult<CreateActivityResult> createActivity(
@Validated @RequestBody CreateActivityParam param) {
logger.info("createActivity CreateActivityParam:{}",
JacksonUtil.writeValueAsString(param));
return CommonResult.success(
convertToCreateActivityResult(activityService.createActivity(param)));
}
创建活动的请求参数
java
@Data
public class CreateActivityParam implements Serializable {
// 活动名称
@NotBlank(message = "活动不能为空!")
private String activityName;
// 活动描述
@NotBlank(message = "活动描述不能为空!")
private String description;
// 活动关联奖品列表
@NotNull(message = "活动奖品列表不能为空!")
//list 是个容器需使用notblank
@Valid //上面的NotEmpty 只能确保 list集合不为空 想要确保 list<CreatePrizeByActivityParam> 里面的元素不为空 需要加 @Valid
private List<CreatePrizeByActivityParam> activityPrizeList;
// 活动关联人员列表
@NotNull(message = "活动人员列表不能为空!")
@Valid
private List<CreateUserByActivityParam> activityUserList;
}
创建活动奖品关联表的请求参数
java
@Data
public class CreatePrizeByActivityParam implements Serializable {
/**
* 奖品id
*/
@NotNull(message = "活动关联的奖品id不能为空!")
private Long prizeId;
/**
* 奖品数量
*/
@NotNull(message = "奖品数量不能为空!")
private Long prizeAmount;
/**
* 奖品等级
*/
@NotBlank(message = "奖品等级不能为空!")
private String prizeTiers;
}
创建活动人员关联表的请求参数
java
@Data
public class CreateUserByActivityParam implements Serializable {
// 活动关联人员 ID
@NotNull(message = "活动关联人员 ID 不能为空!")
private Long userId;
// 活动关联人员名称
@NotBlank(message = "活动关联人员名称不能为空!")
private String userName;
}
活动奖品状态的枚举
java
@AllArgsConstructor
@Getter
public enum ActivityPrizeStatusEnum {
INIT(1, "初始化"),
COMPLETED(2, "已被抽取");
/**
* -- GETTER --
* 获取状态码
*/
private final Integer code;
/**
* -- GETTER --
* 获取描述信息
*/
private final String message;
public static ActivityPrizeStatusEnum forName(String name) {
for (ActivityPrizeStatusEnum activityPrizeStatusEnum : ActivityPrizeStatusEnum.values()) {
if (activityPrizeStatusEnum.name().equalsIgnoreCase(name)) {
return activityPrizeStatusEnum;
}
}
return null;
}
}
活动奖品等级的枚举
java
@AllArgsConstructor
@Getter
public enum ActivityPrizeTiersEnum {
FIRST_PRIZE(1, "一等奖"),
SECOND_PRIZE(2, "二等奖"),
THIRD_PRIZE(3, "三等奖");
private final Integer code;
private final String message;
/**
* 根据名称获取枚举
* @param name
* @return
*/
public static ActivityPrizeTiersEnum forName(String name) {
for (ActivityPrizeTiersEnum activityPrizeTiersEnum : ActivityPrizeTiersEnum.values()) {
if (activityPrizeTiersEnum.name().equalsIgnoreCase(name)) {
return activityPrizeTiersEnum;
}
}
return null;
}
}
活动人员状态枚举
java
@Getter
@AllArgsConstructor
public enum ActivityUserStatusEnum {
INIT(1, "初始化"),
COMPLETED(2, "已被抽取");
private final Integer code;
private final String message;
public static ActivityUserStatusEnum forName(String name){
for(ActivityUserStatusEnum activityUserStatusEnum:ActivityUserStatusEnum.values()){
if(activityUserStatusEnum.name().equals(name)){
return activityUserStatusEnum;
}
}
return null;
}
}
Service层设计与实现:
java
@Service
public interface ActivityService {
//创建活动
CreateActivityDTO createActivity(CreateActivityParam param);
}
java
@Service
public class ActivityServiceImpl implements ActivityService {
private static final Logger logger = LoggerFactory.getLogger(ActivityServiceImpl.class);
/**
* 为了区分业务, 约定活动编号前缀
*/
private final String ACTIVITY_PREFIX = "ACTIVITY_";
/**
* 活动超时时间
*/
private final Long ACTIVITY_TIMEOUT = 60 * 60 * 24 * 3L ;
@Autowired
private UserMapper userMapper;
@Autowired
private PrizeMapper prizeMapper;
@Autowired
private ActivityMapper activityMapper;
@Autowired
private ActivityPrizeMapper activityPrizeMapper;
@Autowired
private ActivityUserMapper activityUserMapper;
@Autowired
private RedisUtil redisUtil;
/**
* 创建活动
*
*/
@Override
@Transactional(rollbackFor = Exception.class) // 由于该方法涉及到多表,因此添加事务
public CreateActivityDTO createActivity(CreateActivityParam param) {
// 校验活动信息是否正确
checkActivityInfo(param);
// 保存活动信息
ActivityDO activityDO = new ActivityDO();
activityDO.setActivityName(param.getActivityName());
activityDO.setDescription(param.getDescription());
activityDO.setStatus(ActivityStatusEnum.RUNNING.name());
activityMapper.insert(activityDO);
//保存活动关联的奖品信息
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);
// 保存活动关联的人员信息
List<CreateUserByActivityParam> userParams = param.getActivityUserList();
List<ActivityUserDO> activityUserDOList = 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(activityUserDOList);
// 整合完整的活动信息,存放 redis
// ActivityDetailDTO 用于存放完整的活动信息(包括 活动数据、奖品数据、人员数据)
// 使用 activityId 这个键,来找到响应的值(ActivityDetailDTO)
// 先获取奖品基本属性表
// 获取需要查询的奖品id
List<Long> prizeIds = param.getActivityPrizeList()
.stream()
.map(CreatePrizeByActivityParam::getPrizeId)
.distinct()
.collect(Collectors.toList());
List<PrizeDO> prizeDOList = prizeMapper.batchSelectByIds(prizeIds);
//
ActivityDetailDTO detailDTO = convertToActivityDetailDTO(activityDO, activityUserDOList, prizeDOList, activityPrizeDOList);
// 放入redis缓存
cacheActivity(detailDTO);
// 构造并返回活动Id
CreateActivityDTO createActivityDTO = new CreateActivityDTO();
createActivityDTO.setActivityId(activityDO.getId());
return createActivityDTO;
}
}
Dao层设计:
java
@Mapper
public interface ActivityMapper {
/**
* 创建活动
* @param activityDO
* @return
*/
@Insert("insert into activity (activity_name, description, status)" +
" values (#{activityName}, #{description}, #{status})")
@Options(useGeneratedKeys = true, keyProperty ="id", keyColumn ="id")
int insert(ActivityDO activityDO);
}
@Mapper
public interface ActivityPrizeMapper {
/**
* 批量插入奖品信息
* @param activityPrizeDOList
* @return
*/
@Insert("<script>" +
" insert into activity_prize (activity_id, prize_id, prize_amount, prize_tiers, status)" +
" values <foreach collection = 'items' item='item' index='index' 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);
}
@Mapper
public interface ActivityUserMapper {
/**
* 批量插入
* @param activityUserDOList
* @return
*/
@Insert("<script>" +
" insert into activity_user (activity_id, user_id, user_name, status)" +
" values <foreach collection = 'items' item='item' index='index' 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);
}
java
/**
* 活动信息的数据对象类
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class ActivityDO extends BaseDO {
/**
* 活动的名称
*/
private String activityName;
/**
* 活动的描述
*/
private String description;
/**
* 活动的状态
*/
private String status;
}
/**
* ActivityPrizeDO 类表示活动奖品的数据对象。
* 该类继承自 BaseDO,包含活动与奖品的关联信息。
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class ActivityPrizeDO extends BaseDO {
/**
* 关联的活动id
*/
private Long activityId;
/**
* 关联的奖品id
*/
private Long prizeId;
/**
* 关联奖品数量
*/
private Long prizeAmount;
/**
* 关联的奖品状态
*/
private String status;
/**
* 关联的奖品等级
*/
private String prizeTiers;
}
/**
* 活动用户数据对象,继承自BaseDO。
* 包含活动与用户的关联信息。
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class ActivityUserDO extends BaseDO {
// 关联的活动ID
private Long activityId;
// 关联的用户ID
private Long userId;
// 用户名称
private String userName;
// 关联人状态
private String status;
}
接口测试:

相关事务的回滚说明:
项目中事务的使用可以简单理解为 "一系列操作要么全成功,要么全失败",就像生活中 "转账" 时,钱从 A 账户转出和转入 B 账户必须同时完成,缺一步都得恢复原样。结合项目场景,事务的作用和实现方式可以拆成部分:
- 抽奖时的 "原子操作"(本地事务)
比如用户抽中一台手机,系统要做两件事:
- 手机库存减 1(确保不会超卖);
- 生成一条 "用户中奖" 记录(证明用户确实中了)。
这两步必须绑在一起:
- 如果库存减 1 成功,但记录没生成(比如突然断电),系统会自动把库存加回去,避免 "少了一台手机却没人中";
- 如果记录生成了但库存没减,也会自动删除记录,防止 "用户中了奖但库存没变化,导致其他人还能抽"。
代码里用@Transactional注解实现这种绑定,就像给这两步加了个 "保护罩",出问题就全退回原样。
在抽奖系统中,用 Redis 缓存活动状态时,结合事务特性保证 "活动状态不变" 的原理,其实是通过 "关联校验" 和 "原子操作" 实现的,就像给活动状态加了一把 "联动锁",具体可以拆成三个关键点:
- 活动状态和关联信息的 "绑定存储"
Redis 里缓存的活动状态(比如 "进行中""已结束")不是孤立的,而是和奖品状态、人员抽奖状态 "绑在一起" 存储:
- 活动状态用一个键存(比如ACTIVITY_STATUS_1001:RUNNING);
- 对应的奖品状态(比如某奖品是否已抽完)用PRIZE_STATUS_1001_2001:NOT_DRAWN(2001 是奖品 ID);
- 人员抽奖状态(比如用户是否已抽过)用USER_DRAW_STATUS_1001_3001:NOT_DRAWN(3001 是用户 ID)。
这就像活动状态是 "总开关",奖品和人员状态是 "分开关",总开关的变化必须先看分开关是否都到位。
- 事务保证 "校验和更新的原子性"
当系统想改活动状态(比如从 "进行中" 改成 "已结束"),会用 Redis 的事务(MULTI/EXEC)做两件事,确保中间没人插队修改:
- 第一步:批量校验分状态
用WATCH命令盯着奖品和人员的状态键,比如检查 "所有奖品是否都已抽完"(PRIZE_STATUS_*都是DRAWN),且 "没有用户正在抽奖"(USER_DRAW_STATUS_*没有DRAWING)。
- 第二步:更新活动状态
如果校验通过,就把活动状态改成ENDED;如果校验失败(比如还有奖品没抽完),事务会自动放弃更新,活动状态保持不变。
就像你想关门(改活动状态),必须先确认所有人都已离开(人员状态)且东西都收拾好了(奖品状态),中途有人突然回来(状态变化),门就关不上。
- 缓存和数据库的 "状态同步机制"
Redis 里的状态最终要和数据库保持一致,这里的事务会同时管两头:
- 当系统在 Redis 里用事务确认 "可以改活动状态" 时,会同时触发数据库的事务:先改数据库里的活动状态,再改 Redis 的缓存状态,中间任何一步失败,两边都会回滚(比如数据库改成功但 Redis 崩了,就把数据库状态改回去)。
- 如果有人直接改了数据库里的奖品状态(比如手动标记某奖品已抽完),系统会发一个 "更新信号" 到 Redis,让 Redis 里的对应状态同步变化,避免缓存和数据库 "对不上" 导致校验出错。
举个例子:为什么活动状态不会乱变?
比如某活动规定 "所有奖品抽完才结束":
- 当最后一个奖品被抽中时,系统会先在 Redis 里用事务校验:检查该奖品状态是否变为 "已抽取"(PRIZE_STATUS_1001_2001:DRAWN),且没有其他奖品还没抽(PRIZE_STATUS_1001_*全是DRAWN)。
- 校验通过后,事务才会把活动状态从RUNNING改成ENDED,并同步到数据库。
- 如果校验时发现还有一个奖品没抽(分状态不对),事务就会放弃,活动状态继续保持RUNNING。
简单说,原理就是 "用 Redis 事务把活动状态和关联状态绑成一个整体,要么一起变,要么都不变",确保活动状态的变化符合业务规则,不会因为中途出问题或数据不同步导致混乱。
抽奖活动列表创建(翻页):
时序图:

前后端交互接口:
java
请求] /activity/find-list?currentPage=1&pageSize=10 GET
[响应]
{
"code":200,
"data";
"total":10
"records":[
{
"activityId": 23,
"activityName":"抽奖测试",
"description":"年会抽奖活动",
"valid": true
},
{
"activityId": 22,
1"activityName":"抽奖测试",
"description":"年会抽奖活动",
"valid": true
}
"msg"111
}
Controller层接口设计:
java
/**
* 查询活动列表
* @param param
* @return
*/
@RequestMapping("/activity/find-list")
public CommonResult<FindActivityListResult> findActivityList(PageParam param){
logger.info("查询活动列表开始 findActivityList PageParam :{}",
JacksonUtil.writeValueAsString(param));
return CommonResult.success(convertToFindActivityListResult(
activityService.findActivityList(param)));
}
/**
* 查询活动列表
* 将FindPrizeListResult 转换成FindActivityListResult
* @param activityList
* @return
*/
private FindActivityListResult convertToFindActivityListResult(PageListDTO<ActivityDTO> activityList) {
// 判断活动列表是否为空,为空时抛出状态码为 301 的"查询活动列表失败"异常
if (null == activityList) {
throw new ControllerException(ControllerErrorCodeConstants.FIND_ACTIVITY_LIST_ERROR);
}
// 创建 FindActivityListResult对象,用于存储转换后的数据
FindActivityListResult result = new FindActivityListResult();
result.setTotal(activityList.getTotal());
result.setRecords(
activityList.getRecords()
.stream()
.map(activityDTO -> {
FindActivityListResult.ActivityInfo activityInfo = new FindActivityListResult.ActivityInfo();
activityInfo.setActivityId(activityDTO.getActivityId());
activityInfo.setActivityName(activityDTO.getActivityName());
activityInfo.setDescription(activityDTO.getDescription());
activityInfo.setValid(activityDTO.valid());
return activityInfo;
}).collect(Collectors.toList())
);
return result;
}
FindActivityListResult 查询结果:
java
/**
* 分页查询结果
*
* @author:
*/
@Data
public class FindActivityListResult implements Serializable {
/**
* 总量
*/
private Integer total;
/**
* 当前列表
*/
private List<ActivityInfo> records;
@Data
public static class ActivityInfo implements Serializable {
/**
* 活动id
*/
private Long activityId;
/**
* 活动名称
*/
private String activityName;
/**
* 活动描述
*/
private String description;
/**
* 活动是否有效
*/
private Boolean valid;
}
}
PageParam请求参数:
java
@Getter
@Setter
/**
* 分页参数
*/
public class PageParam implements Serializable {
//当前页码(默认从第一页开始)
private Integer currentPage = 1;
//当前页数量(默认显示10条数据)
private Integer pageSize = 10;
//计算偏移量
public Integer offset(){
//如果当前是第一页,1-1=0,0*10=0,就是从数据库中第0个数据开始
//如果当前是第二页,2-1=1,1*10=10,就是从数据库中第10个数据开始
return (currentPage - 1) * pageSize;
}
}
Service层接口设计与实现:
java
@Service
public interface ActivityService {
/**
* 翻页查找活动列表
*
*/
PageListDTO<ActivityDTO> findActivityList(PageParam param);
}
接口实现:
java
/**
* 根据分页参数查询活动列表
* <p>
* 此方法首先查询所有活动的总数,然后根据当前页码和页面大小获取当前页的活动列表
* 它将查询结果转换为DTO列表,并返回一个包含总数和当前页DTO列表的PageListDTO对象
*
* @param param 分页参数,包含当前页码和每页大小
* @return 返回一个PageListDTO对象,包含活动总数和当前页的活动DTO列表
*/
@Override
public PageListDTO<ActivityDTO> findActivityList(PageParam param) {
// 查询活动总数
int total = activityMapper.count();
//获取当前页列表
List<ActivityDO> activityDOList = activityMapper.selectActivityList(param.offset(), param.getPageSize());
//将ActivityDO转换为ActivityDTO
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;
}).toList();
//返回包含总数和当前页DTO列表的PageListDTO对象
return new PageListDTO<>(total, activityDTOList);
}
Dao层接口设计:
java
/**
* 分页查询活动列表
*/
@Select("select * from activity order by id desc limit #{offset}, #{pageSize}")
List<ActivityDO> selectActivityList(@Param("offset") Integer offset,
@Param("pageSize") Integer pageSize);
接口测试:

查询活动详情信息:
activityDetailDTO类里面包含抽奖活动所需要的所有参数。
前端:向后端发起请求,从redis中查询相关的数据(如果没有存储,或者redis中的缓存过期,就会查询相关的数据库表,整合所需要的所有数据,再一次存放到redis中),最后奖数据返回给前端。
Controller层接口设计:
java
/**
* 查询活动详情
* @param activityId
* @return
*/
@RequestMapping("/activity-detail/find")
public CommonResult<GetActivityDetailResult> getActivityDetail(Long activityId) {
logger.info("getActivityDetail activityId:{}", activityId);
ActivityDetailDTO detailDTO = activityService.getActivityDetail(activityId);
return CommonResult.success(convertToGetActivityDetailResult(detailDTO));
}
/**
* 将ActivityDetailDTO 转换成GetActivityDetailResult
* @param detailDTO
* @return
*/
private GetActivityDetailResult convertToGetActivityDetailResult(
ActivityDetailDTO detailDTO) {
if(null == detailDTO){
throw new ControllerException(ControllerErrorCodeConstants.GET_ACTIVITY_DETAIL_ERROR);
}
GetActivityDetailResult result = new GetActivityDetailResult();
result.setActivityId(detailDTO.getActivityId());
result.setActivityName(detailDTO.getActivityName());
result.setDescription(detailDTO.getDesc());
result.setValid(detailDTO.valid());
// 抽奖顺序:一等奖、二、三
result.setPrizes(
detailDTO.getPrizeDTOList().stream()
.sorted(Comparator.comparingInt(prizeDTO -> prizeDTO.getTiers().getCode()))
.map(prizeDTO -> {
GetActivityDetailResult.Prize prize = new GetActivityDetailResult.Prize();
prize.setPrizeId(prizeDTO.getPrizeId());
prize.setName(prizeDTO.getName());
prize.setImageUrl(prizeDTO.getImageUrl());
prize.setPrice(prizeDTO.getPrice());
prize.setDescription(prizeDTO.getDescription());
prize.setPrizeTierName(prizeDTO.getTiers().getMessage());
prize.setPrizeAmount(prizeDTO.getPrizeAmount());
prize.setValid(prizeDTO.valid());
return prize;
}).collect(Collectors.toList())
);
result.setUsers(
detailDTO.getUserDTOList().stream()
.map(userDTO -> {
GetActivityDetailResult.User user = new GetActivityDetailResult.User();
user.setUserId(userDTO.getUserId());
user.setUserName(userDTO.getUserName());
user.setValid(userDTO.valid());
return user;
}).collect(Collectors.toList())
);
return result;
}
GetActivityDetailResult返回结果:
java
@Data
public class GetActivityDetailResult 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;
/**
* 奖品等奖
* @see ActivityPrizeTiersEnum#getMessage()
*/
private String prizeTierName;
/**
* 奖品数量
*/
private Long prizeAmount;
/**
* 奖品是否有效
*/
private Boolean valid;
}
@Data
public static class User {
/**
* 用户id
*/
private Long userId;
/**
* 姓名
*/
private String userName;
/**
* 人员是否被抽取
*/
private Boolean valid;
}
}
Service层接口设计与实现:
java
@Service
public interface ActivityService {
/**
* 创建活动
*
*/
CreateActivityDTO createActivity(CreateActivityParam param);
/**
* 翻页查找活动列表
*
*/
PageListDTO<ActivityDTO> findActivityList(PageParam param);
/**
* 获取活动详情属性
* @param activityId
* @return
*/
ActivityDetailDTO getActivityDetail(Long activityId);
}
@Service
public class ActivityServiceImpl implements ActivityService {
private static final Logger logger = LoggerFactory.getLogger(ActivityServiceImpl.class);
/**
* 为了区分业务, 约定活动编号前缀
*/
private final String ACTIVITY_PREFIX = "ACTIVITY_";
/**
* 活动超时时间
*/
private final Long ACTIVITY_TIMEOUT = 60 * 60 * 24 * 3L ;
@Autowired
private UserMapper userMapper;
@Autowired
private PrizeMapper prizeMapper;
@Autowired
private ActivityMapper activityMapper;
@Autowired
private ActivityPrizeMapper activityPrizeMapper;
@Autowired
private ActivityUserMapper activityUserMapper;
@Autowired
private RedisUtil redisUtil;
/**
* 根据活动Id获取活动详情
* 此方法首先从缓存中获取活动详情,如果缓存中不存在,则从数据库中查询活动详情并缓存
*
* @param activityId 活动Id
* @return 返回活动详情DTO
*/
@Override
public ActivityDetailDTO getActivityDetail(Long activityId) {
if (null == activityId) {
logger.warn("查询活动详细信息失败,activityId为空!");
return null;
}
// 查询 redis
ActivityDetailDTO detailDTO = getActivityFromCache(activityId);
if (null != detailDTO) {
logger.info("查询活动详细信息成功!detailDTO={}",
JacksonUtil.writeValueAsString(detailDTO));
return detailDTO;
}
// 如果redis不存在,查表
// 活动表
ActivityDO aDO = activityMapper.selectById(activityId);
// 活动奖品表
List<ActivityPrizeDO> apDOList = activityPrizeMapper.selectByActivityId(activityId);
// 活动人员表
List<ActivityUserDO> auDOList = activityUserMapper.selectByActivityId(activityId);
// 奖品表: 先获取要查询的奖品id
List<Long> prizeIds = apDOList.stream()
.map(ActivityPrizeDO::getPrizeId)
.collect(Collectors.toList());
List<PrizeDO> pDOList = prizeMapper.batchSelectByIds(prizeIds);
// 整合活动详细信息,存放redis
detailDTO = convertToActivityDetailDTO(aDO, auDOList, pDOList, apDOList);
cacheActivity(detailDTO);
// 返回
return detailDTO;
}
}
Dao层设计:
java
/**
* 根据活动id查询奖品信息
* @param activityId
* @return
*/
@Select("select * from activity_prize where activity_id = #{activityId}")
List<ActivityPrizeDO> selectByActivityId(@Param("activityId") Long activityId);
/**
* 根据活动id查询用户信息
* @param activityId
* @return
*/
@Select("select * from activity_user where activity_id = #{activityId}")
List<ActivityUserDO> selectByActivityId(@Param("activityId") Long activityId);
接口测试:
