营销活动:获取商品近期参与的每个活动新增满减送和限时折扣活动

This commit is contained in:
puhui999 2023-12-24 00:32:17 +08:00
parent e8ade5f1fe
commit 60e684c455
8 changed files with 238 additions and 46 deletions

View File

@ -7,26 +7,33 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.promotion.controller.app.activity.vo.AppActivityRespVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountActivityDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
import cn.iocoder.yudao.module.promotion.service.bargain.BargainActivityService;
import cn.iocoder.yudao.module.promotion.service.combination.CombinationActivityService;
import cn.iocoder.yudao.module.promotion.service.discount.DiscountActivityService;
import cn.iocoder.yudao.module.promotion.service.reward.RewardActivityService;
import cn.iocoder.yudao.module.promotion.service.seckill.SeckillActivityService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import jakarta.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
@Tag(name = "用户 APP - 营销活动") // 用于提供跨多个活动的 HTTP 接口
@RestController
@ -40,6 +47,10 @@ public class AppActivityController {
private SeckillActivityService seckillActivityService;
@Resource
private BargainActivityService bargainActivityService;
@Resource
private DiscountActivityService discountActivityService;
@Resource
private RewardActivityService rewardActivityService;
@GetMapping("/list-by-spu-id")
@Operation(summary = "获得单个商品,近期参与的每个活动")
@ -64,45 +75,105 @@ public class AppActivityController {
if (CollUtil.isEmpty(spuIds)) {
return new ArrayList<>();
}
LocalDateTime now = LocalDateTime.now();
// 获取开启的且开始的且没有结束的活动
List<AppActivityRespVO> activityList = new ArrayList<>();
// 1. 拼团活动 - 获取开启的且开始的且没有结束的活动
List<CombinationActivityDO> combinationActivities = combinationActivityService.getCombinationActivityBySpuIdsAndStatusAndDateTimeLt(
spuIds, CommonStatusEnum.ENABLE.getStatus(), now);
if (CollUtil.isNotEmpty(combinationActivities)) {
combinationActivities.forEach(item -> {
activityList.add(new AppActivityRespVO().setId(item.getId())
.setType(PromotionTypeEnum.COMBINATION_ACTIVITY.getType()).setName(item.getName())
.setSpuId(item.getSpuId()).setStartTime(item.getStartTime()).setEndTime(item.getEndTime()));
});
}
// 2. 秒杀活动 - 获取开启的且开始的且没有结束的活动
List<SeckillActivityDO> seckillActivities = seckillActivityService.getSeckillActivityBySpuIdsAndStatusAndDateTimeLt(
spuIds, CommonStatusEnum.ENABLE.getStatus(), now);
if (CollUtil.isNotEmpty(seckillActivities)) {
seckillActivities.forEach(item -> {
activityList.add(new AppActivityRespVO().setId(item.getId())
.setType(PromotionTypeEnum.SECKILL_ACTIVITY.getType()).setName(item.getName())
.setSpuId(item.getSpuId()).setStartTime(item.getStartTime()).setEndTime(item.getEndTime()));
});
}
// 3. 砍价活动 - 获取开启的且开始的且没有结束的活动
List<BargainActivityDO> bargainActivities = bargainActivityService.getBargainActivityBySpuIdsAndStatusAndDateTimeLt(
spuIds, CommonStatusEnum.ENABLE.getStatus(), now);
if (CollUtil.isNotEmpty(bargainActivities)) {
bargainActivities.forEach(item -> {
activityList.add(new AppActivityRespVO().setId(item.getId())
.setType(PromotionTypeEnum.BARGAIN_ACTIVITY.getType()).setName(item.getName())
.setSpuId(item.getSpuId()).setStartTime(item.getStartTime()).setEndTime(item.getEndTime()));
});
}
// TODO 芋艿满减送活动
// TODO 芋艿限时折扣活动
// 1. 拼团活动
getCombinationActivities(spuIds, now, activityList);
// 2. 秒杀活动
getSeckillActivities(spuIds, now, activityList);
// 3. 砍价活动
getBargainActivities(spuIds, now, activityList);
// 4. 限时折扣活动
getDiscountActivities(spuIds, now, activityList);
// 5. 满减送活动
getRewardActivities(spuIds, now, activityList);
return activityList;
}
private void getCombinationActivities(Collection<Long> spuIds, LocalDateTime now, List<AppActivityRespVO> activityList) {
List<CombinationActivityDO> combinationActivities = combinationActivityService.getCombinationActivityBySpuIdsAndStatusAndDateTimeLt(
spuIds, CommonStatusEnum.ENABLE.getStatus(), now);
if (CollUtil.isEmpty(combinationActivities)) {
return;
}
combinationActivities.forEach(item -> {
activityList.add(new AppActivityRespVO().setId(item.getId())
.setType(PromotionTypeEnum.COMBINATION_ACTIVITY.getType()).setName(item.getName())
.setSpuId(item.getSpuId()).setStartTime(item.getStartTime()).setEndTime(item.getEndTime()));
});
}
private void getSeckillActivities(Collection<Long> spuIds, LocalDateTime now, List<AppActivityRespVO> activityList) {
List<SeckillActivityDO> seckillActivities = seckillActivityService.getSeckillActivityBySpuIdsAndStatusAndDateTimeLt(
spuIds, CommonStatusEnum.ENABLE.getStatus(), now);
if (CollUtil.isEmpty(seckillActivities)) {
return;
}
seckillActivities.forEach(item -> {
activityList.add(new AppActivityRespVO().setId(item.getId())
.setType(PromotionTypeEnum.SECKILL_ACTIVITY.getType()).setName(item.getName())
.setSpuId(item.getSpuId()).setStartTime(item.getStartTime()).setEndTime(item.getEndTime()));
});
}
private void getBargainActivities(Collection<Long> spuIds, LocalDateTime now, List<AppActivityRespVO> activityList) {
List<BargainActivityDO> bargainActivities = bargainActivityService.getBargainActivityBySpuIdsAndStatusAndDateTimeLt(
spuIds, CommonStatusEnum.ENABLE.getStatus(), now);
if (CollUtil.isNotEmpty(bargainActivities)) {
return;
}
bargainActivities.forEach(item -> {
activityList.add(new AppActivityRespVO().setId(item.getId())
.setType(PromotionTypeEnum.BARGAIN_ACTIVITY.getType()).setName(item.getName())
.setSpuId(item.getSpuId()).setStartTime(item.getStartTime()).setEndTime(item.getEndTime()));
});
}
private void getDiscountActivities(Collection<Long> spuIds, LocalDateTime now, List<AppActivityRespVO> activityList) {
List<DiscountActivityDO> discountActivities = discountActivityService.getDiscountActivityBySpuIdsAndStatusAndDateTimeLt(
spuIds, CommonStatusEnum.ENABLE.getStatus(), now);
if (CollUtil.isEmpty(discountActivities)) {
return;
}
List<DiscountProductDO> products = discountActivityService.getDiscountProductsByActivityId(
convertSet(discountActivities, DiscountActivityDO::getId));
Map<Long, Long> productMap = convertMap(products, DiscountProductDO::getActivityId, DiscountProductDO::getSpuId);
discountActivities.forEach(item -> {
activityList.add(new AppActivityRespVO().setId(item.getId())
.setType(PromotionTypeEnum.DISCOUNT_ACTIVITY.getType()).setName(item.getName())
.setSpuId(productMap.get(item.getId())).setStartTime(item.getStartTime()).setEndTime(item.getEndTime()));
});
}
private void getRewardActivities(Collection<Long> spuIds, LocalDateTime now, List<AppActivityRespVO> activityList) {
List<RewardActivityDO> rewardActivityList = rewardActivityService.getRewardActivityBySpuIdsAndStatusAndDateTimeLt(
spuIds, PromotionActivityStatusEnum.RUN.getStatus(), now);
if (CollUtil.isEmpty(rewardActivityList)) {
return;
}
Map<Long, Optional<RewardActivityDO>> spuIdAndActivityMap = spuIds.stream()
.collect(Collectors.toMap(
spuId -> spuId,
spuId -> rewardActivityList.stream()
.filter(activity -> activity.getProductSpuIds().contains(spuId))
.max(Comparator.comparing(RewardActivityDO::getCreateTime))));
for (Long supId : spuIdAndActivityMap.keySet()) {
if (spuIdAndActivityMap.get(supId).isEmpty()) {
continue;
}
RewardActivityDO rewardActivityDO = spuIdAndActivityMap.get(supId).get();
activityList.add(new AppActivityRespVO().setId(rewardActivityDO.getId())
.setType(PromotionTypeEnum.REWARD_ACTIVITY.getType()).setName(rewardActivityDO.getName())
.setSpuId(supId).setStartTime(rewardActivityDO.getStartTime()).setEndTime(rewardActivityDO.getEndTime()));
}
}
}

View File

@ -7,9 +7,9 @@ import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountAc
import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountActivityDO;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Set;
/**
* 限时折扣活动 Mapper
@ -27,4 +27,20 @@ public interface DiscountActivityMapper extends BaseMapperX<DiscountActivityDO>
.orderByDesc(DiscountActivityDO::getId));
}
/**
* 获取指定活动编号的活动列表且
* 开始时间和结束时间小于给定时间 dateTime 的活动列表
*
* @param ids 活动编号
* @param dateTime 指定日期
* @return 活动列表
*/
default List<DiscountActivityDO> selectListByIdsAndDateTimeLt(Collection<Long> ids, LocalDateTime dateTime) {
return selectList(new LambdaQueryWrapperX<DiscountActivityDO>()
.in(DiscountActivityDO::getId, ids)
.lt(DiscountActivityDO::getStartTime, dateTime)
.gt(DiscountActivityDO::getEndTime, dateTime)// 开始时间 < 指定时间 < 结束时间也就是说获取指定时间段的活动
.orderByDesc(DiscountActivityDO::getCreateTime));
}
}

View File

@ -2,11 +2,13 @@ package cn.iocoder.yudao.module.promotion.dal.mysql.discount;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* 限时折扣商城 Mapper
@ -30,4 +32,20 @@ public interface DiscountProductMapper extends BaseMapperX<DiscountProductDO> {
// TODO @zhangshuai逻辑里尽量避免写 join 语句哈你可以看看这个查询有什么办法优化目前的一个思路是分 2 次查询性能也是 ok
List<DiscountProductDO> getMatchDiscountProductList(@Param("skuIds") Collection<Long> skuIds);
/**
* 查询出指定 spuId spu 参加的活动最接近现在的一条记录多个的话一个 spuId 对应一个最近的活动编号
*
* @param spuIds spu 编号
* @param status 状态
* @return 包含 spuId activityId map 对象列表
*/
default List<Map<String, Object>> selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(Collection<Long> spuIds, Integer status) {
return selectMaps(new QueryWrapper<DiscountProductDO>()
.select("spu_id AS spuId, MAX(DISTINCT(activity_id)) AS activityId")
.in("spu_id", spuIds)
.eq("activity_status", status)
.groupBy("spu_id"));
}
}

View File

@ -1,14 +1,19 @@
package cn.iocoder.yudao.module.promotion.dal.mysql.reward;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityPageReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 满减送活动 Mapper
@ -35,4 +40,30 @@ public interface RewardActivityMapper extends BaseMapperX<RewardActivityDO> {
.eq(RewardActivityDO::getStatus, status));
}
default List<RewardActivityDO> selectListBySpuIdsAndStatus(Collection<Long> spuIds, Integer status) {
Function<Collection<Long>, String> productScopeValuesFindInSetFunc = ids -> ids.stream()
.map(id -> StrUtil.format("FIND_IN_SET({}, product_spu_ids) ", id))
.collect(Collectors.joining(" OR "));
return selectList(new QueryWrapper<RewardActivityDO>()
.eq("status", status)
.apply(productScopeValuesFindInSetFunc.apply(spuIds)));
}
/**
* 获取指定活动编号的活动列表且
* 开始时间和结束时间小于给定时间 dateTime 的活动列表
*
* @param ids 活动编号
* @param dateTime 指定日期
* @return 活动列表
*/
default List<RewardActivityDO> selectListByIdsAndDateTimeLt(Collection<Long> ids, LocalDateTime dateTime) {
return selectList(new LambdaQueryWrapperX<RewardActivityDO>()
.in(RewardActivityDO::getId, ids)
.lt(RewardActivityDO::getStartTime, dateTime)
.gt(RewardActivityDO::getEndTime, dateTime)// 开始时间 < 指定时间 < 结束时间也就是说获取指定时间段的活动
.orderByDesc(RewardActivityDO::getCreateTime)
);
}
}

View File

@ -6,8 +6,9 @@ import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountAc
import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityUpdateReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountActivityDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO;
import jakarta.validation.Valid;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
@ -89,4 +90,14 @@ public interface DiscountActivityService {
*/
List<DiscountProductDO> getDiscountProductsByActivityId(Collection<Long> activityIds);
/**
* 获取指定 spu 编号最近参加的活动每个 spuId 只返回一条记录
*
* @param spuIds spu 编号
* @param status 状态
* @param dateTime 当前日期时间
* @return 折扣活动列表
*/
List<DiscountActivityDO> getDiscountActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime);
}

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.promotion.service.discount;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.map.MapUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityBaseVO;
@ -15,16 +16,20 @@ import cn.iocoder.yudao.module.promotion.dal.mysql.discount.DiscountActivityMapp
import cn.iocoder.yudao.module.promotion.dal.mysql.discount.DiscountProductMapper;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum;
import cn.iocoder.yudao.module.promotion.util.PromotionUtils;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import jakarta.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
/**
@ -109,7 +114,7 @@ public class DiscountActivityServiceImpl implements DiscountActivityService {
/**
* 校验商品是否冲突
*
* @param id 编号
* @param id 编号
* @param products 商品列表
*/
private void validateDiscountActivityProductConflicts(Long id, List<DiscountActivityBaseVO.Product> products) {
@ -184,4 +189,17 @@ public class DiscountActivityServiceImpl implements DiscountActivityService {
return discountProductMapper.selectList("activity_id", activityIds);
}
@Override
public List<DiscountActivityDO> getDiscountActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime) {
// 1. 查询出指定 spuId spu 参加的活动最接近现在的一条记录多个的话一个 spuId 对应一个最近的活动编号
List<Map<String, Object>> spuIdAndActivityIdMaps = discountProductMapper.selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(spuIds, status);
if (CollUtil.isEmpty(spuIdAndActivityIdMaps)) {
return Collections.emptyList();
}
// 2. 查询活动详情
return discountActivityMapper.selectListByIdsAndDateTimeLt(
convertSet(spuIdAndActivityIdMaps, map -> MapUtil.getLong(map, "activityId")), dateTime);
}
}

View File

@ -6,8 +6,9 @@ import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivi
import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityPageReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityUpdateReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
import jakarta.validation.Valid;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
@ -71,4 +72,14 @@ public interface RewardActivityService {
*/
List<RewardActivityMatchRespDTO> getMatchRewardActivityList(Collection<Long> spuIds);
/**
* 获取指定 spu 编号最近参加的活动每个 spuId 只返回一条记录
*
* @param spuIds spu 编号
* @param status 状态
* @param dateTime 当前日期时间
* @return 满减送活动列表
*/
List<RewardActivityDO> getRewardActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime);
}

View File

@ -11,14 +11,17 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
import cn.iocoder.yudao.module.promotion.dal.mysql.reward.RewardActivityMapper;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum;
import cn.iocoder.yudao.module.promotion.util.PromotionUtils;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import jakarta.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
import static java.util.Arrays.asList;
@ -100,10 +103,11 @@ public class RewardActivityServiceImpl implements RewardActivityService {
}
// TODO @芋艿逻辑有问题需要优化要分成全场和指定来校验
/**
* 校验商品参加的活动是否冲突
*
* @param id 活动编号
* @param id 活动编号
* @param spuIds 商品 SPU 编号数组
*/
private void validateRewardActivitySpuConflicts(Long id, Collection<Long> spuIds) {
@ -125,7 +129,7 @@ public class RewardActivityServiceImpl implements RewardActivityService {
/**
* 获得商品参加的满减送活动的数组
*
* @param spuIds 商品 SPU 编号数组
* @param spuIds 商品 SPU 编号数组
* @param statuses 活动状态数组
* @return 商品参加的满减送活动的数组
*/
@ -163,4 +167,16 @@ public class RewardActivityServiceImpl implements RewardActivityService {
return null;
}
@Override
public List<RewardActivityDO> getRewardActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime) {
// 1. 查询出指定 spuId spu 参加的活动
List<RewardActivityDO> rewardActivityList = rewardActivityMapper.selectListBySpuIdsAndStatus(spuIds, status);
if (CollUtil.isEmpty(rewardActivityList)) {
return Collections.emptyList();
}
// 2. 查询活动详情
return rewardActivityMapper.selectListByIdsAndDateTimeLt(convertSet(rewardActivityList, RewardActivityDO::getId), dateTime);
}
}