Centro comercial Changgou (6): Búsqueda de productos

Autor: Robod
Enlace: https: //zhuanlan.zhihu.com/p/261054951
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.
 

Estadísticas de marca

Cuando busquemos teléfonos inteligentes en JD.com, se enumerarán las marcas relevantes para que los usuarios elijan

Lo que queremos lograr es esta función, que es clasificar y contar las marcas en los resultados de búsqueda.

Esta función es la misma que las estadísticas de clasificación mencionadas en el artículo anterior, así que agregue algunas líneas de código para hacerlo.

Pero cuando busqué "teléfono inteligente", solo aparecieron dos marcas, que obviamente no coincidían con la situación real. La razón es simple, "ik_smart" no divide "smartphone" en "inteligente" y "móvil", así que simplemente cambie el modo de segmentación de palabras a "ik_max_word". Cambie el campo de nombre en SkuInfo de "ik_smart" a "ik_max_word", luego vuelva a importar los datos y pruébelos nuevamente:

Así es.

Estadísticas de especificación

Cuando busquemos en JD.com, la información de la especificación aparecerá para que los usuarios la elijan. También hay información de especificación en nuestro ES, pero toda esta información son cadenas json. Lo que tenemos que hacer es convertir estas cadenas json en una colección de mapas para lograr la misma función que Jingdong.

Agregue el siguiente código al método searchByKeywords de SkuEsServiceImpl:

Como se puede ver en el código, primero agregue las condiciones de búsqueda, luego saque la colección de especificaciones de los resultados de la búsqueda, recorra y almacénela en specMap. Debido a que el resultado de la búsqueda es una cadena json, cada vez que la cadena json se convierte en un conjunto de mapas, luego se recorre el mapa y los datos se extraen del mapa y se colocan en el specSet a su vez. Si no hay un specSet correspondiente en el specMap, será directamente nuevo y se almacenará en él, y si lo es, se sacará directamente del specMap. Finalmente, coloque el specMap en SearchEntity y devuélvalo al front-end.

Examen de condición

Clasificación y filtrado de marcas

Cuando usamos la marca o la categoría como una búsqueda condicional, no tenemos que ocuparnos de las estadísticas de la marca y la categoría. Antes solo había un parámetro de palabras clave, lo escribía directamente en la barra de direcciones, pero ahora que hay más parámetros, es mejor encapsularlo en SearchEntity y luego escribir los parámetros en el cuerpo de la solicitud.

private String keywords;    //前端传过来的关键词信息

private String brand;   //前端传过来的品牌信息

private String category;    //前端传过来的分类信息

Luego agregue código al método searchByKeywords de SkuEsServiceImpl . Mi searchByKeywords actual se ha escrito muy inflado. Permítanme ignorar este problema y finalmente optimizar el código.

Si el parámetro de categoría o marca no está vacío, se utilizará como filtro condicional y no se realizarán estadísticas, de lo contrario se realizarán estadísticas. Cabe señalar que aquí se usa el método withFilter (). De hecho, withQuery () también es posible, pero con withQuery (), la búsqueda resaltada no funcionará, así que úsela withFilter().

Se puede ver que ahora que se especifica la información de la marca pero no se especifica la información de la clasificación, la marca no se contará y se seguirá contando la clasificación, lo que ha logrado nuestro efecto esperado.

Filtro de especificación

Igual que el anterior, cuando pasamos las especificaciones como parámetro al backend, tampoco realizaremos estadísticas de especificación. Para lograr esta función, primero debe agregar un campo en SearchEntity para recibir los parámetros de especificación.

private List<String> searchSpec;  //前端传过来的规格信息

Luego agregue código en el método searchByKeywords de SkuEsServiceImpl :

……
Map<String,String> searchSpec = searchEntity.getSearchSpec();
if (searchSpec != null && searchSpec.size() > 0) {
    for (String key:searchSpec.keySet()){
        //格式:specMap.规格名字.keyword   keyword表示不分词
        boolQueryBuilder.filter(QueryBuilders.termQuery("specMap."+key+".keyword",searchSpec.get(key)));
    }
}
……

Obtenga el searchSpec pasado desde el front-end, luego recorra el contenido de la especificación y luego use boolQueryBuilder.filter () para filtrar.

Se puede ver en la figura que cuando especificamos el color como azul y la versión como "6GB + 64GB", los resultados son todos los resultados que hemos filtrado.

Filtro de precios

Cuando buscamos productos en JD.com, podemos especificar un rango de precios. La misma función se realizará ahora. En primer lugar, todavía tenemos que agregar un campo en SearchEntity para recibir parámetros de intervalo.

private String price;       //前端穿过来的价格区间字符串 300-500元   3000元以上

Luego agregue el código implementado:

……
if (!StringUtils.isEmpty(searchEntity.getPrice())) {
    String[] price = searchEntity.getPrice().replace("元","")
            .replace("以上","").split("-");
    boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(Integer.parseInt(price[0])));
    if (price.length>1){
        boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").lt(Integer.parseInt(price[1])));
    }
}
……

De esta manera, se puede lograr el filtrado de precios.

Función de paginación

La función de paginación es relativamente simple, recibe el parámetro pageNum pasado desde el front-end y luego llama al método nativeSearchQueryBuilder.withPageable () para lograr la paginación.

int pageNum = 1;
if (!StringUtils.isEmpty(searchEntity.getPageNum())) {
    pageNum = Integer.parseInt(searchEntity.getPageNum());
}
nativeSearchQueryBuilder.withPageable(PageRequest.of(pageNum-1,SEARCH_PAGE_SIZE));

El código es muy simple. Asigne un valor predeterminado a pageNum. Si la interfaz no pasa un parámetro, se mostrará la primera página y el número de entradas por página es 10. Definí una constante SEARCH_PAGE_SIZE para representar 10.

Se puede ver que se busca un total de 22 piezas de datos, y 10 piezas por página son 3 páginas, y la paginación es correcta.

Clasificar

La función de clasificación es relativamente simple, es decir, pasar el campo a clasificar y el método de clasificación al método correspondiente.

String sortField = searchEntity.getSortField();
String sortRule = searchEntity.getSortRule();
if (!StringUtils.isEmpty(sortField) && !StringUtils.isEmpty(sortRule)) {
    nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(sortField).order(SortOrder.valueOf(sortRule)));
}

sortField es el campo que se va a ordenar y sortRule es la forma de ordenar. Si estos dos parámetros no están vacíos, simplemente llame al método withSort para agregar condiciones de búsqueda. Eso es.

Destacar

Cuando generalmente buscamos en Jingdong o Taobao, las palabras clave se resaltarán.

De hecho, es muy sencillo de implementar, siempre que el término de búsqueda esté envuelto en html y el estilo se cambie a rojo.

Primero, debe configurar el resaltado , es decir, especificar el dominio de resaltado:

HighlightBuilder.Field field = new HighlightBuilder.Field("name");  //指定域
field.preTags("<em style=\"color:red;\">"); //指定前缀
field.postTags("</em>");    //指定后缀
nativeSearchQueryBuilder.withHighlightFields(field);

En este código, especificamos el dominio a resaltar y le agregamos el código html resaltado.

Después de especificar el campo que se resaltará , debe realizar una búsqueda resaltada y reemplazar los datos no resaltados con los datos resaltados . El queryForPage(SearchQuery query, Class<T> clazz)método se usaba para buscar antes , ahora se cambia a queryForPage(SearchQuery query, Class<T> clazz, SearchResultMapper mapper)método.

AggregatedPage<SkuInfo> skuInfos = elasticsearchTemplate
        .queryForPage(nativeSearchQuery, SkuInfo.class, new SearchResultMapper() {
            @Override
            public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
                List<T> list = new ArrayList<>();
                for (SearchHit hit : response.getHits()) {  //遍历所有数据
                    SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);//非高亮数据
                    HighlightField highlightField = hit.getHighlightFields().get("name");      //高亮数据
                    //将非高亮数据替换成高亮数据
                    if (highlightField != null && highlightField.getFragments() != null) {
                        Text[] fragments = highlightField.getFragments();
                        StringBuilder builder = new StringBuilder();
                        for (Text fragment : fragments) {
                            builder.append(fragment.toString());
                        }
                        skuInfo.setName(builder.toString());
                        list.add((T) skuInfo);
                    }
                }
                return new AggregatedPageImpl<T>(list, pageable, 
                  response.getHits().getTotalHits(),response.getAggregations());
            }
        });

Cuando lo ejecuté, descubrí que la búsqueda de filtro skuInfos.getAggregations()siempre informaba una excepción de puntero nulo. Pensé que era porque la búsqueda de resaltado y la búsqueda de filtro no se podían usar juntas. Finalmente, descubrí que había response.getAggregations()perdido la escritura aquí , y faltaba un parámetro. Así que presta atención aquí.

Optimización de código

Cuando vi el video antes, descubrí que no escribía muy bien. De hecho, solo es necesario verificarlo una vez, pero he preguntado muchas veces. Sin embargo, al final del video, este problema también se mencionó y se mejoró el código. Así que no seguí las instrucciones del video antes, sino que me apreté en un método y pensé en optimizar al final. El último método estaba escrito en más de cien líneas, que era simplemente demasiado hinchado y estaba en el " Manual de desarrollo de Java de Alibaba "También se menciona que un método no debe exceder las 80 líneas. Ahora que se ha implementado la función de búsqueda, puede optimizarla. A continuación se muestra el código que optimicé

@Override
public SearchEntity searchByKeywords(SearchEntity searchEntity) {
    if (searchEntity != null && !StringUtils.isEmpty(searchEntity.getKeywords())) {
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();

        //配置高亮
        HighlightBuilder.Field field = new HighlightBuilder.Field("name");
        field.preTags("<em style=\"color:red;\">");
        field.postTags("</em>");
        nativeSearchQueryBuilder.withHighlightFields(field);

        //条件筛选或者分组统计
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        if (!StringUtils.isEmpty(searchEntity.getCategory())) {     //分类过滤
            boolQueryBuilder.filter(QueryBuilders.termQuery("categoryName", searchEntity.getCategory()));
        } else {
            nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms
                    ("categories_grouping").field("categoryName").size(10000));
        }
        if (!StringUtils.isEmpty(searchEntity.getBrand())) {    //品牌过滤
            boolQueryBuilder.filter(QueryBuilders.termQuery("brandName", searchEntity.getBrand()));
        } else {
            nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms
                    ("brands_grouping").field("brandName").size(10000));
        }
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms
                ("spec_grouping").field("spec.keyword").size(10000));
        if (!StringUtils.isEmpty(searchEntity.getPrice())) {    //价格过滤
            String[] price = searchEntity.getPrice().replace("元", "")
                    .replace("以上", "").split("-");
            boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(Integer.parseInt(price[0])));
            if (price.length > 1) {
                boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").lt(Integer.parseInt(price[1])));
            }
        }
        Map<String, String> searchSpec = searchEntity.getSearchSpec();
        if (searchSpec != null && searchSpec.size() > 0) {
            for (String key : searchSpec.keySet()) {
                boolQueryBuilder.filter(QueryBuilders.termQuery("specMap." + key + ".keyword", searchSpec.get(key)));
            }
        }
        //分页
        int pageNum = (!StringUtils.isEmpty(searchEntity.getPageNum()))
                ? (Integer.parseInt(searchEntity.getPageNum())) : 1;
        nativeSearchQueryBuilder.withPageable(PageRequest.of(pageNum - 1, SEARCH_PAGE_SIZE));
        //排序
        String sortField = searchEntity.getSortField();
        String sortRule = searchEntity.getSortRule();
        if (!StringUtils.isEmpty(sortField) && !StringUtils.isEmpty(sortRule)) {
            nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(sortField).order(SortOrder.valueOf(sortRule)));
        }

        nativeSearchQueryBuilder
                .withQuery(QueryBuilders.queryStringQuery(searchEntity.getKeywords()).field("name"))
                .withFilter(boolQueryBuilder);  //这两行顺序不能颠倒

        AggregatedPage<SkuInfo> skuInfos = elasticsearchTemplate
                .queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class, new SearchResultMapperImpl());

        Aggregations aggregations = skuInfos.getAggregations();
        List<String> categoryList = buildGroupList(aggregations.get("categories_grouping"));
        List<String> brandList = buildGroupList(aggregations.get("brands_grouping"));
        Map<String, Set<String>> specMap = specGroup(aggregations.get("spec_grouping"));

        searchEntity.setTotal(skuInfos.getTotalElements());
        searchEntity.setTotalPages(skuInfos.getTotalPages());
        searchEntity.setCategoryList(categoryList);
        searchEntity.setBrandList(brandList);
        searchEntity.setSpecMap(specMap);
        searchEntity.setRows(skuInfos.getContent());
        return searchEntity;
    }
    return null;
}

//将过滤搜索出来的StringTerms转换成List集合
private List<String> buildGroupList(StringTerms stringTerms) {
    List<String> list = new ArrayList<>();
    if (stringTerms != null) {
        for (StringTerms.Bucket bucket : stringTerms.getBuckets()) {
            list.add(bucket.getKeyAsString());
        }
    }
    return list;
}

//规格统计
private Map<String, Set<String>> specGroup(StringTerms specTerms) {
    Map<String, Set<String>> specMap = new HashMap<>(16);
    for (StringTerms.Bucket bucket : specTerms.getBuckets()) {
        Map<String, String> map = JSON.parseObject(bucket.getKeyAsString(), Map.class);
        for (String key : map.keySet()) {
            Set<String> specSet;
            if (!specMap.containsKey(key)) {
                specSet = new HashSet<>();
                specMap.put(key, specSet);
            } else {
                specSet = specMap.get(key);
            }
            specSet.add(map.get(key));
        }
    }
    return specMap;
}

El parámetro SearchResultMapper en queryForPage fue recogido por mí por separado:

public class SearchResultMapperImpl implements SearchResultMapper {
    @Override
    public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
        List<T> list = new ArrayList<>();
        for (SearchHit hit : response.getHits()) {  //遍历所有数据
            SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);//非高亮数据
            HighlightField highlightField = hit.getHighlightFields().get("name");      //高亮数据
            //将非高亮数据替换成高亮数据
            if (highlightField != null && highlightField.getFragments() != null) {
                Text[] fragments = highlightField.getFragments();
                StringBuilder builder = new StringBuilder();
                for (Text fragment : fragments) {
                    builder.append(fragment.toString());
                }
                skuInfo.setName(builder.toString());
                list.add((T) skuInfo);
            }
        }
        return new AggregatedPageImpl<T>(list, pageable, 
                    response.getHits().getTotalHits(),response.getAggregations());
    }
}

Aunque el código todavía es bastante, la lógica es mucho más clara. Aunque se puede optimizar, no creo que sea necesario. Ahora pruébalo:

Bueno, todas las funciones se ejecutan normalmente.

resumen

El artículo anterior simplemente configuró el entorno, y solo funcionó una búsqueda de palabras clave y estadísticas clasificadas. Entonces este artículo es un complemento del artículo anterior. Se realizan las funciones de estadísticas de marca, estadísticas de especificación, filtrado de condiciones, paginación, clasificación y resaltado. En este punto, la función de búsqueda de productos está completa.

Supongo que te gusta

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