|
@@ -1,9 +1,10 @@
|
|
|
package edu.travel.commodity.service.impl;
|
|
|
|
|
|
import cn.hutool.core.bean.BeanUtil;
|
|
|
+import cn.hutool.core.collection.CollectionUtil;
|
|
|
import cn.hutool.core.util.ObjectUtil;
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
-import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
import edu.travel.commodity.constant.BaseConstant;
|
|
@@ -14,14 +15,23 @@ import edu.travel.commodity.utils.PageUtil;
|
|
|
import edu.travel.exception.BaseException;
|
|
|
import edu.travel.remote.dto.*;
|
|
|
import edu.travel.remote.vo.ShopProductVo;
|
|
|
+import edu.travel.rpc.RPCBaseResponse;
|
|
|
+import lombok.Data;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.data.redis.core.RedisTemplate;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
-import org.springframework.util.Assert;
|
|
|
|
|
|
import java.io.IOException;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.HashMap;
|
|
|
import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.Set;
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
|
+@Slf4j
|
|
|
@Service
|
|
|
public class ShopProductServiceImpl extends ServiceImpl<ShopProductMapper, ShopProduct> implements ShopProductService {
|
|
|
@Autowired
|
|
@@ -38,10 +48,16 @@ public class ShopProductServiceImpl extends ServiceImpl<ShopProductMapper, ShopP
|
|
|
|
|
|
@Autowired
|
|
|
private ShopSpecService shopSpecService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ShopSpecValueService shopSpecValueService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private RedisTemplate<Object, Object> redisTemplate;
|
|
|
|
|
|
@Override
|
|
|
public Page<ShopProductVo> getHotProduct(GetProductByTypeDto type) {
|
|
|
- LambdaQueryWrapper<ShopProduct> query = Wrappers.<ShopProduct>lambdaQuery()
|
|
|
+ LambdaQueryWrapper<ShopProduct> query = new QueryWrapper<ShopProduct>().lambda()
|
|
|
.eq(ShopProduct::getStatus, BaseConstant.BASIC_STATUS_NO_NUM)
|
|
|
.eq(ShopProduct::getCountryId, type.getCountryId());
|
|
|
|
|
@@ -86,4 +102,309 @@ public class ShopProductServiceImpl extends ServiceImpl<ShopProductMapper, ShopP
|
|
|
boolean b4 = shopProductParametersService.insertProductParameters(params.getParameters(), bean);
|
|
|
//TODO 同步到es
|
|
|
}
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public RPCBaseResponse<Void> saveProduct(InsertProductDto params) {
|
|
|
+ ShopProduct bean = BeanUtil.toBean(params, ShopProduct.class);
|
|
|
+ if (ObjectUtil.isEmpty(params.getImages())) {
|
|
|
+ throw new BaseException("至少需要一张图片");
|
|
|
+ }
|
|
|
+ List<FileDto> images = params.getImages();
|
|
|
+ bean.setMainImageUrl(images.get(0).getFilePath());
|
|
|
+ //保存商品
|
|
|
+ this.save(bean);
|
|
|
+ //保存商品规格和规格值
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ @Override
|
|
|
+ public RPCBaseResponse<List<Map<String, Object>>> batchSpecAndValueGenerate(List<InsertProductSpecDto> specList) {
|
|
|
+ if (specList == null || specList.isEmpty()) {
|
|
|
+ return RPCBaseResponse.error("规格参数为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 限制最多输入3组规格
|
|
|
+ if (specList.size() > 3) {
|
|
|
+ return RPCBaseResponse.error("最多只能输入3组规格");
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 自动清除之前的缓存
|
|
|
+ clearSpecCache();
|
|
|
+
|
|
|
+ // 验证每组规格
|
|
|
+ for (InsertProductSpecDto spec : specList) {
|
|
|
+ // 验证规格名称
|
|
|
+ if (ObjectUtil.isEmpty(spec.getSpecName())) {
|
|
|
+ return RPCBaseResponse.error("规格名称不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证规格值集合
|
|
|
+ if (spec.getSpecValues() == null || spec.getSpecValues().isEmpty()) {
|
|
|
+ return RPCBaseResponse.error("规格[" + spec.getSpecName() + "]的规格值不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 限制最多只能有3个规格值
|
|
|
+ if (spec.getSpecValues().size() > 3) {
|
|
|
+ return RPCBaseResponse.error("规格[" + spec.getSpecName() + "]最多只能有3个规格值");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证规格值名称
|
|
|
+ for (SpecValueDto valueDto : spec.getSpecValues()) {
|
|
|
+ if (ObjectUtil.isEmpty(valueDto.getSpecValueName())) {
|
|
|
+ return RPCBaseResponse.error("规格[" + spec.getSpecName() + "]的规格值名称不能为空");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建一个二维数组,用于存储每个规格的规格值对
|
|
|
+ List<List<SpecValueWithNameDto>> allSpecValues = new ArrayList<>();
|
|
|
+
|
|
|
+ for (InsertProductSpecDto spec : specList) {
|
|
|
+ List<SpecValueWithNameDto> specValues = new ArrayList<>();
|
|
|
+
|
|
|
+ for (SpecValueDto valueDto : spec.getSpecValues()) {
|
|
|
+ SpecValueWithNameDto valueWithName = new SpecValueWithNameDto();
|
|
|
+ valueWithName.setSpecName(spec.getSpecName());
|
|
|
+ valueWithName.setSpecValueName(valueDto.getSpecValueName());
|
|
|
+ specValues.add(valueWithName);
|
|
|
+ }
|
|
|
+
|
|
|
+ allSpecValues.add(specValues);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算组合数量
|
|
|
+ int totalCombinations = 1;
|
|
|
+ StringBuilder logMessage = new StringBuilder("规格值数量: ");
|
|
|
+
|
|
|
+ for (int i = 0; i < specList.size(); i++) {
|
|
|
+ InsertProductSpecDto spec = specList.get(i);
|
|
|
+ int valueCount = spec.getSpecValues().size();
|
|
|
+ totalCombinations *= valueCount;
|
|
|
+ logMessage.append(spec.getSpecName()).append("(").append(valueCount).append(")");
|
|
|
+
|
|
|
+ if (i < specList.size() - 1) {
|
|
|
+ logMessage.append(" × ");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ logMessage.append(" = ").append(totalCombinations);
|
|
|
+ log.info(logMessage.toString());
|
|
|
+
|
|
|
+ // 生成笛卡尔积组合
|
|
|
+ List<List<SpecValueWithNameDto>> cartesianResult = new ArrayList<>();
|
|
|
+ generateCartesianCombinations(allSpecValues, 0, new ArrayList<>(), cartesianResult);
|
|
|
+
|
|
|
+ log.info("生成的组合总数: {}", cartesianResult.size());
|
|
|
+
|
|
|
+ // 转换为新格式的结果,每个组合为一个Map,包含规格对象数组和库存
|
|
|
+ List<Map<String, Object>> resultList = cartesianResult.stream()
|
|
|
+ .map(combination -> {
|
|
|
+ Map<String, Object> resultMap = new HashMap<>();
|
|
|
+ List<Map<String, String>> specItems = new ArrayList<>();
|
|
|
+
|
|
|
+ // 将规格值转换为对象数组
|
|
|
+ for (SpecValueWithNameDto item : combination) {
|
|
|
+ Map<String, String> specItem = new HashMap<>();
|
|
|
+ specItem.put("specName", item.getSpecName());
|
|
|
+ specItem.put("specValueName", item.getSpecValueName());
|
|
|
+ specItems.add(specItem);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加规格数组到结果中
|
|
|
+ for (int i = 0; i < specItems.size(); i++) {
|
|
|
+ resultMap.put(String.valueOf(i), specItems.get(i));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加默认库存为0
|
|
|
+ resultMap.put("inventory", "0");
|
|
|
+
|
|
|
+ return resultMap;
|
|
|
+ })
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ return RPCBaseResponse.success("规格组合生成成功", resultList);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("规格组合生成失败:", e);
|
|
|
+ return RPCBaseResponse.error("规格组合生成失败:" + e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 清除规格缓存
|
|
|
+ */
|
|
|
+ private void clearSpecCache() {
|
|
|
+ String specListKey = "product:spec:list";
|
|
|
+ Set<Object> keys = redisTemplate.opsForSet().members(specListKey);
|
|
|
+ if (keys != null) {
|
|
|
+ for (Object key : keys) {
|
|
|
+ redisTemplate.delete("product:spec:" + key);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ redisTemplate.delete(specListKey);
|
|
|
+ log.info("已清空规格缓存");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 递归生成规格值的笛卡尔积组合
|
|
|
+ */
|
|
|
+ private void generateCartesianCombinations(
|
|
|
+ List<List<SpecValueWithNameDto>> allSpecValues,
|
|
|
+ int currentIndex,
|
|
|
+ List<SpecValueWithNameDto> currentCombination,
|
|
|
+ List<List<SpecValueWithNameDto>> result) {
|
|
|
+
|
|
|
+ // 基本情况:已经处理完所有规格
|
|
|
+ if (currentIndex == allSpecValues.size()) {
|
|
|
+ result.add(new ArrayList<>(currentCombination));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取当前层级的规格值
|
|
|
+ List<SpecValueWithNameDto> currentValues = allSpecValues.get(currentIndex);
|
|
|
+
|
|
|
+ // 遍历当前规格的所有值
|
|
|
+ for (SpecValueWithNameDto value : currentValues) {
|
|
|
+ // 添加当前规格值到组合中
|
|
|
+ currentCombination.add(value);
|
|
|
+
|
|
|
+ // 递归处理下一个规格
|
|
|
+ generateCartesianCombinations(allSpecValues, currentIndex + 1, currentCombination, result);
|
|
|
+
|
|
|
+ // 回溯:移除最后添加的规格值
|
|
|
+ currentCombination.remove(currentCombination.size() - 1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成更结构化的规格组合
|
|
|
+ *
|
|
|
+ * @param specList 规格列表
|
|
|
+ * @return 结构化的规格组合结果
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public RPCBaseResponse<List<Map<String, Object>>> generateStructuredSpecCombinations(List<InsertProductSpecDto> specList) {
|
|
|
+ if (specList == null || specList.isEmpty()) {
|
|
|
+ return RPCBaseResponse.error("规格参数为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 限制最多输入3组规格
|
|
|
+ if (specList.size() > 3) {
|
|
|
+ return RPCBaseResponse.error("最多只能输入3组规格");
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 自动清除之前的缓存
|
|
|
+ clearSpecCache();
|
|
|
+
|
|
|
+ // 验证每组规格
|
|
|
+ for (InsertProductSpecDto spec : specList) {
|
|
|
+ // 验证规格名称
|
|
|
+ if (ObjectUtil.isEmpty(spec.getSpecName())) {
|
|
|
+ return RPCBaseResponse.error("规格名称不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证规格值集合
|
|
|
+ if (CollectionUtil.isEmpty(spec.getSpecValues())) {
|
|
|
+ return RPCBaseResponse.error("规格[" + spec.getSpecName() + "]的规格值不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 限制最多只能有10个规格值
|
|
|
+ if (spec.getSpecValues().size() > 8) {
|
|
|
+ return RPCBaseResponse.error("规格[" + spec.getSpecName() + "]最多只能有8个规格值");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理所有规格
|
|
|
+ List<List<SpecValueWithNameDto>> specValueList = new ArrayList<>();
|
|
|
+ for (InsertProductSpecDto insertProductSpecDto : specList) {
|
|
|
+ String specName = insertProductSpecDto.getSpecName();
|
|
|
+ List<SpecValueDto> specValues = insertProductSpecDto.getSpecValues();
|
|
|
+
|
|
|
+ // 为每个规格值创建DTO
|
|
|
+ List<SpecValueWithNameDto> specValueWithNames = specValues.stream()
|
|
|
+ .map(specValue -> new SpecValueWithNameDto(specName, specValue.getSpecValueName()))
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ specValueList.add(specValueWithNames);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 生成规格组合
|
|
|
+ List<List<SpecValueWithNameDto>> combinations = buildCombinations(specValueList);
|
|
|
+
|
|
|
+ // 转换为新格式的结果,每个组合为一个Map,包含规格对象数组和库存
|
|
|
+ List<Map<String, Object>> resultList = combinations.stream()
|
|
|
+ .map(combination -> {
|
|
|
+ Map<String, Object> resultMap = new HashMap<>();
|
|
|
+ List<Map<String, String>> specItems = new ArrayList<>();
|
|
|
+
|
|
|
+ // 将规格值转换为对象数组
|
|
|
+ for (SpecValueWithNameDto item : combination) {
|
|
|
+ Map<String, String> specItem = new HashMap<>();
|
|
|
+ specItem.put("specName", item.getSpecName());
|
|
|
+ specItem.put("specValueName", item.getSpecValueName());
|
|
|
+ specItems.add(specItem);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加规格数组到结果中
|
|
|
+ for (int i = 0; i < specItems.size(); i++) {
|
|
|
+ resultMap.put(String.valueOf(i), specItems.get(i));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加默认库存为0
|
|
|
+ resultMap.put("inventory", "0");
|
|
|
+
|
|
|
+ // 日志记录
|
|
|
+ StringBuilder debug = new StringBuilder("组合: ");
|
|
|
+ for (Map<String, String> spec : specItems) {
|
|
|
+ debug.append(spec.get("specName")).append("=").append(spec.get("specValueName")).append(", ");
|
|
|
+ }
|
|
|
+ log.debug(debug.toString());
|
|
|
+
|
|
|
+ return resultMap;
|
|
|
+ })
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ log.info("生成规格组合总数: {}", resultList.size());
|
|
|
+ return RPCBaseResponse.success("规格组合生成成功", resultList);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("生成规格组合出错", e);
|
|
|
+ return RPCBaseResponse.error("生成规格组合失败:" + e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 递归构建规格值的组合
|
|
|
+ *
|
|
|
+ * @param specValuesList 规格值列表
|
|
|
+ * @return 所有组合的列表
|
|
|
+ */
|
|
|
+ private List<List<SpecValueWithNameDto>> buildCombinations(List<List<SpecValueWithNameDto>> specValuesList) {
|
|
|
+ // 基本情况:如果只有一组规格值,则直接返回包装后的列表
|
|
|
+ if (specValuesList.size() == 1) {
|
|
|
+ return specValuesList.get(0).stream()
|
|
|
+ .map(value -> {
|
|
|
+ List<SpecValueWithNameDto> combination = new ArrayList<>();
|
|
|
+ combination.add(value);
|
|
|
+ return combination;
|
|
|
+ })
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 取出第一组规格值
|
|
|
+ List<SpecValueWithNameDto> first = specValuesList.get(0);
|
|
|
+
|
|
|
+ // 递归处理剩余的规格值
|
|
|
+ List<List<SpecValueWithNameDto>> restCombinations = buildCombinations(specValuesList.subList(1, specValuesList.size()));
|
|
|
+
|
|
|
+ // 合并第一组与剩余组合
|
|
|
+ List<List<SpecValueWithNameDto>> result = new ArrayList<>();
|
|
|
+ for (SpecValueWithNameDto value : first) {
|
|
|
+ for (List<SpecValueWithNameDto> restCombination : restCombinations) {
|
|
|
+ List<SpecValueWithNameDto> newCombination = new ArrayList<>();
|
|
|
+ newCombination.add(value);
|
|
|
+ newCombination.addAll(restCombination);
|
|
|
+ result.add(newCombination);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
}
|