mall + promotion:增加营销活动的 mock 接口

This commit is contained in:
YunaiV 2023-06-10 17:51:15 +08:00
parent 9e894e0430
commit 4f5ac0edbb
14 changed files with 186 additions and 68 deletions

View File

@ -2,7 +2,6 @@ package cn.iocoder.yudao.framework.mybatis.core.query;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;

View File

@ -3,17 +3,17 @@ package cn.iocoder.yudao.module.product.controller.app.spu;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuDetailRespVO;
import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageItemRespVO;
import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageRespVO;
import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageReqVO;
import cn.iocoder.yudao.module.product.convert.spu.ProductSpuConvert;
import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;
import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
@ -40,12 +40,23 @@ public class AppProductSpuController {
private ProductSpuService productSpuService;
@Resource
private ProductSkuService productSkuService;
@Resource
private ProductPropertyValueService productPropertyValueService;
@GetMapping("/list")
@Operation(summary = "获得商品 SPU 列表")
@Parameters({
@Parameter(name = "recommendType", description = "推荐类型", required = true), // 参见 AppProductSpuPageReqVO.RECOMMEND_TYPE_XXX 常量
@Parameter(name = "count", description = "数量", required = true)
})
public CommonResult<List<AppProductSpuPageRespVO>> getSpuList(
@RequestParam("recommendType") String recommendType,
@RequestParam(value = "count", defaultValue = "10") Integer count) {
List<ProductSpuDO> list = productSpuService.getSpuList(recommendType, count);
return success(ProductSpuConvert.INSTANCE.convertListForGetSpuList(list));
}
@GetMapping("/page")
@Operation(summary = "获得商品 SPU 分页")
public CommonResult<PageResult<AppProductSpuPageItemRespVO>> getSpuPage(@Valid AppProductSpuPageReqVO pageVO) {
public CommonResult<PageResult<AppProductSpuPageRespVO>> getSpuPage(@Valid AppProductSpuPageReqVO pageVO) {
PageResult<ProductSpuDO> pageResult = productSpuService.getSpuPage(pageVO);
return success(ProductSpuConvert.INSTANCE.convertPageForGetSpuPage(pageResult));
}

View File

@ -39,6 +39,11 @@ public class AppProductSpuDetailRespVO {
@Schema(description = "单位名", required = true, example = "")
private String unitName;
// ========== 营销相关字段 =========
@Schema(description = "活动排序数组", required = true, example = "1024")
private List<Integer> activityOrders;
// ========== SKU 相关字段 =========
@Schema(description = "规格类型", required = true, example = "true")

View File

@ -20,6 +20,7 @@ public class AppProductSpuPageReqVO extends PageParam {
public static final String SORT_FIELD_SALES_COUNT = "salesCount";
public static final String RECOMMEND_TYPE_HOT = "hot";
public static final String RECOMMEND_TYPE_GOOD = "good";
@Schema(description = "分类编号", example = "1")
private Long categoryId;
@ -33,7 +34,7 @@ public class AppProductSpuPageReqVO extends PageParam {
@Schema(description = "排序方式", example = "true")
private Boolean sortAsc;
@Schema(description = "推荐类型", example = "hot") // 参见 AppProductSpuPageReqVO.RECOMMEND_TYPE_XXX
@Schema(description = "推荐类型", example = "hot") // 参见 AppProductSpuPageReqVO.RECOMMEND_TYPE_XXX
private String recommendType;
@AssertTrue(message = "排序字段不合法")

View File

@ -5,9 +5,9 @@ import lombok.Data;
import java.util.List;
@Schema(description = "用户 App - 商品 SPU 分页项 Response VO")
@Schema(description = "用户 App - 商品 SPU Response VO")
@Data
public class AppProductSpuPageItemRespVO {
public class AppProductSpuPageRespVO {
@Schema(description = "商品 SPU 编号", required = true, example = "1")
private Long id;
@ -35,6 +35,11 @@ public class AppProductSpuPageItemRespVO {
@Schema(description = "库存", required = true, example = "666")
private Integer stock;
// ========== 营销相关字段 =========
@Schema(description = "活动排序数组", required = true, example = "1024")
private List<Integer> activityOrders;
// ========== 统计相关字段 =========
@Schema(description = "商品销量", required = true, example = "1024")

View File

@ -1,14 +1,12 @@
package cn.iocoder.yudao.module.product.convert.spu;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuRespVO;
import cn.iocoder.yudao.module.product.controller.admin.spu.vo.*;
import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuDetailRespVO;
import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageItemRespVO;
import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageRespVO;
import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageReqVO;
import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert;
import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
@ -80,14 +78,15 @@ public interface ProductSpuConvert {
// ========== 用户 App 相关 ==========
default PageResult<AppProductSpuPageItemRespVO> convertPageForGetSpuPage(PageResult<ProductSpuDO> page) {
// 累加虚拟销量
page.getList().forEach(spu -> spu.setSalesCount(spu.getSalesCount() + spu.getVirtualSalesCount()));
// 然后进行转换
return convertPageForGetSpuPage0(page);
}
PageResult<AppProductSpuPageRespVO> convertPageForGetSpuPage(PageResult<ProductSpuDO> page);
PageResult<AppProductSpuPageItemRespVO> convertPageForGetSpuPage0(PageResult<ProductSpuDO> page);
default List<AppProductSpuPageRespVO> convertListForGetSpuList(List<ProductSpuDO> list) {
// 处理虚拟销量
list.forEach(spu -> spu.setSalesCount(spu.getSalesCount() + spu.getVirtualSalesCount()));
return convertListForGetSpuList0(list);
}
@Named("convertListForGetSpuList0")
List<AppProductSpuPageRespVO> convertListForGetSpuList0(List<ProductSpuDO> list);
default AppProductSpuDetailRespVO convertForGetSpuDetail(ProductSpuDO spu, List<ProductSkuDO> skus) {
// 处理 SPU
@ -109,15 +108,9 @@ public interface ProductSpuConvert {
List<AppProductSpuDetailRespVO.Sku> convertListForGetSpuDetail(List<ProductSkuDO> skus);
default ProductSpuDetailRespVO convertForSpuDetailRespVO(ProductSpuDO spu, List<ProductSkuDO> skus) {
ProductSpuDetailRespVO productSpuDetailRespVO = convert03(spu);
// skus 为空直接返回
if (CollUtil.isEmpty(skus)) {
return productSpuDetailRespVO;
}
List<ProductSkuRespVO> skuVOs = ProductSkuConvert.INSTANCE.convertList(skus);
// fix: 因为现在已改为 sku 属性列表 属性 已包含 属性名字 属性值名字 所以不需要再额外处理属性更新时更新 sku 中的属性相关冗余即可
productSpuDetailRespVO.setSkus(skuVOs);
return productSpuDetailRespVO;
ProductSpuDetailRespVO detailRespVO = convert03(spu);
detailRespVO.setSkus(ProductSkuConvert.INSTANCE.convertList(skus));
return detailRespVO;
}
}

View File

@ -5,6 +5,7 @@ import cn.hutool.core.util.ObjectUtil;
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.framework.mybatis.core.query.QueryWrapperX;
import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuExportReqVO;
import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuPageReqVO;
import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageReqVO;
@ -66,7 +67,10 @@ public interface ProductSpuMapper extends BaseMapperX<ProductSpuDO> {
// 推荐类型的过滤条件
if (ObjUtil.equal(pageReqVO.getRecommendType(), AppProductSpuPageReqVO.RECOMMEND_TYPE_HOT)) {
query.eq(ProductSpuDO::getRecommendHot, true);
} else if (ObjUtil.equal(pageReqVO.getRecommendType(), AppProductSpuPageReqVO.RECOMMEND_TYPE_GOOD)) {
query.eq(ProductSpuDO::getRecommendGood, true);
}
// 排序逻辑
if (Objects.equals(pageReqVO.getSortField(), AppProductSpuPageReqVO.SORT_FIELD_SALES_COUNT)) {
query.last(String.format(" ORDER BY (sales_count + virtual_sales_count) %s, sort DESC, id DESC",
@ -80,6 +84,21 @@ public interface ProductSpuMapper extends BaseMapperX<ProductSpuDO> {
return selectPage(pageReqVO, query);
}
default List<ProductSpuDO> selectListByRecommendType(String recommendType, Integer count) {
QueryWrapperX<ProductSpuDO> query = new QueryWrapperX<>();
// 上架状态 且有库存
query.eq("status", ProductSpuStatusEnum.ENABLE.getStatus()).gt("stock", 0);
// 推荐类型的过滤条件
if (ObjUtil.equal(recommendType, AppProductSpuPageReqVO.RECOMMEND_TYPE_HOT)) {
query.eq("recommend_hot", true);
} else if (ObjUtil.equal(recommendType, AppProductSpuPageReqVO.RECOMMEND_TYPE_GOOD)) {
query.eq("recommend_good", true);
}
// 设置最大长度
query.limitN(count);
return selectList(query);
}
/**
* 更新商品 SPU 库存
*
@ -111,33 +130,34 @@ public interface ProductSpuMapper extends BaseMapperX<ProductSpuDO> {
}
/**
* 验证选项卡类型构建条件
* 添加后台 Tab 选项的查询条件
*
* @param tabType 标签类型
* @param queryWrapper 查询条件
* @param query 查询条件
*/
static void appendTabQuery(Integer tabType, LambdaQueryWrapperX<ProductSpuDO> queryWrapper) {
static void appendTabQuery(Integer tabType, LambdaQueryWrapperX<ProductSpuDO> query) {
// 出售中商品
if (ObjectUtil.equals(ProductSpuPageReqVO.FOR_SALE, tabType)) {
queryWrapper.eqIfPresent(ProductSpuDO::getStatus, ProductSpuStatusEnum.ENABLE.getStatus());
query.eqIfPresent(ProductSpuDO::getStatus, ProductSpuStatusEnum.ENABLE.getStatus());
}
// 仓储中商品
if (ObjectUtil.equals(ProductSpuPageReqVO.IN_WAREHOUSE, tabType)) {
queryWrapper.eqIfPresent(ProductSpuDO::getStatus, ProductSpuStatusEnum.DISABLE.getStatus());
query.eqIfPresent(ProductSpuDO::getStatus, ProductSpuStatusEnum.DISABLE.getStatus());
}
// 已售空商品
if (ObjectUtil.equals(ProductSpuPageReqVO.SOLD_OUT, tabType)) {
queryWrapper.eqIfPresent(ProductSpuDO::getStock, 0);
query.eqIfPresent(ProductSpuDO::getStock, 0);
}
// 警戒库存
if (ObjectUtil.equals(ProductSpuPageReqVO.ALERT_STOCK, tabType)) {
queryWrapper.le(ProductSpuDO::getStock, ProductConstants.ALERT_STOCK)
query.le(ProductSpuDO::getStock, ProductConstants.ALERT_STOCK)
// 如果库存触发警戒库存且状态为回收站的话则不在警戒库存列表展示
.notIn(ProductSpuDO::getStatus, ProductSpuStatusEnum.RECYCLE.getStatus());
}
// 回收站
if (ObjectUtil.equals(ProductSpuPageReqVO.RECYCLE_BIN, tabType)) {
queryWrapper.eqIfPresent(ProductSpuDO::getStatus, ProductSpuStatusEnum.RECYCLE.getStatus());
query.eqIfPresent(ProductSpuDO::getStatus, ProductSpuStatusEnum.RECYCLE.getStatus());
}
}
}

View File

@ -98,6 +98,15 @@ public interface ProductSpuService {
*/
PageResult<ProductSpuDO> getSpuPage(AppProductSpuPageReqVO pageReqVO);
/**
* 获得商品 SPU 列表提供给用户 App 使用
*
* @param recommendType 推荐类型
* @param count 数量
* @return 商品 SPU 列表
*/
List<ProductSpuDO> getSpuList(String recommendType, Integer count);
/**
* 更新商品 SPU 库存增量
*

View File

@ -201,6 +201,11 @@ public class ProductSpuServiceImpl implements ProductSpuService {
return productSpuMapper.selectPage(pageReqVO, categoryIds);
}
@Override
public List<ProductSpuDO> getSpuList(String recommendType, Integer count) {
return productSpuMapper.selectListByRecommendType(recommendType, count);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateSpuStock(Map<Long, Integer> stockIncrCounts) {

View File

@ -15,11 +15,15 @@ import java.util.Arrays;
@AllArgsConstructor
public enum PromotionTypeEnum implements IntArrayValuable {
DISCOUNT_ACTIVITY(1, "限时折扣"),
REWARD_ACTIVITY(2, "满减送"),
SECKILL_ACTIVITY(1, "秒杀活动"),
BARGAIN_ACTIVITY(2, "拼团活动"),
COMBINATION_ACTIVITY(3, "砍价活动"),
MEMBER(3, "会员折扣"), // TODO 芋艿待实现 StrUtil.format("会员折扣:省 {} 元", formatPrice(orderItem.getPayPrice() - memberPrice)
COUPON(4, "优惠劵")
DISCOUNT_ACTIVITY(4, "限时折扣"),
REWARD_ACTIVITY(5, "满减送"),
MEMBER(6, "会员折扣"),
COUPON(7, "优惠劵")
;
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(PromotionTypeEnum::getType).toArray();

View File

@ -1,24 +0,0 @@
package cn.iocoder.yudao.module.promotion.controller.app;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
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.RestController;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "用户 App - 营销")
@RestController
@RequestMapping("/market/test")
@Validated
public class AppMarketTestController {
@GetMapping("/get")
@Operation(summary = "获取 market 信息")
public CommonResult<String> get() {
return success("true");
}
}

View File

@ -0,0 +1,65 @@
package cn.iocoder.yudao.module.promotion.controller.app.activity;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.promotion.controller.app.activity.vo.AppActivityRespVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
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 java.time.LocalDateTime;
import java.util.*;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "用户 APP - 营销活动") // 用于提供跨多个活动的 HTTP 接口
@RestController
@RequestMapping("/promotion/activity")
@Validated
public class AppActivityController {
@GetMapping("/list-by-spu-id")
@Operation(summary = "获得单个商品,近期参与的每个活动") // 每种活动只返回一个
@Parameter(name = "spuId", description = "商品编号", required = true)
public CommonResult<List<AppActivityRespVO>> getActivityListBySpuId(@RequestParam("spuId") Long spuId) {
// TODO 芋艿实现
List<AppActivityRespVO> randomList = new ArrayList<>();
Random random = new Random();
for (int i = 0; i < 3; i++) { // 生成5个随机对象
AppActivityRespVO vo = new AppActivityRespVO();
vo.setId(random.nextLong()); // 随机生成一个长整型 ID
vo.setType(i + 1); // 随机生成一个介于0到2之间的整数对应枚举类型的三种类型之一
vo.setName(String.format("活动%d", random.nextInt(100))); // 随机生成一个类似于活动XX的活动名称XX为0到99之间的随机整数
vo.setStartTime(LocalDateTime.now()); // 随机生成一个在过去的一年内的开始时间以毫秒为单位
vo.setEndTime(LocalDateTime.now()); // 随机生成一个在未来的一年内的结束时间以毫秒为单位
randomList.add(vo);
}
return success(randomList);
}
@GetMapping("/list-by-spu-ids")
@Operation(summary = "获得多个商品,近期参与的每个活动") // 每种活动只返回一个key SPU 编号
@Parameter(name = "spuIds", description = "商品编号数组", required = true)
public CommonResult<Map<Long, List<AppActivityRespVO>>> getActivityListBySpuIds(@RequestParam("spuIds") List<Long> spuIds) {
// TODO 芋艿实现
List<AppActivityRespVO> randomList = new ArrayList<>();
Random random = new Random();
for (int i = 0; i < 5; i++) { // 生成5个随机对象
AppActivityRespVO vo = new AppActivityRespVO();
vo.setId(random.nextLong()); // 随机生成一个长整型 ID
vo.setType(random.nextInt(3)); // 随机生成一个介于0到2之间的整数对应枚举类型的三种类型之一
vo.setName(String.format("活动%d", random.nextInt(100))); // 随机生成一个类似于活动XX的活动名称XX为0到99之间的随机整数
vo.setStartTime(LocalDateTime.now()); // 随机生成一个在过去的一年内的开始时间以毫秒为单位
vo.setEndTime(LocalDateTime.now()); // 随机生成一个在未来的一年内的结束时间以毫秒为单位
randomList.add(vo);
}
Map<Long, List<AppActivityRespVO>> map = new HashMap<>();
map.put(109L, randomList);
return success(map);
}
}

View File

@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.promotion.controller.app.activity.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "用户 App - 营销活动 Response VO")
@Data
public class AppActivityRespVO {
@Schema(description = "活动编号", required = true, example = "1024")
private Long id;
@Schema(description = "活动类型", required = true, example = "1") // 对应 PromotionTypeEnum 枚举
private Integer type;
@Schema(description = "活动名称", required = true, example = "618 大促")
private String name;
@Schema(description = "活动开始时间", required = true)
private LocalDateTime startTime;
@Schema(description = "活动结束时间", required = true)
private LocalDateTime endTime;
}

View File

@ -17,10 +17,8 @@ public enum TradeOrderTypeEnum implements IntArrayValuable {
NORMAL(0, "普通订单"),
SECKILL(1, "秒杀订单"),
// TODO 芋艿如下三个字段名字需要改下等后面表设计完成后
KANJIA(2, "砍价订单"),
PINTUAN(3, "拼团订单"),
YUSHOU(4, "预售订单"),
BARGAIN(2, "砍价订单"),
COMBINATION(3, "拼团订单"),
;
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(TradeOrderTypeEnum::getType).toArray();