商品新增,SKU新增

业务背景

SKU属性、SKU规格、SKU

一个权益产品(EquityProduct)下可能有多个权益商品(EquityGoods),而每个权益商品又可以有多个SKU(Stock Keeping Unit,库存单位)。每个SKU由多个属性组成,如配置字段、车辆信息等,每个属性其下是具体的规格。----》一定要理解SKU、SKU属性(skuSpecsName)和SKU规格(skuSpecsField)之间的关系。

例如宝马是一个权益商品,宝马25号是权益商品下具体的一个商品,它的SKU 属性是由商品类型决定的(例如商品为手机,涉及到的参数就是容量,颜色,而商品为车,涉及到的参数就是重量,尺寸),也就是说车作为一个抽象出来的最小库存单位,它的SKU属性可能定义为[颜色、尺寸、重量],而每一个SKU属性以颜色为例,可能在后台设置的就是5种配色(SKU规格),同理其他两种SKU属性,而我们唯一确定一个SKU(具体的一辆车),就是SKU规格的不同组合,例如为[红色、S、1.5t] 和 [蓝色、S、1.5t] ,它们对应就是两辆车。

现在就需要保证SKU规格是不应该存在重复的,我们知道SKU规格本质上就是属性值的组合,如果不同的 SKU 配置有重复的SKU规格组合,可能会导致库存管理混乱或者用户选择错误。

当然正常来说我们如果在用户进行新增SKU的时候,进行限制实际上是可以规避这类事情,它主要是用于这种批量导入SKU规格的时候,对这种混乱顺序需要做一层校验,避免出先相同规格的SKU导致两条记录的。

如果还是不是理解上面三者的关系,可以参考:

关键词:规格项乱序后解决重复生产sku场景

代码实现

我们先看是怎么实现的,以新增sku为例:

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void insertSku(EquityGoodsDTO equityGoodsdto) {
        Long goodsId = equityGoodsdto.getGoodsId();
        checkShelf(goodsId);
        skuInsert(equityGoodsdto);
    }

    /**
     * 商品状态校验 ---业务逻辑可不过多关注
     * @param goodsId
     */
    private void checkShelf(Long goodsId) {
        EquityGoods equityGoods = Optional.ofNullable(baseMapper.selectById(goodsId))
                .orElseThrow(() -> new ServiceException("商品不存在"));
        if (!Objects.equals(equityGoods.getStatus(), 1)) {
            throw new ServiceException("待上架商品允许修改");
        }
    }

核心部分–插入SKU

业务流程:

针对经销商操作的后台程序(toB)

  1. 新增权益产品
  2. 配置多个权益商品
  3. 为每个权益商品配置sku

image.png

首先权益商品是需要先绑定权益产品才能进行新建,这里是分为两部分来进行保存的,权益产品的配置是保存在product表中,而下面的延保配置才算是权益商品,一个权益产品其下可以配置多个权益商品。

新增权益商品的流程是下载模版,得到一个excel文件,再将数据填写其中,之后导入该excel,解析其中数据并对业务数据做校验,无误则新增成功。因为这里的商品属性是一个套餐,它是需要绑定实体的(车、商品),下一步就是配置sku,但这里sku实际上是由后端开发来维护的,所以从某种程度上来说这里的sku实际上是一个简单版本的,因为它交由用户是配置sku(sku是查询已添加好的),而用户是不是能够直接新增sku。

从系统角度来说,因为权益平台作为的是一个权益发放平台,它所要做的是对接原系统传过来的sku即可,并不需要去单独来做车型产品的sku新增。

对sku的不重复问题,一个最为简单的方法就是在由用户配置好其具体的SKU规格之后,我们先按照字母大小进行排序,排完序之后我们再与数据库进行一次校验,如果存在则不允许插入。

核心逻辑:

public void checkAttrRepeat(Long goodsId) {
        // 获取商品信息
        EquityGoods equityGoods = new EquityGoods(goodsId, "conf1,conf2"); // 模拟商品信息
        Long productId = 100L; // 模拟获取的产品ID

        // 获取产品配置
        Map<String, List<EquityProductConfig>> configMap = mockMapper.getProductConfigs(productId)
                .stream().collect(Collectors.groupingBy(EquityProductConfig::getConfCode));

        // 获取商品 SKU
        List<EquityGoodsSku> goodsSkus = mockMapper.getGoodsSkus(goodsId);

        // 获取 SKU 配置字段
        List<String> skuSpecsField = List.of(equityGoods.getSkuSpecsField().split(","));

        // 重复检测
        List<String> attrList = new ArrayList<>();
        Map<String, List<String>> repeatMap = new HashMap<>();

        goodsSkus.forEach(sku -> {
            String confCode = sku.getConfCode();
            EquityProductConfig productConfig = configMap.get(confCode).get(0);

            // 生成属性组合
            String values = skuSpecsField.stream().map(checkField -> {
                return Optional.ofNullable(productConfig.getConfName()).orElse("");
            }).collect(Collectors.joining());

            String attr = values + "@";
            attrList.add(attr);
            repeatMap.putIfAbsent(attr, new ArrayList<>());
            repeatMap.get(attr).add(confCode);
        });

        // 检查是否存在重复组合
        for (int i = 0; i < attrList.size(); i++) {
            for (int j = i + 1; j < attrList.size(); j++) {
                if (attrList.get(i).contains(attrList.get(j)) || attrList.get(j).contains(attrList.get(i))) {
                    String repeatAttrFirst = attrList.get(i);
                    String repeatAttrSecond = attrList.get(j);
                    String firstSku = String.join("、", repeatMap.get(repeatAttrFirst));
                    String secondSku = String.join("、", repeatMap.get(repeatAttrSecond));

                    String errorMsg = String.format("sku属性重复:[属性:【%s】配置:【%s】与属性:【%s】配置:【%s】]",
                            repeatAttrFirst, firstSku, repeatAttrSecond, secondSku);
                    throw new RuntimeException(errorMsg);
                }
            }
        }
    }

新增商品

在电商系统中,如果要新增一个商品,它是需要保存多方的数据,而每一个其下又是各种配置项,这种处理逻辑如果不做好分类到后面就很容易绕进去,建议以这样的形式来设置前端请求参数:

image.png

其中每一个List都代表是和商品表关联的,通过productId来联系的,在新增商品的过程,它实际是按照顺序进行添加的,它是一个流程链,用于收集所有用户填的信息,并依次将其保存在对应的表中

image.png

其中比较重要就是配置商品的SKU属性、规格,最后下单的时候也是通过SKU为单位来处理,这里我们需要首先确定需要配置的SKU属于哪一种类型,因为不同商品类型具有不同的SKU属性(例如衣物就是尺码,颜色,而手机就是内存等),下面这里就是以衣服为例,当然这里实际上根据用户选择的商品类型(product_attribute_category_id),类型为服装- T恤,那么会展示属于T恤的SKU属性(在product_attribute中)查询其SKU属性表来做的。

用户在配置SKU属性、规格后,会将其信息保存在sku_stock表中,同时关联其库存信息

image.png

product_attribute表结构:

image.png

image.png

例如这里我们product_attribute_category_id假设为1,那么我们将会展示[尺寸,颜色,商品编号,适用季节,适用人群,上市时间,袖长],对应我们看图也是这样。

image.png

注意这里用户并不会接触到对具体的SKU属性或规格的添加,我们规定的逻辑是,如果用户需要新增的商品包含一种特色的SKU属性,例如 图案 ,他要用到这个SKU属性,就需要先进行在后台进行新增SKU属性,流程如下:

image.png

image.png

可以选择添加SKU属性,也可以选择编辑原有SKU属性,修改SKU规格

image.png

这里的SKU属性、规格都会保存到上面的表 product_attribute 中

  • 如果是添加原来没有的SKU属性,点击新增SKU属性,再配置该SKU属性其下SKU规格参数,这里的type标识为参数(1)

  • 而如果是修改原来的SKU属性的SKU规格,点击编辑SKU属性,这里的type标识为规格(0)

具体的我们来看看这里是如何做SKU规格重复问题是怎么解决的?

很遗憾在原代码逻辑中,这里插入是没有做额外的处理,所以它插入数据库,可能导致相同的sku属性可以插入多条,这样导致在展示C端界面的时候读取对应的SKU属性就会有问题。

代码实现

新建商品流程:

 @Override
    public int create(PmsProductParam productParam) {
        int count;
        //创建商品
        PmsProduct product = productParam;
        product.setId(null);
        productMapper.insertSelective(product);
        //根据促销类型设置价格:会员价格、阶梯价格、满减价格
        Long productId = product.getId();
        //会员价格
        relateAndInsertList(memberPriceDao, productParam.getMemberPriceList(), productId);
        //阶梯价格
        relateAndInsertList(productLadderDao, productParam.getProductLadderList(), productId);
        //满减价格
        relateAndInsertList(productFullReductionDao, productParam.getProductFullReductionList(), productId);
        //处理sku的编码
        handleSkuStockCode(productParam.getSkuStockList(),productId);
        //添加sku库存信息
        relateAndInsertList(skuStockDao, productParam.getSkuStockList(), productId);
        //添加商品参数,添加自定义商品规格
        relateAndInsertList(productAttributeValueDao, productParam.getProductAttributeValueList(), productId);
        //关联专题
        relateAndInsertList(subjectProductRelationDao, productParam.getSubjectProductRelationList(), productId);
        //关联优选
        relateAndInsertList(prefrenceAreaProductRelationDao, productParam.getPrefrenceAreaProductRelationList(), productId);
        count = 1;
        return count;
    }

很有意思的一点,这里relateAndInsertList方法利用反射首先对填充数据进行校验和设置其id、productId为null,因为数据库是采用主键自增的顺序,确保数据库自动生成每个新记录的唯一 ID,避免重复或手动指定的 ID 冲突,:

/**
     * 建立和插入关系表操作
     *
     * @param dao       可以操作的dao
     * @param dataList  要插入的数据
     * @param productId 建立关系的id
     */
    private void relateAndInsertList(Object dao, List dataList, Long productId) {
        try {
            // 如果传入的 dataList 为空,则直接返回,避免后续操作
            if (CollectionUtils.isEmpty(dataList)) return;

            // 遍历 dataList 中的每一项,设置其 ID 为 null(用于确保插入时 ID 是由数据库生成)
            for (Object item : dataList) {
                // 获取并调用 "setId" 方法,设置 ID 为 null
                Method setId = item.getClass().getMethod("setId", Long.class);
                setId.invoke(item, (Long) null);  // 设置为 null,表示插入新记录,数据库会生成 ID

                // 获取并调用 "setProductId" 方法,设置与商品的关联 ID
                Method setProductId = item.getClass().getMethod("setProductId", Long.class);
                setProductId.invoke(item, productId);  // 设置与商品的 ID 关联
            }

            // 获取并调用 dao 的 "insertList" 方法,批量插入 dataList 中的数据
            Method insertList = dao.getClass().getMethod("insertList", List.class);
            insertList.invoke(dao, dataList);

        } catch (Exception e) {
            // 捕获任何异常并记录日志,抛出 RuntimeException 异常
            LOGGER.warn("创建产品出错:{}", e.getMessage());
            throw new RuntimeException(e.getMessage());
        }
    }

数据库设计

新增商品,由用户按照表单链的形式填充参数,这里productId 由数据库自增,不需要用户单独传入,所有填充的数据存放在product表中

product

image.png

填写完商品的基本信息(名称,标题)之后,需要配置商品的SKU属性,我们需要知道用户新增商品具体属于哪一个模块,由用户进行选择,传入product_attribute_category_id,这里可以看看具体由哪些模块。

product_category

image.png

拿到product_attribute_category_id,之后我们需要去查看该商品对应模块预设的SKU属性,这个属性是提前配置好的,这里查询product_attribute,显示所有的SKU属性、规格。

product_attribute:商品属性

image.png

image.png

用户按照需求选择勾选SKU规格(这里也建议用表格的形式,用户需要先下载excel表格,再填写excel并导入表格的形式来填写),系统会显示勾选的SKU规格的不同组合,用户需要具体配置每件SKU的价格、库存,所有数据将保存在 sku_stock 表中

sku_stock

image.png

image.png

该表后续将在下单的时候用于判断当前购买的SKU库存是否充足。

至此与新增商品相关的表逻辑就完结了。

其他功能,例如展示当前大模块有多少SKU属性、SKU规格,对应下表中的attribute_count,param_count,可以通过这个product_attribute_category_id来进行关联

product_attribute_category

image.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值