美文网首页
实战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