8_搭建商城搜索微服务

搜索服务的父项目: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-联想

在这里插入图片描述


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值