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

news/2024/5/19 0:28:22 标签: elasticsearch, 流程图, java

文章目录

  • ①. sku在es中存储模型分析
  • ②. 建立product的索引信息
  • ③. nested数据类型场景
  • ④. 商品上架逻辑
  • ⑤. 上架商品服务核心代码
  • ⑥. 上架库存服务核心代码
  • ⑦. 上架检索服务核心代码
  • ⑧. 进行上架流程图(重要)

①. 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},
        {分辨率:全高清}
	]
  • ④. 方案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

结论:如果将规格参数单独建立索引,会出现检索时出现大量数据传输的问题,会引起网络网络

②. 建立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" }
                }
            }
        }
    }
}

③. nested数据类型场景

  • ①. 属性是"type": “nested”,因为是内部的属性进行检索

  • ②. 数组类型的对象会被扁平化处理(对象的每个属性会分别存储到一起)举个例子说明

java">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" }}
      ]
    }
  }
}

在这里插入图片描述

④. 商品上架逻辑

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

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

java">@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;
    }
}
  • ③. 通过前端传递的spuId将Spu的基本信息查询出来、查询的表是pms_spu_info表,这个表中和SkuModel表字段一样的有spuId、skuId、catelogId、brandId。其他的字段如:skuPrice、skuImg、brandImg、catalogName、hasStock、hotScore需要重新查询并设置到SkuModel中去
    在这里插入图片描述

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

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

java">esModel.setHotScore(0L);
  • ⑥. 获取到品牌id、获取到分类id
java">//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());
}
  • ⑦. 设置检索属性
java">//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());
  • ⑧. 将数据发送给es进行保存
    在这里插入图片描述

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

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

java">   /**
     * 商品上架
     * @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();
        }
    }

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

java">/**
 * 查询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;
}

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

java">/**
 * 上架商品
 * @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());
    }
}
java">@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;
    }
}

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

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

  • ②. 上架成功后可以看到数据保存到了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"
      }
    }
  }
}

http://www.niftyadmin.cn/n/1941.html

相关文章

Exception-Error

知错好过不知 刷抖音的时候看到这么一段话&#xff1a; 一个人的最低境界是从不反思&#xff0c;就是我做错了什么事儿&#xff0c;不知道&#xff01;我总觉得是别人的错&#xff0c;我从不觉得是自己错。 然后还有一种比这个境界高一点的境界&#xff0c;我知道我错了&…

【Vue】环境搭建

Vue 简介&#xff1a; 一套用于构建用户界面的渐进式框架。与其它大型框架不同的是&#xff0c;Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层&#xff0c;不仅易于上手&#xff0c;还便于与第三方库或既有项目整合。另一方面&#xff0c;当与现代化的工具链以…

隔离出来的“陋室铭”

被隔离了 日常锻炼身体就是去公司旁边的酒店游泳&#xff0c;结果酒店里除了小阳人&#xff0c;我就喜提次密称号&#xff0c;7天隔离走起&#xff1b;又因为不想耽误家里孩子上学&#xff0c;老人外出&#xff0c;就选择了单独隔离&#xff0c;结果就拉到了单独的隔离点&…

Android - UI开发指南

文章目录调试工具查看view的边界和margin、padding查看布局属性层次和控件的属性布局开发布局命名空间 app、android、toolsConstraintLayout调试工具 查看view的边界和margin、padding 开发者选项 > 显示布局边界 开启这个选项后界面上每一个空间周围会多出来红色和蓝色的…

【Linux学习】基础IO

目录前言一、C语言文件IO1. C语言文件接口以及打开方式2. 对当前路径的理解3. 默认打开的三个流二、 系统文件IO1. 系统接口openwritereadclose系统接口和库函数2. 文件描述符及其分配规则文件描述符文件描述符分配原则3. 重定向及dup2系统调用重定向标准输出和标准错误的区别d…

基于51单片机的信号发生器设计

目 录 引言... 1 1 课题背景意义及研究内容... 1 1.1 课题背景及研究意义... 1 1.2 课题研究内容... 2 2 设计方案选择... 2 2.1 系统控制芯片选择... 2 2.2 信号发生方式选择... 2 2.3 系统整体设计方案... 2 3 系统硬件设计... 3 3.1 单片机最小系统... 3 3.1.1单片机S…

C++11:新特性(11-20)

十一&#xff1a;委托构造函数 C11中&#xff0c;委托构造函数使用它所属类的其他构造函数执行它自己的初始化过程&#xff0c;或这说自己的一些职责委托给了其他构造函数。 格式为&#xff1a; class data { public://构造函数data(int a,int b,int c):_a(a),_b(b),_c(c){}…

linux基本操作之gvim

文章目录 一、GVIM介绍及安装二、配置GVIM界面2.1、简单.vimrc配置使用2.2、功能齐全的.vimrc及插件配置2.3、gvim常用窗口编辑操作2.4、gvim三种工作模式三、gvim常用命令(均在命令模式下才可用)3.1、常用的光标移动快捷键3.2、删除操作(delete)以`d`为关键字3.3、改变和替…