8_搭建商城搜索微服务[通俗易懂]

8_搭建商城搜索微服务[通俗易懂]搜索服务的父项目:supergo_search1、建Module:supergo_search2、删除src搜索服务的提供者:supergo_search_service90031、建Module:supergo_search_service90032、改pom<?xmlversion=”1.0″encoding=”UTF-8″?><projectxmlns=”http://maven.apache.org/POM/4.0.0″xmlns:xsi=

大家好,又见面了,我是你们的朋友全栈君。

搜索服务的父项目:supergo_search

1、建Module:supergo_search

2、删除src


搜索服务的提供者:supergo_search_service9003

1、建Module:supergo_search_service9003

2、改pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>supergo_search</artifactId>
        <groupId>com.supergo</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>supergo_search_service9003</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <!--spring-es-->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-elasticsearch</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>com.supergo</groupId>
            <artifactId>supergo-mapper</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.supergo</groupId>
            <artifactId>supergo-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.14.8</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

</project>

3、启动类

package com.supergo.search;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import tk.mybatis.spring.annotation.MapperScan;

@SpringBootApplication
@EnableEurekaClient
@MapperScan("com.supergo.search.mapper")
public class SearchApplication9003 { 
   
    public static void main(String[] args) { 
   
        SpringApplication.run(SearchApplication9003.class, args);
    }
}

4、建 yml

本次使用的ElasticSearch版本为5.6.8,需要在yml中配置连接

# 端口
server:
  port: 9003

# 名字
spring:
  application:
    name: supergo-manager # 代表的就是我以什么样的名字入驻进的注册中心
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
    driver-class-name: com.mysql.jdbc.Driver # mysql驱动类
    url: jdbc:mysql://127.0.0.1:3306/supergo?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
    username: root
    password: 123456

    # druid 专属配置
    druid:
      initial-size: 5
      min-idle: 5
      max-active: 20
      max-wait: 1000
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      validation-query: select 'x'
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      pool-prepared-statements: true
      max-open-prepared-statements: 50
      max-pool-prepared-statement-per-connection-size: 20

      # stat是统计,wall是SQL防火墙,防SQL注入的,log4j是用来输出统计数据的
      # filters: stat,wall,log4j,config
      ##是否启用StatFilter默认值true: 排除一些不必要的url
      web-stat-filter:
        enabled: true
        exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
      #是否启用StatViewServlet默认值true
      stat-view-servlet:
        allow: 127.0.0.1 #IP 白名单
        url-pattern: /druid/* #监控地址,默认 /druid/*
        login-username: admin
        login-password: admin #
  # deny:IP #黑名单

  # 最大请求文件的大小
  servlet:
    multipart:
      max-request-size: 5MB
 # ElasticSearch连接配置
  data:
    elasticsearch:
      cluster-name: cluster_es
      cluster-nodes: 192.168.77.138:9300

eureka:
  client:
    register-with-eureka: true # 表示将自己注册到 eureka server ,默认为 true
    fetch-registry: true # 表示是否从eureka server 抓取已有的注册信息,默认为true。单节点为所谓,集群必须为 true,才能配合ribbon使用负载均衡
    service-url:
      # 单机版:只用注册进一个服务中心【defaultZone: http://127.0.0.1:7001/eureka/】
      defaultZone: http://eureka7001.com:7001/eureka/
      # 集群版:需要同时注册进每个注册中心
  # defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com/eureka/
  # 显示的服务主机名称
  instance:
    prefer-ip-address: true # 访问路径显示 ip【统一:方便调试】
    ip-address: 127.0.0.1
    instance-id: ${ 
   eureka.instance.ip-address}.${ 
   server.port}
    lease-renewal-interval-in-seconds: 3
    lease-expiration-duration-in-seconds: 10
    #网关设置了根路径,默认监控路径发生了变化
    health-check-url-path: /api/actuator/health

#actuator服务监控与管理
management:
  endpoint:
    #开启端点
    shutdown:
      enabled: true
    health:
      show-details: always
  # 加载所有的端点
  endpoints:
    web:
      exposure:
        include: "*"

5、实体类

接口对应的实体类:

package com.supergo.search.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

/** * @Author: xj0927 * @Description: * @Date Created in 2021-01-04 12:26 * @Modified By: */
@Document(indexName = "supergo", type = "goods")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class GoodsEntity { 
   
    @Id
    @Field(type = FieldType.Long, store = true)
    private Long id;
    @Field(type = FieldType.text, store = true, analyzer = "ik_max_word")
    private String goods_name;
    @Field(type = FieldType.keyword, store = true)
    private String seller_id;
    @Field(type = FieldType.keyword, store = true)
    private String nick_name;
    @Field(type = FieldType.Long, store = true)
    private long brand_id;
    @Field(type = FieldType.keyword, store = true)
    private String brand_name;
    @Field(type = FieldType.Long, store = true)
    private long category1_id;
    @Field(type = FieldType.keyword, store = true)
    private String cname1;
    @Field(type = FieldType.Long, store = true)
    private long category2_id;
    @Field(type = FieldType.keyword, store = true)
    private String cname2;
    @Field(type = FieldType.Long, store = true)
    private long category3_id;
    @Field(type = FieldType.keyword, store = true)
    private String cname3;
    @Field(type = FieldType.keyword, store = true, index = false)
    private String small_pic;
    @Field(type = FieldType.Float, store = true)
    private double price;
}

搜索结果实体类

package com.supergo.search.entity;

import java.io.Serializable;
import java.util.List;

/** * @Author: xj0927 * @Description: * @Date Created in 2021-01-04 12:28 * @Modified By: */
public class SearchResult implements Serializable { 
   
    private List<GoodsEntity> goodsList;
    private List<?> aggs;

    public List<GoodsEntity> getGoodsList() { 
   
        return goodsList;
    }

    public void setGoodsList(List<GoodsEntity> goodsList) { 
   
        this.goodsList = goodsList;
    }

    public List<?> getAggs() { 
   
        return aggs;
    }

    public void setAggs(List<?> aggs) { 
   
        this.aggs = aggs;
    }
}


6、接口

mysql操作相关的接口:

package com.supergo.search.mapper;

import com.supergo.search.entity.GoodsEntity;
import org.apache.ibatis.annotations.Select;

import java.util.List;

/** * @Author: xj0927 * @Description: * @Date Created in 2021-01-04 12:27 * @Modified By: */
public interface GoodsMapper { 
   
    @Select("SELECT\n" +
            "\ta.id,\n" +
            "\ta.goods_name,\n" +
            "\ta.seller_id,\n" +
            "\tb.nick_name,\n" +
            "\ta.brand_id,\n" +
            "\tc.name brand_name,\n" +
            "\ta.category1_id,\n" +
            "\td.NAME cname1,\n" +
            "\ta.category2_id,\n" +
            "\te.NAME cname2,\n" +
            "\ta.category3_id,\n" +
            "\tf.NAME cname3,\n" +
            "\ta.small_pic,\n" +
            "\ta.price\n" +
            "FROM\n" +
            "\ttb_goods a\n" +
            "LEFT JOIN tb_seller b ON a.seller_id = b.seller_id\n" +
            "LEFT JOIN tb_brand c ON a.brand_id = c.id\n" +
            "LEFT JOIN tb_item_cat d ON a.category1_id = d.id\n" +
            "LEFT JOIN tb_item_cat e ON a.category2_id = e.id\n" +
            "LEFT JOIN tb_item_cat f ON a.category3_id = f.id\n" +
            "WHERE\n" +
            "\ta.is_delete = 0\n" +
            "AND a.is_marketable = 1")
    List<GoodsEntity> getGoodsList();
}

elasticsearch操作相关的接口:

package com.supergo.search.repository;

import com.supergo.search.entity.GoodsEntity;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

/** * @Author: xj0927 * @Description: * @Date Created in 2021-01-04 12:31 * @Modified By: */
public interface GoodsRepository extends ElasticsearchRepository<GoodsEntity, Long> { 
   

}


7、导入索引库

service

从mysql数据库查询数据,然后再导入es

@Service
public class SearchService { 
   
    
    @Autowired
    private GoodsMapper goodsMapper;
    
    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    /*** * 查询数据库将所有商品数据导入到索引库 */
    public HttpResult importGoods() { 
   
        //查询数据库
        List<GoodsEntity> goodsList = goodsMapper.getGoodsList();
        System.out.println(goodsList);
        //把商品数据写入索引库
        System.out.println("数据导入开始。。。");
        goodsList.forEach(g -> goodsRepository.save(g));
        System.out.println("数据导入完成!");
        return HttpResult.ok();
    }
}

controller

@RestController
public class SearchController { 
   

    @Autowired
    private SearchService searchService;

    @RequestMapping("/goods/import")
    public HttpResult goodsImport() { 
   
        new Thread(() -> searchService.importGoods()).start();
        return HttpResult.ok();
    }
}

测试

浏览器输入:

http://localhost:9003/goods/import

查看索引:

在这里插入图片描述

数据成功导入ElasticSearch


8、搜索索引库

数据导入Es后,下面开始搜索服务的创建

先看京东的搜索方式:

在输入栏搜索“苹果”,会出现按不同方式的聚合结果

在这里插入图片描述

然后在分类栏,选择”苹果”,

在这里插入图片描述

对地址url进行转义解析:

在这里插入图片描述

本次也是使用类型方案:关键词使用查询,限制条件使用过滤

service

@Service
public class SearchService { 
   
    
    @Autowired
    private GoodsMapper goodsMapper;
    
    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    /** * @Description: 商品查询 * 原则:第一个参数使用查询方式,其他参数使用过滤 [提高效率] * @Author: xj0927 * @Date Created in 2021/1/4 19:55 */
    public SearchResult search(String keyword, Map<String, String> filters, int page, int size) { 
   

        //设置查询条件
        BoolQueryBuilder builder = QueryBuilders.boolQuery()
                .should(QueryBuilders.multiMatchQuery(keyword, "goods_name"));

        //判断是否有过滤条件
        if (filters != null && !filters.isEmpty()) { 
   
            filters.keySet().forEach(key -> { 
   
                builder.filter(QueryBuilders.termQuery(key, filters.get(key)));
            });
        }

        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(builder) //查询条件
                .withPageable(PageRequest.of(page, size))//设置分页信息
                .addAggregation(AggregationBuilders.terms("category_aggs").field("cname3"))//聚合条件1
                .addAggregation(AggregationBuilders.terms("brand_aggs").field("brand_name"))//聚合条件2
                .withHighlightFields(new HighlightBuilder.Field("name").preTags("<span style='color:red>").postTags("</span>"))//设置高亮显示
                .build(); //创建q

        //执行查询
        AggregatedPage<GoodsEntity> sResult = elasticsearchTemplate.queryForPage(query, GoodsEntity.class, new SearchResultMapper() { 
   
            @Override
            public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) { 
   
                List<GoodsEntity> goodsList = new ArrayList<>();
                SearchHits searchHits = searchResponse.getHits();
                searchHits.forEach(hits -> { 
   
                    //原文档部分
                    GoodsEntity goodsEntity = new GoodsEntity();
                    goodsEntity.setId((Long) hits.getSource().get("id"));
                    goodsEntity.setGoods_name((String) hits.getSource().get("goods_name"));
                    goodsEntity.setSeller_id((String) hits.getSource().get("seller_id"));
                    goodsEntity.setNick_name((String) hits.getSource().get("nick_name"));
                    goodsEntity.setBrand_id((Integer) hits.getSource().get("brand_id"));
                    goodsEntity.setBrand_name((String) hits.getSource().get("brand_name"));
                    goodsEntity.setCategory1_id((Integer) hits.getSource().get("category1_id"));
                    goodsEntity.setCname1((String) hits.getSource().get("cname1"));
                    goodsEntity.setCategory2_id((Integer) hits.getSource().get("category2_id"));
                    goodsEntity.setCname2((String) hits.getSource().get("cname2"));
                    goodsEntity.setCategory3_id((Integer) hits.getSource().get("category3_id"));
                    goodsEntity.setCname3((String) hits.getSource().get("cname3"));
                    goodsEntity.setSmall_pic((String) hits.getSource().get("small_pic"));
                    goodsEntity.setPrice((Double) hits.getSource().get("price"));
                    //取高亮部分
                    HighlightField highlightField = hits.getHighlightFields().get("goods_name");
                    if (highlightField != null) { 
   
                        String hl = highlightField.getFragments()[0].string();
                        goodsEntity.setGoods_name(hl);
                    }
                    goodsList.add(goodsEntity);
                });
                AggregatedPage<T> aggregatedPage = new AggregatedPageImpl<T>((List<T>) goodsList, pageable, searchHits.totalHits, searchResponse.getAggregations());
                return aggregatedPage;
            }
        });
        //取查询结果
        List<GoodsEntity> content = sResult.getContent();
        SearchResult searchResult = new SearchResult();
        searchResult.setGoodsList(content);

        //取分类聚合结果
        Terms termsCat = (Terms) sResult.getAggregation("category_aggs");
        List<String> catAggsList = termsCat.getBuckets().stream().map(e -> e.getKeyAsString()).collect(Collectors.toList());
        Map catAggsMap = new HashMap();
        catAggsMap.put("name", "分类");
        catAggsMap.put("field", "cname3");
        catAggsMap.put("content", catAggsList);

        //取品牌聚合结果
        Terms termsBrand = (Terms) sResult.getAggregation("brand_aggs");
        List<String> brandAggsList = termsBrand.getBuckets().stream().map(e -> e.getKeyAsString()).collect(Collectors.toList());
        Map brandAggsMap = new HashMap();
        brandAggsMap.put("name", "品牌");
        brandAggsMap.put("field", "brand_name");
        brandAggsMap.put("content", brandAggsList);
        List aggsList = new ArrayList();
        aggsList.add(brandAggsMap);
        aggsList.add(catAggsMap);

        //设置过滤条件
        searchResult.setAggs(aggsList);
        return searchResult;
    }
}

controller

![5](images/5.png)RestController
public class SearchController { 
   

    @Autowired
    private SearchService searchService;

    @RequestMapping("/goods/search")
    public SearchResult search(@RequestParam(required = true) String keyword,
                               @RequestParam(required = false, value = "ev") String filter,
                               @RequestParam(defaultValue = "1") int page,
                               @RequestParam(defaultValue = "5") int size) { 
   
        //filter的参数形式:ev=brand_name-小米|category3_id-255、
        Map<String, String> filterMap = null;
        try { 
   
            if (StringUtils.isNotBlank(filter)) { 
   
                String[] filters = filter.split("\\|");
                filterMap = Stream.of(filters).collect(Collectors.toMap(e -> e.split("-")[0].trim(), e -> e.split("-")[1].trim()));
            }
        } catch (Exception e) { 
   
            e.printStackTrace();
        }
        System.out.println(filter);
        System.out.println(filterMap);
        SearchResult searchResult = searchService.search(keyword, filterMap, page, size);
        return searchResult;
    }
}


测试

浏览器输入:

http://localhost:9003/goods/search?keyword=手机&ev=brand_name-联想

在这里插入图片描述


版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/152834.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号