diff --git a/cash-api/product-server/src/main/java/com/czg/controller/user/UProductController.java b/cash-api/product-server/src/main/java/com/czg/controller/user/UProductController.java new file mode 100644 index 000000000..ecaf23664 --- /dev/null +++ b/cash-api/product-server/src/main/java/com/czg/controller/user/UProductController.java @@ -0,0 +1,37 @@ +package com.czg.controller.user; + +import com.czg.log.annotation.OperationLog; +import com.czg.product.param.MiniHomeProductParam; +import com.czg.product.service.UProductService; +import com.czg.product.vo.MiniAppHomeProductVo; +import com.czg.resp.CzgResult; +import lombok.AllArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + + +/** + * 用户端商品相关接口 + * + * @author Tankaikai tankaikai@aliyun.com + * @since 1.0 2025-02-16 + */ +@AllArgsConstructor +@RestController +@RequestMapping("/user/product") +public class UProductController { + private final UProductService uProductService; + + + /** + * 小程序点餐-首页-商品列表 + */ + @GetMapping("/miniApp/home/queryProduct") + @OperationLog("小程序点餐-首页-商品列表") + public CzgResult queryProductForMiniAppHome(MiniHomeProductParam param) { + MiniAppHomeProductVo data = uProductService.queryProductForMiniAppHome(param); + return CzgResult.success(data); + } + +} \ No newline at end of file diff --git a/cash-common/cash-common-service/src/main/java/com/czg/product/dto/ProdGroupDTO.java b/cash-common/cash-common-service/src/main/java/com/czg/product/dto/ProdGroupDTO.java index 76485ba03..867c34982 100644 --- a/cash-common/cash-common-service/src/main/java/com/czg/product/dto/ProdGroupDTO.java +++ b/cash-common/cash-common-service/src/main/java/com/czg/product/dto/ProdGroupDTO.java @@ -16,7 +16,6 @@ import java.io.Serializable; import java.time.LocalDateTime; import java.time.LocalTime; import java.util.List; -import java.util.Map; /** * 商品分组 @@ -100,9 +99,4 @@ public class ProdGroupDTO implements Serializable { * 商品简要信息列表 */ private List productList; - - /** - * 分组商品信息,用于小程序首页展示 - */ - private List> products; } \ No newline at end of file diff --git a/cash-common/cash-common-service/src/main/java/com/czg/product/dto/ProductDTO.java b/cash-common/cash-common-service/src/main/java/com/czg/product/dto/ProductDTO.java index 4dfe4c8a8..867f48831 100644 --- a/cash-common/cash-common-service/src/main/java/com/czg/product/dto/ProductDTO.java +++ b/cash-common/cash-common-service/src/main/java/com/czg/product/dto/ProductDTO.java @@ -84,7 +84,7 @@ public class ProductDTO implements Serializable { @NotBlank(message = "商品类型不能为空", groups = DefaultGroup.class) private String type; /** - * 0 固定套餐 1可选套餐 + * 套餐类型 0 固定套餐 1可选套餐 */ private Integer groupType; /** diff --git a/cash-common/cash-common-service/src/main/java/com/czg/product/param/MiniHomeProductParam.java b/cash-common/cash-common-service/src/main/java/com/czg/product/param/MiniHomeProductParam.java new file mode 100644 index 000000000..fd98f87ec --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/product/param/MiniHomeProductParam.java @@ -0,0 +1,31 @@ +package com.czg.product.param; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 小程序首页商品请求参数 + * @author tankaikai + * @since 2025-02-17 15:02 + */ +@Data +public class MiniHomeProductParam implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 商品分组ID + */ + private Long prodGroupId; + + /** + * 商品分组ID + */ + @JSONField(serialize = false) + private List productIdList; +} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/product/service/UProductService.java b/cash-common/cash-common-service/src/main/java/com/czg/product/service/UProductService.java new file mode 100644 index 000000000..1c268de49 --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/product/service/UProductService.java @@ -0,0 +1,16 @@ +package com.czg.product.service; + +import com.czg.product.entity.Product; +import com.czg.product.param.MiniHomeProductParam; +import com.czg.product.vo.MiniAppHomeProductVo; +import com.mybatisflex.core.service.IService; + +/** + * 用户端商品Service + * @author tankaikai + * @since 2025-02-17 14:57 + */ +public interface UProductService extends IService { + + MiniAppHomeProductVo queryProductForMiniAppHome(MiniHomeProductParam param); +} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/product/vo/MiniAppHomeProdGroupVo.java b/cash-common/cash-common-service/src/main/java/com/czg/product/vo/MiniAppHomeProdGroupVo.java new file mode 100644 index 000000000..8840a386b --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/product/vo/MiniAppHomeProdGroupVo.java @@ -0,0 +1,22 @@ +package com.czg.product.vo; + +import com.czg.product.entity.ProdGroup; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/** + * 分组商品信息,用于小程序首页展示 + * @author tankaikai + * @since 2025-02-18 09:34 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class MiniAppHomeProdGroupVo extends ProdGroup { + /** + * 分组商品信息,用于小程序首页展示 + */ + private List products; + +} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/product/vo/MiniAppHomeProductInfoVo.java b/cash-common/cash-common-service/src/main/java/com/czg/product/vo/MiniAppHomeProductInfoVo.java new file mode 100644 index 000000000..3ace845a1 --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/product/vo/MiniAppHomeProductInfoVo.java @@ -0,0 +1,70 @@ +package com.czg.product.vo; + +import com.czg.product.dto.ProdSkuDTO; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.List; + +/** + * 小程序首页商品信息VO + * @author tankaikai + * @since 2025-02-17 14:41 + */ +@Data +public class MiniAppHomeProductInfoVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 商品id + */ + private Long id; + /** + * 商品名称 + */ + private String name; + /** + * 商品封面图 + */ + private String coverImg; + /** + * 商品图片集url + */ + private Object images; + /** + * 单位名称 + */ + private String unitName; + /** + * 分类名称 + */ + private String categoryName; + /** + * 商品类型 single-单规格商品 sku-多规格商品 package-套餐商品 weight-称重商品 coupon-团购券 + */ + private String type; + /** + * 套餐类型 0 固定套餐 1可选套餐 + */ + private Integer groupType; + /** + * 商品可选规格 {"口味":[{"甜度":["少甜","中甜","多甜"]},{"辣度":["微辣","重辣","变态辣"]},{"小料":["葱花","香菜","折耳根"]}]} + */ + private Object selectSpecInfo; + /** + * 最低售价 + */ + private BigDecimal lowPrice; + /** + * 会员最低售价 + */ + private BigDecimal lowMemberPrice; + /** + * 商品SKU列表 + */ + private List skuList; +} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/product/vo/MiniAppHomeProductVo.java b/cash-common/cash-common-service/src/main/java/com/czg/product/vo/MiniAppHomeProductVo.java new file mode 100644 index 000000000..307b37d19 --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/product/vo/MiniAppHomeProductVo.java @@ -0,0 +1,31 @@ +package com.czg.product.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 小程序首页商品展示VO + * @author tankaikai + * @since 2025-02-17 14:41 + */ +@Data +public class MiniAppHomeProductVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 热销商品 + */ + List hots; + + + /** + * 分组商品信息 + */ + List productInfo; + +} diff --git a/cash-service/product-service/src/main/java/com/czg/service/product/service/impl/UProductServiceImpl.java b/cash-service/product-service/src/main/java/com/czg/service/product/service/impl/UProductServiceImpl.java new file mode 100644 index 000000000..3b6061d77 --- /dev/null +++ b/cash-service/product-service/src/main/java/com/czg/service/product/service/impl/UProductServiceImpl.java @@ -0,0 +1,166 @@ +package com.czg.service.product.service.impl; + +import cn.hutool.core.collection.CollUtil; +import com.czg.enums.DeleteEnum; +import com.czg.enums.StatusEnum; +import com.czg.enums.YesNoEnum; +import com.czg.product.dto.ProdSkuDTO; +import com.czg.product.dto.ProductDTO; +import com.czg.product.entity.ProdGroup; +import com.czg.product.entity.ProdGroupRelation; +import com.czg.product.entity.ProdSku; +import com.czg.product.entity.Product; +import com.czg.product.param.MiniHomeProductParam; +import com.czg.product.service.UProductService; +import com.czg.product.vo.MiniAppHomeProdGroupVo; +import com.czg.product.vo.MiniAppHomeProductInfoVo; +import com.czg.product.vo.MiniAppHomeProductVo; +import com.czg.sa.StpKit; +import com.czg.service.product.mapper.ProdGroupMapper; +import com.czg.service.product.mapper.ProdGroupRelationMapper; +import com.czg.service.product.mapper.ProdSkuMapper; +import com.czg.service.product.mapper.ProductMapper; +import com.mybatisflex.core.query.QueryWrapper; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.format.TextStyle; +import java.util.*; +import java.util.stream.Collectors; + +import static com.czg.product.entity.table.ProductTableDef.PRODUCT; +import static com.czg.product.entity.table.ShopProdCategoryTableDef.SHOP_PROD_CATEGORY; +import static com.czg.product.entity.table.ShopProdSpecTableDef.SHOP_PROD_SPEC; +import static com.czg.product.entity.table.ShopProdUnitTableDef.SHOP_PROD_UNIT; + +/** + * 商品 + * + * @author Tankaikai tankaikai@aliyun.com + * @since 1.0 2025-02-16 + */ +@Slf4j +@AllArgsConstructor +@Service +public class UProductServiceImpl extends ServiceImpl implements UProductService { + + private final ProductMapper productMapper; + private final ProdSkuMapper prodSkuMapper; + private final ProdGroupMapper prodGroupMapper; + private final ProdGroupRelationMapper prodGroupRelationMapper; + + private QueryWrapper buildQueryWrapper(MiniHomeProductParam param) { + Long shopId = StpKit.USER.getShopId(0L); + String weekDayEnName = getWeekDayEnName(); + LocalTime now = LocalTime.now().withNano(0); + QueryWrapper queryWrapper = query(); + queryWrapper.select(PRODUCT.ALL_COLUMNS) + .select(SHOP_PROD_UNIT.NAME.as(ProductDTO::getUnitName)) + .select(SHOP_PROD_CATEGORY.NAME.as(ProductDTO::getCategoryName)) + .select(SHOP_PROD_SPEC.NAME.as(ProductDTO::getSpecName), SHOP_PROD_SPEC.FULL_NAME.as(ProductDTO::getSpecFullName)) + .from(PRODUCT) + .leftJoin(SHOP_PROD_UNIT).on(SHOP_PROD_UNIT.ID.eq(PRODUCT.UNIT_ID)) + .leftJoin(SHOP_PROD_CATEGORY).on(SHOP_PROD_CATEGORY.ID.eq(PRODUCT.CATEGORY_ID)) + .leftJoin(SHOP_PROD_SPEC).on(SHOP_PROD_SPEC.ID.eq(PRODUCT.SPEC_ID)) + .where(PRODUCT.SHOP_ID.eq(shopId)) + .and(PRODUCT.DAYS.like(weekDayEnName)) + .and(PRODUCT.START_TIME.le(now)) + .and(PRODUCT.END_TIME.ge(now)) + .and(PRODUCT.IS_DEL.eq(DeleteEnum.NORMAL.value())) + .and(PRODUCT.IS_SALE.eq(YesNoEnum.YES.value())) + .orderBy(PRODUCT.SORT, false) + .orderBy(PRODUCT.ID, false); + if (CollUtil.isNotEmpty(param.getProductIdList())) { + queryWrapper.and(PRODUCT.ID.in(param.getProductIdList())); + } + return queryWrapper; + } + + private QueryWrapper buildHotsQueryWrapper(MiniHomeProductParam param) { + // 查询在可售时间内的热门商品 + QueryWrapper queryWrapper = buildQueryWrapper(param); + queryWrapper.and(PRODUCT.IS_HOT.eq(YesNoEnum.YES.value())); + return queryWrapper; + } + + private List buildProductList(QueryWrapper queryWrapper) { + List dtoList = productMapper.selectListByQueryAs(queryWrapper, ProductDTO.class); + List prodIdList = dtoList.stream().map(ProductDTO::getId).distinct().toList(); + List skuList = prodSkuMapper.selectListByQueryAs(query().in(ProdSku::getProductId, prodIdList), ProdSkuDTO.class); + Map> collect = skuList.stream().collect(Collectors.groupingBy(ProdSkuDTO::getProductId)); + List products = new ArrayList<>(); + for (ProductDTO dto : dtoList) { + MiniAppHomeProductInfoVo prod = new MiniAppHomeProductInfoVo(); + prod.setId(dto.getId()); + prod.setName(dto.getName()); + prod.setCoverImg(dto.getCoverImg()); + prod.setImages(dto.getImages()); + prod.setUnitName(dto.getUnitName()); + prod.setCategoryName(dto.getCategoryName()); + prod.setType(dto.getType()); + prod.setGroupType(dto.getGroupType()); + prod.setSelectSpecInfo(dto.getSelectSpecInfo()); + + List list = collect.getOrDefault(dto.getId(), Collections.emptyList()); + Optional lowPriceIsPresent = list.stream().map(ProdSkuDTO::getSalePrice).min(BigDecimal::compareTo); + lowPriceIsPresent.ifPresent(prod::setLowPrice); + + Optional lowMemberPriceIsPresent = list.stream().map(ProdSkuDTO::getMemberPrice).min(BigDecimal::compareTo); + lowMemberPriceIsPresent.ifPresent(prod::setLowMemberPrice); + + prod.setSkuList(list); + products.add(prod); + } + return products; + } + + @Override + public MiniAppHomeProductVo queryProductForMiniAppHome(MiniHomeProductParam param) { + Long shopId = StpKit.USER.getShopId(0L); + MiniAppHomeProductVo vo = new MiniAppHomeProductVo(); + + List hots = buildProductList(buildHotsQueryWrapper(param)); + vo.setHots(hots); + // 查询可用商品分组,构建分组商品数据 + QueryWrapper queryWrapper = query() + .eq(ProdGroup::getShopId, shopId) + .eq(ProdGroup::getStatus, StatusEnum.ENABLED.value()) + .orderBy(ProdGroup::getSort, true); + if (param.getProdGroupId() != null) { + queryWrapper.eq(ProdGroup::getId, param.getProdGroupId()); + } + List groupList = prodGroupMapper.selectListByQueryAs(queryWrapper, MiniAppHomeProdGroupVo.class); + for (MiniAppHomeProdGroupVo dto : groupList) { + List productIdList = prodGroupRelationMapper.selectObjectListByQueryAs(query().select(ProdGroupRelation::getProductId).eq(ProdGroupRelation::getProdGroupId, dto.getId()).orderBy(ProdGroupRelation::getSort, true), Long.class); + if (CollUtil.isNotEmpty(productIdList)) { + param.setProductIdList(productIdList); + List products = buildProductList(buildQueryWrapper(param)); + dto.setProducts(products); + } else { + dto.setProducts(new ArrayList<>()); + } + } + vo.setProductInfo(groupList); + return vo; + } + + /** + * 获取当前日期是星期几的英文名称 + * + * @return 星期几的英文名称,例如 "Monday" 或 "Friday" + */ + private String getWeekDayEnName() { + // 获取当前日期 + LocalDate currentDate = LocalDate.now(); + // 获取当前日期是星期几,返回一个 DayOfWeek 枚举类型的实例 + DayOfWeek dayOfWeek = currentDate.getDayOfWeek(); + return dayOfWeek.getDisplayName(TextStyle.FULL, Locale.ENGLISH); + } + +} \ No newline at end of file