栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

乐购商城项目技术总结 -- ElasticSearch

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

乐购商城项目技术总结 -- ElasticSearch

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录
  • 前言
  • 一、ElasticSearch相关概念
    • 1. 基本概念
    • 2. ElasticSearch有什么用?
  • 二、ElasticSearch安装
    • 1. docker镜像下载
    • 2. 安装es容器
    • 3. 开启远程连接
  • 三、项目整合ElasticSearch


前言

Elasticsearch 是一个分布式的免费开源搜索和分析引擎,适用于包括文本、数字、地理空间、结构化和非结构化数据等在内的所有类型的数据。Elasticsearch 在 Apache Lucene 的基础上开发而成,由 Elasticsearch N.V.(即现在的 Elastic)于 2010 年首次发布。Elasticsearch 以其简单的 REST 风格 API、分布式特性、速度和可扩展性而闻名,是 Elastic Stack 的核心组件;Elastic Stack 是一套适用于数据采集、扩充、存储、分析和可视化的免费开源工具。人们通常将 Elastic Stack 称为 ELK Stack(代指 Elasticsearch、Logstash 和 Kibana),目前 Elastic Stack 包括一系列丰富的轻量型数据采集代理,这些代理统称为 Beats,可用来向 Elasticsearch 发送数据。


一、ElasticSearch相关概念
1. 基本概念

Elasticsearch是面向文档(document oriented)的,这意味着它可以存储整个对象或文档(document)。 然而它不仅仅是存储,还会索引(index)每个文档的内容使之可以被搜索。在Elasticsearch中,你可以对 文档(而非成行成列的数据)进行索引、搜索、排序、过滤。

用Mysql这样的数据库存储就会容易想到建立一张User表,有很多的字段等,在Elasticsearch里这就是一个文档,当然这个文档会属于一个User的类型,各种各样的类型存在于一个索引当中。

关系数据库      ⇒ 数据库        ⇒  表         ⇒ 行              ⇒ 列(Columns)
 
Elasticsearch  ⇒ 索引(Index)   ⇒ 类型(type)  ⇒ 文档(Docments)  ⇒ 字段(Fields)  
2. ElasticSearch有什么用?

ElasticSearch在速度和扩展性上方面都表现出色, 而且还能够索引多种类型的内容, 这意味着其可用于多种用例:

  • 应用程序搜索
  • 网站搜索
  • 企业搜索
  • 日志处理和分析
  • 基础设施指标和容器检测
  • 地理空间数据分析和可视化
  • 安全分析
  • 业务分析
二、ElasticSearch安装

本项目中使用Docker安装

1. docker镜像下载
docker pull elasticsearch:5.6.8
2. 安装es容器
docker run -di --name=es -p 9200:9200 -p 9300:9300 elasticsearch:5.6.8

9200端口(Web管理平台端口) 9300(服务默认端口)
浏览器输入地址访问: http://110.42.216.234:9200 (注意: 请修改成自己的虚拟机ip)

3. 开启远程连接

上面完成安装后,es并不能正常使用,elasticsearch从5版本以后默认不开启远程连接,程序直接连接会报如下错误:

failed to load elasticsearch nodes : org.elasticsearch.client.transport.NoNodeAvailableException: None of the configured nodes are available: [{#transport#-1}{5ttLpMhkRjKLkvoY7ltUWg} {192.168.211.132}{192.168.211.132:9300}]

我们需要修改es配置开启远程连接,代码如下:

登录容器

docker exec -it es /bin/bash

进入config目录

cd config

调用ls命令可以看到如下文件

elasticsearch.yml log4j2.properties scripts

修改elasticsearch.yml文件

vi elasticsearch.yml

修改如下图:

同时添加下面一行代码:

cluster.name: my-elasticsearch

重启docker

docker restart es

三、项目整合ElasticSearch

pom.xml文件中添加ElasticSearch的依赖



	org.springframework.boot 
	spring-boot-starter-data-elasticsearch

配置文件 search-service.yml 中ElasticSearch的相关配置:

server:
  port: 9006

其他配置...


spring:
  data:
    elasticsearch:
	  cluster-name: elasticsearch 
	  cluster-nodes: 192.168.220.110:9300
  elasticsearch:
    rest:
	  uris: 192.168.220.110:9200

编写启动类 SearchApplication.java

package com.gyh.legou.search;

@SpringBootApplication //spring boot 
@EnableDiscoveryClient //将微服务注册到注册中心 
@EnableFeignClients //通过feign调用其他微服务 
@EnableCircuitBreaker //开启熔断,微服务容错保护 
public class SearchApplication {

	public static void main(String[] args) { 	
		SpringApplication.run(SearchApplication.class, args);
	} 
	
}

我们创建一个类,封装要保存到索引库的数据,并设置映射属性:

package com.gyh.legou.search.po;

@Data
@document(indexName = "goods_legou", type = "docs_legou", shards = 1, replicas = 0)
public class Goods {

	@Id
	private Long id; // spuId
	
	@Field(type = FieldType.Text, analyzer = "ik_max_word")
	private String all; // 所有需要被搜索的信息,包含标题,分类,甚至品牌 

	@Field(type = FieldType.Keyword, index = false)
	private String subTitle;// 卖点
	
	private Long brandId;// 品牌id
	
	private Long cid1;// 1级分类id
	
	private Long cid2;// 2级分类id
	
	private Long cid3;// 3级分类id
	
	private Date createTime;// 创建时间
	
	private List price;// 价格
	
	@Field(type = FieldType.Keyword, index = false)
	private String skus;// sku信息的json结构
	
	private Map specs;// 可搜索的规格参数,key是参数名,值是参数值
}

一些特殊字段解释:

  • all:用来进行全文检索的字段,里面包含标题、商品分类信息
  • price:价格数组,是所有sku的价格集合。方便根据价格进行筛选过滤
  • skus:用于页面展示的sku信息,不索引,不搜索。包含skuId、image、price、title字段
  • specs:所有规格参数的集合。key是参数名,值是参数值。

例如:我们在specs中存储 内存:4G,6G,颜色为红色,转为json就是:

{
	"specs":{
		"内存":[4G,6G],
		"颜色":"红色" 
	}
}

当存储到索引库时,elasticsearch会处理为两个字段:

  • specs.内存:[4G,6G]
  • specs.颜色:红色

创建 GoodsRepository 类

package com.gyh.legou.search.dao;

public interface GoodsDao extends ElasticsearchRepository {

}

接下来导入数据。导入数据其实就是查询数据,然后把查询到的Spu转变为Goods来保存,因此我们先编写一个 IndexService,然后在里面定义一个方法, 把Spu转为Goods

package com.gyh.legou.search.service;

@Service
public class IndexService {

    @Autowired
    private GoodsDao goodsDao;

    @Autowired
    private CategoryClient categoryClient;

    @Autowired
    private SpecParamClient specParamClient;

    @Autowired
    private SkuClient skuClient;

    @Autowired
    private SpuDetailClient spuDetailClient;


    
    public Goods buildGoods(Spu spu) {

        //准备数据
        //商品分类名称
        List names = this.categoryClient.queryNameByIds(Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3()));
        String all = spu.getTitle() + " " + StringUtils.join(names, " ");

        //sku集合
        List skus = skuClient.selectSkusBySpuId(spu.getId());

        //处理sku
        //把商品价格取出单独存放,便于展示
        List prices = new ArrayList<>();

        List> skuList = new ArrayList<>();

        for(Sku sku : skus){
            prices.add(sku.getPrice());
            Map map = new HashMap<>();
            map.put("id", sku.getId());
            map.put("title", sku.getTitle());
            map.put("image", StringUtils.isBlank(sku.getImages()) ? "" : sku.getImages().split(",")[0]);
            map.put("price", sku.getPrice());
            skuList.add(map);
        }

        //spec
        Map specs = new HashMap<>();

        //spuDetail
        SpuDetail spuDetail = spuDetailClient.edit(spu.getId());


        //通用规格参数值
        Map genericMap = JsonUtils.parseMap(spuDetail.getGenericSpec(), String.class, String.class);

        //特有规格参数的值
        Map> specialMap = JsonUtils.nativeRead(spuDetail.getSpecialSpec(), new TypeReference>>() {
        });

        SpecParam specParam = new SpecParam();
        specParam.setCid(spu.getCid3());
        specParam.setSearching(true);
        List params = specParamClient.selectSpecParamApi(specParam);

        for (SpecParam param : params) {
            //今后显示的名称
            String name = param.getName();
            //品牌,机身颜色
            // 通用参数
            Object value = null;
            if (param.getGeneric()) {
                //通用参数
                value = genericMap.get(name);
                if (param.getNumeric()) {
                    //数值类型需要加分段
                    value = this.chooseSegment(value.toString(), param);
                }
            } else {
                //特有参数
                value = specialMap.get(name);
            }
            if (null == value) {
                value = "其他";
            }
            specs.put(name, value);
        }


        Goods goods = new Goods();

        goods.setId(spu.getId());
        //这里如果要加品牌,可以再写个BrandClient,根据id查品牌
        goods.setAll(all);
        goods.setSubTitle(spu.getSubTitle());
        goods.setBrandId(spu.getBrandId());
        goods.setCid1(spu.getCid1());
        goods.setCid2(spu.getCid2());
        goods.setCid3(spu.getCid3());
        goods.setCreateTime(spu.getCreateTime());
        goods.setPrice(prices);
        goods.setSkus(JsonUtils.serialize(skuList));
        goods.setSpecs(specs);

        return goods;
    }


    private String chooseSegment(String value, SpecParam p) {
        double val = NumberUtils.toDouble(value);
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = NumberUtils.toDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = NumberUtils.toDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();//4.5 4-5英寸
                }
                break;
            }
        }
        return result;
    }


    
    public void deleteIndex(Long id) {
        goodsDao.deleteById(id);
    }
    
}

然后编写一个测试类,循环查询Spu,然后调用IndexService中的方法,把SPU变为Goods,然后写入 索引库:

package com.gyh.legou.search;

@RunWith(SpringRunner.class) 
@SpringBootTest(classes = SearchApplication.class) 
public class ESLoadDataTest {
	
	@Autowired
    private IndexService indexService;
    @Autowired
    private SpuClient spuClient;
    @Autowired
    private GoodsDao goodsDao;
    
	@Test
	public void loadData() { 
		// 查询spu
		// PageResult result = this.goodsClient.querySpuByPage(page,rows, true, null);
		// List spus = result.getItems();
		
		List spus = spuClient.selectAll();
		
		// spu转为goods
		List goods = spus.stream().map(spu -> this.indexService.buildGoods(spu)).collect(Collectors.toList()); 
	
		// 把goods放入索引库
		goodsDao.saveAll(goods); 
		
	}
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/306288.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号