• 商城项目20_sku在es中储存模型分析、索引建立、上架逻辑、核心上架、流程图


    ①. sku在es中存储模型分析

    • ①. 需求:上架的商品才可以在网站展示、上架的商品需要可以被检索

    • ②. 分析:商品上架在es中是存sku还是spu?

    1. 检索的时候输入名字,是需要按照sku的title进行全文检索的
    2. 检素使用商品规格,规格是spu的公共属性,每个spu是一样的
    3. 按照分类id进去的都是直接列出spu的,还可以切换
    4. 我们如果将sku的全量信息保存到es中(包括spu属性)就太多字段了
    • ③. 方案1:推荐使用
      缺点:如果每个sku都存储规格参数(如尺寸、cpu等),会有冗余存储,因为每个spu对应的sku的规格参数都一样
    {
        skuId:1
        spuId:11
        skyTitile:华为xx
        price:999
        saleCount:99
        attr:[
            {尺寸:5},
            {CPU:高通945},
            {分辨率:全高清}
    	]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • ④. 方案2:假设我们有10000个sku,spu为4000个,再根据4000个spu查询对应的属性,封装了4000个id,8B*4000=32000B=32KB。如果有100万人同一时刻进行搜索,那么就有100万 * 32kb = 320G
      结论:如果将规格参数单独建立索引,会出现检索时出现大量数据传输的问题,会引起网络网络
    sku索引
    {
        spuId:1
        skuId:11
    }
    attr索引
    {
        spuId:1
        attr:[
            {尺寸:5},
            {CPU:高通945},
            {分辨率:全高清}
    	]
    }
    先找到4000个符合要求的spu,再根据4000个spu查询对应的属性,封装了4000个id,long 8B*4000=32000B=32KB
    1K个人检索,就是32MB
    
    结论:如果将规格参数单独建立索引,会出现检索时出现大量数据传输的问题,会引起网络网络
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    ②. 建立product的索引信息

    • ①. { “type”: “keyword” }:保持数据精度问题,可以检索,但不分词

    • ②. “analyzer”: “ik_smart” :中文分词器

    • ③. “index”: false:不可被检索,不生成index

    • ④. “doc_values”: false:默认为true,不可被聚合,es就不会维护一些聚合的信息

    PUT product
    {
        "mappings":{
            "properties": {
                "skuId":{ "type": "long" },
                "spuId":{ "type": "keyword" },  # 不可分词
                "skuTitle": {
                    "type": "text",
                    "analyzer": "ik_smart"  # 中文分词器
                },
                "skuPrice": { "type": "keyword" },  # 保证精度问题
                "skuImg"  : { "type": "keyword" },  # 视频中有false
                "saleCount":{ "type":"long" },
                "hasStock": { "type": "boolean" },
                "hotScore": { "type": "long"  },
                "brandId":  { "type": "long" },
                "catalogId": { "type": "long"  },
                "brandName": {"type": "keyword"}, # 视频中有index=false
                "brandImg":{
                    "type": "keyword",
                    "index": false,  # 不可被检索,不生成index,只用做页面使用
                    "doc_values": false # 不可被聚合,默认为true
                },
                "catalogName": {"type": "keyword" }, # 视频里有index=false
                "attrs": {
                    "type": "nested",
                    "properties": {
                        "attrId": {"type": "long"  },
                        "attrName": {
                            "type": "keyword",
                            "index": false,
                            "doc_values": false
                        },
                        "attrValue": {"type": "keyword" }
                    }
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    ③. nested数据类型场景

    1. 新建一个对象
    PUT my-index-000001/_doc/1
    {
      "group" : "fans",
      "user" : [ 
        {
          "first" : "John",
          "last" :  "Smith"
        },
        {
          "first" : "Alice",
          "last" :  "White"
        }
      ]
    }
    2. 结果会在es中保存为如下的方式
    {
      "group" :        "fans",
      "user.first" : [ "alice", "john" ],
      "user.last" :  [ "smith", "white" ]
    }
    3. 进行查询,发现两条记录都能查询出来
    GET my-index-000001/_search
    {
      "query": {
        "bool": {
          "must": [
            { "match": { "user.first": "Alice" }},
            { "match": { "user.last":  "Smith" }}
          ]
        }
      }
    }
    4. 删除刚刚新建的索引
    DELETE my-index-000001
    5. 使用nested处理扁平化操作
    PUT my-index-000001
    {
      "mappings": {
        "properties": {
          "user": {
            "type": "nested" 
          }
        }
      }
    }
    6. 在这里进行重新插入数据
    PUT my-index-000001/_doc/1
    {
      "group" : "fans",
      "user" : [
        {
          "first" : "John",
          "last" :  "Smith"
        },
        {
          "first" : "Alice",
          "last" :  "White"
        }
      ]
    }
    7. 后续就会发现已经查询不记录了
    GET my-index-000001/_search
    {
      "query": {
        "bool": {
          "must": [
            { "match": { "user.first": "Alice" }},
            { "match": { "user.last":  "Smith" }}
          ]
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    在这里插入图片描述

    ④. 商品上架逻辑

    • ①. 在es中创建好对应的映射关系、在前端页面点击商品上架按钮
      在这里插入图片描述

    • ②. 创建好与es对应的SkuModel

    @Data
    public class SkuEsModel {
        private Long skuId;
        private Long spuId;//keyword
        private String skuTitle;
        private BigDecimal skuPrice;//keyword
        private String skuImg;//keyword
        private Long saleCount;
        private Boolean hasStock;
        private Long hotScore;
        private Long brandId;
        private Long catalogId;
        private String brandName;
        private String brandImg;
        private String catalogName;
        private List<Attrs> attrs;
        @Data
        public static class Attrs{
            private  Long attrId;
            private String attrName;//不被检索
            private String attrValue;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • ③. 通过前端传递的spuId将Spu的基本信息查询出来、查询的表是pms_spu_info表,这个表中和SkuModel表字段一样的有spuId、skuId、catelogId、brandId。其他的字段如:skuPrice、skuImg、brandImg、catalogName、hasStock、hotScore需要重新查询并设置到SkuModel中去
      在这里插入图片描述

    • ④. 发送远程调用、库存系统查询是否有库存
      在这里插入图片描述

    • ⑤. 热度评分(这里设置为0L),没有进行扩展了

    esModel.setHotScore(0L);
    
    • 1
    • ⑥. 获取到品牌id、获取到分类id
    //TODO 3.获取到品牌id
    Long brandId = sku.getBrandId();
    BrandEntity brandEntity = brandService.getById(brandId);
    if(brandEntity!=null){
        esModel.setBrandName(brandEntity.getName());
        esModel.setBrandImg(brandEntity.getLogo());
    }
    //TODO 4.获取到分类id
    Long catalogId = sku.getCatalogId();
    CategoryEntity categoryEntity = categoryService.getById(catalogId);
    if(categoryEntity!=null){
        esModel.setCatalogName(categoryEntity.getName());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • ⑦. 设置检索属性
    //TODO 4. 查询当前sku的所有可以用来检索的规格参数
    //4.1 根据spu_id查询出所有的ProductAttrValueEntity信息
    List<ProductAttrValueEntity> baseAttrs = attrValueService.baseAttrlistforspu(spuId);
    //4.2 从ProductAttrValueEntity中收集attr_id
    List<Long> attrIds = baseAttrs.stream().map(item -> {
        return item.getAttrId();
    }).collect(Collectors.toList());
    //4.3 根据attr_id查询出所有的AttrEntity对象
    List<AttrEntity> attrsData = attrService.listByIds(attrIds);
    List<SkuEsModel.Attrs> attrsList = attrsData.stream().filter(item -> {
        return item.getSearchType() == 1;
    }).map(item -> {
        SkuEsModel.Attrs attrs = new SkuEsModel.Attrs();
        ProductAttrValueEntity attr_id = attrValueService.getOne(new QueryWrapper<ProductAttrValueEntity>().eq("attr_id", item.getAttrId()));
        BeanUtils.copyProperties(attr_id, attrs);
        return attrs;
    }).collect(Collectors.toList());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • ⑧. 将数据发送给es进行保存
      在这里插入图片描述

    • ⑨. 修改当前spu的状态、设置为已发布

    ⑤. 上架商品服务核心代码

       /**
         * 商品上架
         * @param spuId
         */
        @Transactional
        @Override
        public void up(Long spuId) {
            //1、查出当前spuid对应的所有sku信息、品牌的名字
            List<SkuInfoEntity>skus=skuInfoService.getSkuBySpuId(spuId);
            //TODO 4. 查询当前sku的所有可以用来检索的规格参数
            //4.1 根据spu_id查询出所有的ProductAttrValueEntity信息
            List<ProductAttrValueEntity> baseAttrs = attrValueService.baseAttrlistforspu(spuId);
            //4.2 从ProductAttrValueEntity中收集attr_id
            List<Long> attrIds = baseAttrs.stream().map(item -> {
                return item.getAttrId();
            }).collect(Collectors.toList());
            //4.3 根据attr_id查询出所有的AttrEntity对象
            List<AttrEntity> attrsData = attrService.listByIds(attrIds);
            List<SkuEsModel.Attrs> attrsList = attrsData.stream().filter(item -> {
                return item.getSearchType() == 1;
            }).map(item -> {
                SkuEsModel.Attrs attrs = new SkuEsModel.Attrs();
                ProductAttrValueEntity attr_id = attrValueService.getOne(new QueryWrapper<ProductAttrValueEntity>().eq("attr_id", item.getAttrId()));
                BeanUtils.copyProperties(attr_id, attrs);
                return attrs;
            }).collect(Collectors.toList());
    
            // TODO 1. 发送远程调用、库存系统查询是否有库存
            Map<Long, Boolean> stockMap=null;
            try{
                List<Long> skuIdList = skus.stream().map(SkuInfoEntity::getSkuId).collect(Collectors.toList());
                R skuHasStock = wareFeignService.getSkuHasStock(skuIdList);
    
                TypeReference<List<SkuHasStockVo>> typeReference = new TypeReference<List<SkuHasStockVo>>() {
                };
                List<SkuHasStockVo> data = skuHasStock.getData(typeReference);
                stockMap = data.stream().collect(Collectors.toMap(SkuHasStockVo::getSkuId, item -> item.getHasStock()));
                System.out.println("------------"+data);
            }catch (Exception e){
                log.error("库存服务查询异常,原因{}",e);
    
            }
    
            Map<Long, Boolean> finalStockMap = stockMap;
            List<SkuEsModel> upProducts = skus.stream().map(sku -> {
                //组装需要的数据
                SkuEsModel esModel = new SkuEsModel();
                BeanUtils.copyProperties(sku,esModel);
                //skuPrice、skuImg、、brandImg、catalogName
                esModel.setSkuPrice(sku.getPrice());
                esModel.setSkuImg(sku.getSkuDefaultImg());
                //(hasStock、hotScore)
                // TODO 1. 发送远程调用、库存系统查询是否有库存
                if(finalStockMap ==null){
                    esModel.setHasStock(true);
                }else{
                    esModel.setHasStock(finalStockMap.get(sku.getSkuId()));
                }
                // TODO 2. 热度评分
                esModel.setHotScore(0L);
                //TODO 3.获取到品牌id
                Long brandId = sku.getBrandId();
                BrandEntity brandEntity = brandService.getById(brandId);
                if(brandEntity!=null){
                    esModel.setBrandName(brandEntity.getName());
                    esModel.setBrandImg(brandEntity.getLogo());
                }
                //TODO 4.获取到分类id
                Long catalogId = sku.getCatalogId();
                CategoryEntity categoryEntity = categoryService.getById(catalogId);
                if(categoryEntity!=null){
                    esModel.setCatalogName(categoryEntity.getName());
                }
                //设置检索属性
                esModel.setAttrs(attrsList);
                return esModel;
            }).collect(Collectors.toList());
    
            //TODO 5.将数据发送给es进行保存
            try{
                R r = searchFeignService.productStatusUp(upProducts);
                if(r.getCode()==0){
                    //远程调用成功
                    //TODO 6. 修改当前spu的状态
                    this.baseMapper.updateSpuStatus(spuId, ProductConstant.StatusEnum.SPU_UP.getCode());
                }else{
                    //远程调用失败
                    //TODO 7.接口幂等性?重试机制
                    //Feign的调用流程
                    /**
                     * 1、构造请求数据,将对象转成JSON
                     *    RequestTemplate template=buildTemplateFromArgs.create(argv);
                     * 2、发送请求进行执行(执行成功会解码响应数据)
                     * 3、执行请求有重试机制
                     *   while(true){
                     *       try{
                     *           executeAndDecode(template);
                     *       }catch(){
                     *          try{retryer.continueOrPropagate(e)}catch(){throws ex;}
                     *       }
                     *   }
                     */
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107

    ⑥. 上架库存服务核心代码

    /**
     * 查询sku是否有库存
     * @param skuIds
     * @return
     */
    @PostMapping("/hasstock")
    public R  getSkusHasStock(@RequestBody List<Long> skuIds){
        //返回当前sku的id和当前sku的库存量是多少
        List<SkuHasStockVo> vos = wareSkuService.getSkuHasStock(skuIds); //查库存
        return R.ok().setData(vos);
    }
    
    @Override
    public List<SkuHasStockVo> getSkuHasStock(List<Long> skuIds) {
        List<SkuHasStockVo> collect = skuIds.stream().map(skuId -> {
            SkuHasStockVo vo = new SkuHasStockVo();
            // 查询当前sku的总库存量
            Long count = baseMapper.getSkuStock(skuId);
            vo.setSkuId(skuId);
            vo.setHasStock(count==null?false:count>0); //有库存
            return vo;
        }).collect(Collectors.toList());
        return collect;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    ⑦. 上架检索服务核心代码

    /**
     * 上架商品
     * @param skuEsModel
     * @return
     */
    @PostMapping("/product")
    public R productStatusUp(@RequestBody List<SkuEsModel> skuEsModel) {
        boolean b=false;
        try{
            b = productSaveService.productStatusUp(skuEsModel);
        }catch (Exception e){
            log.error("ElasticSearch商品上架错误,{}",e);
            return R.error(BizCodeEnum.PRODUCT_UP_EXCEPTION.getCode(), BizCodeEnum.PRODUCT_UP_EXCEPTION.getMsg());
        }
        if(!b){
            return R.ok();
        }else {
            return R.error(BizCodeEnum.PRODUCT_UP_EXCEPTION.getCode(), BizCodeEnum.PRODUCT_UP_EXCEPTION.getMsg());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    @Slf4j
    @Service
    @SuppressWarnings("all")
    public class ProductSaveServiceImpl implements ProductSaveService {
    
        @Autowired
        private RestHighLevelClient restHighLevelClient;
    
        @Override
        public Boolean productStatusUp(List<SkuEsModel> skuEsModels) throws IOException {
            //数据保存到es中
            //1.给es中建立一个索引。product,建立映射关系(在es中提前创建)
    
            //2.给es中保存数据,
            // BulkRequest bulkRequest, RequestOptions options
            BulkRequest bulkRequest = new BulkRequest();
            //构造批量操作
            for (SkuEsModel model : skuEsModels) {
                //构造保存的请求
                IndexRequest indexRequest = new IndexRequest(EsConstant.PRODUCT_INDEX);
                indexRequest.id(model.getSkuId().toString());//当前商品的sku的id
                String s = JSON.toJSONString(model);
                indexRequest.source(s, XContentType.JSON);
                bulkRequest.add(indexRequest);
            }
            BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);//批量保存数据到es
    
            //TODO 1.如果批量错误,就可以处理错误
            boolean b = bulk.hasFailures();//统计哪些商品上架失败
            List<String> collect = Arrays.stream(bulk.getItems()).map(item -> {
                //拿到每一个的处理结果,进行处理
                return item.getId();
            }).collect(Collectors.toList());
            log.info("商品上架完成:{},返回数据: {}",collect,bulk.toString());
    
            return b;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    ⑧. 进行上架流程图(重要)

    • ①. 点击页面上架
      在这里插入图片描述

    • ②. 上架成功后可以看到数据保存到了es、并且pms_info_spu的状态变为了上架处理
      在这里插入图片描述
      在这里插入图片描述

    • ③. 商品上架流程图
      在这里插入图片描述

    • ④. 我们自己在es中的映射

    PUT /gulimall_product
    {
      "mappings": {
        "properties": {
          "attrs": {
            "type": "nested",
            "properties": {
              "attrId": {
                "type": "long"
              },
              "attrName": {
                "type": "keyword"
              },
              "attrValue": {
                "type": "keyword"
              }
            }
          },
          "brandId": {
            "type": "long"
          },
          "brandImg": {
            "type": "keyword"
          },
          "brandName": {
            "type": "keyword"
          },
          "catalogId": {
            "type": "long"
          },
          "catalogName": {
            "type": "keyword"
          },
          "hasStock": {
            "type": "boolean"
          },
          "hotScore": {
            "type": "long"
          },
          "saleCount": {
            "type": "long"
          },
          "skuId": {
            "type": "long"
          },
          "skuImg": {
            "type": "keyword"
          },
          "skuPrice": {
            "type": "keyword"
          },
          "skuTitle": {
            "type": "text",
            "analyzer": "ik_smart"
          },
          "spuId": {
            "type": "keyword"
          }
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
  • 相关阅读:
    数据结构插入排序
    产品新闻稿撰写流程是怎样的,纯干货
    websocket中的STOMP 协议:sockjs-client 和 stompjs
    11.软件测试-----性能测试
    Qt在空窗口中创建自己的按钮
    MySQL学习——选项文件的使用
    (一)JAVA设计模式——设计模式概述
    函数递归(C语言)(详细过程!)
    A-Level经济题解析及练习Policy options for Common Resources
    【Linux】系统api和指令的模拟实现
  • 原文地址:https://blog.csdn.net/TZ845195485/article/details/127590192