• java实现商品属性sku功能记录


    直入主题

    一、数据库设计

     产品表(t_product):商品主表,上关联店铺商户、分类(merchat_id、category_id),下关联产品属性和sku,其中一些字段可根据需求进行变动。

     产品属性表(t_product_attr):商品属性表,存放产品对应的属性及规格

     产品SKU表(t_product_sku):根据商品属性和规格,计算排列出所有情况,生成的sku数据列表,返回客户端后,由客户端填充对应价格、库存、logo等参数,交由该表存储

    drop table if exists t_product;
    
    /*==============================================================*/
    /* Table: t_product                                             */
    /*==============================================================*/
    create table t_product
    (
       id                   int(11) not null auto_increment,
       merchat_id           int(11) comment '商户Id(0为总后台管理员创建,不为0的时候是商户后台创建)',
       category_id          int(11) comment '商品所在的系统分类',
       name                 varchar(128) comment '名称',
       description          varchar(256) comment '描述',
       bar_code             varchar(15) comment '产品条码(一维码)',
       keyword              varchar(512) comment '关键词',
       logo                 varchar(256) comment '主图',
       photo                varchar(1024) comment '轮播图',
       is_show              tinyint default 0 comment '是否上架:0下架,1上架',
       fictitiou_id         tinyint default 0 comment '虚拟id(优惠券id)',
       fictitiou_type       tinyint default 0 comment '虚拟类型:0优惠券,1流量券,2话费券',
       type                 tinyint comment '0虚拟,1实体',
       price                double(11, 2) default 0.00 comment '价格',
       postage              double comment '邮费',
       cost                 double comment '成本价',
       ot_price             double comment '市场价',
       integral             int default 0 comment '积分',
       discount             double default 0 comment '折扣',
       max_deduction_integral double(11, 2) default 0.00 comment '积分最大可抵扣金额',
       start_buy            int(11) default 1 comment '起购数量',
       stock                int(11) default 0 comment '库存',
       sales                int(11) default 0 comment '销量',
       attr_result          text comment 'attrJSON结果',
       info_html            text comment '详情图',
       param_html           text comment '参数图',
       is_delete            tinyint default 0 comment '是否删除,0否,1是',
       is_postage           tinyint default 0 comment '是否包邮,0否,1是',
       is_new               tinyint default 0 comment '是否新品,0否,1是',
       is_recommend         tinyint default 0 comment '是否推荐,0否,1是',
       update_time          datetime comment '更新时间',
       create_time          datetime comment '创建时间',
       primary key (id)
    );
    
    alter table t_product comment '产品';
    
    
    
    drop table if exists t_product_attr;
    
    /*==============================================================*/
    /* Table: t_product_attr */
    /*==============================================================*/
    create table t_product_attr
    (
    id int(11) not null auto_increment,
    product_id int(11) comment '商品id',
    is_hidden bool default 0 comment '是否隐藏,0否,是',
    attr_name varchar(20) comment '属性名称',
    attr_values varchar(255) comment '属性值,逗号拼接',
    defaul_value varchar(255) comment '默认属性值',
    primary key (id)
    );
    
    alter table t_product_attr comment '产品属性';
    
    alter table t_product_attr add constraint FK_Reference_58 foreign key (product_id)
    references t_product (id) on delete restrict on update restrict;
    
    
    
    drop table if exists t_product_sku;
    
    /*==============================================================*/
    /* Table: t_product_sku */
    /*==============================================================*/
    create table t_product_sku
    (
    id int(11) not null auto_increment,
    product_id int(11) not null comment '商品id',
    is_default tinyint not null default 0 comment '是否默认,0否,是',
    logo varchar(256) comment '主图',
    suk varchar(128) not null comment '商品属性索引值 (attr_value|attr_value[|....])',
    stock int(11) not null default 0 comment '库存',
    sales int(11) not null default 0 comment '销量',
    cost double not null default 0.00 comment '成本价',
    price double not null default 0.00 comment '价格',
    primary key (id)
    );
    
    alter table t_product_sku comment '商品属性解析结果';
    
    alter table t_product_sku add constraint FK_Reference_86 foreign key (product_id)
    references t_product (id) on delete restrict on update restrict;

     注意:商品属性的生成与变更与产品本身隔离处理,产品主表只涉及单纯的CRUD,所以此处就不再列出涉及产品相关的代码

    二、服务端逻辑

    /**
         * 解析属性,得到属性格式
         * @param id
         * @param jsonStr
         * @return
         */
        public ResponseVO getAttrFormat(Integer id, String jsonStr) {
            if (id == null){
                return ResponseVO.error("请选择商品");
            }
            Product product = productMapper.selectByPrimaryKey(id);
            if (product == null){
                return ResponseVO.error("商品不存在");
            }
            AttrDetailDto detailDTO = analysisAttr(jsonStr);
            List<ProductSkuDto> newList = new ArrayList<>();
            for (Map<String, Map<String,String>> map : detailDTO.getRes()) {
                // 封装结果
                ProductSkuDto productSkuDto = new ProductSkuDto();
                productSkuDto.setDetail(map.get("detail"));
                productSkuDto.setCost(product.getCost());
                productSkuDto.setPrice(product.getPrice());
                productSkuDto.setSales(product.getSales());
                productSkuDto.setLogo(product.getLogo());
                newList.add(productSkuDto);
            }
            return ResponseVO.success(newList);
        }
    
    
    
        /**
         * 解析属性规则算法
         * @param jsonStr
         * @return
         */
        public AttrDetailDto analysisAttr(String jsonStr){
            JSONObject jsonObject = JSON.parseObject(jsonStr);
            List<ProductAttrDto> productAttrList = JSONArray.parseArray(jsonObject.get("attrs").toString(),
                    ProductAttrDto.class);
            // 声明当前属性所拥有规格模板
            List<String> currentAttrSpecTemp = new ArrayList<>();
            List<Map<String,Map<String,String>>> res =new ArrayList<>();
            if(productAttrList.size() > 1){
                // 如果大于1,说明增加了多个属性
                for (int i=0; i<productAttrList.size()-1; i++){// 遍历属性
                    if(i == 0) {
                        // 如果是第一个属性 获取第一个属性的元素规格列表,给模板temp0进行遍历操作,如果不是,说明已经拼接过一轮,继续拼接
                        currentAttrSpecTemp = productAttrList.get(i).getDetail();
                    }
    
                    // 清空初始化模板集合,用于存储拼接后的规格信息,注意,只能在这个位置初始化,如果拿到上一个for循环之上,下面给将skuTemp实例的引用赋值之后(currentAttrSpecTemp = skuTemp),currentAttrSpecTemp就会因为skuTemp.add(skuDetail);而实时增加数据,导致出现ConcurrentModificationException异常
                    List<String> skuTemp = new LinkedList<>();
    
                    // 遍历元素规格列表
                    for (String skuName : currentAttrSpecTemp) {
                        // 当前元素拼接下一个元素
    
                        // 将下一个属性的 元素依次遍历
                        List<String> nextAttrSpecTemp = productAttrList.get(i + 1).getDetail();
                        for (String nextSpecName : nextAttrSpecTemp) {
                            String skuDetail = "";
                            if(i == 0){
                                // 如果为0,就使用第一属性的名称去拼接第一个规格,用下一个属性名称拼接下一个属性的当前规格
                                // 颜色_白色-体积_小
                                skuDetail = productAttrList.get(i).getAttrName() + "_" + skuName + "-"
                                        + productAttrList.get(i+1).getAttrName() + "_" + nextSpecName;
                            }else{
                                // 如果不为0,表示不是第一个属性,那么就使用之前拼接得到的结果,继续拼接
                                // 颜色_白色-体积_小-温度_冰
                                skuDetail = skuName + "-"
                                        + productAttrList.get(i+1).getAttrName() + "_" + nextSpecName;
                            }
                            // 将解析拼接后的规格存入
                            skuTemp.add(skuDetail);
    
                            // 如果是数组中的倒数第二个元素,因为会向后拼接一个元素(productAttrList.get(i+1)),所以此处表示已经拼接结束
                            if(i == productAttrList.size() - 2){
                                Map<String,Map<String,String>> skuDetailTemp = new LinkedHashMap<>();
    
                                // 将一组结果,拆分成键值对的方式存储
                                Map<String,String> skuDetailTempKv = new LinkedHashMap<>();
    
                                // 根据-分成数组,得到[颜色_白色,体积_小,温度_冰]
                                List<String> attr_sku_arr = Arrays.asList(skuDetail.split("-"));
                                for (String h : attr_sku_arr) {
                                    // _分成数组,得到[颜色,白色]
                                    List<String> attrBySpecArr = Arrays.asList(h.split("_"));
    
                                    // 如果大于1,说明属性有对应的 规格值
                                    if(attrBySpecArr.size() > 1){
                                        // 按照属性名:规格值的结构存入临时模板列表
                                        skuDetailTempKv.put(attrBySpecArr.get(0), attrBySpecArr.get(1));
                                    }else{
                                        // 未获取到属性名,按空字符串存储
                                        skuDetailTempKv.put(attrBySpecArr.get(0),"");
                                    }
                                }
                                // 得到[颜色:白色,体积:小,温度:冰]
                                skuDetailTemp.put("detail",skuDetailTempKv);
                                // 将这一组结果存入到最终将要返回的数据中
                                res.add(skuDetailTemp);
                            }
                        }
                    }
                    // 走到这里,表示第一个元素和第二个元素已经全部生成完毕,将得到的结果列表交给temp0去执行下一趟
                    if(!skuTemp.isEmpty()){
                        // 这里只能将自己的参数给这个值,不能将引用也给他,否则在内循环的时候,就会照成java.util.ConcurrentModificationException
                        currentAttrSpecTemp = skuTemp;
                    }
                }
            }else{
                // 只有一种属性
                List<String> temp0Arr = new ArrayList<>();
    
                // 清空初始化模板集合,用于存储拼接后的规格信息
                List<String> skuTemp = new LinkedList<>();
    
                for (ProductAttrDto productAttr : productAttrList) {
    
                    for (String str : productAttr.getDetail()) { // 这这种属性和其中的规格遍历得到属性规格键值对,不需要拼接下一个属性
                        Map<String,Map<String,String>> skuDetailTemp = new LinkedHashMap<>();
                        //List<Map<String,String>> list1 = new ArrayList<>();
                        temp0Arr.add(productAttr.getAttrName()+"_"+str);
                        Map<String,String> skuDetailTempKv = new LinkedHashMap<>();
                        skuDetailTempKv.put(productAttr.getAttrName(),str);
                        //list1.add(map1);
                        skuDetailTemp.put("detail",skuDetailTempKv);
                        res.add(skuDetailTemp);
    
    
                        // 将解析拼接后的规格存入
                        skuTemp.add(productAttr.getAttrName() + "-" + str);
                    }
                    currentAttrSpecTemp = skuTemp;
                }
            }// 此时已得到每一组的匹配详情,(所有可能产生的匹配结果列表),将其已文本,和键值对的方式返回
            AttrDetailDto detailDTO = new AttrDetailDto();
            detailDTO.setData(currentAttrSpecTemp);
            detailDTO.setRes(res);
            return detailDTO;
        }
    
    
    
    
        @Transactional(rollbackFor = Exception.class)
        public ResponseVO productAttrAdd(Integer productId, String jsonStr) {
            JSONObject jsonObject = JSON.parseObject(jsonStr);
            // 传入属性列表[{"attrName":"颜色","defaulValue":"","isHidden":1,"detail":["白色","黑色"]},{"attrName":"体积","defaulValue":"","isHidden":1,"detail":["小","中","大"]},{"attrName":"温度","defaulValue":"","isHidden":1,"detail":["冰","常温"]}]
            List<ProductAttrDto> attrDtoList = JSON.parseArray(
                    jsonObject.get("attrs").toString(),
                    ProductAttrDto.class);
            List<ProductSkuDto> valueDtoList = JSON.parseArray(// 传入解析后的规格详情列表[{"price":120,"cost":0.2,"sales":52,"logo":"https://image.dayouqiantu.cn/5ca011a1cd487.jpg","detail":{"颜色":"白色","体积":"小","温度":"冰"},"check":false},{"price":120,"cost":0.2,"sales":52,"logo":"https://image.dayouqiantu.cn/5ca011a1cd487.jpg","detail":{"颜色":"白色","体积":"小","温度":"常温"},"check":false},{"price":120,"cost":0.2,"sales":52,"logo":"https://image.dayouqiantu.cn/5ca011a1cd487.jpg","detail":{"颜色":"白色","体积":"中","温度":"冰"},"check":false},{"price":120,"cost":0.2,"sales":52,"logo":"https://image.dayouqiantu.cn/5ca011a1cd487.jpg","detail":{"颜色":"白色","体积":"中","温度":"常温"},"check":false},{"price":120,"cost":0.2,"sales":52,"logo":"https://image.dayouqiantu.cn/5ca011a1cd487.jpg","detail":{"颜色":"白色","体积":"大","温度":"冰"},"check":false},{"price":120,"cost":0.2,"sales":52,"logo":"https://image.dayouqiantu.cn/5ca011a1cd487.jpg","detail":{"颜色":"白色","体积":"大","温度":"常温"},"check":false},{"price":120,"cost":0.2,"sales":52,"logo":"https://image.dayouqiantu.cn/5ca011a1cd487.jpg","detail":{"颜色":"黑色","体积":"小","温度":"冰"},"check":false},{"price":120,"cost":0.2,"sales":52,"logo":"https://image.dayouqiantu.cn/5ca011a1cd487.jpg","detail":{"颜色":"黑色","体积":"小","温度":"常温"},"check":false},{"price":120,"cost":0.2,"sales":52,"logo":"https://image.dayouqiantu.cn/5ca011a1cd487.jpg","detail":{"颜色":"黑色","体积":"中","温度":"冰"},"check":false},{"price":120,"cost":0.2,"sales":52,"logo":"https://image.dayouqiantu.cn/5ca011a1cd487.jpg","detail":{"颜色":"黑色","体积":"中","温度":"常温"},"check":false},{"price":120,"cost":0.2,"sales":52,"logo":"https://image.dayouqiantu.cn/5ca011a1cd487.jpg","detail":{"颜色":"黑色","体积":"大","温度":"冰"},"check":false},{"price":120,"cost":0.2,"sales":52,"logo":"https://image.dayouqiantu.cn/5ca011a1cd487.jpg","detail":{"颜色":"黑色","体积":"大","温度":"常温"},"check":false}]
                    jsonObject.get("sku").toString(),
                    ProductSkuDto.class);
    
            Product product = productMapper.selectByPrimaryKey(productId);
            //取最小价格
            Double minPrice = product.getPrice();
            // 计算库存
            Integer stock = product.getStock();
    
            if(attrDtoList.isEmpty() || valueDtoList.isEmpty()){
                return ResponseVO.error("请设置至少一个属性!");
            }else{
                //插入之前清空
                productAttrClear(product.getId());
            }
    
    
            for (ProductAttrDto attrDto : attrDtoList) {
    
    
                ProductAttr attr = new ProductAttr(); // 记录商品属性
                attr.setProductId(productId);
                attr.setAttrName(attrDto.getAttrName());
                attr.setDefaulValue(attrDto.getDefaulValue());
                //根据,号拼接商品属性下的规格 "大,中,小"
                String attrValues = "";
                for (String valueName : attrDto.getDetail()){
                    // 如果未指定默认值,那么就使用第一个
                    if (attr.getDefaulValue() == null || attr.getDefaulValue().length() == 0){
                        attr.setDefaulValue(valueName);
                    }
    
                    attrValues += attrValues.length() > 0 ? "," + valueName : valueName;
                }
                attr.setAttrValues(attrValues);
                productAttrMapper.insertSelective(attr);
            }
    
    
            for (ProductSkuDto attrValuesDTO : valueDtoList) {
    
                ProductSku attrValues = new ProductSku();// 记录商品属性规格详情信息
                attrValues.setProductId(productId);
                List<String> stringList = attrValuesDTO.getDetail().values()
                        .stream().collect(Collectors.toList());
                Collections.sort(stringList);
    
    
                // 将属性对应的每一种规格通过,号拼接,存入表中,同一产品唯一 "冰,小,白色"
                String suk = "";
                for (String sukName : stringList){
                    suk += suk.length() > 0 ? "," + sukName : sukName;
                }
                attrValues.setSuk(suk);
                attrValues.setPrice(attrValuesDTO.getPrice());
                attrValues.setCost(attrValuesDTO.getCost());
                attrValues.setStock(attrValuesDTO.getSales());
                attrValues.setLogo(attrValuesDTO.getLogo());
    
                productSkuMapper.insertSelective(attrValues);
    
    
                // 计算价格
                minPrice = minPrice > attrValues.getPrice() ? attrValues.getPrice() : minPrice;
    
                // 计算库存
                stock += attrValues.getStock();
            }
    
    
    
            Map<String,Object> map = new LinkedHashMap<>();
            map.put("attr",jsonObject.get("attr"));
            map.put("sku",jsonObject.get("sku"));
    
            //设置库存及价格
            product.setPrice(minPrice);
            product.setStock(stock);
            product.setAttrResult(JSON.toJSONString(map));
            int result = productMapper.updateByPrimaryKeySelective(product);
            if (result != 1){
                return ResponseVO.error("请设置至少一个属性!");
            }
            return ResponseVO.success();
    
        }
    
    
        /**
         * 清空属性及sku
         * @param productId
         * @return
         */
        public void productAttrClear(Integer productId) {
            productAttrMapper.deleteByProductId(productId);
            productSkuMapper.deleteByProductId(productId);
        }

    三、Controller展示

    @ApiOperation(value = "生成属性")
        @PostMapping(value = "getAttrFormat")
        @ApiImplicitParams({
                @ApiImplicitParam(name = "productId", value = "商品id",  paramType = "query", dataType =  "Integer"),
                @ApiImplicitParam(name = "jsonStr", value = "属性json格式(转成swagger注意双引号中文切换):{"attrs":[{"attrName":"颜色","defaulValue":"","isHidden":1,"detail":["白色","黑色"]},{"attrName":"体积","defaulValue":"","isHidden":1,"detail":["小","中","大"]},{"attrName":"温度","defaulValue":"","isHidden":1,"detail":["冰","常温"]}],"sku":[]}",  paramType = "query", dataType =  "String")
        })
        public ResponseVO getAttrFormat(Integer productId, String jsonStr){
            return productService.getAttrFormat(productId,jsonStr);
        }
    
        @ApiOperation(value = "设置保存属性")
        @PostMapping(value = "productAttrAdd")
        @ApiImplicitParams({
                @ApiImplicitParam(name = "productId", value = "商品id",  paramType = "query", dataType =  "Integer"),
                @ApiImplicitParam(name = "jsonStr", value = "属性json格式(转成swagger注意双引号中文切换):{"attrs":[{"attrName":"颜色","defaulValue":"","isHidden":1,"detail":["白色","黑色"]},{"attrName":"体积","defaulValue":"","isHidden":1,"detail":["小","中","大"]},{"attrName":"温度","defaulValue":"","isHidden":1,"detail":["冰","常温"]}],"sku":[{"price":120,"cost":0.2,"sales":52,"logo":"https://image.dayouqiantu.cn/5ca011a1cd487.jpg","detail":{"颜色":"白色","体积":"小","温度":"冰"}},{"price":120,"cost":0.2,"sales":52,"logo":"https://image.dayouqiantu.cn/5ca011a1cd487.jpg","detail":{"颜色":"白色","体积":"小","温度":"常温"}},{"price":120,"cost":0.2,"sales":52,"logo":"https://image.dayouqiantu.cn/5ca011a1cd487.jpg","detail":{"颜色":"白色","体积":"中","温度":"冰"}},{"price":120,"cost":0.2,"sales":52,"logo":"https://image.dayouqiantu.cn/5ca011a1cd487.jpg","detail":{"颜色":"白色","体积":"中","温度":"常温"}},{"price":120,"cost":0.2,"sales":52,"logo":"https://image.dayouqiantu.cn/5ca011a1cd487.jpg","detail":{"颜色":"白色","体积":"大","温度":"冰"}},{"price":120,"cost":0.2,"sales":52,"logo":"https://image.dayouqiantu.cn/5ca011a1cd487.jpg","detail":{"颜色":"白色","体积":"大","温度":"常温"}},{"price":120,"cost":0.2,"sales":52,"logo":"https://image.dayouqiantu.cn/5ca011a1cd487.jpg","detail":{"颜色":"黑色","体积":"小","温度":"冰"}},{"price":120,"cost":0.2,"sales":52,"logo":"https://image.dayouqiantu.cn/5ca011a1cd487.jpg","detail":{"颜色":"黑色","体积":"小","温度":"常温"}},{"price":120,"cost":0.2,"sales":52,"logo":"https://image.dayouqiantu.cn/5ca011a1cd487.jpg","detail":{"颜色":"黑色","体积":"中","温度":"冰"}},{"price":120,"cost":0.2,"sales":52,"logo":"https://image.dayouqiantu.cn/5ca011a1cd487.jpg","detail":{"颜色":"黑色","体积":"中","温度":"常温"}},{"price":120,"cost":0.2,"sales":52,"logo":"https://image.dayouqiantu.cn/5ca011a1cd487.jpg","detail":{"颜色":"黑色","体积":"大","温度":"冰"}},{"price":120,"cost":0.2,"sales":52,"logo":"https://image.dayouqiantu.cn/5ca011a1cd487.jpg","detail":{"颜色":"黑色","体积":"大","温度":"常温"}}]}",  paramType = "query", dataType =  "String")
        })
        public ResponseVO productAttrAdd(Integer productId, String jsonStr){
            return productService.productAttrAdd(productId, jsonStr);
        }
    
        @ApiOperation(value = "清除属性")
        @PostMapping(value = "productAttrClear")
        @ApiImplicitParams({
                @ApiImplicitParam(name = "productId", value = "商品id",  paramType = "query", dataType =  "Integer")
        })
        public ResponseVO clearAttr(Integer productId){
            productService.productAttrClear(productId);
            return ResponseVO.success();
        }

    四、业务逻辑说明

      1、执行步骤:

        1)创建产品

        2)调用getAttrFormat接口,根据传入的属性,生成对应的sku列表

        3)前端根据返回的sku列表,展示对应的sku信息,填入对应sku的价格、库存、logo

        4)调用productAttrAdd接口,将封装好的sku列表 和 用户输入的属性,存入t_product_attr、t_product_sku表中,并更新t_product表中的库存(所有sku库存总和)和价格(所有sku中最低价格),将入参转为json存入主表的attr_result字段中,方便查询

    五、前端页面效果图

      1、填写属性

       2、点击生成,解析属性得到sku,3、填充或更改logo、金额、库存、成本价 并 提交

       

      本文参考jeck胡老师的实战项目源码,仅供参考,如有不妥,请联系删除

    {


    ProductAttr attr = new ProductAttr(); // 记录商品属性
    attr.setProductId(productId);
    attr.setAttrName(attrDto.getAttrName());
    attr.setDefaulValue(attrDto.getDefaulValue());
    //根据,号拼接商品属性下的规格 "大,中,小"
    String attrValues = "";
    for (String valueName : attrDto.getDetail()){
    // 如果未指定默认值,那么就使用第一个
    if (attr.getDefaulValue() == null || attr.getDefaulValue().length() == 0){
    attr.setDefaulValue(valueName);
    }

    attrValues += attrValues.length() > 0 ? "," + valueName : valueName;
    }
    attr.setAttrValues(attrValues);
    productAttrMapper.insertSelective(attr);
    }
  • 相关阅读:
    菜鸟小试牛刀。。
    RDBMS中部分关于可用性的特性
    ORA01403:no data found exception的解决小道
    oracle的存储结构(一)
    过度使用DBLINK带来的问题
    如何远程指定查询分区表的某个分区
    oracle显式游标不关闭、不关闭就再次打开会不会报错?
    Http方式下载Servlet实现
    Javazip压缩文件乱码问题
    mysql “Access denied for user 'root'@'localhost'
  • 原文地址:https://www.cnblogs.com/fuhui-study-footprint/p/14317647.html
Copyright © 2020-2023  润新知