谷粒商城-day13-es和商品上架

es kibana docker 安装

docker pull elasticsearch:7.4.2
docker pull kibana:7.4.2
# 将docker里的目录挂载到linux的 /home 目录中
# 修改/home 就可以改掉docker里的
mkdir -p /home/elasticsearch/config
mkdir -p /home/elasticsearch/data

# es可以被远程任何机器访问
echo "http.host: 0.0.0.0" >/home/elasticsearch/config/elasticsearch.yml

# 递归更改权限,es需要访问
chmod -R 777 /home/elasticsearch/
# 9200是用户交互端口 9300是集群心跳端口
# -e指定是单阶段运行
# -e指定占用的内存大小
docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
-e  "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms64m -Xmx512m" \
-v /home/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /home/elasticsearch/data:/usr/share/elasticsearch/data \
-v  /home/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:7.4.2 

# 设置开机启动elasticsearch
docker update elasticsearch --restart=always
# kibana指定了了ES交互端口9200  # 5600位kibana主页端口
docker run --name kibana -e ELASTICSEARCH_HOSTS=http://192.168.8.182:9200 -p 5601:5601 -d kibana:7.4.2

# 设置开机启动kibana
docker update kibana  --restart=always

es 的使用

学习环境 kibana 的 dev console

内容 : 学习方式

  1. 理解 es 中存储结构:对比 mysql 理解
  2. 理解 DSL 查询:bank的例子自己尝试

查看下es集群信息

# 查看所有节点
GET _cat/nodes
# 查看es健康情况
GET _cat/health
# 查看主节点信息
GET _cat/master
# 查看所有索引
GET _cat/indices

增删查改操作

# 新增文档 POST
POST customer/external/1
{
  "name": "John Doe"
}

# 修改文档 PUT
PUT customer/external/1?if_seq_no=2&if_primary_term=1
{
  "name": "John Doe12"
}

# 查询文档 GET
GET /customer/external/1

POST customer/external/_bulk
{"index":{"_id":"1"}} 
{"name": "John Doe"}
{"index":{"_id":"2"}}
{"name": "Paul Doe"}


GET bank/_search?q=*&sort=account_number:asc

DSL 查询语法

# Query DSL
# 1.排序 分页 只返回指定字段
POST bank/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "account_number": {
        "order": "asc"
      },
      "balance": {
        "order": "desc"
      }
    }
  ],
  "_source": ["account_number", "balance"]
}


# 2.match 精确/模糊查询
## 2.1 精确匹配 查询数值类型字段
GET bank/_search
{
  "query": {
    "match": {
      "account_number": 20
    }
  }
}
## 2.2模糊匹配-分词查询字符串类型字段
## 会将address拆分成mill + lane分别模糊匹配 
GET bank/_search
{
  "query": {
    "match": {
      "address": "mill lane"
    }
  }
}

## 2.3 精确匹配字符串 text 文本才可以
GET bank/_search
{
  "query": {
    "match": {
      "address.keyword": "198 Mill Lane"
    }
  }
}

# 3. 短语匹配 match_phrase
## 与 match 对比理解 整体匹配不分词
GET bank/_search
{
  "query": {
    "match_phrase": {
      "address": "mill lane"
    }
  }
}

## 4.多字段分词匹配统一条件
## 相当于调用两次 分词匹配 match
GET bank/_search
{
  "query": {
    "multi_match": {
      "query": "Mill Road",
      "fields": ["state", "address"]
    }
  }
}

# 5.bool 符合查询 拼接查询条件
## 分为两组
## and or 判断
## must must_not 是否包含
## should 得分
GET bank/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "gender": "M"
          }
        },
        {
          "match": {
            "address": "mill"
          }
        }
      ],
      "must_not": [
        {
          "match": {
            "age": "28"
          }
        }
      ],
      "should": [
        {
          "match": {
            "lastname": "Hines"
          }
        }
      ]
    }
  }
}

## 6.filter结果过滤 must_not 的兄弟们
# 添加只查询年龄在 1030
GET bank/_search
{
	"query": {
		"bool": {
			"must": [{
					"match": {
						"gender": "M"
					}
				},
				{
					"match": {
						"address": "mill"
					}
				}
			],
			"must_not": [{
				"match": {
					"age": 28
				}
			}],
			"should": [{
				"match": {
					"lastname": "Hines"
				}
			}],
			"filter": [{
				"range": {
					"age": {
						"gte": 30,
						"lte": 39
					}
				}
			}]
		}
	}
}

## 7.term 其实我不太知道有啥用
## 建议使用 match + keyword???
GET bank/_search
{
  "query": {
    "term": {
      "age": {
        "value": 28
      }
    }
  }
}

aggregations 聚合函数

# aggregations 聚合函数
# 1. terms 分组求和 count
# 按照age来分组求和
GET bank/_search
{
  "aggs": {
    "ageAgg": {
      "terms": {
        "field": "age",
        "size": 3
      }
    }
  }
}
# 2. avg 求平均
GET bank/_search
{
  "aggs": {
    "balanceAvg": {
      "avg": {
        "field": "balance"
      }
    }
  }
}
# 3. 清除不想看到的命中hit
GET bank/_search
{
  "aggs": {
    "balanceAvg": {
      "avg": {
        "field": "balance"
      }
    }
  },
  "size": 0
}
# 4. 聚合内聚合
# 查询每个年龄段内的平均工资
GET bank/_search
{
  "aggs": {
    "ageAgg": {
      "terms": {
        "field": "age"
      },
      "aggs": {
        "balance": {
          "avg": {
            "field": "balance"
          }
        }
      }
    }
  },
  "size": 0
}
# 5.看一个综合案例
# 查出年龄分布 并且这些年龄段中M的平均薪资和F的平均薪资以及各年龄段中总体的平均薪资
GET bank/_search
{
  "aggs": {
    "ageAgg": {
      "terms": {
        "field": "age"
      },
      "aggs": {
        "genderAgg": {
          "terms": {
            "field": "gender.keyword"
          },
          "aggs": {
            "balanceAvg": {
              "avg": {
                "field": "balance"
              }
            }
          }
        },
        "ageBalanceAvg": {
            "avg": {
              "field": "balance"
            }
          }
      }
    }
  },
  "size": 0
}

mapping

# mapping 相当于 mysql 中表结构定义
# 注意 es 中的 mapping 不能直接修改的特性
# 1.查看所有属性定义
GET bank/_mapping
# 2. 创建映射
PUT /myindex
{
  "mappings": {
    "properties": {
      "age": {
        "type": "integer",
        "boost": "2"
      },
      "email": {
        "type": "keyword"
      },
      "name": {
        "type": "text"
      }
    }
  }
}
GET /myindex/_mapping
# 3.添加字段映射(添加表字段)
PUT /myindex/_mapping
{
  "properties": {
    "employee_id": {
      "type": "keyword",
      "index": false
    }
  }
}
# 4. 修改(迁移索引)
PUT /myindex_new
{
  "mappings": {
    "properties": {
      "age": {
        "type": "integer",
        "boost": "3"
      },
      "email": {
        "type": "keyword"
      },
      "name": {
        "type": "text"
      }
    }
  }
}

GET myindex/_search

POST myindex/_bulk
{"create": {"_id": 1}}
{"age": 10,"email": "1529219@qq.com","name": "zza"}
{"create": {"_id": 2}}
{"age": 11,"email": "1529219@qq.com","name": "zzb"}
{"create": {"_id": 3}}
{"age": 11,"email": "1529219@qq.com","name": "zzc"}
{"create": {"_id": 4}}
{"age": 12,"email": "1529219@qq.com","name": "zzd"}
{"create": {"_id": 5}}
{"age": 13,"email": "1529219@qq.com","name": "zze"}

GET myindex_new/_search

POST _reindex
{
  "source": {
    "index": "myindex"
  },
  "dest": {
    "index": "myindex_new"
  }
}

GET myindex_new/_search

sku 在 es 当中的存储模型

PUT product
{
    "mappings":{
        "properties": {
            "skuId":{ "type": "long" },
            "spuId":{ "type": "keyword" },  
            "skuTitle": {
                "type": "text",
                "analyzer": "ik_smart" 
            },
            "skuPrice": { "type": "keyword" },  
            "skuImg"  : { "type": "keyword" }, 
            "saleCount":{ "type":"long" },
            "hasStock": { "type": "boolean" },
            "hotScore": { "type": "long"  },
            "brandId":  { "type": "long" },
            "catalogId": { "type": "long"  },
            "brandName": {"type": "keyword"},
            "brandImg":{
                "type": "keyword",
                "index": false,  
                "doc_values": false
            },
            "catalogName": {"type": "keyword" },
            "attrs": {
                "type": "nested",
                "properties": {
                    "attrId": {"type": "long"  },
                    "attrName": {
                        "type": "keyword",
                        "index": false,
                        "doc_values": false
                    },
                    "attrValue": {"type": "keyword" }
                }
            }
        }
    }
}

nested 数据模型

es默认会对数组对象扁平化处理,导致结果出现意外情况,这是我们需要指定为 nested 嵌入式类型

DELETE my_index

PUT my_index/_doc/1
{
  "group" : "fans",
  "user" : [ 
    {
      "first" : "John",
      "last" :  "Smith"
    },
    {
      "first" : "Alice",
      "last" :  "White"
    }
  ]
}

GET my_index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "user.first": "Alice"
          }
        },
        {
          "match": {
            "user.last": "Smith"
          }
        }
      ]
    }
  }
}

# 可以查得到数据,应该是无法查询到的

GET my_index/_mapping

# 删除后重新导入数据
DELETE my_index

PUT my_index
{
  "mappings": {
    "properties": {
      "user": {
        "type": "nested"
      }
    }
  }
}

PUT my_index/_doc/1
{
  "group" : "fans",
  "user" : [ 
    {
      "first" : "John",
      "last" :  "Smith"
    },
    {
      "first" : "Alice",
      "last" :  "White"
    }
  ]
}

GET my_index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "user.first": "Alice"
          }
        },
        {
          "match": {
            "user.last": "Smith"
          }
        }
      ]
    }
  }
}
# 再次查询就查不到了

构造 sku 检索属性

这节课主要是构造 up 上架商品的接口的大体框架

controller

// SpuInfoController 
/// product/spuinfo/{spuinfo}/up
    @PostMapping("/{spuId}/up")
    public R spuUp(@PathVariable("spuId") Long spuId){
        spuInfoService.up(spuId);

        return R.ok();
    }

放在 common 的 to 中,这里基本是根据 es 构造的索引创建的 bean

@Data
public class SkuEsModel {
    private Long skuId;

    private Long spuId;

    private String skuTitle;

    private BigDecimal skuPrice;

    private String skuImg;

    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;
    }

}

service

@Override
    public void up(Long spuId) {
        // 1. 查出当前 spuid 对应的所有 sku 信息,品牌的名字
        List<SkuInfoEntity> skus = skuInfoService.getSkuBySpuId(spuId);

        // TODO 4.查询当前 sku 的所有可以被检索的规格属性

        // 2. 封装每个 sku 的信息
        skus.stream().map(sku -> {
            // 组装需要的数据
            SkuEsModel esModel = new SkuEsModel();
            BeanUtils.copyProperties(sku, esModel);

            esModel.setSkuPrice(sku.getPrice());
            esModel.setSkuImg(sku.getSkuDefaultImg());
            // TODO 1.远程调用库存系统 是否有库存

            // TODO 2.热度评分.0.

            // TODO 3.品牌和分类的名字信息
            BrandEntity brand = brandService.getById(esModel.getBrandId());
            esModel.setBrandName(brand.getName());
            esModel.setBrandImg(brand.getLogo());

            CategoryEntity category = categoryService.getById(esModel.getCatalogId());
            esModel.setCatalogName(category.getName());

            return esModel;
        }).collect(Collectors.toList());

        // TODO 5. 将数据发送给 es 进行保存

    }

构造 sku 检索属性

其实不怎么复杂,基本的增删改查加上一些集合操作,体会下这里的 stream map 即可

@Override
    public void up(Long spuId) {
        // 1. 查出当前 spuid 对应的所有 sku 信息,品牌的名字
        List<SkuInfoEntity> skus = skuInfoService.getSkuBySpuId(spuId);

        // TODO 4.查询当前 sku 的所有可以被检索的规格属性
        List<ProductAttrValueEntity> baseAttrs = productAttrValueService.baseAttrListforspu(spuId);
        List<Long> attrIds = baseAttrs.stream().map(attr -> {
            return attr.getAttrId();
        }).collect(Collectors.toList());

        List<Long> searchAttrIds = attrService.selectSearchAttrs(attrIds);

        Set<Long> idSet = new HashSet<>(searchAttrIds);

        List<SkuEsModel.Attrs> attrsList = baseAttrs.stream().filter(item -> {
            return idSet.contains(item.getAttrId());
        }).map(item -> {
            SkuEsModel.Attrs attrs = new SkuEsModel.Attrs();
            BeanUtils.copyProperties(item, attrs);
            return attrs;
        }).collect(Collectors.toList());

        // 2. 封装每个 sku 的信息
        skus.stream().map(sku -> {
            // 组装需要的数据
            SkuEsModel esModel = new SkuEsModel();
            BeanUtils.copyProperties(sku, esModel);

            esModel.setSkuPrice(sku.getPrice());
            esModel.setSkuImg(sku.getSkuDefaultImg());
            // TODO 1.远程调用库存系统 是否有库存

            // TODO 2.热度评分.0
            esModel.setHotScore(0L);

            // TODO 3.品牌和分类的名字信息
            BrandEntity brand = brandService.getById(esModel.getBrandId());
            esModel.setBrandName(brand.getName());
            esModel.setBrandImg(brand.getLogo());

            CategoryEntity category = categoryService.getById(esModel.getCatalogId());
            esModel.setCatalogName(category.getName());

            // 设置检索属性
            esModel.setAttrs(attrsList);

            return esModel;
        }).collect(Collectors.toList());

        // TODO 5. 将数据发送给 es 进行保存

    }

远程查询库存&泛型结果对接

本节也比较简单,也是老套路,注意点有两个

  1. 远程调用接口使用泛型R,避免类型转换的麻烦
  2. 第二条之前提到过,远程调用要考虑到超时等情况 ,try catch 异常,set默认值
@Override
    public void up(Long spuId) {
        // 1. 查出当前 spuid 对应的所有 sku 信息,品牌的名字
        List<SkuInfoEntity> skus = skuInfoService.getSkuBySpuId(spuId);

        List<Long> skuIdList = skus.stream().map(SkuInfoEntity::getSkuId).collect(Collectors.toList());

        // TODO 1.远程调用库存系统 是否有库存
        Map<Long, Boolean> stockMap = null;
        try {
            R<List<SkuHasStockVo>> skuHasStock = wareFeignService.getSkusHasStock(skuIdList);
            stockMap = skuHasStock.getData().stream()
                    .collect(Collectors.toMap(SkuHasStockVo::getSkuId, item -> item.getHasStock()));
        } catch (Exception e) {
            log.error("库存服务异常:原因{}", e);
        }


        // TODO 4.查询当前 sku 的所有可以被检索的规格属性
        List<ProductAttrValueEntity> baseAttrs = productAttrValueService.baseAttrListforspu(spuId);
        List<Long> attrIds = baseAttrs.stream().map(attr -> {
            return attr.getAttrId();
        }).collect(Collectors.toList());

        List<Long> searchAttrIds = attrService.selectSearchAttrs(attrIds);

        Set<Long> idSet = new HashSet<>(searchAttrIds);

        List<SkuEsModel.Attrs> attrsList = baseAttrs.stream().filter(item -> {
            return idSet.contains(item.getAttrId());
        }).map(item -> {
            SkuEsModel.Attrs attrs = new SkuEsModel.Attrs();
            BeanUtils.copyProperties(item, attrs);
            return attrs;
        }).collect(Collectors.toList());

        // 2. 封装每个 sku 的信息
        Map<Long, Boolean> finalStockMap = stockMap;
        skus.stream().map(sku -> {
            // 组装需要的数据
            SkuEsModel esModel = new SkuEsModel();
            BeanUtils.copyProperties(sku, esModel);

            esModel.setSkuPrice(sku.getPrice());
            esModel.setSkuImg(sku.getSkuDefaultImg());

            // 设置库存信息
            if (finalStockMap == null) {
                esModel.setHasStock(true);
            } else {
                esModel.setHasStock(finalStockMap.get(sku.getSkuId()));
            }

            // TODO 2.热度评分.0
            esModel.setHotScore(0L);

            // TODO 3.品牌和分类的名字信息
            BrandEntity brand = brandService.getById(esModel.getBrandId());
            esModel.setBrandName(brand.getName());
            esModel.setBrandImg(brand.getLogo());

            CategoryEntity category = categoryService.getById(esModel.getCatalogId());
            esModel.setCatalogName(category.getName());

            // 设置检索属性
            esModel.setAttrs(attrsList);

            return esModel;
        }).collect(Collectors.toList());

        // TODO 5. 将数据发送给 es 进行保存

    }

给 R 添加泛型

/**
 * 返回数据
 *
 * @author Mark sunlightcs@gmail.com
 */
public class R<T> extends HashMap<String, Object> {
    private static final long serialVersionUID = 1L;

    private T data;

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

feign

@FeignClient("gulimall-ware")
public interface WareFeignService {

    /**
     * 1.R 设计的时候加上泛型
     * <p>
     * 2.直接返回我们需要的数据
     * <p>
     * 3.自己封装解析结果
     *
     * @param skuIds
     * @return
     */
    @PostMapping("/ware/waresku/hasstock")
    R<List<SkuHasStockVo>> getSkusHasStock(@RequestBody List<Long> skuIds);
}

WareSkuController

// 查询 sku 是否有库存
    @PostMapping("/hasstock")
    public R<List<SkuHasStockVo>> getSkusHasStock(@RequestBody List<Long> skuIds) {
        // sku_id, stock
        List<SkuHasStockVo> vos = wareSkuService.getSkusHasStock(skuIds);

        R<List<SkuHasStockVo>> ok = R.ok();
        ok.setData(vos);

        return ok;
    }
@Override
    public List<SkuHasStockVo> getSkusHasStock(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>0);
            return vo;
        }).collect(Collectors.toList());

        return collect;
    }
// mapper
    long getSkuStock(Long skuId);
<select id="getSkuStock" resultType="java.lang.Long">
        select SUM(stock - stock_locked)
        from `wms_ware_sku`
        where sku_id = #{skuId};
    </select>

远程上架

这里的实现思路是调用远程服务 search ,在 search 模块里面把 商品的数据传到 es ,远程调用成功把spu状态改为已上架

但这里上架成功之后,没有修改上架状态

抽取响应结果

image-20220727114010916

image-20220727152302369

这里要提一嘴,这里雷神又debug了下,因为 R 是 hashmap ,序列化时会失效,改了另外一种方式,用了泛型

public R setData(Object data) {
        put("data", data);
        return this;
    }

    // 利用 fastjson 进行你装
    public <T> T getData(TypeReference<T> typeReference) {
        Object data = get("data"); // 默认是 map
        String s = JSON.toJSONString(data);

        T t = JSON.parseObject(s, typeReference);
        return t;
    }
R r = wareFeignService.getSkusHasStock(skuIdList);
            
            TypeReference<List<SkuHasStockVo>> typeReference = new TypeReference<List<SkuHasStockVo>>(){};
            stockMap = r.getData(typeReference).stream()
                    .collect(Collectors.toMap(SkuHasStockVo::getSkuId, item -> item.getHasStock()));

其实这个功能我这边也常用,就是map转具体类的,不过用的形参不是TypeReference,直接传的 Class ,下面给出我这边的工具类供参考

public class MapObjectUtil {
    // 改为 借助于 json 对象实现,兼容现在的 json 转换体系
    //map转java对象
    public static Object mapToObjectJson(Map<String, Object> map, Class<?> beanClass) throws Exception {
        String jsonStr = JSONObject.toJSONString(map);
        return JSONObject.parseObject(jsonStr, beanClass);
    }

    //java对象转map
    public static Map<String, Object> objectToMapJson(Object obj) {
        String jsonStr = JSONObject.toJSONString(obj);
        return JSONObject.parseObject(jsonStr);
    }
    
}

结束

上架商品的功能就写到这里了,后续应该还会再完善简化下,其实很多都是重复的增删改查,主要还是看看 es 的应用场景,还有 远程调用的时候处理的一些细节,但可能这块得到的知识容量还是不怎么大

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值