Changgou Mall (5): Elasticsearch realiza la búsqueda de productos

Autor: Robod
Enlace: https: //zhuanlan.zhihu.com/p/261054645
Fuente: saber casi con
derechos de autor del autor. Para reimpresiones comerciales, comuníquese con el autor para obtener autorización, y para reimpresiones no comerciales, indique la fuente.
 

Preparación preliminar

La tarea de hoy es utilizar ElasticSearcher para realizar la función de búsqueda de productos. Para la instalación y el uso básico de Elasticsearch, IK tokenizer y Kibana, consulte mi otro artículo Elasticsearch Getting Started Guide .

Construcción de proyecto API para microservicios de búsqueda

Cree un módulo llamado changgou-service-search-api en changgou-service-api . Las funciones que queremos implementar más adelante están todas basadas en Spring Data ElasticSearch , por lo que las dependencias relacionadas no pueden ser menores:

<dependencies>
    <!--goods API依赖-->
    <dependency>
        <groupId>com.robod</groupId>
        <artifactId>changgou-service-goods-api</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <!--SpringDataES依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    </dependency>
</dependencies>

Construcción de microservicios de búsqueda

En un nuevo proyecto changgou-service-search bajo changgou-service como un servicio de micro-búsqueda. Las interfaces JavaBean y Feign del proyecto API deben usarse en el microservicio de búsqueda, por lo que search-api y goods-api se agregan como dependencias .

<dependencies>
    <!--依赖search api-->
    <dependency>
        <groupId>com.robod</groupId>
        <artifactId>changgou-service-search-api</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>com.robod</groupId>
        <artifactId>changgou-service-goods-api</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>

Las clases de inicio y los archivos de configuración son naturalmente indispensables

@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
@EnableEurekaClient
@EnableFeignClients(basePackages = "com.robod.goods.feign")
@EnableElasticsearchRepositories(basePackages = "com.robod.mapper")
public class SearchApplication {

    public static void main(String[] args) {
        //解决SpringBoot的netty和elasticsearch的netty相关jar冲突
        System.setProperty("es.set.netty.runtime.available.processors", "false");
        SpringApplication.run(SearchApplication.class,args);
    }
}

server:
  port: 18085
spring:
  application:
    name: search
  data:
    elasticsearch:
      cluster-name: my-application        # 集群节点的名称,就是在es的配置文件中配置的
      cluster-nodes: 192.168.31.200:9300  # 这里用的是TCP端口所以是9300
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:7001/eureka
  instance:
    prefer-ip-address: true
feign:
  hystrix:
    enabled: true
#超时配置
ribbon:
  ReadTimeout: 500000   # Feign请求读取数据超时时间

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 50000   # feign连接超时时间

Importar datos a ES

La importación de datos de MySQL a ES se divide aproximadamente en los siguientes pasos:

Primero, necesitamos crear un JavaBean para definir la configuración de mapeo relevante, Índice, Tipo, Campo. Cree un JavaBean llamado SkuInfo en el paquete com.robod.entity de changgou-service-search-api :

@Data
@Document(indexName = "sku_info", type = "docs")
public class SkuInfo implements Serializable {

    @Id
    private Long id;//商品id,同时也是商品编号

    /**
     * SKU名称
     * FieldType.Text支持分词
     * analyzer 创建索引的分词器
     * searchAnalyzer 搜索时使用的分词器
     */
    @Field(type = FieldType.Text, analyzer = "ik_smart",searchAnalyzer = "ik_smart")
    private String name;

    @Field(type = FieldType.Double)
    private Long price;//商品价格,单位为:元

    private Integer num;//库存数量

    private String image;//商品图片

    private String status;//商品状态,1-正常,2-下架,3-删除

    private LocalDateTime createTime;//创建时间

    private LocalDateTime updateTime;//更新时间

    private String isDefault; //是否默认

    private Long spuId;//SPU_ID

    private Long categoryId;//类目ID

    @Field(type = FieldType.Keyword)
    private String categoryName;//类目名称,不分词

    @Field(type = FieldType.Keyword)
    private String brandName;//品牌名称,不分词

    private String spec;//规格

    private Map<String, Object> specMap;//规格参数

}

En SkuInfo, Index se establece en "sku_info", Tpye se establece en "docs" y la segmentación de palabras se establece para varios campos. Luego, cree una interfaz de Feign SkuFeign en el paquete com.robod.goods.feign de changgou-service-goods-api :

@FeignClient(name = "goods")
@RequestMapping("/sku")
public interface SkuFeign {

    /**
     * 查询所有的sku数据
     * @return
     */
    @GetMapping
    Result<List<Sku>> findAll();
}

Usaremos este Feign para llamar al método findAll en el microservicio Goods para obtener todos los datos de Sku en la base de datos. Finalmente, en el microservicio changgou-service-search, escriba el código relevante de las capas Controller, Service y Dao para realizar la función de importación de datos.

//SkuEsController
@GetMapping("/import")
public Result importData(){
    skuEsService.importData();
    return new Result(true, StatusCode.OK,"数据导入成功");
}
-----------------------------------------------------------
//SkuEsServiceImpl
@Override
public void importData() {
    List<Sku> skuList = skuFeign.findAll().getData();
    List<SkuInfo> skuInfos = JSON.parseArray(JSON.toJSONString(skuList), SkuInfo.class);
    //将spec字符串转化成map,map的key会自动生成Field
    for (SkuInfo skuInfo : skuInfos) {
        Map<String,Object> map = JSON.parseObject(skuInfo.getSpec(),Map.class);
        skuInfo.setSpecMap(map);
    }
    skuEsMapper.saveAll(skuInfos);
}
-------------------------------------------------------------
//继承自ElasticsearchRepository,泛型为SkuInfo,主键类型为Long
public interface SkuEsMapper extends ElasticsearchRepository<SkuInfo,Long> {
}

Ahora ejecute el programa y visite http: // localhost: 18085 / search / import para comenzar a importar.

Después de una larga espera, se importaron con éxito más de 90.000 datos a ES. Lleva mucho tiempo, unos quince minutos, puede estar relacionado con la configuración de la máquina virtual.

Cuando terminé esto, lo envié a Github, luego lo cambié y volví a la versión enviada anteriormente. Luego comencé el proyecto e informé un error que decía que la inyección de Bean falló. Estaba desconcertado. Esta es la versión normal que envié antes. ¿Por qué salió mal? Luego hojeó cuidadosamente el registro y encontró una línea

Esto parece ser un problema con el índice, elimine el índice, inicie el proyecto, no hay problema, vuelva a importar los datos a ES, ¡y listo!

Realización de funciones

Buscar por palabra clave

Antes de comenzar a implementar esta función, primero debe definir el formato de los parámetros de front-end y back-end. El mapa se usa en el video, pero creo que el mapa no es bueno y su legibilidad es demasiado pobre. Un mejor enfoque es encapsular una clase de entidad, por lo que agregué SearchEntity como el formato de los parámetros de front-end y back-end en el proyecto search-api:

@Data
public class SearchEntity {
    
    private long total;     //搜索结果的总记录数

    private int totalPages; //查询结果的总页数

    private List<SkuInfo> rows; //搜索结果的集合

    public SearchEntity() {
    }

    public SearchEntity(List<SkuInfo> rows, long total, int totalPages) {
        this.rows = rows;
        this.total = total;
        this.totalPages = totalPages;
    }
}

Luego escribe el código correspondiente en el microservicio de búsqueda

@GetMapping
public Result<SearchEntity> searchByKeywords(@RequestParam(required = false)String keywords) {
    SearchEntity searchEntity = skuEsService.searchByKeywords(keywords);
    return new Result<>(true,StatusCode.OK,"根据关键词搜索成功",searchEntity);
}
---------------------------------------------------------------------------------------------------
@Override
public SearchEntity searchByKeywords(String keywords) {
    NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
    if (!StringUtils.isEmpty(keywords)) {
        nativeSearchQueryBuilder.withQuery(QueryBuilders.queryStringQuery(keywords).field("name"));
    }
    AggregatedPage<SkuInfo> skuInfos = elasticsearchTemplate
        .queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class);
    List<SkuInfo> content = skuInfos.getContent();
    return new SearchEntity(content,skuInfos.getTotalElements(),skuInfos.getTotalPages());
}

Luego comencé el proyecto y lo visité http://localhost:18085/search?keywords=小米. El resultado fue un error y se informó que no se pudo mapear. Luego encontré lo siguiente en el mensaje de error:

Probablemente signifique que hay un problema con LocalDateTime, porque la clase Date no es muy buena, así que la cambié a LocaDateTime. Miré el contenido en Kibana y encontré

Resulta que ES divide automáticamente LocalDateTime en varios archivos, pero no quiero que se divida en varios archivos y no quiero usar Date, ¿qué debo hacer? Encontré un método en Internet y resolví con éxito mi problema, que es agregar anotaciones @JsonSerialize y @JsonDeserialize , así que agregué algunas anotaciones a createTime y updateTime de SkuInfo:

/**
* 只用后两个注解就可以实现LocalDateTime不分成多个Field,但是格式不对。
* 所以还需要添加前面两个注解去指定格式与时区
**/
@Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd HH:mm:ss || yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime createTime;//创建时间

@Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd HH:mm:ss || yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime updateTime;//更新时间

Ahora importar de nuevo

No hay ningún problema con el formato ahora, probémoslo ahora

¡OK!

Estadísticas de clasificación

Cuando buscamos un producto en Xiaomi Mall, se mostrarán las siguientes categorías para ayudar a los usuarios a filtrar más productos. En el diseño de la tabla de Changbu Mall, también hay un campo llamado categoryName. El siguiente paso es realizar la clasificación y las estadísticas de los datos que buscamos.

Lo que queremos lograr es el efecto en la figura, pero en Elasticsearch en lugar de MySQL.

Modifique SearchEntity y agregue un campo categoryList:

private List<String> categoryList;  //分类集合

Modifique el método searchByKeywords en SkuEsServiceImpl y agregue el código para agrupar estadísticas:

public SearchEntity searchByKeywords(String keywords) {
    NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
    if (!StringUtils.isEmpty(keywords)) {
        nativeSearchQueryBuilder.withQuery(QueryBuilders.queryStringQuery(keywords).field("name"));
        //terms: Create a new aggregation with the given name.
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("categories_grouping")
                                                .field("categoryName"));
    }
    NativeSearchQuery nativeSearchQuery = nativeSearchQueryBuilder.build();
    AggregatedPage<SkuInfo> skuInfos = elasticsearchTemplate.queryForPage(nativeSearchQuery, SkuInfo.class);
    StringTerms stringTerms = skuInfos.getAggregations().get("categories_grouping");
    List<String> categoryList = new ArrayList<>();
    for (StringTerms.Bucket bucket : stringTerms.getBuckets()) {
        categoryList.add(bucket.getKeyAsString());
    }
    return new SearchEntity(skuInfos.getTotalElements(),skuInfos.getTotalPages(),
                            categoryList,skuInfos.getContent());
}

Ahora probemos de nuevo:

¡OK! Se ha realizado la función de agrupar estadísticas.

resumen

Este artículo escribe principalmente sobre la creación del entorno Elasticsearch y luego la importación de los datos en ES. Finalmente, se realizan las funciones de búsqueda de palabras clave y estadísticas de clasificación.

Supongo que te gusta

Origin blog.csdn.net/qq_17010193/article/details/114391744
Recomendado
Clasificación