Spring项目-抽奖系统(实操项目-用户管理接口)(THREE)
^__^
(oo)\______
(__)\ )\/\
||----w |
|| ||
一、:前言:
登录接口博客链接:Spring项目-抽奖系统(实操项目-登录接口)(TWO)-CSDN博客
写完注册和登录接口后,接下来就做抽奖奖品、抽奖活动相应的创建展示过程。
UI页面如下:
接下俩将一一完成下列这些接口。
二:用户管理:
2.1:人员列表展示:
我们在参与抽奖的人员列表中,必须能够做到展示所有抽奖人员,时序图如下:

2.1.1:Controller层:
该层需要注意:
1.参数identity(表示具体要展示什么身份),当不传时,依旧可以访问,只要JWT令牌通过!!
2.当传入参数时,需要判断传入的身份是否在预知范围内,用UserIdentityEnum进行简单判断
@RequestMapping("/base-user/find-list")
public CommonResult<List<UserBaseInfoResult>> findUserBaseInfoList(String identity) {
log.info("findUserBaseInfoList FindUsersParam:{}",identity);
List<UserDTO> userDTOList = null;
if(!StringUtils.hasText(identity)) {
userDTOList = userService.findUserList(null);
}else if(UserIdentityEnum.forname(identity) != null) {
userDTOList = userService.findUserList(identity);
}
return CommonResult.succcess(covertToFindUserListResult(userDTOList));
}
private List<UserBaseInfoResult> covertToFindUserListResult(List<UserDTO> userDTOList) {
if(CollectionUtil.isEmpty(userDTOList)) {
return Arrays.asList();
}
return userDTOList.stream().map(userDTO -> {
UserBaseInfoResult userBaseInfoResult = new UserBaseInfoResult();
userBaseInfoResult.setUserName(userDTO.getUserName());
userBaseInfoResult.setIdentity(userDTO.getIdentity());
userBaseInfoResult.setUserId(userDTO.getUserId());
return userBaseInfoResult;
}).collect(Collectors.toList());
}
2.1.2:Service层:
@Service
public interface UserService {
/**
* 查找用户列表
*
*/
List<UserDTO> findUserList(String identity);
}
ServiceImpl层:
@Override
public List<UserDTO> findUserList(String identity) {
/* if(param == null) {
throw new ServiceException(ServiceErrorCodeConstatns.IDENTITY_ERROR);
}*/
List<UserDO> userDOList = userMapper.selectUserListByIdentity(identity);
return userDOList.stream().map(userDO -> {
UserDTO userDTO = new UserDTO();
userDTO.setUserId(userDO.getId());
userDTO.setUserName(userDO.getUserName());
userDTO.setIdentity(userDO.getIdentity());
userDTO.setEmail(userDO.getEmail());
userDTO.setPhoneNumber(userDO.getPhoneNumber().getPhoneNumber());
return userDTO;
}).collect(Collectors.toList());
}
2.1.3:Dao层:
由于identity不是必传项,因此此次需要用到动态sql,选用mybaits的xml形式实现动态sql:
具体的实现方法如下:
步骤一:
设置配置项:
# 配置 mybatis xml 的⽂件路径,在 resources/mapper 创建所有表的 xml ⽂件
mybatis.mapper-locations=classpath:mapper/**Mapper.xml
步骤二:
创建mapper文件与xxxMapper.xml文件;
步骤三:
填写xml文件中基础代码:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.lotterysystemplus.dao.mapper.UserMapper">
<select id="selectUserListByIdentity" resultType="com.example.lotterysystemplus.dao.dataobject.UserDO">
select * from user
<if test="identity != null">
where identity = #{identity}
</if>
order by id desc
</select>
</mapper>
步骤五:
编写UserMapper包中的相关代码:
List<UserDO> selectUserListByIdentity(@Param("identity") String identity);
三:奖品模块:
写完人员管理模块之后,就开始写奖品模块,
奖品模块分为两个板块:
1.设置奖品模块
2.奖品展示模块
UI界面如下:
首先编写创建奖品模块:
3.1:创建奖品:
首先写保存图片相关接口:
添加配置项:
## 图⽚服务 ##
pic.local-path=D:/PIC
# spring boot3 升级配置名
spring.web.resources.static-locations=classpath:/static/,file:${pic.local-path}
3.1.1:controller层:
@RequestMapping("/pic/upload")
public String upLodePic(MultipartFile file) {
return pictureService.savePicture(file);
}
3.1.2:Service层:
存放图片的服务是为了后期创建奖品时发挥作用!!
创建PictureService:
public interface PictureService {
/**
* 保存图片
* @param pic
* @return
*/
String savePicture(MultipartFile pic);
}
3.1.3:创建PictureServiceImpl:
@Service
public class PictureServiceImpl implements PictureService {
/**
* 设置图片存放路径
*/
@Value("${pic.local-path}")
private String picLocalPath;
@Override
public String savePicture(MultipartFile pic) {
File dir = new File(picLocalPath);
if(!dir.exists()) {
dir.mkdirs();
}
//获取文件名
String fileName = pic.getOriginalFilename();
assert fileName != null;
//获取文件后缀名
String suffixName = fileName.substring(fileName.lastIndexOf("."));
//生成随机数防止图片重名
fileName = UUID.randomUUID()+suffixName;
try {
//设置图片路径
pic.transferTo(new File(picLocalPath+"/"+fileName));
} catch (IOException e) {
throw new RuntimeException(e);
}
return fileName;
}
}
3.2:创建奖品相关接口:
3.2.1:controller层:
/**
* RequestPart:用于接收表单数据 multipart/form-data
* 创建奖品
* @param param
* @param picFile
* @return
*/
@RequestMapping("/prize/create")
public CommonResult<Integer> createPrize(@RequestPart("param") @Valid CreatePrizeParam param,
@RequestPart("prizePic") MultipartFile picFile) {
log.info("createPrize CreatePrizeParam:{} MultipartFile:{}",
JacksonUtil.writeValueAsString(param),picFile);
return CommonResult.succcess(prizeService.createPrize(param,picFile));
}
注意:
@RequestPart注解用于form-data格式的发送,但是对于文件的上传只有一个@RequestPart是远远不够的,还需要加一个序列化与发序列化的转化器:
如果没有这个转换器,当我们在前端测试时,就会报此类错误:
大概意思就是:
前端给我们传过来的是数据类型是application/octet-stream,但是由于我们写了@RequestPart注解,希望接收到的是form-data表单格式数据,因此报错!!
所以对于文件的传输前端传过来的默认是octet-stream格式,我们需要手动写一个转换器,代码如下:
/**
* 实现二进制到对象的转换
*/
@Component
public class MultipartJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {
protected MultipartJackson2HttpMessageConverter(ObjectMapper objectMapper)
{
// MediaType.APPLICATION_OCTET_STREAM 表⽰这个转换器⽤于处理⼆进制流数据,通常⽤于⽂件上传。
super(objectMapper, MediaType.APPLICATION_OCTET_STREAM);
}
@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return false;
}
@Override
public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) {
return false;
}
@Override
protected boolean canWrite(MediaType mediaType) {
return false;
}
}
3.2.2:service层:
@Service
public interface PrizeService {
Integer createPrize(CreatePrizeParam param, MultipartFile file);
}
3.2.3:serviceimpl层:
service层的具体实现。
@Service
public class PrizeServiceImpl implements PrizeService {
@Autowired
private PrizeMapper prizeMapper;
@Autowired
private PictureService pictureService;
@Override
public Integer createPrize(CreatePrizeParam param, MultipartFile file) {
String fileName = pictureService.savePicture(file);
PrizeDO prizeDO = new PrizeDO();
prizeDO.setName(param.getPrizeName());
prizeDO.setDescription(param.getDescription());
prizeDO.setPrice(param.getPrice());
prizeDO.setImageUrl(fileName);
prizeMapper.insert(prizeDO);
return prizeDO.getId();
}
}
3.3:展示奖品+翻页功能:
将创建好的奖品进行展示,从数据库获取信息,发送给前端进行展示,并且带有翻页功能:
翻页功能我们通过sql中的limit语句进行操作。需要传入两个参数:offset、pagesize完相应的功能。
3.3.1:controller层:
@RequestMapping("/prize/find-list")
public CommonResult<PrizeFindListResult> prizeFindList(@Valid PageListParam param) {
log.info("prizeFindList PageListParam:{}",
JacksonUtil.writeValueAsString(param));
PrizeListDTO<PrizeDTO> prizeDTOList = prizeService.findPrizeList(param);
return CommonResult.succcess(convertToPrizeList(prizeDTOList));
}
3.3.2:service层:
@Service
public interface PrizeService {
Integer createPrize(CreatePrizeParam param, MultipartFile file);
PrizeListDTO<PrizeDTO> findPrizeList(PageListParam param);
}
3.3.3: serviceImpl层:
service具体实现:
@Override
public PrizeListDTO<PrizeDTO> findPrizeList(PageListParam param) {
int count = prizeMapper.count();
List<PrizeDO> prizeDOList = prizeMapper.queryPrizeByPage(param.offset(), param.getPageSize());
List<PrizeDTO> prizeDTOList = new ArrayList<>();
for (PrizeDO prizeDO:prizeDOList) {
PrizeDTO prizeDTO = new PrizeDTO();
prizeDTO.setId(prizeDO.getId());
prizeDTO.setName(prizeDO.getName());
prizeDTO.setDescription(prizeDO.getDescription());
prizeDTO.setPrice(prizeDO.getPrice());
prizeDTO.setImageUrl(prizeDO.getImageUrl());
prizeDTOList.add(prizeDTO);
}
return new PrizeListDTO<>(count,prizeDTOList);
}
3.3.4:Dao层:
@Select("select count(1) from prize")
int count();
@Select("select * from prize order by id desc limit #{offset} , #{pageSize}")
List<PrizeDO> queryPrizeByPage(@Param("offset") Integer offset,
@Param("pageSize") Integer pageSize);
四:活动模块:
创建完奖品之后,接下俩就需要设置好活动内容,最后开始仅从抽奖:
4.1:创建活动:
前后端约定:
[ 请求 ] /activity/create POST{"activityName": " 抽奖测试 ","description": " 年会抽奖活动 ","activityPrizeList": [{"prizeId": 13,"prizeAmount": 1,"prizeTiers": "FIRST_PRIZE"},"prizeId": 12,"prizeAmount": 1,"prizeTiers": "SECOND_PRIZE"}],"activityUserList": [{"userId": 25,"userName": " 郭靖 "},{"userId": 23,"userName": " 杨康 "}]}[ 响应 ]{"code": 200,"data": {"activityId": 23},"msg": ""}
4.1.1:controller层:
有了前后端约定,就可以直接定义后端controller接口,接收类型、返回类型都表达的非常清楚:
public class ActivityController {
private static final Logger logger = LoggerFactory.getLogger(ActivityController.class);
@Resource
private ActivityService createActivityService;
/**
* 创建活动
* @param param
* @return
*/
@RequestMapping("/activity/create")
public CommonResult<Long> createActivity(@Valid @RequestBody CreateActivityParam param) {
logger.info("createActivity CreateActivityParam: " +
JacksonUtil.writeValueAsString(param));
CreateActivityDTO createActivit = createActivityService.createActivity(param);
return CommonResult.succcess(createActivit.getActivityid());
}
}
注:大家也可以根据自己的喜好可以对返回值在进行一层封装和判断!!
4.1.2: service层:
@Service
public interface ActivityService {
public CreateActivityDTO createActivity(CreateActivityParam param);
}
serviceImpl:
需要注意一个非常重要的一点:
我们需要将创建好的数据放入redis缓存当中,提高后期抽奖后活动奖品展示的效率!!
service层接口的具体实现:
@Override
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<CreateActivityParam.CreatePrizeByActivityParam> createActivityParams = param.getActivityPrizeList();
List<ActivityPrizeDO> activityPrizeDOList = new ArrayList<>();
createActivityParams.forEach(item->{
ActivityPrizeDO activityPrizeDO = new ActivityPrizeDO();
activityPrizeDO.setActivityId(activityDO.getId());
activityPrizeDO.setPrizeId(item.getPrizeId());
activityPrizeDO.setPrizeAmount(item.getPrizeAmount());
activityPrizeDO.setPrizeTiers(item.getPrizeTiers());
activityPrizeDO.setStatus(ActivityPrizeStatusEnum.INIT.name());
activityPrizeDOList.add(activityPrizeDO);
});
activityPrizeMapper.batchInsert(activityPrizeDOList);
//关联人员信息,并保存至数据库
List<CreateActivityParam.CreateUserByActivityParam> createUserByActivityParams = param.getActivityUserList();
List<ActivityUserDO> activityUserDOList = new ArrayList<>();
createUserByActivityParams.forEach(item->{
ActivityUserDO activityUserDO = new ActivityUserDO();
activityUserDO.setActivityId(activityDO.getId());
activityUserDO.setUserId(item.getUserId());
activityUserDO.setUserName(item.getUserName());
activityUserDO.setStatus(ActivityUserStatusEnum.INIT.name());
activityUserDOList.add(activityUserDO);
});
activityUserMapper.batchInsert(activityUserDOList);
//查询奖品信息列表
//使用stream中的map方法映射:提取ActivityPrizeDO类型中的每个prizeid映射,并且使用distinct方法去重
List<Long> prizeIds = activityPrizeDOList.stream()
.map(ActivityPrizeDO::getPrizeId)
.distinct().toList();
List<PrizeDO> prizeDOList = prizeMapper.batchSelectByIds(prizeIds);
//将活动以及关联的奖品缓存至redis
cacheActivity(convertToActivityDetilDTO(activityDO,activityPrizeDOList,activityUserDOList,prizeDOList));
//返回活动id
CreateActivityDTO createActivityDTO = new CreateActivityDTO();
createActivityDTO.setActivityid(activityDO.getId());
return createActivityDTO;
}
//校验活动信息
private void checkActivityInfo(CreateActivityParam param) {
//判断创建的活动是否为空
if(param == null) {
throw new ServiceException(ServiceErrorCodeConstatns.CREATE_ACTIVITY_IS_EMPTY);
}
//校验所选人员id是否存在
List<Long> userIds = param.getActivityUserList()
.stream().map(CreateActivityParam.CreateUserByActivityParam::getUserId)
.distinct().toList();
List<Long> existUserIds = userMapper.selectByIdsList(userIds);
if(existUserIds == null) {
throw new ServiceException(ServiceErrorCodeConstatns.ACTIVITY_USER_ERROR);
}
userIds.forEach(id->{
if (!existUserIds.contains(id)) {
throw new ServiceException(ServiceErrorCodeConstatns.ACTIVITY_USER_ERROR);
}
});
//奖品id在奖品表中是否存在
List<Long> prizeIds = param.getActivityPrizeList().stream()
.map(CreateActivityParam.CreatePrizeByActivityParam::getPrizeId)
.distinct().toList();
List<Long> existPrizeIds = prizeMapper.selectByIdsList(prizeIds);
if(existPrizeIds == null) {
throw new ServiceException(ServiceErrorCodeConstatns.ACTIVITY_PRIZE_ERROR);
}
prizeIds.forEach(id->{
if (!existPrizeIds.contains(id)) {
throw new ServiceException(ServiceErrorCodeConstatns.ACTIVITY_PRIZE_ERROR);
}
});
//人员数量大于等于奖品数量
int userAmount = param.getActivityUserList().size();
long prizeAmount = param.getActivityPrizeList().stream()
.mapToLong(CreateActivityParam.CreatePrizeByActivityParam::getPrizeAmount)
.sum();
if (userAmount < prizeAmount) {
throw new ServiceException(ServiceErrorCodeConstatns.USER_PRIZE_AMOUNT_ERROR);
}
//活动奖品等级有效性
param.getActivityPrizeList().forEach(
prize->{
if(null == ActivityPrizeTiersEnum.forName(prize.getPrizeTiers())) {
throw new ServiceException(ServiceErrorCodeConstatns.ACTIVITY_PRIZE_TIERS_ERROR);
}
}
);
}
/**
* 将所获取的详细信息存放至redis缓存
* @param detailDTO
*/
private void cacheActivity(ActivityDetailDTO detailDTO) {
// key: ACTIVITY_+
// value: ActivityDetailDTO(json)
if(detailDTO == null || detailDTO.getActivityId() == null) {
log.error("要缓存的活动信息不存在");
return;
}
try {
redisUtil.set(ACTIVITY_PREFIX + detailDTO.getActivityId(),
JacksonUtil.writeValueAsString(detailDTO),
ACTIVITY_TIMEOUT);
}catch (Exception e) {
logger.error("缓存活动异常,ActivityDetailDTO={}",
JacksonUtil.writeValueAsString(detailDTO), e);
}
}
}
4.1.3:dao层:
与数据库进行直接接触的层:
mapper层代码实现:
@Mapper
public interface ActivityMapper {
@Insert("insert into activity (activity_name, description, status)" +
" values (#{activityName}, #{description}, #{status})")
@Options(useGeneratedKeys = true, keyProperty ="id", keyColumn ="id")
Integer insert( ActivityDO activityDO);
@Select("select count(1) from activity")
long count();
@Select("select * from activity order by id desc limit #{offset} , #{pageSize}")
List<ActivityDO> queryActivitiesByPage(@Param("offset") Long offset,
@Param("pageSize") Long pageSize);
}
4.2:展示活动:
前后端交互约定:
[ 请求 ] /activity/find-list?currentPage=1&pageSize=10 GET[ 响应 ]{"code": 200,"data": {"total": 10,"records": [{"activityId": 23,"activityName": " 抽奖测试 ","description": " 年会抽奖活动 ","valid": true},{"activityId": 22,"activityName": " 抽奖测试 ","description": " 年会抽奖活动 ","valid": true},{"activityId": 21,"activityName": " 节⽇抽奖 ","description": " ⽐特年会抽奖活动 ","valid": true}]},"msg": ""}
4.2.1:controller层:
@RequestMapping("/activity/find-list")
public CommonResult<FindActivityListResult> findActivityList(
@Valid PageListParam param) {
logger.info("findActivityList :{}",JacksonUtil.writeValueAsString(param));
PageListDTO<ActivityDTO> pageListDTO = createActivityService.findActivityList(param);
return CommonResult.succcess(covertToFindActivityList(pageListDTO));
}
private FindActivityListResult covertToFindActivityList(PageListDTO<ActivityDTO> pageListDTO) {
if(pageListDTO == null) {
throw new ControllerException(ControllerErrorCodeConstants.FIND_ACITVITY_LIST_ERROR);
}
FindActivityListResult activityListResult = new FindActivityListResult();
activityListResult.setTotal(pageListDTO.getTotal());
activityListResult.setRecords(
pageListDTO.getRecords().stream()
.map(item->{
FindActivityListResult.FindActivityInfo findActivityInfo = new FindActivityListResult.FindActivityInfo();
findActivityInfo.setActivityId(item.getActivityId());
findActivityInfo.setActivityName(item.getActivityName());
findActivityInfo.setDescription(item.getDescription());
findActivityInfo.setValid(item.valid());
return findActivityInfo;
}).collect(Collectors.toList())
);
return activityListResult;
}
}
4.2.2:service层:
需要注意的是,展示活动列表,获取活动信息还是从数据库中去获取,因为redis中的信息有设置过期时间,所以为了稳定去数据库直接拿!
当后期设计抽奖后展示详细信息时可以从redis中去快速拿!
@Service
public interface ActivityService {
PageListDTO<ActivityDTO> findActivityList(PageListParam param);
}
serviceimpl层:
/**
* 活动列表展示
* @return
*/
@Override
public PageListDTO<ActivityDTO> findActivityList(PageListParam param) {
//total总数
long count = activityMapper.count();
//获取活动列表
List<ActivityDO> activityDOList = activityMapper.queryActivitiesByPage(param.offset(),param.getPageSize());
List<ActivityDTO> activityDTOList = new ArrayList<>();
activityDOList.forEach(activityDO -> {
ActivityDTO activityDTO = new ActivityDTO();
activityDTO.setActivityId(activityDO.getId());
activityDTO.setActivityName(activityDO.getActivityName());
activityDTO.setDescription(activityDO.getDescription());
activityDTO.setStatus(ActivityStatusEnum.fromName(activityDO.getStatus()));
activityDTOList.add(activityDTO);
});
return new PageListDTO<>(count,activityDTOList);
}
4.2.3:dao层:
@Mapper
public interface ActivityMapper {
@Insert("insert into activity (activity_name, description, status)" +
" values (#{activityName}, #{description}, #{status})")
@Options(useGeneratedKeys = true, keyProperty ="id", keyColumn ="id")
Integer insert( ActivityDO activityDO);
@Select("select count(1) from activity")
long count();
@Select("select * from activity order by id desc limit #{offset} , #{pageSize}")
List<ActivityDO> queryActivitiesByPage(@Param("offset") Long offset,
@Param("pageSize") Long pageSize);
@Select("select * from activity where id = #{activityId}")
ActivityDO selectByActivityId(@Param("activityId") Long activityId);
}
接下来就是重头戏了,对于整个抽奖的设计!!