前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >8_搭建商城搜索微服务[通俗易懂]

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

作者头像
全栈程序员站长
发布2022-09-12 21:04:29
6110
发布2022-09-12 21:04:29
举报

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

搜索服务的父项目:supergo_search

1、建Module:supergo_search
2、删除src

搜索服务的提供者:supergo_search_service9003

1、建Module:supergo_search_service9003

2、改pom

代码语言:javascript
复制
<?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、启动类

代码语言:javascript
复制
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中配置连接

代码语言:javascript
复制
# 端口
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、实体类

接口对应的实体类:

代码语言:javascript
复制
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;
}

搜索结果实体类

代码语言:javascript
复制
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操作相关的接口:

代码语言:javascript
复制
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操作相关的接口:

代码语言:javascript
复制
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

代码语言:javascript
复制
@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
代码语言:javascript
复制
@RestController
public class SearchController { 
   

    @Autowired
    private SearchService searchService;

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

测试

浏览器输入:

代码语言:javascript
复制
http://localhost:9003/goods/import

查看索引:

在这里插入图片描述
在这里插入图片描述

数据成功导入ElasticSearch


8、搜索索引库

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

先看京东的搜索方式:

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

在这里插入图片描述
在这里插入图片描述

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

在这里插入图片描述
在这里插入图片描述

对地址url进行转义解析:

在这里插入图片描述
在这里插入图片描述

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

service
代码语言:javascript
复制
@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
代码语言:javascript
复制
![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;
    }
}

测试

浏览器输入:

代码语言:javascript
复制
http://localhost:9003/goods/search?keyword=手机&ev=brand_name-联想
在这里插入图片描述
在这里插入图片描述

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 搜索服务的父项目:supergo_search
    • 1、建Module:supergo_search
      • 2、删除src
      • 搜索服务的提供者:supergo_search_service9003
        • 1、建Module:supergo_search_service9003
          • 2、改pom
            • 3、启动类
              • 4、建 yml
                • 5、实体类
                  • 6、接口
                    • 7、导入索引库
                      • service
                      • controller
                      • 测试
                    • 8、搜索索引库
                      • service
                      • controller
                      • 测试
                  相关产品与服务
                  云数据库 SQL Server
                  腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档