1. 活动创建
需求回顾
创建的活动信息包含:
- 活动名称
- 活动描述
- 关联的一批奖品,关联时需要选择奖品等级(一等奖、二等奖、三等奖),及奖品库存
- 圈选一批人员参与抽奖
tip:什么时候设置奖品数量和奖品等级?
肯定是在创建活动的时候,这两项不能作为基本属性放到奖品表中(那样无意义,每次创建活动数量和等级可能都不一样),因此需要将其放到活动奖品奖品关联表中,如下:
时序图
tip:为了快速获取抽奖信息,所以将其存放到 Redis 缓存中
约定前后端交互接口
[请求] /activity/create POST
{
"activityName":"测试活动抽奖",
"description":"测试活动抽奖",
"activityPrizeList":[{"prizeId":2, "prizeAmount":1, "prizeTiers":"FIRST_PRIZE"}, {"prizeId":3, "prizeAmount":2, "prizeTiers":"SECOND_PRIZE"}],
"activityUserList":[{"userId":4, "userName":"郭靖"}, {"userId":5, "userName":"黄蓉"}, {"userId":6, "userName":"杨康"}]
}
[响应]
{
"code": 200,
"data": {
"activityId": 1
},
"msg": ""
}
Controller 层接口设计
java">package com.example.lotterysystem.controller;import com.example.lotterysystem.common.errorcode.ControllerErrorCodeConstants;
import com.example.lotterysystem.common.exception.ControllerException;
import com.example.lotterysystem.common.pojo.CommonResult;
import com.example.lotterysystem.common.utils.JacksonUtil;
import com.example.lotterysystem.controller.param.CreateActivityParam;
import com.example.lotterysystem.controller.result.CreateActivityResult;
import com.example.lotterysystem.service.ActivityService;
import com.example.lotterysystem.service.dto.CreateActivityDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class ActivityController {@Autowiredprivate ActivityService activityService;private static final Logger logger = LoggerFactory.getLogger(ActivityController.class);@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)));}private CreateActivityResult convertToCreateActivityResult(CreateActivityDTO createActivityDTO) {if (null == createActivityDTO) {throw new ControllerException(ControllerErrorCodeConstants.CREATE_ACTIVITY_ERROR);}CreateActivityResult result = new CreateActivityResult();result.setActivityId(createActivityDTO.getActivityId());return result;}
}
CreateActivityParam
java">package com.example.lotterysystem.controller.param;import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;import java.io.Serializable;
import java.util.List;@Data
public class CreateActivityParam implements Serializable {// 活动名称@NotBlank(message = "活动名称不能为空!")private String activityName;// 活动描述@NotBlank(message = "活动描述不能为空!")private String description;// 活动关联奖品列表@NotEmpty(message = "活动关联奖品列表不能为空!")@Valid // 上面 NotEmpty 只能确保 list 不能为空,想要 list 里面 CreatePrizeByActivityParam 的字段不能为空,需要加上 Validprivate List<CreatePrizeByActivityParam> activityPrizelist;// 活动关联人员列表@NotEmpty(message = "活动关联人员列表不能为空!")@Validprivate List<CreateUserByActivityParam> activityUserlist;
}
CreateUserByActivityParam
java">package com.example.lotterysystem.controller.param;import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;import java.io.Serializable;@Data
public class CreateUserByActivityParam implements Serializable {// 活动关联的人员id@NotNull(message = "活动关联的人员 id 不能为空!")private Long userId;// 活动关联的人员姓名@NotBlank(message = "活动关联的人员姓名不能为空!")private String userName;
}
ControllerErrorCodeConstants
java">// package com.example.lotterysystem.common.errorcode;// ------ 活动模块错误码 ------ErrorCode CREATE_ACTIVITY_ERROR = new ErrorCode(300, "活动创建失败!");
Service 层接口设计
java">package com.example.lotterysystem.service;import com.example.lotterysystem.controller.param.CreateActivityParam;
import com.example.lotterysystem.service.dto.CreateActivityDTO;public interface ActivityService {// 创建活动CreateActivityDTO createActivity(CreateActivityParam param);
}
CreateActivityDTO
java">package com.example.lotterysystem.service.dto;import lombok.Data;@Data
public class CreateActivityDTO {// 活动idprivate Long activityId;
}
接口实现
java">package com.example.lotterysystem.service.impl;import com.example.lotterysystem.common.errorcode.ServiceErrorCodeConstants;
import com.example.lotterysystem.common.exception.ServiceException;
import com.example.lotterysystem.common.utils.JacksonUtil;
import com.example.lotterysystem.common.utils.RedisUtil;
import com.example.lotterysystem.controller.param.CreateActivityParam;
import com.example.lotterysystem.controller.param.CreatePrizeByActivityParam;
import com.example.lotterysystem.controller.param.CreateUserByActivityParam;
import com.example.lotterysystem.dao.dataobject.ActivityDO;
import com.example.lotterysystem.dao.dataobject.ActivityPrizeDO;
import com.example.lotterysystem.dao.dataobject.ActivityUserDO;
import com.example.lotterysystem.dao.dataobject.PrizeDO;
import com.example.lotterysystem.dao.mapper.*;
import com.example.lotterysystem.service.ActivityService;
import com.example.lotterysystem.service.dto.ActivityDetailDTO;
import com.example.lotterysystem.service.dto.CreateActivityDTO;
import com.example.lotterysystem.service.enums.ActivityPrizeStatusEnum;
import com.example.lotterysystem.service.enums.ActivityPrizeTiersEnum;
import com.example.lotterysystem.service.enums.ActivityStatusEnum;
import com.example.lotterysystem.service.enums.ActivityUserStatusEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.View;import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;@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;@Autowiredprivate UserMapper userMapper;@Autowiredprivate PrizeMapper prizeMapper;@Autowiredprivate ActivityMapper activityMapper;@Autowiredprivate ActivityUserMapper activityUserMapper;@Autowiredprivate ActivityPrizeMapper activityPrizeMapper;@Autowiredprivate RedisUtil redisUtil;@Autowiredprivate View error;@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.setStaus(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)// 先获取奖品基本属性表// 获取需要查询的奖品idList<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);cacheActivity(detailDTO);// 构造返回CreateActivityDTO createActivityDTO = new CreateActivityDTO();createActivityDTO.setActivityId(activityDO.getId());return createActivityDTO;}// 缓存完整的活动信息 ActivityDetailDTOprivate void cacheActivity(ActivityDetailDTO detailDTO) {// key: ACTIVITY_12// value: ActivityDetailDTO(需先转为 json)if (null == detailDTO || null == detailDTO.getActivityId()) {logger.warn("要缓存的活动信息不存在!");return;}/*** 前面创建三张表时,遇到异常会进行回滚,但这里缓存完整活动信息时遇到异常是不需要回滚的* 因为即使 redis 没有缓存成功,当抽奖端查询活动信息时依然能根据前面创建好的三张表重新将活动缓存到 redis 中* 因此此处用 try catch 包住该段代码,使其即使抛出异常,也不会触发回滚*/try {redisUtil.set(ACTIVITY_PREFIX+detailDTO.getActivityId(),JacksonUtil.writeValueAsString(detailDTO),ACTIVITY_TIMEOUT);} catch (Exception e) {logger.error("缓存活动异常,ActivityDetailDTO={}", JacksonUtil.writeValueAsString(detailDTO), e);}}// 根据活动 id 从缓存中获取活动详细信息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;}}private ActivityDetailDTO convertToActivityDetailDTO(ActivityDO activityDO,List<ActivityUserDO> activityUserDOList,List<PrizeDO> prizeDOList,List<ActivityPrizeDO> activityPrizeDOList) {ActivityDetailDTO detailDTO = new ActivityDetailDTO();detailDTO.setActivityId(activityDO.getId());detailDTO.setActivityName(activityDO.getActivityName());detailDTO.setDesc(activityDO.getDescription());detailDTO.setStatus(ActivityStatusEnum.forName(activityDO.getStaus()));// apDO(活动关联奖品属性): {prizeId, amount, status}, {prizeId, amount, status}// pDO(奖品基础属性): {prizeId, name...}, {prizeId, name...}, {prizeId, name...}List<ActivityDetailDTO.PrizeDTO> prizeDTOList = activityPrizeDOList.stream().map(apDO -> {ActivityDetailDTO.PrizeDTO prizeDTO = new ActivityDetailDTO.PrizeDTO();prizeDTO.setPrizeId(apDO.getPrizeId());// Optional<> 防止对象存在空指针异常//.stream() 对 prizeDOList 进行流式处理,拿到每一个 prizeDO 和外层 apDO 的 id 去进行对比// .findFirst() 返回遇到的第一个 id 相等的对象Optional<PrizeDO> optionalPrizeDO = prizeDOList.stream().filter(prizeDO -> prizeDO.getId().equals(apDO.getPrizeId())).findFirst();// ifPresent:如果 PrizeDO 不为空,才执行该方法,不用自己写 if 判断 PrizeDO 是否为空了optionalPrizeDO.ifPresent(prizeDO -> {// 下面四个属性需要遍历 pDO,根据 apDO 中的 prizeId 和 pDO 中的 prizeId 找到对应的属性进行设置prizeDTO.setName(prizeDO.getName());prizeDTO.setImageUrl(prizeDO.getImageUrl());prizeDTO.setPrice(prizeDO.getPrice());prizeDTO.setDescription(prizeDO.getDescription());});prizeDTO.setTiers(ActivityPrizeTiersEnum.forName(apDO.getPrizeTiers()));prizeDTO.setPrizeAmount(apDO.getPrizeAmount());prizeDTO.setStatus(ActivityPrizeStatusEnum.forName(apDO.getStatus()));return prizeDTO;}).collect(Collectors.toList());detailDTO.setPrizeDTOList(prizeDTOList);// auDO 就是 activityUserDOList<ActivityDetailDTO.UserDTO> userDTOList = activityUserDOList.stream().map(auDO -> {ActivityDetailDTO.UserDTO userDTO = new ActivityDetailDTO.UserDTO();userDTO.setUserId(auDO.getUserId());userDTO.setUserName(auDO.getUserName());userDTO.setStatus(ActivityUserStatusEnum.forName(auDO.getStatus()));return userDTO;}).collect(Collectors.toList());detailDTO.setUserDTOList(userDTOList);return detailDTO;}// 校验活动有效性private void checkActivityInfo(CreateActivityParam param) {if(null == param) {throw new ServiceException(ServiceErrorCodeConstants.CREATE_ACTIVITY_INFO_IS_EMPTY);}// 人员 id 在人员表中是否存在List<Long> userIds = param.getActivityUserlist().stream().map(CreateUserByActivityParam::getUserId).distinct() // 去重.collect(Collectors.toList());// 假设传过去的参数为 1 2 3,若人员表中只存在 1 2,则只返回 1 2List<Long> existUserIds = userMapper.selectExistByIds(userIds);userIds.forEach(id -> {// 若 userMapper 返回的人员表参数中不包含某个创建的活动关联人员id,则抛出异常if (!existUserIds.contains(id)) {throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_USER_ERROR);}});// 奖品 id 在奖品表中是否存在List<Long> prizeIds = param.getActivityPrizelist().stream().map(CreatePrizeByActivityParam::getPrizeId).distinct().collect(Collectors.toList());List<Long> existPrizeIds = prizeMapper.selectExistByIds(prizeIds);prizeIds.forEach(id -> {if (!existPrizeIds.contains(id)) {throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_PRIZE_ERROR);}});// 人员数量是否大于等于奖品数量int userAmount = param.getActivityUserlist().size();long prizeAmount = param.getActivityPrizelist().stream().mapToLong(CreatePrizeByActivityParam::getPrizeAmount).sum();if (userAmount < prizeAmount) {throw new ServiceException(ServiceErrorCodeConstants.USER_PRIZE_AMOUNT_ERROR);}// 校验活动奖品等级有效性(看传入参数是否为一等二等三等奖)param.getActivityPrizelist().forEach(prize -> {if (null == ActivityPrizeTiersEnum.forName(prize.getPrizeTiers())) {throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_PRIZE_TIERS_ERROR);}});}
}
插件:GenerateAllSetter 快速生成 Set 方法
活动/活动奖品/活动人员状态/奖品等级 枚举类(Enum)
java">package com.example.lotterysystem.service.enums;import lombok.AllArgsConstructor;
import lombok.Getter;// 活动状态
@AllArgsConstructor
@Getter
public enum ActivityStatusEnum {RUNNING(1, "活动进行中"),COMPLETED(2, "活动已完成");private final Integer code;private final String message;public static ActivityStatusEnum forName(String name) {for (ActivityStatusEnum activityStatusEnum : ActivityStatusEnum.values()) {if (activityStatusEnum.name().equalsIgnoreCase(name)) {return activityStatusEnum;}}return null;}
}package com.example.lotterysystem.service.enums;import lombok.AllArgsConstructor;
import lombok.Getter;// 活动奖品状态
@AllArgsConstructor
@Getter
public enum ActivityPrizeStatusEnum {INIT(1, "初始状态"),COMPLETED(2, "已被抽取");private final Integer code;private final String message;public static ActivityPrizeStatusEnum forName(String name) {for (ActivityPrizeStatusEnum activityPrizeStatusEnum : ActivityPrizeStatusEnum.values()) {if (activityPrizeStatusEnum.name().equalsIgnoreCase(name)) {return activityPrizeStatusEnum;}}return null;}
}package com.example.lotterysystem.service.enums;import lombok.AllArgsConstructor;
import lombok.Getter;// 活动人员状态
@AllArgsConstructor
@Getter
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().equalsIgnoreCase(name)) {return activityUserStatusEnum;}}return null;}
}package com.example.lotterysystem.service.enums;import lombok.AllArgsConstructor;
import lombok.Getter;// 奖品等级
@AllArgsConstructor
@Getter
public enum ActivityPrizeTiersEnum {FIRST_PRIZE(1, "一等奖"),SECOND_PRIZE(2, "二等奖"),THIRD_PRIZE(3, "三等奖");private final Integer code;private final String message;public static ActivityPrizeTiersEnum forName(String name) {for (ActivityPrizeTiersEnum activityPrizeTiersEnum : ActivityPrizeTiersEnum.values()) {if (activityPrizeTiersEnum.name().equalsIgnoreCase(name)) {return activityPrizeTiersEnum;}}return null;}
}
ActivityDetailDTO
java">package com.example.lotterysystem.service.dto;import com.example.lotterysystem.service.enums.ActivityPrizeStatusEnum;
import com.example.lotterysystem.service.enums.ActivityPrizeTiersEnum;
import com.example.lotterysystem.service.enums.ActivityStatusEnum;
import com.example.lotterysystem.service.enums.ActivityUserStatusEnum;
import lombok.Data;import java.math.BigDecimal;
import java.util.List;@Data
public class ActivityDetailDTO {// 活动信息// 活动idprivate 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;@Datapublic static class PrizeDTO {// 奖品idprivate 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);}}@Datapublic static class UserDTO {// 用户idprivate Long userId;// 姓名private String userName;// 状态private ActivityUserStatusEnum status;// 通过该方法判断当前人员是否中奖public Boolean valid() {return status.equals(ActivityUserStatusEnum.INIT);}}
}
ServiceErrorCodeConstants
java">// package com.example.lotterysystem.common.errorcode;// ------ 活动模块错误码 ------ErrorCode CREATE_ACTIVITY_INFO_IS_EMPTY = new ErrorCode(300, "创建的活动信息为空!");ErrorCode ACTIVITY_USER_ERROR = new ErrorCode(301, "活动关联的人员不存在!");ErrorCode ACTIVITY_PRIZE_ERROR = new ErrorCode(302, "活动关联的奖品不存在!");ErrorCode USER_PRIZE_AMOUNT_ERROR = new ErrorCode(303, "活动关联的人员数量必须等于或大于奖品数量!");ErrorCode ACTIVITY_PRIZE_TIERS_ERROR = new ErrorCode(304, "活动奖品等级设置错误!");
Dao 层接口设计
ActivityMapper
java">package com.example.lotterysystem.dao.mapper;import com.example.lotterysystem.dao.dataobject.ActivityDO;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;@Mapper
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);
}
ActivityDO
java">package com.example.lotterysystem.dao.dataobject;import lombok.Data;
import lombok.EqualsAndHashCode;@Data
@EqualsAndHashCode(callSuper = true)
public class ActivityDO extends BaseDO{// 活动名称private String activityName;// 活动描述private String description;// 活动状态private String staus;}
ActivityPrizeMapper
java">package com.example.lotterysystem.dao.mapper;import com.example.lotterysystem.dao.dataobject.ActivityPrizeDO;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Param;import java.util.List;@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' 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);}
ActivityPrizeDO
java">package com.example.lotterysystem.dao.dataobject;import lombok.Data;
import lombok.EqualsAndHashCode;@Data
@EqualsAndHashCode(callSuper = true)
public class ActivityPrizeDO extends BaseDO{// 关联的活动idprivate Long activityId;// 关联的奖品idprivate Long prizeId;// 奖品数量private Long prizeAmount;// 奖品等级private String prizeTiers;// 奖品状态private String status;
}
PrizeMapper 新增接口
java">// package com.example.lotterysystem.dao.mapper;@Select("<script>" +" select id from prize" +" where id in" +" <foreach item='item' collection='items' open='(' separator=',' close=')'>" +" #{item}" +" </foreach>" +" </script>")List<Long> selectExistByIds(@Param("items") List<Long> ids);@Select("<script>" +" select * from prize" +" where id in" +" <foreach item='item' collection='items' open='(' separator=',' close=')'>" +" #{item}" +" </foreach>" +" </script>")List<PrizeDO> batchSelectByIds(@Param("items") List<Long> ids);
ActivityUserMapper
java">package com.example.lotterysystem.dao.mapper;import com.example.lotterysystem.dao.dataobject.ActivityUserDO;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Param;import java.util.List;@Mapper
public interface ActivityUserMapper {@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);
}
ActivityUserDO
java">package com.example.lotterysystem.dao.dataobject;import lombok.Data;
import lombok.EqualsAndHashCode;@Data
@EqualsAndHashCode(callSuper = true)
public class ActivityUserDO extends BaseDO{// 关联的活动idprivate Long activityId;// 关联的人员idprivate Long userId;// 姓名private String userName;// 关联人员状态private String status;
}
Postman 测试
先启动 redis 服务
使用无效的人员数据进行测试:
使用无效的奖品数据测试:
使用抽奖人员比奖品少的数据进行测试:
使用有效数据测试:
查看数据库
活动表里存放了新建的活动:
活动奖品关联表中存放了活动关联的奖品:
活动人员关联表中存放了活动关联的人员:
查看 Redis,存放了新建的活动
活动创建页面前端实现
在创建活动之前,需要先查询到奖品列表和用户列表才能提供给前端进行圈选
// create-activity.html<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>创建抽奖活动</title><link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/4.5.2/css/bootstrap.min.css"><link rel="stylesheet" href="./css/base.css"><link rel="stylesheet" href="./css/toastr.min.css"><style>body {font-family: Arial, sans-serif;background-color: #fff;margin: 0;padding: 0;}.container {max-width: 800px;margin: 30px auto;padding: 20px 30px;background-color: #fff;}.prize-checkbox {margin-bottom: 10px;}.modal {display: none; /* 初始状态下模态框不可见 */position: fixed;z-index: 1;left: 0;top: 0;bottom: 0;right: 0;overflow: hidden;overflow-y: auto;background-color: rgba(0, 0, 0, 0.1);}.modal-content {background-color: #fefefe;margin: 5% auto;padding: 20px;border: 1px solid #888;width: 610px;}.modal-content h2{display: flex;justify-content: space-between;align-items: center;font-weight: 600;font-size: 18px;color: #000000;height: 50px;border-bottom: 1px solid #DEDEDE;margin-bottom: 30px;}#prizesContainer{height: 406px;margin-bottom: 40px;overflow-y: auto;padding: 0 26px;}.close {color: #000;float: right;font-size: 28px;cursor: pointer;}.close:hover,.close:focus {color: black;text-decoration: none;cursor: pointer;}.prize-item,.user-item {display: flex;align-items: center;margin-bottom: 10px;justify-content: center;}.custom-p {font-size: 16px;/* 设置字体粗细 */font-weight: bold;margin-right: 90px; /* 右侧外边距 */}.prize-item input[type="checkbox"],.user-item input[type="checkbox"] {margin-right: 99px;}.prize-item label,.user-item label {margin-right: 11px;margin-bottom: 0;width: 200px;}.prize-item select {margin-left: auto; /* 将下拉选择框放置在末尾 */}.prize-item .form-control {width: 96px;height: 36px;line-height: 36px;margin-right: 56px;}.h-title{font-weight: 600;font-size: 30px;letter-spacing: 1px;color: #000000;line-height: 50px;text-align: center;margin-bottom: 40px;}.desc-row{margin-bottom: 60px;}.form-btn-box{display: flex;align-items: center;justify-content: center;}.form-btn-box button{width: 148px;height: 48px;}.pre-btn{margin-right: 20px;}</style>
</head>
<body>
<div class="container"><h2 class="h-title">创建抽奖活动</h2><form id="activityForm"><div class="form-group"><label for="activityName">活动名称</label><input type="text" placeholder="请输入活动名称" class="form-control" class="form-control" id="activityName" name="activityName" required></div><div class="form-group desc-row"><label for="description">活动描述</label><textarea id="description" placeholder="请输入活动描述" rows="5" cols="33" class="form-control" name="description" required></textarea></div><div class="form-btn-box"><button id="buttonPrizes" type="button" class="btn btn-primary pre-btn" onclick="showPrizesModal()">圈选奖品</button><button id="buttonUsers" type="button" class="btn btn-primary pre-btn" onclick="showUsersModal()">圈选人员</button><button type="submit" class="btn btn-primary" id="createActivity">创建活动</button></div></form>
</div>
<!-- toast提示 -->
<div class="toast"></div>
<!-- 奖品选择模态框 -->
<div id="prizesModal" class="modal"><div class="modal-content"><h2>奖品列表<span class="close" onclick="hidePrizesModal()">×</span></h2><div class="prize-item"><p class="custom-p">勾选</p><p class="custom-p">奖品名</p><p class="custom-p">数量</p><p class="custom-p">奖品等级</p></div><div id="prizesContainer"><!-- 奖品列表将动态插入这里 --></div><div class="form-btn-box"><button type="button" class="btn btn-secondary pre-btn" onclick="hidePrizesModal()">取消</button><button type="button" class="btn btn-primary" onclick="submitPrizes()">确定</button></div></div>
</div>
<!-- 人员选择模态框 -->
<div id="usersModal" class="modal"><div class="modal-content"><h2>人员列表<span class="close" onclick="hideUsersModal()">×</span></h2><div id="usersContainer"><!-- 奖品列表将动态插入这里 --></div><div class="form-btn-box"><button type="button" class="btn btn-secondary pre-btn" onclick="hideUsersModal()">取消</button><button type="button" class="btn btn-primary" onclick="submitUsers()">确定</button></div></div>
</div><!-- JavaScript代码 -->
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery-validate/1.19.3/jquery.validate.min.js"></script>
<script src="./js/toastr.min.js"></script>
<script>var userToken = localStorage.getItem("user_token");// 初始时奖品列表为空,勾选的奖品会存放到这里var selectedPrizes = [];// 显示奖品选择模态框function showPrizesModal() {$('#prizesModal').css('display', 'block');}// 隐藏奖品选择模态框function hidePrizesModal() {$('#prizesModal').css('display', 'none');}// 获取奖品列表的函数function fetchPrizes() {$.ajax({url: '/prize/find-list',type: 'GET',dataType: 'json',data: { currentPage: 1, pageSize: 100},headers: {// jwt"user_token": userToken},success: function(result) {var prizes = result.data.records;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";//让父页面一起跳转}}});}// 提交奖品数据的函数function submitPrizes() {selectedPrizes = [];// 将选中的奖品信息存储在selectedPrizes$('.prize-item input[type="checkbox"]:checked').each(function() {var prizeId = +$(this).val();var prizeAmount = +$('input[name="quantity-' + prizeId + '"]').val();var prizeTiers = $('select[name="level-' + prizeId + '"]').val();selectedPrizes.push({prizeId: prizeId,prizeAmount: prizeAmount,prizeTiers: prizeTiers});});// 关闭模态框hidePrizesModal();// 修改按钮var nextButton = document.getElementById('buttonPrizes');if (selectedPrizes.length > 0) {nextButton.textContent = '圈选奖品(已选)';} else {nextButton.textContent = '圈选奖品';}}// 初始时人员列表为空var selectedUsers = [];// 显示人员选择模态框function showUsersModal() {$('#usersModal').css('display', 'block');}// 隐藏人员选择模态框function hideUsersModal() {$('#usersModal').css('display', 'none');}// 获取人员列表的函数function fetchUsers() {$.ajax({url: '/base-user/find-list',type: 'GET',dataType: 'json',data: { identity: 'NORMAL' },headers: {// jwt"user_token": userToken},success: function(result) {var users = result.data;var usersContainer = $('#usersContainer');usersContainer.empty(); // 清空当前人员列表users.forEach(function(user) {console.info(user);usersContainer.append($('<div class="user-item">').append(`<input type="checkbox" id="user-${user.userId}" name="user-${user.userId}" value="${user.userId}"><label for="user-${user.userId}">${user.userName}</label>`));});},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 submitUsers() {selectedUsers = [];// 将选中的奖品信息存储在selectedUsers$('.user-item input[type="checkbox"]:checked').each(function() {var userId = +$(this).val();var userName = $(this).next('label').text();selectedUsers.push({userId: userId,userName: userName});});// 关闭模态框hideUsersModal();// 修改按钮var nextButton = document.getElementById('buttonUsers');if (selectedUsers.length > 0) {nextButton.textContent = '圈选人员(已选)';} else {nextButton.textContent = '圈选人员';}}// 绑定表单提交事件$('#createActivity').click(function(event){event.stopPropagation()$('#activityForm').validate({rules:{activityName:"required",description:{required:true,}},messages:{activityName:"请输入活动名称",description:"请输入活动描述"},// 验证通过才会触发submitHandler:function(form){console.log('selectedPrizes',selectedPrizes)console.log('selectedUsers',selectedUsers)// 如果未选择奖品则进行toast提示if(selectedPrizes.length==0){alert('请至少选择一个奖品')return false}// 如果未选择人员则进行toast提示if(selectedUsers.length==0){alert('请至少选择一个人员, 人员数量应大于等于奖品总量')return false}// 获取提交表单信息var data = {activityName:'',description:'',activityPrizeList:[],activityUserList:[]}data.activityName = $('#activityName').val()data.description = $('#description').val()data.activityPrizeList = selectedPrizesdata.activityUserList = selectedUserssubmitActivity(data)}})})// 提交活动信息接口function submitActivity(data){$.ajax({url: '/activity/create',type: 'POST',contentType: 'application/json',data: JSON.stringify(data),headers: {// jwt"user_token": userToken},success: function(result) {if (result.code != 200) {alert("创建失败!" + result.msg);} else {alert("创建成功!");// 向父页面传值 https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessagewindow.parent.postMessage({from:'activities-list.html',id:'#activitiesList'},'*');}},error:function(err){console.log(err);if(err!=null && err.status==401){alert("用户未登录, 即将跳转到登录页!");// 跳转登录页window.location.href = "/blogin.html";window.parent.location.href = "/blogin.html";//让父页面一起跳转}}});}// 获取奖品/人员列表并填充模态框$(document).ready(function() {fetchPrizes();fetchUsers();});// 显示奖品选择模态框$(document).ready(function() {$('#activityForm').on('click', 'button圈选奖品', function() {showPrizesModal();});});// 显示人员选择模态框$(document).ready(function() {$('#activityForm').on('click', 'button圈选人员', function() {showUsersModal();});});
</script>
</body>
</html>
测试
2. 活动列表展示(翻页)
时序图
约定前后端交互接口
[请求] /activity/find-list?currentPage=1&pageSize=10 GET
{}
[响应]
{
}
Controller 层接口设计
java"> // 查询活动列表@RequestMapping("/activity/find-list")public CommonResult<FindActivityListResult> findActivityLIst(PageParam param) {logger.info("findActivityLIst PageParam:{}", JacksonUtil.writeValueAsString(param));return CommonResult.success(convertToFindActivityListResult(activityService.findActivityList(param)));}private FindActivityListResult convertToFindActivityListResult(PageListDTO<ActivityDTO> activityList) {if (null == activityList) {throw new ControllerException(ControllerErrorCodeConstants.FIND_ACTIVITY_LIST_ERROR);}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">package com.example.lotterysystem.controller.result;import lombok.Data;import java.io.Serializable;
import java.util.List;// 查询活动列表
@Data
public class FindActivityListResult implements Serializable {// 奖品总量private Integer total;// 当前列表private List<ActivityInfo> records;@Datapublic static class ActivityInfo implements Serializable {// 活动idprivate Long activityId;// 活动名称private String activityName;// 活动描述private String description;// 活动是否有效private Boolean valid;}
}
Service 层接口设计
java">// package com.example.lotterysystem.service;// 翻页查询活动(摘要)列表PageListDTO<ActivityDTO> findActivityList(PageParam param);
ActivityDTO
java">package com.example.lotterysystem.service.dto;import com.example.lotterysystem.service.enums.ActivityStatusEnum;
import lombok.Data;@Data
public class ActivityDTO {// 活动idprivate Long activityId;// 活动名称private String activityName;// 活动描述private String description;// 活动状态private ActivityStatusEnum status;// 判断当前的活动是否有效public Boolean valid() {return status.equals(ActivityStatusEnum.RUNNING);}
}
接口实现
java">// package com.example.lotterysystem.service.impl;@Overridepublic PageListDTO<ActivityDTO> findActivityList(PageParam param) {// 获取总量int total = activityMapper.count();// 获取当前页列表List<ActivityDO> activityDOList = activityMapper.selectActivityList(param.offset(), param.getPageSize());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;}).collect(Collectors.toList());return new PageListDTO<>(total, activityDTOList);}
Dao 层接口设计
java">// package com.example.lotterysystem.dao.mapper;// 查询总量@Select("select count(1) from activity")int count();/*** 先根据 id 进行降序排序,然后根据偏移量获取数据库中对应大小的数据* 通过 list 返回** @param offset* @param pageSize* @return*/@Select("select * from activity order by id desc limit #{offset}, #{pageSize}")List<ActivityDO> selectActivityList(@Param("offset") Integer offset, @Param("pageSize") Integer pageSize);
Postman 测试
tip:测试时别忘了在拦截器中忽略该路径
活动列表页前端实现
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>活动列表</title><link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/bootstrap/4.5.2/css/bootstrap.min.css"><link rel="stylesheet" href="./css/base.css"><style>body {font-family: Arial, sans-serif;background-color: #f2f2f2;}.activity-list {padding:0 30px;}#activities{height: calc(100vh - 134px);overflow-y: auto;padding-right: 10px;}.activity-item {display: flex;align-items: center;justify-content: space-between;background-color: #f7f7f7;padding: 24px;border-radius: 4px;overflow: hidden;margin-bottom: 10px;border-radius: 8px;padding-bottom: 12px;}.activity-info{width: calc(100% - 120px);}.activity-info h4{width: 100%;font-weight: 600;font-size: 15px;color: #000000;margin-bottom: 4px;}.activity-info p{font-weight: 400;font-size: 14px;color: #666666;margin: 0;line-height: 28px;}.active a{font-weight: 400;font-size: 15px;color: red;margin-bottom: 0;display: block;width: 250px;}.inactive a{font-weight: 400;font-size: 15px;color: gray;margin-bottom: 0;display: block;width: 250px;}.pagination {display: flex;justify-content: flex-end;margin-top: 18px;padding-right: 16px;}.pagination button {margin: 0 5px; /* 按钮之间的间距保持不变 */border-radius: 5px; /* 设置圆角为20像素,可以根据需要调整 */border: 1px solid #007bff;background-color: #fff;padding: 0px 8px; /* 可以添加一些内边距,使按钮看起来更饱满 */cursor: pointer; /* 将鼠标光标改为指针形状,提升用户体验 */font-size: 13px;}.pagination span{margin: 0 10px;font-size: 14px;}.pagination input{width: 80px;text-align: center;}.activity-list h2 {font-weight: 600;font-size: 18px;color: #000000;height: 70px;display: flex;align-items: center;margin-bottom: 0;}</style>
</head>
<body style="background-color: white">
<div class="activity-list"><h2>活动列表</h2><div id="activities"><!-- 活动列表将动态插入这里 --></div><div class="pagination"><button class="btn-outline-primary" onclick="fetchActivities(1)">首页</button><button class="btn-outline-primary" onclick="previousPage()">上一页</button><span>第 <input type="number" id="pageInput" min="1" value="1" /> 页</span><button class="btn-outline-primary" onclick="nextPage()">下一页</button><button class="btn-outline-primary" onclick="fetchActivities(totalPages)">尾页</button></div>
</div><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,则重置为1if (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');// 在添加新内容前,先清空listContainerlistContainer.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>
</body>
</html>
测试
3. 查询活动详情接口
时序图
约定前后端交互接口
[请求] /activity-detail/find?activityId=24 GET
[响应]
{
"code": 200,
"data": {
"activityId": 1,
"activityName": "测试活动抽奖",
"desc": "测试活动抽奖",
"valid": true,
"prizeDTOList": [
{
"prizeId": 2,
"name": "手机",
"imageUrl": "0a767457-513d-4b02-bdb4-fbf7f57b5b8c.png",
"price": 5000.00,
"description": "手机",
"tiers": "一等奖",
"prizeAmount": 1,
"valid": true
},
{
"prizeId": 3,
"name": "吹风机",
"imageUrl": "72f4d9a5-b7c8-48eb-b26e-39d2c685beb6.png",
"price": 200.00,
"description": "吹风机",
"tiers": "二等奖",
"prizeAmount": 2,
"valid": true
}
],
"userDTOList": [
{
"userId": 4,
"userName": "郭靖",
"valid": true
},
{
"userId": 5,
"userName": "黄蓉",
"valid": true
},
{
"userId": 6,
"userName": "杨康",
"valid": true
}
]
},
"msg": ""
}
Controller 层
ActivityController 新增
java"> // 查询活动详情@RequestMapping("/activity-detail/find")private CommonResult<GetActivityDetailResult> getActivityDetail(Long activityId) {logger.info("getActivityDetail activityId:{}", activityId);ActivityDetailDTO detailDTO = activityService.getActivityDetail(activityId);return CommonResult.success(convertToGetActivityDetailResult(detailDTO));}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.setDesc(detailDTO.getDesc());result.setValid(detailDTO.valid());// 抽奖顺序:一等奖、二等奖、三等奖result.setPrizeDTOList(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.setTiers(prizeDTO.getTiers().getMessage());prize.setPrizeAmount(prizeDTO.getPrizeAmount());prize.setValid(prizeDTO.valid());return prize;}).collect(Collectors.toList()));result.setUserDTOList(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">package com.example.lotterysystem.controller.result;import com.example.lotterysystem.service.enums.ActivityPrizeTiersEnum;import lombok.Data;import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;@Data
public class GetActivityDetailResult implements Serializable {// 活动信息// 活动idprivate Long activityId;// 活动名称private String activityName;// 活动描述private String desc;// 活动是否有效private Boolean valid;// 奖品信息(列表)private List<Prize> prizeDTOList;// 人员信息(列表)private List<User> userDTOList;@Datapublic static class Prize {// 奖品idprivate Long prizeId;// 奖品名private String name;// 图片索引private String imageUrl;// 价格private BigDecimal price;// 描述private String description;// 奖品等级private String tiers;/*** 奖品数量* @see ActivityPrizeTiersEnum#getMessage()*/private Long prizeAmount;// 奖品是否被抽取private Boolean valid;}@Datapublic static class User {// 用户idprivate Long userId;// 姓名private String userName;// 该人员是否抽中private Boolean valid;}
}
ControllerErrorCodeConstants
java">// package com.example.lotterysystem.common.errorcode;ErrorCode GET_ACTIVITY_DETAIL_ERROR = new ErrorCode(302, "查询活动详细信息失败!");
Service 层
ActivityService 新增接口
java">// package com.example.lotterysystem.service;// 获取活动详细属性ActivityDetailDTO getActivityDetail(Long activityId);
接口实现
java"> @Overridepublic ActivityDetailDTO getActivityDetail(Long activityId) {if (null == activityId) {logger.warn("查询活动详细信息失败,因为 activityId 为空!");return null;}// 查询 redisActivityDetailDTO 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);// 查奖品表,还需要奖品的其他属性,这些属性在活动奖品表中没有,因此需要查奖品表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 层代码
ActivityMapper
java">// package com.example.lotterysystem.dao.mapper;@Select("select * from activity where id = #{id}")ActivityDO selectById(@Param("id") Long id);
ActivityUserMapper
java">// package com.example.lotterysystem.dao.mapper;@Select("select * from activity_user where activity_id = #{activityId}")List<ActivityUserDO> selectByActivityId(@Param("activityId") Long activityId);
ActivityPrizeMapper
java">// package com.example.lotterysystem.dao.mapper;@Select("select * from activity_prize where activity_id = #{activityId}")List<ActivityPrizeDO> selectByActivityId(@Param("activityId") Long activityId);