美文网首页
实战Elasticsearch、springboot、dubbo

实战Elasticsearch、springboot、dubbo

作者: 金州留白 | 来源:发表于2020-03-02 04:58 被阅读0次

    ​一、版本说明

    • Elasticsearch: 7.3.2

    • Springboot: 2.1.7

    • Dubbo: 2.7.1

    • Vue: 3.12.0

    • Nuxt: 2.0.0

    • bootstrapVue: 2.0.0

    二、整合es、springboot、dubbo

    2.1、pom文件

    2.1.1、service的pom.xml

    <?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">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.1.7.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.yuyuda</groupId>
        <artifactId>search-service</artifactId>
        <version>1.0.0</version>
        <name>search-service</name>
        <description>搜索服务</description>
    ​
        <properties>
            <java.version>1.8</java.version>
            <dubbo.version>2.7.1</dubbo.version>
            <es.version>7.3.2</es.version>
        </properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-spring-boot-starter</artifactId>
                <version>${dubbo.version}</version>
            </dependency>
    ​
            <!--es-->
            <dependency>
                <groupId>org.elasticsearch.client</groupId>
                <artifactId>elasticsearch-rest-high-level-client</artifactId>
                <version>${es.version}</version>
            </dependency>
            <dependency>
                <groupId>org.elasticsearch.client</groupId>
                <artifactId>elasticsearch-rest-client</artifactId>
                <version>${es.version}</version>
            </dependency>
            <dependency>
                <groupId>org.elasticsearch</groupId>
                <artifactId>elasticsearch</artifactId>
                <version>${es.version}</version>
            </dependency>
    ​
             <!--dubbo-->
             <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo</artifactId>
                <version>${dubbo.version}</version>
            </dependency>
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-dependencies-zookeeper</artifactId>
                <version>${dubbo.version}</version>
                <type>pom</type>
            </dependency>
    ​
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    ​
        <distributionManagement>
            <repository>
                <id>nexus</id>
                <name>Nexus</name>
                <url>http://192.168.3.25:8081/repository/maven-releases/</url>
            </repository>
            <snapshotRepository>
                <id>nexus</id>
                <name>Nexus</name>
                <url>http://192.168.3.25:8081/repository/maven-snapshots/</url>
            </snapshotRepository>
        </distributionManagement>
    ​
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    </project>
    

    2.1.2、controller的pom.xml

    <?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">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.1.7.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.yuyuda</groupId>
        <artifactId>search-edge</artifactId>
        <version>1.0.0-release</version>
        <name>search-edge</name>
        <description>搜索相关边缘服务</description>
    ​
        <properties>
            <java.version>1.8</java.version>
            <dubbo.version>2.7.1</dubbo.version>
            <swagger.version>2.9.2</swagger.version>
        </properties>
    ​
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-spring-boot-starter</artifactId>
                <version>${dubbo.version}</version>
            </dependency>
    ​
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo</artifactId>
                <version>${dubbo.version}</version>
            </dependency>
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-dependencies-zookeeper</artifactId>
                <version>${dubbo.version}</version>
                <type>pom</type>
            </dependency>
    ​
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-swagger2</artifactId>
                <version>${swagger.version}</version>
            </dependency>
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-swagger-ui</artifactId>
                <version>${swagger.version}</version>
            </dependency>
        </dependencies>
    ​
        <distributionManagement>
            <repository>
                <id>nexus</id>
                <name>Nexus</name>
                <url>http://192.168.3.25:8081/repository/maven-releases/</url>
            </repository>
            <snapshotRepository>
                <id>nexus</id>
                <name>Nexus</name>
                <url>http://192.168.3.25:8081/repository/maven-snapshots/</url>
            </snapshotRepository>
        </distributionManagement>
    ​
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    </project>
    

    2.2、application.yml文件

    2.2.1、service的application.yml文件

    dubbo:
      application:
        name: search-service
      registry:
        address: zookeeper://127.0.0.1:2181
      metadata-report:
        address: zookeeper://127.0.0.1:2181
      protocol:
        name: dubbo
        port: 20884
    
    spring:
      data:
        elasticsearch:
          cluster-nodes: 192.168.3.15:9200
    server:
      port: 10008
    ​
    #分页每页显示多少条
    pageSize: 3
    

    2.2.2、controller的application.yml文件

    dubbo:
      application:
        name: search-edge
      registry:
        address: zookeeper://127.0.0.1:2181
      metadata-report:
        address: zookeeper://127.0.0.1:2181
      protocol:
        name: dubbo
        port: 20884
    ​
    server:
      port: 10009
    spring:
      profiles:
        active: dev
    

    2.3、代码整合springboot与es

    EsConfig.java

    package com.yuyuda.search.config;
    ​
    import org.apache.http.HttpHost;
    import org.elasticsearch.client.RestClient;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    /**
     * 配置es交由spring管理
     *
     * @author LiuZhixian lzx@yuyuda.com
     * @version 1.0
     * @date 2019/9/27 10:23
     */
    @Configuration
    public class EsConfig {
        @Value("${spring.data.elasticsearch.cluster-nodes}")
        private String clusterNodes;
        
        @Bean
        public RestHighLevelClient client() {
            String[] node = clusterNodes.split(":");
            String host = node[0];
            Integer port = Integer.valueOf(node[1]);
            RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(new HttpHost(host, port, "http")));
            return client;
        }
    }
    

    三、ES RESTful常用api介绍

    3.1、Indices Api创建索引

    3.1.1、索引设置

    curl -X PUT "localhost:9200/index-product?pretty" -H 'Content-Type: application/json' -d'
    {
        "settings" : {
            "index" : {
                "number_of_shards" : 3,  //分片数
                "number_of_replicas" : 2 //副本数
            }
        }
    }
    '
    

    3.1.2、索引mappings

    //索引类型映射,即相当于MySQL的建表
    curl -X PUT "localhost:9200/index-product?pretty" -H 'Content-Type: application/json' -d'
    {
        "settings" : {
            "number_of_shards" : 1
        },
        "mappings" : {
            "type1" : {
                "properties" : {
                    "field1" : { "type" : "text" }
                }
            }
        }
    }
    '
    

    注:其他api去文档上查找,这里相当于介绍流程。查找链接
    https://www.elastic.co/guide/en/elasticsearch/reference/6.0/indices.html

    image

    3.2、插入数据

    curl -X PUT "localhost:9200/index-product/_doc/1?pretty" -H 'Content-Type: application/json' -d'
    {
        "user" : "kimchy",
        "post_date" : "2009-11-15T14:12:12",
        "message" : "trying out Elasticsearch"
    }
    '
    ​
    https://www.elastic.co/guide/en/elasticsearch/reference/6.0/docs.html
    

    3.3、查询数据

    3.3.1、简单查询

    curl -X GET "localhost:9200/twitter/_doc/1?pretty"

    3.3.2、查询所有

    curl -X GET "localhost:9200/index-product/_search?pretty" -H 'Content-Type: application/json' -d'
    {
        "query": {
            "match_all": {}
        }
    }
    '
    

    3.3.3、match单个字段查询

    curl -X GET "localhost:9200/index-product/_search?pretty" -H 'Content-Type: application/json' -d'
    {
        "query": {
            "match" : {
                "message" : {    //查询的字段
                    "query" : "this is a test",  //需要输入的字符串
                    "operator" : "and" //字符串被分词this、is、 a、test都必须满足,如果是or值,是或者的意思
                }
            }
        }
    }
    '
    

    3.3.4、mutliMatch不确定字段查询

    curl -X GET "localhost:9200/index-product/_search?pretty" -H 'Content-Type: application/json' -d'
    {
      "query": {
        "multi_match" : {
          "query":    "this is a test", 
          "fields": [ "subject", "message" ]  //两个字段同时满足
        }
      }
    }
    '
    

    四、Springboot中的ES操作类封装

    4.1、EsUtil.java

    package com.yuyuda.search.util;
    ​
    import com.alibaba.fastjson.JSON;
    import com.yate.search.dto.Page;
    import com.yate.search.entity.EsEntity;
    import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
    import org.elasticsearch.action.bulk.BulkRequest;
    import org.elasticsearch.action.delete.DeleteRequest;
    import org.elasticsearch.action.index.IndexRequest;
    import org.elasticsearch.action.search.SearchRequest;
    import org.elasticsearch.action.search.SearchResponse;
    import org.elasticsearch.client.RequestOptions;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.elasticsearch.client.indices.GetIndexRequest;
    import org.elasticsearch.common.xcontent.XContentType;
    import org.elasticsearch.index.query.QueryBuilder;
    import org.elasticsearch.index.reindex.DeleteByQueryRequest;
    import org.elasticsearch.search.SearchHit;
    import org.elasticsearch.search.builder.SearchSourceBuilder;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    ​
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.List;
    ​
    /**
     * Elastic search 工具类
     *
     * @author lzx@yuyuda.com
     * @version 1.0
     * @date 2020/2/28 下午11:56
     **/
    @Component
    public class EsUtil {
        @Autowired
        private RestHighLevelClient client;
    ​
        /**
         * 检验某个索引是否存在
         *
         * @param index
         * @return boolean
         * @author lzx@yuyuda.com
         * @date 2020/2/29 上午12:44
         */
        public boolean indexExist(String index) throws Exception {
            GetIndexRequest request = new GetIndexRequest(index);
            request.local(false);
            request.humanReadable(true);
            request.includeDefaults(false);
            return client.indices().exists(request, RequestOptions.DEFAULT);
        }
    ​
        /**
         * 插入或更新一条记录
         *
         * @param index
         * @param entity
         * @return void
         * @author lzx@yuyuda.com
         * @date 2020/2/29 上午12:52
         */
        public void insertOrUpdateOne(String index, EsEntity entity) throws Exception {
            IndexRequest request = new IndexRequest(index);
            request.id(entity.getId() + "");
            request.source(JSON.toJSONString(entity.getData()), XContentType.JSON);
            client.index(request, RequestOptions.DEFAULT);
        }
    ​
        /**
         * 批量插入数据
         *
         * @param index
         * @param list
         * @return void
         * @author lzx@yuyuda.com
         * @date 2020/2/29 上午12:58
         */
        public void insertBatch(String index, List<EsEntity> list) throws Exception {
            BulkRequest request = new BulkRequest();
            list.forEach(item -> request.add(new IndexRequest(index).id(item.getId())
                    .source(JSON.toJSONString(item.getData()), XContentType.JSON)));
    ​
    ​
            client.bulk(request, RequestOptions.DEFAULT);
        }
    ​
        /**
         * 批量删除索引中的数据
         *
         * @param index  index
         * @param idList 待删除列表
         * @return void
         * @author lzx@yuyuda.com
         * @date 2020/2/29 上午3:38
         */
        public <T> void deleteDataBatch(String index, Collection<T> idList) throws IOException {
            BulkRequest request = new BulkRequest();
            idList.forEach(item -> request.add(new DeleteRequest(index, item.toString())));
            client.bulk(request, RequestOptions.DEFAULT);
        }
    ​
        /**
         * 根据条件删除索引中的数据
         *
         * @param index
         * @param builder
         * @return void
         * @author lzx@yuyuda.com
         * @date 2020/2/29 上午3:33
         */
        public void deleteDataByQuery(String index, QueryBuilder builder) throws IOException {
            DeleteByQueryRequest request = new DeleteByQueryRequest(index);
            request.setQuery(builder);
            //设置批量删除操作数量,最大为10000
            request.setBatchSize(10000);
            request.setConflicts("proceed");
            client.deleteByQuery(request, RequestOptions.DEFAULT);
        }
    ​
        /**
         * 搜索[带分页]
         *
         * @param index    index
         * @param builder  查询参数
         * @param tClass   结果类对象
         * @param pageSize 每页多少条
         * @return java.util.List<T>
         * @author lzx@yuyuda.com
         * @date 2020/2/29 上午2:28
         */
        public <T> Page<T> searchPage(String index, SearchSourceBuilder builder, Class<T> tClass, int pageSize) throws IOException {
            SearchRequest request = new SearchRequest(index);
            request.source(builder);
            SearchResponse search = client.search(request, RequestOptions.DEFAULT);
            SearchHit[] hits = search.getHits().getHits();
            int total = (int) search.getHits().getTotalHits().value;
            ArrayList<T> res = new ArrayList<>(hits.length);
            for (SearchHit hit : hits) {
                res.add(JSON.parseObject(hit.getSourceAsString(), tClass));
            }
            Page<T> page = new Page<>();
            page.setTotal(total);
            double count = (double) total / (double) pageSize;
            page.setPageCount((int) Math.ceil(count));
            page.setPageList(res);
            return page;
        }
    ​
        /**
         * 搜索[不带分页]
         *
         * @param index   index
         * @param builder 查询参数
         * @param tClass  结果类对象
         * @return java.util.List<T>
         * @author lzx@yuyuda.com
         * @date 2020/2/29 上午2:28
         */
        public <T> List<T> search(String index, SearchSourceBuilder builder, Class<T> tClass) throws IOException {
            SearchRequest request = new SearchRequest(index);
            request.source(builder);
            SearchResponse search = client.search(request, RequestOptions.DEFAULT);
            SearchHit[] hits = search.getHits().getHits();
            int total = (int) search.getHits().getTotalHits().value;
            ArrayList<T> res = new ArrayList<>(hits.length);
            for (SearchHit hit : hits) {
                res.add(JSON.parseObject(hit.getSourceAsString(), tClass));
            }
            return res;
        }
    ​
        /**
         * 删除索引
         *
         * @param index
         * @return void
         * @author lzx@yuyuda.com
         * @date 2020/2/29 上午3:28
         */
        public void deleteIndex(String index) throws IOException {
            client.indices().delete(new DeleteIndexRequest(index), RequestOptions.DEFAULT);
        }
    }
    

    4.2、EsEntity.java ES实体

    package com.yuyuda.search.entity;
    ​
    import lombok.Data;
    ​
    /**
    ​
     * ES基础实体
    ​
     *
    ​
     * @author lzx@yuyuda.com
    ​
     * @version 1.0
    ​
     * @date 2020/2/29 上午12:47
    ​
     **/
    @Data
    public final class EsEntity<T> {
    ​
        private String id;
    ​
        private T data;
    ​
    }
    

    4.3、分页DTO Page.java

    package com.yuyuda.search.dto;
    ​
    import lombok.Data;
    import java.io.Serializable;
    import java.util.List;
    /**
     * 分页实体
     *
     * @author lzx@yuyuda.com
     * @version 1.0
     * @date 2020/3/1 上午4:28
     **/
    @Data
    public class Page<T> implements Serializable {
        /**
         * 总记录数
         */
        private int total;
    ​
    ​
        /**
         * 页数
         */
        private int pageCount;
    ​
    ​
        /**
         * 列表
         */
        private List<T> pageList;
    }
    

    五、后端业务流程

    5.1、ProductDto.java定义

    package com.yuyuda.search.dto;
    ​
    import com.yate.product.entity.YateProductProperty;
    import lombok.Data;
    import java.io.Serializable;
    import java.math.BigDecimal;
    import java.util.Date;
    ​
    /**
     * ES产品实体
     *
     * @author lzx@yuyuda.com
     * @version 1.0
     * @date 2020/2/29 上午1:55
     **/
    @Data
    public class Product implements Serializable {
        /**
         * 产品ID
         */
        private Long id;
        private String idStr;
    ​
        /**
         * 产品名称
         */
        private String productTitle;
    ​
        /**
         * 产品竞价价格
         */
        private BigDecimal biddingPrice;
    ​
        /**
         * 产品单价
         */
        private BigDecimal productUnitPrice;
    ​
        /**
         * 产品图片
         */
        private String productImg;
    ​
        /**
         * 产品添加时间
         */
        private Date createTime;
    ​
        /**
         * 产品所属商家
         */
        private String businessName;
    ​
        /**
         * 产品所属商家ID
         */
        private String businessId;
    ​
        /**
         * 产品所属分类
         */
        private String catagory;
    ​
        /**
         * 产品分类ID
         */
        private String catId;
    ​
        /**
         * 产品属性
         */
        private YateProductProperty productProperty;
    }
    

    5.2、interface定义

    package com.yuyuda.search.service;
    ​
    import com.yate.search.dto.Page;
    import com.yate.search.dto.Product;
    import java.io.IOException;
    import java.util.List;
    ​
    /**
     * 产品搜索服务
     *
     * @author lzx@yuyuda.com
     * @version 1.0
     * @date 2020/2/29 上午5:08
     **/
    public interface IProductSearchService {
        /**
         * 向索引中插入一条产品信息
         *
         * @param product
         * @return void
         * @throws Exception
         * @author lzx@yuyuda.com
         * @date 2020/2/29 上午5:13
         */
        void putOne(Product product) throws Exception;
    ​
        /**
         * 通过id查询索引中的某条产品数据
         *
         * @param id
         * @return com.yuyuda.search.dto.Product
         * @throws IOException
         * @author lzx@yuyuda.com
         * @date 2020/2/29 上午5:13
         */
        Product getProductById(int id) throws IOException;
    ​
        /**
         * 获取所有产品数据
         *
         * @param
         * @return java.util.List<com.yuyuda.search.dto.Product>
         * @throws IOException
         * @author lzx@yuyuda.com
         * @date 2020/2/29 上午5:13
         */
        List<Product> getAll() throws IOException;
    ​
        /**
         * 根据字段和关键字 查询产品数据
         *
         * @param field
         * @param keyword
         * @return java.util.List<com.yuyuda.search.dto.Product>
         * @throws IOException
         * @author lzx@yuyuda.com
         * @date 2020/2/29 上午5:13
         */
        List<Product> searchByKeyword(String field, String keyword) throws IOException;
    ​
        /**
        * 字段不确定查询产品数据
        * @param keyword
        * @param page
        * @return com.yate.search.dto.Page<com.yuyuda.search.dto.Product>
        * @author lzx@yuyuda.com
        * @date 2020/3/1 上午4:34
        */
        Page<Product> unSureFieldSearchByKeyword(String keyword, int page) throws IOException;
    }
    

    5.3、service即dubbo的producer

    package com.yate.search.service;
    ​
    import com.yate.search.dto.Page;
    import com.yate.search.dto.Product;
    import com.yuyuda.search.entity.EsEntity;
    import com.yuyuda.search.util.EsUtil;
    import org.apache.dubbo.config.annotation.Service;
    import org.elasticsearch.client.RequestOptions;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.elasticsearch.client.indices.CreateIndexRequest;
    import org.elasticsearch.client.indices.CreateIndexResponse;
    import org.elasticsearch.common.settings.Settings;
    import org.elasticsearch.common.xcontent.XContentType;
    import org.elasticsearch.index.query.BoolQueryBuilder;
    import org.elasticsearch.index.query.QueryBuilders;
    import org.elasticsearch.index.query.TermQueryBuilder;
    import org.elasticsearch.search.builder.SearchSourceBuilder;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    ​
    import javax.annotation.PostConstruct;
    import java.io.IOException;
    import java.util.List;
    ​
    /**
     * 产品搜索服务
     *
     * @author lzx@yuyuda.com
     * @version 1.0
     * @date 2020/2/29 上午1:02
     **/
    @Service
    public class ProductSearchServiceImpl implements IProductSearchService {
        @Autowired
        private EsUtil esUtil;
    ​
        @Autowired
        private RestHighLevelClient client;
    ​
        @Value("${pageSize}")
        private int pageSize;
    ​
        /**
         * 产品索引名称
         */
        private static final String INDEX_PRODUCT = "index-product";
    ​
        /**
         * 产品索引格式
         */
        private static final String CREATE_INDEX = "{\n" +
                "  \"properties\": {\n" +
                "    \"id\": {\n" +
                "      \"type\": \"long\"\n" +
                "    },\n" +
                "    \"productTitle\":{\n" +
                "      \"type\": \"text\",\n" +
                "      \"index\": true\n" +
                "    },\n" +
                "    \"biddingPrice\": {\n" +
                "      \"type\": \"double\"\n" +
                "    },\n" +
                "    \"productUnitPrice\": {\n" +
                "      \"type\": \"double\"\n" +
                "    },\n" +
                "    \"productImg\": {\n" +
                "      \"type\": \"text\"\n" +
                "    },\n" +
                "    \"createTime\": {\n" +
                "      \"type\": \"date\",\n" +
                "      \"format\": \"strict_date_optional_time||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis\"\n" +
                "    },\n" +
                "    \"businessName\": {\n" +
                "      \"type\": \"text\",\n" +
                "      \"index\": true\n" +
                "    },\n" +
                "    \"businessId\": {\n" +
                "      \"type\": \"text\"\n" +
                "    },\n" +
                "    \"catagory\": {\n" +
                "      \"type\": \"text\",\n" +
                "      \"index\": true\n" +
                "    },\n" +
                "    \"catId\": {\n" +
                "      \"type\": \"text\"\n" +
                "    },\n" +
                "    \"productProperties\": {\n" +
                "      \"type\": \"nested\"\n" +
                "    }\n" +
                "  }\n" +
                "}";
    ​
    ​
        /**
         * 初始化创建索引
         *
         * @return void
         * @author lzx@yuyuda.com
         * @date 2020/2/29 上午4:59
         */
        @PostConstruct
        public void createProductIndex() {
            try {
                boolean indexExist = esUtil.indexExist(INDEX_PRODUCT);
                if (indexExist) {
                    return;
                }
                CreateIndexRequest request = new CreateIndexRequest(INDEX_PRODUCT);
                request.settings(Settings.builder().put("index.number_of_shards", 1).put("index.number_of_replicas", 1));
                request.mapping(CREATE_INDEX, XContentType.JSON);
                CreateIndexResponse createIndexResponse = client.indices().create(request, RequestOptions.DEFAULT);
                if (!createIndexResponse.isAcknowledged()) {
                    throw new RuntimeException("创建索引失败");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    ​
        /**
         * 向索引中插入一条产品信息
         *
         * @return void
         * @throws Exception
         * @author lzx@yuyuda.com
         * @date 2020/2/29 上午5:13
         */
        @Override
        public void putOne(Product product) throws Exception {
            EsEntity<Product> esEntity = new EsEntity<>();
            esEntity.setId(product.getId() + "");
            esEntity.setData(product);
            esUtil.insertOrUpdateOne(INDEX_PRODUCT, esEntity);
        }
    ​
        /**
         * 通过id查询索引中的某条产品数据
         *
         * @return com.yate.yuyuda.dto.Product
         * @author lzx@yuyuda.com
         * @date 2020/2/29 上午5:13
         */
        @Override
        public Product getProductById(int id) throws IOException {
            SearchSourceBuilder builder = new SearchSourceBuilder();
            builder.query(new TermQueryBuilder("id", id));
            List<Product> search = esUtil.search(INDEX_PRODUCT, builder, Product.class);
            if (search.size() > 0) {
                return search.get(0);
            } else {
                return null;
            }
        }
    ​
        /**
         * 获取所有产品数据
         *
         * @return java.util.List<com.yuyuda.search.dto.Product>
         * @author lzx@yuyuda.com
         * @date 2020/2/29 上午5:13
         */
        @Override
        public List<Product> getAll() throws IOException {
            return esUtil.search(INDEX_PRODUCT, new SearchSourceBuilder(), Product.class);
        }
    ​
        /**
         * 根据字段和关键字 查询产品数据
         *
         * @return java.util.List<com.yate.search.dto.Product>
         * @author lzx@yuyuda.com
         * @date 2020/2/29 上午5:13
         */
        @Override
        public List<Product> searchByKeyword(String field, String keyword) throws IOException {
            BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
            boolQueryBuilder.must(QueryBuilders.matchQuery(field, keyword));
            SearchSourceBuilder builder = new SearchSourceBuilder();
            builder.size(10).query(boolQueryBuilder);
            return esUtil.search(INDEX_PRODUCT, builder, Product.class);
        }
    ​
        /**
         * 字段不确定查询产品数据
         *
         * @param keyword
         * @param page
         * @return java.util.Page<com.yuyuda.search.dto.Product>
         * @throws IOException
         * @author lzx@yuyuda.com
         * @date 2020/3/1 上午2:11
         */
        @Override
        public Page<Product> unSureFieldSearchByKeyword(String keyword, int page) throws IOException {
            BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
            boolQueryBuilder.must(QueryBuilders.multiMatchQuery(keyword, "businessName", "catagory", "productTitle"));
            SearchSourceBuilder builder = new SearchSourceBuilder();
            int start = (page - 1) * pageSize;
            builder.from(start).size(pageSize).query(boolQueryBuilder);
            return esUtil.searchPage(INDEX_PRODUCT, builder, Product.class, pageSize);
        }
    }
    

    5.4、controller即dubbo的consumer

    package com.yuyuda.search.controller;
    ​
    import com.yuyuda.search.dto.Page;
    import com.yuyuda.search.dto.Product;
    import com.yuyuda.search.service.IProductSearchService;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiOperation;
    import org.apache.dubbo.config.annotation.Reference;
    import org.springframework.web.bind.annotation.*;
    ​
    import java.io.IOException;
    ​
    /**
     * 产品搜索操作控制器
     *
     * @author lzx@yuyuda.com
     * @date 2020/2/29 上午8:53
     */
    @RestController
    @RequestMapping("/search")
    @Api(value = "SearchProductController", tags = "搜索操作控制器")
    public class SearchProductController {
        @Reference(interfaceClass = IProductSearchService.class, check = false)
        private IProductSearchService productSearchService;
    ​
        /**
        * 根据关键词进行搜索
        * @param keyword
        * @return java.util.Page<com.yuyu.search.dto.Product>
        * @author lzx@yuyuda.com
        * @date 2020/2/29 上午9:05
        */
        @GetMapping("/get_product_by_keyword")
        @ApiOperation("根据关键词进行搜索")
        public Page<Product> getProductByKeyword(
                @RequestParam(value = "keyword", required = true) String keyword,
                @RequestParam(value = "page") int page
        ) {
            Page<Product> productPage = null;
            try {
                page = page > 0 ? page : 1;
                productPage = productSearchService.unSureFieldSearchByKeyword(keyword, page);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return productPage;
        }
    }
    

    六、前端业务流程

    6.1、使用nuxtjs的注意事项

    npm install -g npx
    npx create-nuxt-app project-name
    ​
    使用npx时,将require() 改成import报错解决办法
    1 在dev、start指定--exec babel-node
    2 在.babelrc文件中指定 "presets": ["es2015"]
    3 并安装babel-preset-es2015
    npm install babel-preset-es2015
    ​
    使用npx时,scss支持报错,解决办法
    安装支持
    npm install sass-loader node-sass
    

    6.2、nuxt.config.js的配置

    export default {
      mode: 'universal',
      /*
      ** Headers of the page
      */
      head: {
        title: '搜索服务',
        meta: [
          { charset: 'utf-8' },
          { name: 'viewport', content: 'width=device-width, initial-scale=1' },
          { hid: 'description', name: 'description', content: process.env.npm_package_description || '' }
        ],
        link: [
          { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
        ]
      },
      /*
      ** Customize the progress-bar color
      */
      loading: { color: '#fff' },
      /*
      ** Global CSS
      */
      css: [
    ​
    ​
      ],
      /*
      ** Plugins to load before mounting the App
      */
      plugins: [
    ​
    ​
      ],
      /*
      ** Nuxt.js dev-modules
      */
      buildModules: [
      ],
      /*
      ** Nuxt.js modules
      */
      modules: [
        'bootstrap-vue/nuxt',
        '@nuxtjs/axios',
        // 请求代理配置,解决跨域
        '@gauseen/nuxt-proxy',
      ],
      proxyTable: {     //axios跨域处理
        '/api': {       //此处并非和url一致
          target:'http://192.168.3.15:9999/',
          changeOrigin:true, //允许跨域
          pathRewrite:{
            '^/api': ''
          }
        }
      },
      /*
      ** Build configuration
      */
      build: {
        /*
        ** You can extend webpack config here
        */
        extend (config, ctx) {
        }
      },
    ​
    ​
      router:{
        middleware:['authenticated']
      },
    ​
      server: {
        port: 3000, // default: 3000
        host: '0.0.0.0' // default: localhost
      }
    }
    

    6.3、pages目录下新建searchInput.vue

    <template>
    <div class="top-advice">
      <b-container class="search-bar">
        <b-row>
           <b-col cols="6">
            <div class="search-bar-area">
              <b-input-group>
                <b-form-input type="text" size="md" v-model="search" @keyup.enter="searchProduct"></b-form-input>
                <b-input-group-append>
                  <b-button size="md" variant="warning" @click="searchProduct">搜索</b-button>
                </b-input-group-append>
              </b-input-group>
            </div>
          </b-col>
         </b-row>
      </b-container>
    </div>
    </template>
    ​
    <script>
        export default {
            data() {
                return {
                    search: ''
                }
            },
            methods: {
                searchProduct: function () {
                    if (this.search) {
                        window.location.href = "/search?keyword=" + this.search
                    }
                }
            }
        }
    </script>
    

    6.4、在pages目录下新建search.vue

    <template>
      <div class="container wrap">
        <div class="content">
          <list 
            :productList="productList" 
            :pageTotal="pageTotal" 
            :pageCount="pageCount" 
            @currPage="updatePage"
          />
        </div>
      </div>
    </template>
    ​
    <script>
        import axios from "axios";
        import List from "../components/list/List";
        
        export default {
            components: {
                List,
            },
            data() {
                return {
                    productPage: null
                }
            },
            computed: {
                productList: function () {
                    return this.productPage ? this.productPage.pageList : []
                },
                pageTotal: function () {
                    return this.productPage ? this.productPage.total : 0
                },
                pageCount: function () {
                    return this.productPage ? this.productPage.pageCount : 0
                }
            },
            methods: {
                updatePage: function (page) {
                    const keyword = encodeURIComponent(this.$route.query.keyword.toString());
                    axios.get("/api/s/search/get_product_by_keyword?keyword=" + keyword + "&page=" + page).then(({status, data}) => {
                        if (status === 200 && data.code === 200) {
                            this.productPage = data.result
                        }
                    });
                }
            },
            async asyncData({app}) {
                let keyword = encodeURIComponent(app.context.query.keyword);
                const {status, data} = await axios.get("http://192.168.3.15:3000/api/s/search/get_product_by_keyword?keyword=" + keyword  + "&page=1");
                let productPage = null;
                if(status === 200 && data.code === 200) {
                    productPage = data.result;
                }
                return {
                    productPage
                }
            }
        }
    </script>
    

    6.5、在components/list的目录下新建List.vue组件

    <template>
      <div class="list">
        <b-container class="bv-example-row">
          <b-row class="recommended">
            <b-col class="none-padding">
              <ul class="ibody">
                <li
                  v-for="item in productList"
                  :key="item.productTitle" @click="jumpProductInfo(item.id)">
                  <img :src="item.productImg" class="image"/>
                  <ul class="cbody">
                    <li class="title">{{ item.productTitle }}</li>
                    <li class="market-price">市场价: <span class="yuan">¥</span><span class="price">{{ item.productUnitPrice }}</span>
                    </li>
                    <li class="manufacturer-price">厂价: <span class="yuan">¥</span><span
                      class="price">{{ item.productUnitPrice }}</span></li>
                  </ul>
                </li>
              </ul>
            </b-col>
          </b-row>
          <div class="overflow-auto page-right">
            <div class="mt-3">
              <b-pagination
                v-model="currentPage"
                pills
                align="right"
                :total-rows="pageCount"
                :per-page="perPage"
                first-text="首页"
                prev-text="<上一页"
                next-text="下一页>"
                last-text="末页"
                @input="$emit('currPage', currentPage)"
              ></b-pagination>
            </div>
          </div>
        </b-container>
      </div>
    </template>
    ​
    <script>
        export default {
            props: ["productList", "pageTotal", "pageCount"],
            data: () => {
                return {
                    img: '/img/11.jpg',
                    perPage: 1,
                    currentPage: 1
                }
            },
            methods: {
                jumpProductInfo: function (id) {
                    window.location.href = "/info?id=" + id
                }
            }
        }
    </script>
    ​
    <style lang="scss">
      .list {
        background-color: #FFFFFF;
        margin-top: 15px;
    ​
        .page-right {
          a {
            color: #646464;
          }
        }
    ​
        .recommended {
          .none-padding {
            padding: 0;
          }
        }
      }
    ​
      .ibody {
        background-color: #fff;
        border-bottom-left-radius: 4px;
        border-bottom-right-radius: 4px;
        box-sizing: border-box;
        padding-left: 2px;
    ​
        > li {
          float: left;
          list-style: none;
          padding: 10px;
          background: #fff;
          transition: background-color .5s;
          cursor: pointer;
    ​
          .image {
            height: 208px;
            width: 348px;
          }
    ​
          .cbody {
            list-style: none;
    ​
            .title {
              font-size: 16px;
              color: #222;
              margin-top: 10px;
              margin-bottom: 8px;
              margin-left: -40px;
              font-weight: 600;
              white-space: nowrap;
              overflow: hidden;
              text-overflow: ellipsis;
            }
    ​
            .manufacturer-price, .market-price {
              margin-left: -40px;
    ​
              .yuan {
                color: #ff5000;
                font-family: Arial;
                font-size: 12px;
              }
    ​
              .price {
                color: #ff5000;
                font-family: Arial;
              }
            }
          }
        }
      }
    </style>
    

    6.6、关于vuejs组件的传值介绍

    6.6.1、父组件向子组件传值

    • 父组件中传的值,在引用组件的地方加上属性,如

        <child :name=”name”/>
      
    • 子组件用props进行声明

        props: [“name”]   
      

    父组件:parent.vue

    <template>
      <div>
    <child :name=”name”/>
      </div>
    </template>
    <script>
      import Child from "./child";
      export default {
        components: {
          Child
        },
        data(){
          return {
            name: "aaa"
          }
        }
      }
    </script>
    

    子组件:child.vue

    <template>
      <div>
        {{name}}
      </div>
    </template>
    ​
    <script>
     export default {
        props: ["name"]    
      }
    </script>
    

    6.6.2、子组件向父组件传值

    • 子组件需要利用事件进行触发,再配合$emit(“update”)进行触发发射

    • 父组件中需要定义子组件发射过来的事件@update=”updateAge”

    子组件:

    <template>
      <button @click="$emit('update', param)">发射</button>
    </template>
    

    父组件:

    <template>
      <div>
        <child @update=”updateAge”/>
      </div>
    </template>
    ​
    <script>
      import Child from "./child";
      export default {
        components: {
          Child
        },
        methods: {
          updateAge: function(param) {
             Console.log(param)
          }
        }  
      }
    </script>
    

    6.6.3、跨组件传值

    • 即需要使用vuex来传递,我此处是nuxtjs3.0之后的写法,例如公用菜单,任何页面都有可能用。

    • nuxtServerInit的使用,nuxtServerInit方法只有在store目录下index.js才能被调用,其他模块中写是调用不到的,我实践的时候花了一晚上才调试通。

    6.6.3.1、在store目录下建立菜单模块menu.js

    const state = () => ({
      menu: []
    });
    ​
    const mutations = {
      setMenu(state, v) {
        state.menu = v
      }
    };
    ​
    export default {
      namespaced: true,
      state,
      mutations
    }
    

    6.6.3.2、在index.js中使用menu.js

    import Menu from "./menu" 
    const store = {
      modules: {
        Menu   //这里非常重要,当时我就卡在这里,因为没有写这个
      },
    ​
      actions: {
        async nuxtServerInit({commit}, {req, app}) {
          const {status, data} = await app.$axios.post("/api/p/product/tree_product_catagory");
          if (status === 200 && data.code === 200 && data.result.length > 0) {
            for (let i = 0; i < data.result.length; i++) {
              data.result[i]['type'] = data.result[i]['idStr'].toString();
            }
            commit('menu/setMenu', data.result)
          } else {
            commit('menu/setMenu', [])
          }
        }
      }
    };
    ​
    export default store
    

    最后,一篇关于Elasticsearch、Springboot、Dubbo、Vue、Nuxt实现的搜索框服务实战到这里就结束了。
    其实,偶尔写写公众号也是对自己用过的知识的一种背书、巩固、分享,因为不积跬步,无以至千里;不积小流,无以成江海。今年虽然被疫情搞得经济破了产,经济压力很大,心像打翻的五味瓶,惶惶不可终日,心中也有一首<<天问>>,“悟过改更,我又何言?”。也许是天将降大任于是人也,必先苦其心志,劳其筋骨,饿其体肤,空乏其身,行拂乱其所为,所以动心忍性,曾益其所不能吧。现在虽悟过改更,我又何言,但在技术上有所突破的成就感算是给了我丝丝安慰。

    相关文章

      网友评论

          本文标题:实战Elasticsearch、springboot、dubbo

          本文链接:https://www.haomeiwen.com/subject/qaxykhtx.html