package com.cyksj.service.mange.goods; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.cyksj.common.exception.BusinessRuntimeException; import com.cyksj.common.util.Jsons; import com.cyksj.mapper.GoodsDonMapper; import com.cyksj.mapper.GoodsDonSkuMapper; import com.cyksj.mapper.GoodsDonSpecMapper; import com.cyksj.model.entity.GoodsDon; import com.cyksj.model.entity.GoodsDonSku; import com.cyksj.model.entity.GoodsDonSpec; import com.cyksj.model.manage.request.ReqGoodsSkuInsert; import com.cyksj.model.manage.request.ReqGoodsSkuSpec; import com.cyksj.model.manage.request.ReqGoodsSpecSingle; import com.cyksj.model.request.CombinationSkuReq; import com.cyksj.service.mange.CmsGoodsDonSpecService; import com.fasterxml.jackson.databind.JavaType; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; /** * @author chan * @date 2022/9/27 5:19 PM */ @Slf4j @Service @RequiredArgsConstructor public class CmsGoodsDonSpecServiceImpl extends ServiceImpl implements CmsGoodsDonSpecService { private final GoodsDonMapper goodsDonMapper; private final GoodsDonSkuMapper goodsDonSkuMapper; @Override @Transactional(rollbackFor = Throwable.class) public void insertOrUpdateSkuSpec(ReqGoodsSkuInsert insert, Boolean isUpdate) throws Exception { GoodsDon goods = goodsDonMapper.selectById(insert.getGoodsId()); if (null == goods) { throw new RuntimeException("找不到商品spu."); } GoodsDonSpec loveSpec = this.getOne(Wrappers.lambdaQuery(GoodsDonSpec.class).eq(GoodsDonSpec::getGoodsId, insert.getGoodsId())); Map> dbSpecMap = null; Map dbNodeMap = null; if (null != loveSpec) { insert.setId(loveSpec.getId()); String specsJson = loveSpec.getSpecsJson(); dbSpecMap = Jsons.parseList(specsJson, ReqGoodsSkuSpec.class).stream().collect(Collectors.groupingBy(ReqGoodsSkuSpec::getKey)); } if (isUpdate && loveSpec == null) { throw BusinessRuntimeException.getInstance("所更新的平台id:{0}属性不存在", insert.getGoodsId()); } List specs = insert.getSpecs(); if (null == specs || specs.isEmpty()) { throw new RuntimeException("缺少商品总规格."); } // 属性最大id Integer maxId = 0; // 统计总属性数 Integer total = 0; // 判断id是否有重复 Map map = null; // 规格数量 int specNumber = specs.size(); dbNodeMap = checkUpdateGoodsSpec(goods.getId(), dbSpecMap, dbNodeMap, specs); for (ReqGoodsSkuSpec spec : specs) { if (StringUtils.isBlank(spec.getKey())) { throw new RuntimeException("存在空白规格名."); } List values = spec.getValues(); if (null == values || values.isEmpty()) { throw new RuntimeException("规格[" + spec.getKey() + "]中缺少属性."); } total += values.size(); for (ReqGoodsSkuSpec.Node e : values) { if (null == e.getId() || e.getId() < 0) { e.setId(maxId + 1); } if (StringUtils.isBlank(e.getValue())) { throw new RuntimeException("规格[" + spec.getKey() + "]中存在空白属性."); } if (e.getId() > maxId) { maxId = e.getId(); } //新增时 实物价格要填入 if (goods.getType() == 2 && insert.getId() == null && (e.getPrice() == null || e.getPrice().compareTo(BigDecimal.ZERO) < 0)) { throw BusinessRuntimeException.getInstance("实物规格:{0}对应价格请输入", e.getValue()); } if (null == map) { map = new HashMap<>(16); } String benefitsList = e.getBenefitsList(); if (StrUtil.isNotBlank(benefitsList)) { Set benefitsSkuIds = Jsons.parseSet(benefitsList, GoodsDonSku.class); e.setBenefitsList(Jsons.toJson(benefitsSkuIds)); } syncSpecNodeSku(goods.getId(), dbNodeMap, e); ReqGoodsSpecSingle single = new ReqGoodsSpecSingle(); single.setId(e.getId()); single.setKey(spec.getKey()); single.setName(e.getValue()); map.put(e.getId(), single); } } if (map.size() != total) { throw new RuntimeException("规格中存在重复id."); } GoodsDonSpec spec = new GoodsDonSpec(); spec.setId(insert.getId()); spec.setGoodsId(insert.getGoodsId()); spec.setMaxId(maxId); spec.setSpecsJson(Jsons.toJson(specs)); spec.setSpecNumber(specNumber); spec.setSpecMapping(Jsons.toJson(map)); if (insert.getId() != null) { this.updateById(spec); return; } this.save(spec); } @Override public void skuAppend(ReqGoodsSkuInsert insert, GoodsDon goods) throws Exception { GoodsDonSpec loveSpec = this.getOne(Wrappers.lambdaQuery(GoodsDonSpec.class).eq(GoodsDonSpec::getGoodsId, insert.getGoodsId())); if (null == loveSpec) { throw new RuntimeException("该商品未设置总规格, 请设置总规格."); } List skus = insert.getSkus(); if (null == skus || skus.isEmpty()) { throw new RuntimeException("参数错误."); } int specNumber = loveSpec.getSpecNumber(); JavaType type = Jsons.javaType().constructMapType(HashMap.class, Integer.class, ReqGoodsSpecSingle.class); Map map = Jsons.parseObject(loveSpec.getSpecMapping(), type); StringBuilder builder = new StringBuilder(); List specList = null; GoodsDonSku goodsDonSku = null; // 判断属性是否属于同一个key Set set = null; for (ReqGoodsSkuInsert.Sku sku : insert.getSkus()) { // 跳过空值的sku // 解析sku的规格属性 final String specIds = sku.getSpecIds(); if (StringUtils.isBlank(specIds)) { throw new RuntimeException("请选择商品的规格."); } int[] ids = Jsons.parseObject(specIds, int[].class); // if (ids.length != specNumber) { // throw new RuntimeException("sku规格数量 与 总规格数量 不一致."); // } // init builder.delete(0, builder.length()); if (null == specList) { specList = new ArrayList<>(specNumber); } else { specList.clear(); } if (null == set) { int capacity = (specNumber + 1) * 4 / 3 + 1; set = new HashSet<>(capacity); } else { set.clear(); } for (int id : ids) { ReqGoodsSpecSingle single = map.get(id); if (null == single) { throw new RuntimeException("总规格属性id中 不存在 sku中的id."); } builder.append(single.getName()).append(';'); specList.add(single); set.add(single.getKey()); } // if (set.size() != specNumber) { // throw new RuntimeException("sku中存在多个属性属于同一个规格."); // } String specVal = builder.toString(); String specJson = Jsons.toJson(specList); if (null == goodsDonSku) { goodsDonSku = new GoodsDonSku(); goodsDonSku.setGoodsId(insert.getGoodsId()); } BeanUtil.copyProperties(sku, goodsDonSku); String specIdsStr = Jsons.toJson(ids); //升序 Arrays.sort(ids); String dupSpecIds = Jsons.toJson(ids); GoodsDonSku specIds_exists = goodsDonSkuMapper.selectOne(Wrappers.lambdaQuery(GoodsDonSku.class) .and(qr -> qr.eq(GoodsDonSku::getDupSpecIds, dupSpecIds).or().eq(GoodsDonSku::getSpecIds, specIdsStr)) .eq(GoodsDonSku::getGoodsId, goods.getId()) .last("limit 1")); if (specIds_exists != null) { log.info("库中存在相同二级分类的规格,spec_ids:{}", dupSpecIds); continue; } goodsDonSku.setSpecIds(specIdsStr); goodsDonSku.setDupSpecIds(dupSpecIds); goodsDonSku.setSpecVal(specVal); goodsDonSku.setSpecJson(specJson); String benefitsList = goodsDonSku.getBenefitsList(); if (StrUtil.isNotBlank(benefitsList)) { Set benefitsSkuIds = Jsons.parseSet(benefitsList, GoodsDonSku.class); goodsDonSku.setIsBenefits(true); goodsDonSku.setBenefitsList(Jsons.toJson(benefitsSkuIds)); } if (goods.getType() == 1) { if (sku.getMonths() != null && sku.getMonths() > 0 && sku.getDays() != null && sku.getDays() > 0) throw BusinessRuntimeException.getInstance("续费天数和续费月数不能同时存在"); if (sku.getMonths() == null && sku.getDays() == null) throw BusinessRuntimeException.getInstance("请输入对应续费时间"); } String specificGoodsIds = goodsDonSku.getSpecificGoodsIds(); if (StrUtil.isNotBlank(specificGoodsIds)) { Set specificGids = Jsons.parseSet(specificGoodsIds, Long.class); Integer count = goodsDonMapper.selectCount(Wrappers.lambdaQuery(GoodsDon.class) .in(GoodsDon::getId, specificGids) .eq(GoodsDon::getStatus, true) .eq(GoodsDon::getDeleted, true)); if (count != specificGids.size()) { throw BusinessRuntimeException.getInstance("存在被删除或已下架的平台"); } BigDecimal specificPrice = goodsDonSku.getSpecificPrice(); if (specificPrice == null || specificPrice.compareTo(BigDecimal.ZERO) <= 0) { throw BusinessRuntimeException.getInstance("请输入正确的特定价格"); } } goodsDonSkuMapper.insert(goodsDonSku); } } @Override public void combinationSku(Long goodsId, List combinations) throws Exception { GoodsDon goodsDon = goodsDonMapper.selectById(goodsId); if (goodsDon == null) throw BusinessRuntimeException.getInstance("平台不存在"); if (goodsDon.getType() != 2) { throw BusinessRuntimeException.getInstance("暂只支持实物规格"); } GoodsDonSpec loveSpec = this.getOne(Wrappers.lambdaQuery(GoodsDonSpec.class).eq(GoodsDonSpec::getGoodsId, goodsId)); if (null == loveSpec) { throw new RuntimeException("该商品未设置总规格, 请设置总规格."); } String specsJson = loveSpec.getSpecsJson(); List specsList = Jsons.parseList(specsJson, ReqGoodsSkuSpec.class); Map> specValueMap = specsList.stream().collect(Collectors.groupingBy(ReqGoodsSkuSpec::getKey)); if (combinations.isEmpty()) { throw BusinessRuntimeException.getInstance("请选择对应的规格组合"); } List skus = new LinkedList<>(); ReqGoodsSkuInsert reqGoodsSkuInsert = new ReqGoodsSkuInsert(); reqGoodsSkuInsert.setGoodsId(goodsId); List> specIdLists = new LinkedList<>(); CombinationSkuReq request = combinations.get(0); List reqGoodsSkuSpecs = specValueMap.get(request.getKey()); if (reqGoodsSkuSpecs == null) { throw BusinessRuntimeException.getInstance("传入的总规格属性:{0}不存在", request.getKey()); } Map> nodeIdMap = reqGoodsSkuSpecs.get(0).getValues().stream().collect(Collectors.groupingBy(ReqGoodsSkuSpec.Node::getId)); if (CollUtil.isNotEmpty(request.getValues())) { unifiedHandleCombinationSku(combinations, request.getValues(), specValueMap, nodeIdMap, specIdLists, skus); } else { ReqGoodsSkuSpec reqGoodsSkuSpec = reqGoodsSkuSpecs.get(0); unifiedHandleCombinationSku(combinations,reqGoodsSkuSpec.getValues(),specValueMap, nodeIdMap,specIdLists,skus); } reqGoodsSkuInsert.setSkus(skus); skuAppend(reqGoodsSkuInsert, goodsDon); } public void unifiedHandleCombinationSku(List combinations, List values, Map> specValueMap, Map> nodeIdMap, List> specIdLists, List skus) throws Exception { for (ReqGoodsSkuSpec.Node node_value : values) { ReqGoodsSkuInsert.Sku sku = new ReqGoodsSkuInsert.Sku(); sku.setPrice(BigDecimal.ZERO); List specIds = new LinkedList<>(); unifiedHandleSecondCombinationSku(node_value, nodeIdMap, sku, specIds); recurCombinationSku(combinations, combinations.size(), 1, 1, specIds, specIdLists, sku, specValueMap, skus); } } public void unifiedHandleSecondCombinationSku(ReqGoodsSkuSpec.Node temp_node, Map> nodeIdMap, ReqGoodsSkuInsert.Sku sku, List specIds) { List nodes = nodeIdMap.get(temp_node.getId()); if (CollUtil.isEmpty(nodes)) { throw BusinessRuntimeException.getInstance("{0}规格不存在", temp_node.getValue()); } ReqGoodsSkuSpec.Node node = nodes.get(0); BigDecimal price = node.getPrice(); if (price == null && node.getValue().contains("无")) { price = BigDecimal.ZERO; } if (price == null) { throw BusinessRuntimeException.getInstance("该实物子规格:{0}未配置价格", node.getValue()); } sku.setPrice(sku.getPrice().add(price)); specIds.add(temp_node.getId()); if (StrUtil.isNotEmpty(node.getBenefitsList())) { sku.setIsBenefits(true); sku.setBenefitsList(node.getBenefitsList()); } } /** * @param combinations 传入的组合数据 * @param total 总共需要组合的大小 * @param has 已有组合数大小 * @param start 下一个组合数组下表 * @param specIds 组合规格ids * @param sku 待插入库中的sku * @param specValueMap 总规格库中对应values */ private void recurCombinationSku(List combinations, Integer total, Integer has, Integer start, List specIds, List> specIdLists, ReqGoodsSkuInsert.Sku sku, Map> specValueMap, List skus) throws Exception { if (has == total) { if (specIdLists.contains(specIds)) { return; } sku.setSpecIds(Jsons.toJson(specIds)); skus.add(sku); return; } CombinationSkuReq request = combinations.get(start); List reqGoodsSkuSpecs = specValueMap.get(request.getKey()); if (reqGoodsSkuSpecs == null) { throw BusinessRuntimeException.getInstance("总规格属性{0}不存在", request.getKey()); } ReqGoodsSkuSpec reqGoodsSkuSpec = reqGoodsSkuSpecs.get(0); Map> nodeIdMap = reqGoodsSkuSpec.getValues().stream().collect(Collectors.groupingBy(ReqGoodsSkuSpec.Node::getId)); if (CollUtil.isNotEmpty(request.getValues())) { createNewSkuAndRecurCombination(combinations, request.getValues(), nodeIdMap, has, start, specIds, specIdLists, sku, specValueMap, skus); return; } createNewSkuAndRecurCombination(combinations, reqGoodsSkuSpec.getValues(), nodeIdMap, has, start, specIds, specIdLists, sku, specValueMap, skus); } /** * 新的规格 递归 */ public void createNewSkuAndRecurCombination(List combinations, List values, Map> nodeIdMap, Integer has, Integer start, List specIds, List> specIdLists, ReqGoodsSkuInsert.Sku sku, Map> specValueMap, List skus) throws Exception { for (ReqGoodsSkuSpec.Node node_value : values) { BigDecimal last_price = sku.getPrice(); unifiedHandleSecondCombinationSku(node_value, nodeIdMap, sku, specIds); recurCombinationSku(combinations, combinations.size(), has + 1, start + 1, specIds, specIdLists, sku, specValueMap, skus); specIds.remove(node_value.getId()); sku = new ReqGoodsSkuInsert.Sku(); sku.setPrice(last_price); } } /** * 更新删除总属性 一级分类 校验 */ public Map checkUpdateGoodsSpec(Long goodsId, Map> dbSpecMap, Map dbNodeMap, List specs) { if (CollUtil.isNotEmpty(dbSpecMap)) { Map> updateSpecMap = specs.stream().collect(Collectors.groupingBy(ReqGoodsSkuSpec::getKey)); dbNodeMap = new ConcurrentHashMap<>(); for (String key : dbSpecMap.keySet()) { List reqGoodsSkuSpecs = updateSpecMap.get(key); if (CollUtil.isNotEmpty(reqGoodsSkuSpecs)) { ReqGoodsSkuSpec reqGoodsSkuSpec = reqGoodsSkuSpecs.get(0); if (CollUtil.isEmpty(reqGoodsSkuSpec.getValues())) { //删除整个一级分类key String specFirstKey = reqGoodsSkuSpec.getKey(); GoodsDonSku sku = goodsDonSkuMapper.selectOne(Wrappers.lambdaQuery(GoodsDonSku.class) .eq(GoodsDonSku::getGoodsId, goodsId) .like(GoodsDonSku::getSpecJson, specFirstKey).last("limit 1")); if (sku != null) { throw BusinessRuntimeException.getInstance("删除总规格分类,存在使用该总规格分类的规格"); } } else { Map> updateSecondMap = reqGoodsSkuSpec.getValues().stream().filter(node -> node.getId() != null).collect(Collectors.groupingBy(ReqGoodsSkuSpec.Node::getId)); ReqGoodsSkuSpec dbReqGoodsSkuSpec = dbSpecMap.get(key).get(0); for (ReqGoodsSkuSpec.Node dbSecondValue : dbReqGoodsSkuSpec.getValues()) { if (updateSecondMap.get(dbSecondValue.getId()) == null) { GoodsDonSku sku = goodsDonSkuMapper.selectOne(Wrappers.lambdaQuery(GoodsDonSku.class) .eq(GoodsDonSku::getGoodsId, goodsId) .like(GoodsDonSku::getSpecVal, dbSecondValue.getValue()).last("limit 1")); if (sku != null) { throw BusinessRuntimeException.getInstance("删除总规格分类,存在使用该总规格分类的规格"); } } dbNodeMap.put(dbSecondValue.getId(), dbSecondValue); } } } //去除总规格分类时 //校验是否已引用该属性规格 if (CollUtil.isEmpty(reqGoodsSkuSpecs)) { ReqGoodsSkuSpec dbReqGoodsSkuSpec = dbSpecMap.get(key).get(0); //删除整个一级分类key String specFirstKey = dbReqGoodsSkuSpec.getKey(); GoodsDonSku sku = goodsDonSkuMapper.selectOne(Wrappers.lambdaQuery(GoodsDonSku.class) .eq(GoodsDonSku::getGoodsId, goodsId) .like(GoodsDonSku::getSpecJson, specFirstKey).last("limit 1")); if (sku != null) { throw BusinessRuntimeException.getInstance("删除总规格分类,存在使用该总规格分类的规格"); } } } return dbNodeMap; } return null; } /** * 更新二级分类标题价格等 同步库中sku */ public void syncSpecNodeSku(Long goodsId, Map dbNodeMap, ReqGoodsSkuSpec.Node e) { if (CollUtil.isNotEmpty(dbNodeMap)) { ReqGoodsSkuSpec.Node dbNode = dbNodeMap.get(e.getId()); if (dbNode == null) { return; } AtomicBoolean isUpdateTitle = new AtomicBoolean(false); AtomicBoolean isUpdatePrice = new AtomicBoolean(false); AtomicBoolean isUpdateBenefits = new AtomicBoolean(false); if (!StrUtil.equals(dbNode.getValue(), e.getValue())) { isUpdateTitle.set(true); } if (e.getPrice() == null) { e.setPrice(BigDecimal.ZERO); } if (dbNode.getPrice() != null && e.getPrice().compareTo(dbNode.getPrice()) != 0) { isUpdatePrice.set(true); } if (!StrUtil.equals(dbNode.getBenefitsList(), e.getBenefitsList())) { isUpdateBenefits.set(true); } if (isUpdateTitle.get() || isUpdatePrice.get() || isUpdateBenefits.get()) { List goodsDonSkus = goodsDonSkuMapper.selectList(Wrappers.lambdaQuery(GoodsDonSku.class) .eq(GoodsDonSku::getGoodsId, goodsId)); goodsDonSkus.forEach(sku -> { try { List specIdsList = Jsons.parseList(sku.getSpecIds(), Integer.class); if (specIdsList.contains(dbNode.getId())) { if (isUpdateTitle.get()) { sku.setSpecJson(StrUtil.replace(sku.getSpecJson(), dbNode.getValue(), e.getValue())); sku.setSpecVal(StrUtil.replace(sku.getSpecVal(), dbNode.getValue(), e.getValue())); } if (isUpdatePrice.get()) { sku.setPrice(sku.getPrice().subtract(dbNode.getPrice()).add(e.getPrice())); } if (isUpdateBenefits.get()) { if (StrUtil.isEmpty(e.getBenefitsList())) { sku.setBenefitsList(StrUtil.EMPTY); } else { try { sku.setBenefitsList(e.getBenefitsList()); } catch (Exception exception) { } } } goodsDonSkuMapper.updateById(sku); } } catch (Exception exception) { } }); } } } }