Merge remote-tracking branch 'origin/feature/mall_product' into feature/mall_product

# Conflicts:
#	yudao-module-mall/yudao-module-product-biz/pom.xml
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java
This commit is contained in:
YunaiV
2023-06-09 08:55:45 +08:00
40 changed files with 861 additions and 289 deletions

View File

@@ -9,7 +9,6 @@ import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentCreat
import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentPageReqVO;
import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentRespVO;
import cn.iocoder.yudao.module.product.convert.comment.ProductCommentConvert;
import cn.iocoder.yudao.module.product.dal.dataobject.comment.ProductCommentDO;
import cn.iocoder.yudao.module.product.service.comment.ProductCommentService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -18,6 +17,7 @@ import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@@ -37,8 +37,13 @@ public class AppCommentController {
@GetMapping("/page")
@Operation(summary = "获得商品评价分页")
public CommonResult<PageResult<AppCommentRespVO>> getCommentPage(@Valid AppCommentPageReqVO pageVO) {
PageResult<ProductCommentDO> pageResult = productCommentService.getCommentPage(pageVO, Boolean.TRUE);
return success(ProductCommentConvert.INSTANCE.convertPage02(pageResult));
return success(productCommentService.getCommentPage(pageVO, Boolean.TRUE));
}
@GetMapping("/get-count")
@Operation(summary = "获得商品评价分页 tab count")
public CommonResult<Map<String, Long>> getCommentPage(@Valid Long spuId) {
return success(productCommentService.getCommentPageTabsCount(spuId, Boolean.TRUE));
}
@PostMapping(value = "/create")

View File

@@ -14,8 +14,57 @@ import javax.validation.constraints.NotNull;
@ToString(callSuper = true)
public class AppCommentPageReqVO extends PageParam {
/**
* 所有
*/
public static final Integer ALL = 0;
/**
* 所有数量 key
*/
public static final String ALL_COUNT = "allCount";
/**
* 好评
*/
public static final Integer FAVOURABLE_COMMENT = 1;
/**
* 好评数量 key
*/
public static final String FAVOURABLE_COMMENT_COUNT = "favourableCommentCount";
/**
* 中评
*/
public static final Integer MEDIOCRE_COMMENT = 2;
/**
* 中评数量 key
*/
public static final String MEDIOCRE_COMMENT_COUNT = "mediocreCommentCount";
/**
* 差评
*/
public static final Integer NEGATIVE_COMMENT = 3;
/**
* 差评数量 key
*/
public static final String NEGATIVE_COMMENT_COUNT = "negativeCommentCount";
/**
* 默认匿名昵称
*/
public static final String ANONYMOUS_NICKNAME = "匿名用户";
@Schema(description = "商品SPU编号", example = "29502")
@NotNull(message = "商品SPU编号不能为空")
private Long spuId;
@Schema(description = "app 评论页 tab 类型 (0 全部、1 好评、2 中评、3 差评)", example = "0")
@NotNull(message = "商品SPU编号不能为空")
private Integer type;
}

View File

@@ -5,7 +5,6 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
import java.util.List;
@@ -27,7 +26,7 @@ public class AppCommentRespVO extends AppCommentBaseVO {
@Schema(description = "订单项编号", required = true, example = "24965")
private Long id;
@Schema(description = "是否匿名[0:不匿名 1:匿名]", required = true)
@Schema(description = "是否匿名", required = true)
private Boolean anonymous;
@Schema(description = "交易订单编号", required = true, example = "24428")
@@ -36,7 +35,7 @@ public class AppCommentRespVO extends AppCommentBaseVO {
@Schema(description = "交易订单项编号", required = true, example = "8233")
private Long orderItemId;
@Schema(description = "商家是否回复[1:回复 0:未回复]", required = true)
@Schema(description = "商家是否回复", required = true)
private Boolean replied;
@Schema(description = "回复管理员编号", example = "22212")
@@ -60,4 +59,6 @@ public class AppCommentRespVO extends AppCommentBaseVO {
@Schema(description = "创建时间", required = true)
private LocalDateTime createTime;
@Schema(description = "最终评分", required = true)
private Integer finalScore;
}

View File

@@ -6,11 +6,7 @@ import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuDetail
import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageItemRespVO;
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;
@@ -23,12 +19,8 @@ import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_ENABLE;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_EXISTS;
@Tag(name = "用户 APP - 商品 SPU")
@RestController
@@ -38,10 +30,6 @@ public class AppProductSpuController {
@Resource
private ProductSpuService productSpuService;
@Resource
private ProductSkuService productSkuService;
@Resource
private ProductPropertyValueService productPropertyValueService;
@GetMapping("/page")
@Operation(summary = "获得商品 SPU 分页")
@@ -50,23 +38,12 @@ public class AppProductSpuController {
return success(ProductSpuConvert.INSTANCE.convertPageForGetSpuPage(pageResult));
}
// TODO 芋艿:等会看看
@GetMapping("/get-detail")
@Operation(summary = "获得商品 SPU 明细")
@Parameter(name = "id", description = "编号", required = true)
public CommonResult<AppProductSpuDetailRespVO> getSpuDetail(@RequestParam("id") Long id) {
// 获得商品 SPU
ProductSpuDO spu = productSpuService.getSpu(id);
if (spu == null) {
throw exception(SPU_NOT_EXISTS);
}
if (!ProductSpuStatusEnum.isEnable(spu.getStatus())) {
throw exception(SPU_NOT_ENABLE);
}
// 查询商品 SKU
List<ProductSkuDO> skus = productSkuService.getSkuListBySpuId(spu.getId());
// 拼接
return success(ProductSpuConvert.INSTANCE.convertForGetSpuDetail(spu, skus));
return success(productSpuService.getAppProductSpuDetail(id));
}
}

View File

@@ -21,6 +21,15 @@ import lombok.*;
@AllArgsConstructor
public class ProductPropertyDO extends BaseDO {
/**
* 默认属性id
*/
public static final Long PROPERTY_ID = 0L;
/**
* 默认属性名字
*/
public static final String PROPERTY_NAME = "默认";
/**
* 主键
*/

View File

@@ -22,6 +22,15 @@ import lombok.*;
@AllArgsConstructor
public class ProductPropertyValueDO extends BaseDO {
/**
* 默认属性值id
*/
public static final Long VALUE_ID = 0L;
/**
* 默认属性值名字
*/
public static final String VALUE_NAME = "默认";
/**
* 主键
*/

View File

@@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.product.dal.mysql.comment;
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;
@@ -34,11 +35,33 @@ public interface ProductCommentMapper extends BaseMapperX<ProductCommentDO> {
.orderByDesc(ProductCommentDO::getId));
}
static void appendTabQuery(LambdaQueryWrapperX<ProductCommentDO> queryWrapper, Integer type) {
// 构建好评查询语句
if (ObjectUtil.equal(type, AppCommentPageReqVO.FAVOURABLE_COMMENT)) {
// 好评计算 (商品评分星级+服务评分星级) >= 8
queryWrapper.apply("(scores + benefit_scores) >= 8");
}
// 构建中评查询语句
if (ObjectUtil.equal(type, AppCommentPageReqVO.MEDIOCRE_COMMENT)) {
// 中评计算 (商品评分星级+服务评分星级) > 4 且 (商品评分星级+服务评分星级) < 8
queryWrapper.apply("(scores + benefit_scores) > 4 and (scores + benefit_scores) < 8");
}
// 构建差评查询语句
if (ObjectUtil.equal(type, AppCommentPageReqVO.NEGATIVE_COMMENT)) {
// 差评计算 (商品评分星级+服务评分星级) <= 4
queryWrapper.apply("(scores + benefit_scores) <= 4");
}
}
default PageResult<ProductCommentDO> selectPage(AppCommentPageReqVO reqVO, Boolean visible) {
return selectPage(reqVO, new LambdaQueryWrapperX<ProductCommentDO>()
LambdaQueryWrapperX<ProductCommentDO> queryWrapper = new LambdaQueryWrapperX<ProductCommentDO>()
.eqIfPresent(ProductCommentDO::getSpuId, reqVO.getSpuId())
.eqIfPresent(ProductCommentDO::getVisible, visible)
.orderByDesc(ProductCommentDO::getId));
.eqIfPresent(ProductCommentDO::getVisible, visible);
// 构建评价查询语句
appendTabQuery(queryWrapper, reqVO.getType());
// 按评价时间排序最新的显示在前面
queryWrapper.orderByDesc(ProductCommentDO::getCreateTime);
return selectPage(reqVO, queryWrapper);
}
default void updateCommentVisible(Long id, Boolean visible) {
@@ -74,4 +97,13 @@ public interface ProductCommentMapper extends BaseMapperX<ProductCommentDO> {
update(null, lambdaUpdateWrapper);
}
default Long selectTabCount(Long spuId, Boolean visible, Integer type) {
LambdaQueryWrapperX<ProductCommentDO> queryWrapper = new LambdaQueryWrapperX<ProductCommentDO>()
.eqIfPresent(ProductCommentDO::getSpuId, spuId)
.eqIfPresent(ProductCommentDO::getVisible, visible);
// 构建评价查询语句
appendTabQuery(queryWrapper, type);
return selectCount(queryWrapper);
}
}

View File

@@ -1,12 +1,9 @@
package cn.iocoder.yudao.module.product.dal.mysql.sku;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
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.product.dal.dataobject.sku.ProductSkuDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
@@ -63,23 +60,4 @@ public interface ProductSkuMapper extends BaseMapperX<ProductSkuDO> {
return selectList(new QueryWrapper<ProductSkuDO>().apply("stock <= warn_stock"));
}
// TODO @puhui999貌似 IN 不出来数据哈。直接全部查询出来,处理就好列;
/**
* 更新 sku 属性值时使用的分页查询
*
* @param pageParam 页面参数
* @return {@link PageResult}<{@link ProductSkuDO}>
*/
default PageResult<ProductSkuDO> selectPage(PageParam pageParam) {
return selectPage(pageParam, new LambdaQueryWrapper<ProductSkuDO>().isNotNull(ProductSkuDO::getProperties));
}
/**
* 查询 sku properties 不等于 null 的数量
*
* @return {@link Long}
*/
default Long selectCountByPropertyNotNull() {
return selectCount(new LambdaQueryWrapper<ProductSkuDO>().isNotNull(ProductSkuDO::getProperties));
}
}

View File

@@ -68,7 +68,10 @@ public class ProductCategoryServiceImpl implements ProductCategoryService {
throw exception(CATEGORY_EXISTS_CHILDREN);
}
// 校验分类是否绑定了 SPU
validateProductCategoryIsHaveBindSpu(id);
Long count = productSpuService.getSpuCountByCategoryId(id);
if (0 != count) {
throw exception(CATEGORY_HAVE_BIND_SPU);
}
// 删除
productCategoryMapper.deleteById(id);
}
@@ -96,14 +99,6 @@ public class ProductCategoryServiceImpl implements ProductCategoryService {
}
}
// TODO @puhui999不用抽方法因为不太会复用这个方法哈。
private void validateProductCategoryIsHaveBindSpu(Long id) {
Long count = productSpuService.getSpuCountByCategoryId(id);
if (0 != count) {
throw exception(CATEGORY_HAVE_BIND_SPU);
}
}
@Override
public ProductCategoryDO getCategory(Long id) {
return productCategoryMapper.selectById(id);

View File

@@ -7,10 +7,13 @@ import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommen
import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentUpdateVisibleReqVO;
import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentAdditionalReqVO;
import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentPageReqVO;
import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentRespVO;
import cn.iocoder.yudao.module.product.dal.dataobject.comment.ProductCommentDO;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.Map;
/**
* 商品评论 Service 接口
*
@@ -50,7 +53,7 @@ public interface ProductCommentService {
* @param visible 是否可见
* @return 商品评价分页
*/
PageResult<ProductCommentDO> getCommentPage(AppCommentPageReqVO pageVO, Boolean visible);
PageResult<AppCommentRespVO> getCommentPage(AppCommentPageReqVO pageVO, Boolean visible);
/**
* 创建商品评论
@@ -68,4 +71,12 @@ public interface ProductCommentService {
*/
void additionalComment(MemberUserRespDTO user, AppCommentAdditionalReqVO createReqVO);
/**
* 评论页面标签数
*
* @param spuId spu id
* @param visible 是否可见
* @return 获得商品评价分页 tab count
*/
Map<String, Long> getCommentPageTabsCount(Long spuId, Boolean visible);
}

View File

@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.product.service.comment;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentPageReqVO;
@@ -7,17 +8,28 @@ import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommen
import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentUpdateVisibleReqVO;
import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentAdditionalReqVO;
import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentPageReqVO;
import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentRespVO;
import cn.iocoder.yudao.module.product.convert.comment.ProductCommentConvert;
import cn.iocoder.yudao.module.product.dal.dataobject.comment.ProductCommentDO;
import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
import cn.iocoder.yudao.module.product.dal.mysql.comment.ProductCommentMapper;
import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
import cn.iocoder.yudao.module.trade.api.order.TradeOrderApi;
import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderRespDTO;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.ORDER_NOT_FOUND;
/**
* 商品评论 Service 实现类
@@ -30,6 +42,11 @@ public class ProductCommentServiceImpl implements ProductCommentService {
@Resource
private ProductCommentMapper productCommentMapper;
@Resource
private TradeOrderApi tradeOrderApi;
@Resource
private ProductSpuService productSpuService;
@Override
public PageResult<ProductCommentDO> getCommentPage(ProductCommentPageReqVO pageReqVO) {
@@ -53,13 +70,48 @@ public class ProductCommentServiceImpl implements ProductCommentService {
}
@Override
public PageResult<ProductCommentDO> getCommentPage(AppCommentPageReqVO pageVO, Boolean visible) {
return productCommentMapper.selectPage(pageVO, visible);
public Map<String, Long> getCommentPageTabsCount(Long spuId, Boolean visible) {
Map<String, Long> countMap = new HashMap<>(4);
// 查询商品 id = spuId 的所有评论数量
countMap.put(AppCommentPageReqVO.ALL_COUNT, productCommentMapper.selectTabCount(spuId, visible, AppCommentPageReqVO.ALL));
// 查询商品 id = spuId 的所有好评数量
countMap.put(AppCommentPageReqVO.FAVOURABLE_COMMENT_COUNT, productCommentMapper.selectTabCount(spuId, visible, AppCommentPageReqVO.FAVOURABLE_COMMENT));
// 查询商品 id = spuId 的所有中评数量
countMap.put(AppCommentPageReqVO.MEDIOCRE_COMMENT_COUNT, productCommentMapper.selectTabCount(spuId, visible, AppCommentPageReqVO.MEDIOCRE_COMMENT));
// 查询商品 id = spuId 的所有差评数量
countMap.put(AppCommentPageReqVO.NEGATIVE_COMMENT_COUNT, productCommentMapper.selectTabCount(spuId, visible, AppCommentPageReqVO.NEGATIVE_COMMENT));
return countMap;
}
@Override
public PageResult<AppCommentRespVO> getCommentPage(AppCommentPageReqVO pageVO, Boolean visible) {
PageResult<AppCommentRespVO> result = ProductCommentConvert.INSTANCE.convertPage02(productCommentMapper.selectPage(pageVO, visible));
result.getList().forEach(item -> {
// 判断用户是否选择匿名
if (ObjectUtil.equal(item.getAnonymous(), true)) {
item.setUserNickname(AppCommentPageReqVO.ANONYMOUS_NICKNAME);
}
// 计算评价最终综合评分 最终星数 = (商品评星 + 服务评星) / 2
BigDecimal sumScore = new BigDecimal(item.getScores() + item.getBenefitScores());
BigDecimal divide = sumScore.divide(BigDecimal.valueOf(2L), 0, RoundingMode.DOWN);
item.setFinalScore(divide.intValue());
});
return result;
}
@Override
public void createComment(ProductCommentDO productComment, Boolean system) {
if (!system) {
// TODO 判断订单是否存在 fix
TradeOrderRespDTO order = tradeOrderApi.getOrder(productComment.getOrderId());
if (null == order) {
throw exception(ORDER_NOT_FOUND);
}
// TODO 判断 SPU 是否存在 fix
ProductSpuDO spu = productSpuService.getSpu(productComment.getSpuId());
if (null == spu) {
throw exception(SPU_NOT_EXISTS);
}
// 判断当前订单的当前商品用户是否评价过
ProductCommentDO exist = productCommentMapper.findByUserIdAndOrderIdAndSpuId(productComment.getId(), productComment.getOrderId(), productComment.getSpuId());
if (null != exist) {

View File

@@ -71,8 +71,8 @@ public class ProductPropertyServiceImpl implements ProductPropertyService {
// 更新
ProductPropertyDO updateObj = ProductPropertyConvert.INSTANCE.convert(updateReqVO);
productPropertyMapper.updateById(updateObj);
// TODO @puhui是不是只要传递变量不传递整个 updateObj 变量哈
productSkuService.updateSkuProperty(updateObj);
// 更新 sku 相关属性
productSkuService.updateSkuProperty(updateObj.getId(), updateObj.getName());
}
@Override

View File

@@ -73,10 +73,8 @@ public class ProductPropertyValueServiceImpl implements ProductPropertyValueServ
// 更新
ProductPropertyValueDO updateObj = ProductPropertyValueConvert.INSTANCE.convert(updateReqVO);
productPropertyValueMapper.updateById(updateObj);
// TODO 芋艿:更新时,需要看看 sku 表 fix
// TODO @puhui是不是只要传递变量不传递整个 updateObj 变量哈
productSkuService.updateSkuPropertyValue(updateObj);
// 更新 sku 相关属性
productSkuService.updateSkuPropertyValue(updateObj.getId(), updateObj.getName());
}
@Override

View File

@@ -2,8 +2,6 @@ package cn.iocoder.yudao.module.product.service.sku;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO;
import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyDO;
import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyValueDO;
import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
import java.util.Collection;
@@ -111,16 +109,18 @@ public interface ProductSkuService {
/**
* 更新 sku 属性
*
* @param updateObj 属性对象
* @param propertyId 属性 id
* @param propertyName 属性名
* @return int 影响的行数
*/
int updateSkuProperty(ProductPropertyDO updateObj);
int updateSkuProperty(Long propertyId, String propertyName);
/**
* 更新 sku 属性值
*
* @param updateObj 属性值对象
* @param propertyValueId 属性值 id
* @param propertyValueName 属性值名字
* @return int 影响的行数
*/
int updateSkuPropertyValue(ProductPropertyValueDO updateObj);
int updateSkuPropertyValue(Long propertyValueId, String propertyValueName);
}

View File

@@ -2,9 +2,8 @@ package cn.iocoder.yudao.module.product.service.sku;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuBaseVO;
import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO;
import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert;
import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyDO;
@@ -80,16 +79,25 @@ public class ProductSkuServiceImpl implements ProductSkuService {
@Override
public void validateSkuList(List<ProductSkuCreateOrUpdateReqVO> skus, Boolean specType) {
// 非多规格,不需要校验
if (ObjectUtil.notEqual(specType, true)) {
return;
}
// 0、校验skus是否为空
if (CollUtil.isEmpty(skus)) {
throw exception(SKU_NOT_EXISTS);
}
// 单规格处理
if (ObjectUtil.equal(specType, false)) {
ProductSkuCreateOrUpdateReqVO skuVO = skus.get(0);
// 赋予单规格默认属性
List<ProductSkuBaseVO.Property> properties = new ArrayList<>();
ProductSkuBaseVO.Property property = new ProductSkuBaseVO.Property();
property.setPropertyId(ProductPropertyDO.PROPERTY_ID);
property.setPropertyName(ProductPropertyDO.PROPERTY_NAME);
property.setValueId(ProductPropertyValueDO.VALUE_ID);
property.setValueName(ProductPropertyValueDO.VALUE_NAME);
properties.add(property);
skuVO.setProperties(properties);
// 单规格不需要后续的校验
return;
}
// 1、校验属性项存在
Set<Long> propertyIds = skus.stream().filter(p -> p.getProperties() != null)
// 遍历多个 Property 属性
@@ -156,81 +164,51 @@ public class ProductSkuServiceImpl implements ProductSkuService {
}
@Override
public int updateSkuProperty(ProductPropertyDO updateObj) {
// TODO 看了一下数据库有关于 json 字符串的处理,怕数据库出现兼容问题这里还是用数据库常规操作来实现
// TODO @puhui999直接全部查询处理批量处理就好列一般项目的商品不会超过几十万的哈。
Long count = productSkuMapper.selectCountByPropertyNotNull();
int currentPage = 1;
public int updateSkuProperty(Long propertyId, String propertyName) {
// 获取所有的 sku
List<ProductSkuDO> skuDOList = productSkuMapper.selectList();
// 处理后需要更新的 sku
List<ProductSkuDO> updateSkus = new ArrayList<>();
if (count == 0) {
if (CollUtil.isEmpty(skuDOList)) {
return 0;
}
int pageSize = 100;
for (int i = 0; i <= count / 100; i++) {
PageParam pageParam = new PageParam().setPageNo(currentPage + i).setPageSize(pageSize);
// 分页查找出 sku 属性不为 null 的
PageResult<ProductSkuDO> skuPage = productSkuMapper.selectPage(pageParam);
List<ProductSkuDO> records = skuPage.getList();
if (CollUtil.isEmpty(records)) {
break;
}
records.stream().filter(sku -> sku.getProperties() != null)
.forEach(sku -> sku.getProperties().forEach(property -> {
if (property.getPropertyId().equals(updateObj.getId())) {
property.setPropertyName(updateObj.getName());
updateSkus.add(sku);
}
}));
}
skuDOList.stream().filter(sku -> sku.getProperties() != null)
.forEach(sku -> sku.getProperties().forEach(property -> {
if (property.getPropertyId().equals(propertyId)) {
property.setPropertyName(propertyName);
updateSkus.add(sku);
}
}));
if (CollUtil.isEmpty(updateSkus)) {
return 0;
}
// TODO @puhui999貌似 updateBatch 自己会拆分批次,这里不用再拆分了
// 每批处理的大小
int batchSize = 1000;
for (int i = 0; i < updateSkus.size(); i += batchSize) {
List<ProductSkuDO> batchSkuDOs = updateSkus.subList(i, Math.min(i + batchSize, updateSkus.size()));
productSkuMapper.updateBatch(batchSkuDOs, batchSize);
}
productSkuMapper.updateBatch(updateSkus);
return updateSkus.size();
}
@Override
public int updateSkuPropertyValue(ProductPropertyValueDO updateObj) {
// TODO 看了一下数据库有关于 json 字符串的处理,怕数据库出现兼容问题这里还是用数据库常规操作来实现
Long count = productSkuMapper.selectCountByPropertyNotNull();
int currentPage = 1;
public int updateSkuPropertyValue(Long propertyValueId, String propertyValueName) {
// 获取所有的 sku
List<ProductSkuDO> skuDOList = productSkuMapper.selectList();
// 处理后需要更新的 sku
List<ProductSkuDO> updateSkus = new ArrayList<>();
if (count == 0) {
if (CollUtil.isEmpty(skuDOList)) {
return 0;
}
int pageSize = 100;
for (int i = 0; i <= count / 100; i++) {
PageParam pageParam = new PageParam().setPageNo(currentPage + i).setPageSize(pageSize);
// 分页查找出 sku 属性不为 null 的
PageResult<ProductSkuDO> skuPage = productSkuMapper.selectPage(pageParam);
List<ProductSkuDO> records = skuPage.getList();
if (CollUtil.isEmpty(records)) {
break;
}
records.stream()
.filter(sku -> sku.getProperties() != null)
.forEach(sku -> sku.getProperties().forEach(property -> {
if (property.getValueId().equals(updateObj.getId())) {
property.setValueName(updateObj.getName());
updateSkus.add(sku);
}
}));
}
skuDOList.stream()
.filter(sku -> sku.getProperties() != null)
.forEach(sku -> sku.getProperties().forEach(property -> {
if (property.getValueId().equals(propertyValueId)) {
property.setValueName(propertyValueName);
updateSkus.add(sku);
}
}));
if (CollUtil.isEmpty(updateSkus)) {
return 0;
}
// 每批处理的大小
int batchSize = 1000;
for (int i = 0; i < updateSkus.size(); i += batchSize) {
List<ProductSkuDO> batchSkuDOs = updateSkus.subList(i, Math.min(i + batchSize, updateSkus.size()));
productSkuMapper.updateBatch(batchSkuDOs, batchSize);
}
productSkuMapper.updateBatch(updateSkus);
return updateSkus.size();
}

View File

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.product.service.spu;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
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.AppProductSpuPageReqVO;
import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
@@ -135,4 +136,11 @@ public interface ProductSpuService {
*/
Long getSpuCountByCategoryId(Long id);
/**
* 通过 spu id 获取商品 SPU 明细
*
* @param id id
* @return 用户 App - 商品 SPU 明细
*/
AppProductSpuDetailRespVO getAppProductSpuDetail(Long id);
}

View File

@@ -7,15 +7,21 @@ import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCategoryListReqVO;
import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO;
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.AppProductSpuPageReqVO;
import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert;
import cn.iocoder.yudao.module.product.convert.spu.ProductSpuConvert;
import cn.iocoder.yudao.module.product.dal.dataobject.category.ProductCategoryDO;
import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyDO;
import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyValueDO;
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.dal.mysql.spu.ProductSpuMapper;
import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
import cn.iocoder.yudao.module.product.service.brand.ProductBrandService;
import cn.iocoder.yudao.module.product.service.category.ProductCategoryService;
import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;
import cn.iocoder.yudao.module.product.service.property.bo.ProductPropertyValueDetailRespBO;
import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
import com.google.common.collect.Maps;
import org.springframework.context.annotation.Lazy;
@@ -51,6 +57,9 @@ public class ProductSpuServiceImpl implements ProductSpuService {
private ProductBrandService brandService;
@Resource
private ProductCategoryService categoryService;
@Resource
@Lazy // 循环依赖,避免报错
private ProductPropertyValueService productPropertyValueService;
@Override
@Transactional(rollbackFor = Exception.class)
@@ -141,7 +150,11 @@ public class ProductSpuServiceImpl implements ProductSpuService {
// 校验存在
validateSpuExists(id);
// 校验商品状态不是回收站不能删除
validateSpuStatus(id);
ProductSpuDO spuDO = productSpuMapper.selectById(id);
// 判断 SPU 状态是否为回收站
if (ObjectUtil.notEqual(spuDO.getStatus(), ProductSpuStatusEnum.RECYCLE.getStatus())) {
throw exception(SPU_NOT_RECYCLE);
}
// 删除 SPU
productSpuMapper.deleteById(id);
@@ -155,20 +168,6 @@ public class ProductSpuServiceImpl implements ProductSpuService {
}
}
/**
* 验证 SPU 状态是否为回收站
*
* @param id id
*/
// TODO puhui999感觉不用独立出来一个方法直接在 deleteSpu 方法中校验即可
private void validateSpuStatus(Long id) {
ProductSpuDO spuDO = productSpuMapper.selectById(id);
// 判断 SPU 状态是否为回收站
if (ObjectUtil.notEqual(spuDO.getStatus(), ProductSpuStatusEnum.RECYCLE.getStatus())) {
throw exception(SPU_NOT_RECYCLE);
}
}
@Override
public ProductSpuDO getSpu(Long id) {
return productSpuMapper.selectById(id);
@@ -263,4 +262,35 @@ public class ProductSpuServiceImpl implements ProductSpuService {
return productSpuMapper.selectCount(ProductSpuDO::getCategoryId, id);
}
@Override
public AppProductSpuDetailRespVO getAppProductSpuDetail(Long id) {
// 获得商品 SPU
ProductSpuDO spu = getSpu(id);
if (spu == null) {
throw exception(SPU_NOT_EXISTS);
}
if (!ProductSpuStatusEnum.isEnable(spu.getStatus())) {
throw exception(SPU_NOT_ENABLE);
}
// 查询商品 SKU
List<ProductSkuDO> skus = productSkuService.getSkuListBySpuId(spu.getId());
List<ProductPropertyValueDetailRespBO> propertyValues = new ArrayList<>();
// 单规格商品 赋予默认属性值
if (ObjectUtil.equal(spu.getSpecType(), false)) {
ProductPropertyValueDetailRespBO respBO = new ProductPropertyValueDetailRespBO();
respBO.setPropertyId(ProductPropertyDO.PROPERTY_ID);
respBO.setPropertyName(ProductPropertyDO.PROPERTY_NAME);
respBO.setValueId(ProductPropertyValueDO.VALUE_ID);
respBO.setValueName(ProductPropertyValueDO.VALUE_NAME);
propertyValues.add(respBO);
} else {
// 多规格商品则查询商品属性
propertyValues = productPropertyValueService
.getPropertyValueDetailList(ProductSkuConvert.INSTANCE.convertPropertyValueIds(skus));
}
// 拼接
return ProductSpuConvert.INSTANCE.convertForGetSpuDetail(spu, skus, propertyValues);
}
}

View File

@@ -11,15 +11,22 @@ import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommen
import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentUpdateVisibleReqVO;
import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentAdditionalReqVO;
import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentPageReqVO;
import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentRespVO;
import cn.iocoder.yudao.module.product.convert.comment.ProductCommentConvert;
import cn.iocoder.yudao.module.product.dal.dataobject.comment.ProductCommentDO;
import cn.iocoder.yudao.module.product.dal.mysql.comment.ProductCommentMapper;
import cn.iocoder.yudao.module.product.enums.comment.ProductCommentScoresEnum;
import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
import cn.iocoder.yudao.module.trade.api.order.TradeOrderApi;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Lazy;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
@@ -39,8 +46,14 @@ public class ProductCommentServiceImplTest extends BaseDbUnitTest {
private ProductCommentMapper productCommentMapper;
@Resource
@Lazy
private ProductCommentServiceImpl productCommentService;
@MockBean
private TradeOrderApi tradeOrderApi;
@MockBean
private ProductSpuService productSpuService;
public String generateNo() {
return DateUtil.format(new Date(), "yyyyMMddHHmmss") + RandomUtil.randomInt(100000, 999999);
}
@@ -70,6 +83,23 @@ public class ProductCommentServiceImplTest extends BaseDbUnitTest {
o.setScores(ProductCommentScoresEnum.FOUR.getScores());
o.setReplied(Boolean.TRUE);
o.setVisible(Boolean.TRUE);
o.setId(generateId());
o.setUserId(generateId());
o.setAnonymous(Boolean.TRUE);
o.setOrderId(generateId());
o.setOrderItemId(generateId());
o.setSpuId(generateId());
o.setSkuId(generateId());
o.setDescriptionScores(ProductCommentScoresEnum.FOUR.getScores());
o.setBenefitScores(ProductCommentScoresEnum.FOUR.getScores());
o.setDeliveryScores(ProductCommentScoresEnum.FOUR.getScores());
o.setContent("真好吃");
o.setReplyUserId(generateId());
o.setReplyContent("确实");
o.setReplyTime(LocalDateTime.now());
o.setAdditionalTime(LocalDateTime.now());
o.setCreateTime(LocalDateTime.now());
o.setUpdateTime(LocalDateTime.now());
});
productCommentMapper.insert(productComment);
@@ -77,7 +107,7 @@ public class ProductCommentServiceImplTest extends BaseDbUnitTest {
Long spuId = productComment.getSpuId();
// 测试 userNickname 不匹配
productCommentMapper.insert(cloneIgnoreId(productComment, o -> o.setUserNickname("王三")));
productCommentMapper.insert(cloneIgnoreId(productComment, o -> o.setUserNickname("王三").setScores(ProductCommentScoresEnum.ONE.getScores())));
// 测试 orderId 不匹配
productCommentMapper.insert(cloneIgnoreId(productComment, o -> o.setOrderId(generateId())));
// 测试 spuId 不匹配
@@ -107,8 +137,25 @@ public class ProductCommentServiceImplTest extends BaseDbUnitTest {
PageResult<ProductCommentDO> all = productCommentService.getCommentPage(new ProductCommentPageReqVO());
assertEquals(8, all.getTotal());
PageResult<ProductCommentDO> visible = productCommentService.getCommentPage(new AppCommentPageReqVO(), Boolean.TRUE);
assertEquals(7, visible.getTotal());
// 测试获取所有商品分页评论数据
PageResult<AppCommentRespVO> result1 = productCommentService.getCommentPage(new AppCommentPageReqVO(), Boolean.TRUE);
assertEquals(7, result1.getTotal());
// 测试获取所有商品分页中评数据
PageResult<AppCommentRespVO> result2 = productCommentService.getCommentPage(new AppCommentPageReqVO().setType(AppCommentPageReqVO.MEDIOCRE_COMMENT), Boolean.TRUE);
assertEquals(2, result2.getTotal());
// 测试获取指定 spuId 商品分页中评数据
PageResult<AppCommentRespVO> result3 = productCommentService.getCommentPage(new AppCommentPageReqVO().setSpuId(spuId).setType(AppCommentPageReqVO.MEDIOCRE_COMMENT), Boolean.TRUE);
assertEquals(2, result3.getTotal());
// 测试分页 tab count
Map<String, Long> tabsCount = productCommentService.getCommentPageTabsCount(spuId, Boolean.TRUE);
assertEquals(6, tabsCount.get(AppCommentPageReqVO.ALL_COUNT));
assertEquals(4, tabsCount.get(AppCommentPageReqVO.FAVOURABLE_COMMENT_COUNT));
assertEquals(2, tabsCount.get(AppCommentPageReqVO.MEDIOCRE_COMMENT_COUNT));
assertEquals(0, tabsCount.get(AppCommentPageReqVO.NEGATIVE_COMMENT_COUNT));
}
@Test