This commit is contained in:
shuhongfan
2023-09-04 16:40:17 +08:00
commit cf5ac25c14
8267 changed files with 1305066 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
.idea
target/
*.iml

View File

@@ -0,0 +1,13 @@
FROM openjdk:11-jdk
LABEL maintainer="研究院研发组 <research@itcast.cn>"
# 时区修改为东八区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
WORKDIR /app
ARG JAR_FILE=target/*.jar
ADD ${JAR_FILE} app.jar
EXPOSE 8080
ENTRYPOINT ["sh","-c","java -Djava.security.egd=file:/dev/./urandom -jar $JAVA_OPTS app.jar"]

View File

@@ -0,0 +1,120 @@
<?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>
<groupId>com.sl-express</groupId>
<artifactId>sl-express-parent</artifactId>
<version>1.4</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.sl-express.ms.search</groupId>
<artifactId>sl-express-ms-search-service</artifactId>
<version>1.1-SNAPSHOT</version>
<description>搜索微服务</description>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<sl-express-common.version>1.2-SNAPSHOT</sl-express-common.version>
<sl-express-mq.version>1.1-SNAPSHOT</sl-express-mq.version>
<sl-express-ms-search-domain.version>1.1-SNAPSHOT</sl-express-ms-search-domain.version>
<sl-express-ms-work-api.version>1.1-SNAPSHOT</sl-express-ms-work-api.version>
<sl-express-ms-oms-api.version>1.1-SNAPSHOT</sl-express-ms-oms-api.version>
<elasticsearch.version>7.17.5</elasticsearch.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
</properties>
<dependencies>
<dependency>
<groupId>com.sl-express.ms.search</groupId>
<artifactId>sl-express-ms-search-domain</artifactId>
<version>${sl-express-ms-search-domain.version}</version>
</dependency>
<dependency>
<groupId>com.sl-express.common</groupId>
<artifactId>sl-express-common</artifactId>
<version>${sl-express-common.version}</version>
</dependency>
<dependency>
<groupId>com.sl-express.mq</groupId>
<artifactId>sl-express-mq</artifactId>
<version>${sl-express-mq.version}</version>
</dependency>
<dependency>
<groupId>com.sl-express.ms.work</groupId>
<artifactId>sl-express-ms-work-api</artifactId>
<version>${sl-express-ms-work-api.version}</version>
</dependency>
<dependency>
<groupId>com.sl-express.ms.oms</groupId>
<artifactId>sl-express-ms-oms-api</artifactId>
<version>${sl-express-ms-oms-api.version}</version>
</dependency>
<!--elasticsearch-->
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>${elasticsearch.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.13.0</version>
</dependency>
<dependency>
<groupId>jakarta.json</groupId>
<artifactId>jakarta.json-api</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
<goal>build-info</goal>
</goals>
</execution>
</executions>
<configuration>
<!--指定主类-->
<mainClass>com.sl.SearchApplication</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,14 @@
package com.sl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableFeignClients
@SpringBootApplication
public class SearchApplication {
public static void main(String[] args) {
SpringApplication.run(SearchApplication.class, args);
}
}

View File

@@ -0,0 +1,48 @@
package com.sl.ms.search.config;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
/**
* ES配置类
*/
@Data
@Slf4j
@Configuration
@ConfigurationProperties(prefix = "elasticsearch")
public class ElasticSearchConfig {
private String host;
private Integer port;
@Resource
private ObjectMapper objectMapper;
@Bean
public ElasticsearchClient client() {
log.info("es连接ip{},端口:{}", host, port);
// 基本的连接
RestClient restClient = RestClient.builder(new HttpHost(host, port)).build();
// 使用自定义json序列化
JacksonJsonpMapper jacksonJsonpMapper = new JacksonJsonpMapper(objectMapper);
// Create the transport with a Jackson mapper
ElasticsearchTransport transport = new RestClientTransport(restClient, jacksonJsonpMapper);
// And create the API client
return new ElasticsearchClient(transport);
}
}

View File

@@ -0,0 +1,32 @@
package com.sl.ms.search.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.LocalDateTime;
import static cn.hutool.core.date.DatePattern.NORM_DATETIME_FORMATTER;
/**
* LocalDateTime序列化配置
**/
@Configuration
public class LocalDateTimeSerializingConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper om = new ObjectMapper();
JavaTimeModule javaTimeModule = new JavaTimeModule();
// 序列化
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(NORM_DATETIME_FORMATTER));
// 反序列化
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(NORM_DATETIME_FORMATTER));
om.registerModule(javaTimeModule);
return om;
}
}

View File

@@ -0,0 +1,46 @@
package com.sl.ms.search.controller;
import com.sl.ms.search.domain.dto.CourierTaskDTO;
import com.sl.ms.search.domain.dto.CourierTaskPageQueryDTO;
import com.sl.ms.search.service.CourierTaskService;
import com.sl.transport.common.util.PageResponse;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
/**
* 快递员任务Controller
**/
@RestController
@Api(tags = "快递员任务")
@RequestMapping("courierSearch")
public class CourierTaskController {
@Resource
private CourierTaskService courierTaskService;
@PostMapping("pageQuery")
@ApiOperation(value = "分页查询")
public PageResponse<CourierTaskDTO> pageQuery(@RequestBody CourierTaskPageQueryDTO pageQueryDTO) {
return courierTaskService.pageQuery(pageQueryDTO);
}
@PostMapping
@ApiOperation(value = "新增/全量修改快递员任务")
public void saveOrUpdate(@RequestBody CourierTaskDTO courierTaskDTO) {
courierTaskService.saveOrUpdate(courierTaskDTO);
}
@GetMapping("/{id}")
@ApiOperation(value = "根据取派件id查询快递员任务")
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "取派件id", required = true, dataTypeClass = Long.class)
})
public CourierTaskDTO findById(@PathVariable("id") Long id) {
return courierTaskService.findById(id);
}
}

View File

@@ -0,0 +1,112 @@
package com.sl.ms.search.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* 快递员任务实体类
**/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class CourierTaskEntity {
/**
* 取派件任务id
*/
private Long id;
/**
* 订单id
*/
private Long orderId;
/**
* 运单id
*/
private String transportOrderId;
/**
* 电话
*/
private String phone;
/**
* 姓名
*/
private String name;
/**
* 任务类型
*/
private Integer taskType;
/**
* 任务状态
*/
private Integer status;
/**
* 快递员id
*/
private Long courierId;
/**
* 机构id
*/
private Long agencyId;
/**
* 地址
*/
private String address;
/**
* 预计开始时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime estimatedStartTime;
/**
* 实际开始时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime actualStartTime;
/**
* 预计结束时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime estimatedEndTime;
/**
* 实际结束时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime actualEndTime;
/**
* 是否删除
*/
private Integer isDeleted;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime created;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime updated;
}

View File

@@ -0,0 +1,47 @@
package com.sl.ms.search.enums;
import cn.hutool.core.util.EnumUtil;
import com.sl.transport.common.enums.BaseExceptionEnum;
/**
* 搜索微服务异常枚举
**/
public enum ExceptionEnum implements BaseExceptionEnum {
ES_ACCESS_ERROR(9901, "访问es出现未知异常"),
TASK_NOT_FOUND(9902, "任务不存在!");
private final Integer code;
private final Integer status;
private final String value;
ExceptionEnum(Integer code, String value) {
this.code = code;
this.value = value;
this.status = 500;
}
ExceptionEnum(Integer code, Integer status, String value) {
this.code = code;
this.value = value;
this.status = status;
}
@Override
public Integer getCode() {
return this.code;
}
@Override
public String getValue() {
return this.value;
}
@Override
public Integer getStatus() {
return this.status;
}
public static ExceptionEnum codeOf(Integer code) {
return EnumUtil.getBy(ExceptionEnum::getCode, code);
}
}

View File

@@ -0,0 +1,60 @@
package com.sl.ms.search.mq;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import com.sl.ms.search.service.CourierTaskService;
import com.sl.transport.common.constant.Constants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 快递员任务消息处理
**/
@Slf4j
@Component
public class CourierTaskMQListener {
@Resource
private CourierTaskService courierTaskService;
@Resource
private ElasticsearchClient client;
@Value("${sl.es.index_name}")
private String indexName;
/**
* 新增/更新快递员任务
*
* @param msg 消息
*/
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = Constants.MQ.Queues.COURIER_TASK_SAVE_OR_UPDATE),
exchange = @Exchange(name = Constants.MQ.Exchanges.COURIER_TASK, type = ExchangeTypes.TOPIC),
key = Constants.MQ.RoutingKeys.COURIER_TASK_SAVE_OR_UPDATE
))
public void listenCourierTaskCreateMsg(String msg) {
log.info("接收到新增/更新快递员任务的消息 ({})-> {}", Constants.MQ.Queues.COURIER_TASK_SAVE_OR_UPDATE, msg);
// TODO 具体业务逻辑的处理
}
/**
* 创建运单后同步数据到es
*
* @param msg 消息
*/
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = Constants.MQ.Queues.SEARCH_TRANSPORT_ORDER_CREATED),
exchange = @Exchange(name = Constants.MQ.Exchanges.TRANSPORT_ORDER_DELAYED, type = ExchangeTypes.TOPIC, delayed = Constants.MQ.DELAYED),
key = Constants.MQ.RoutingKeys.TRANSPORT_ORDER_CREATE
))
public void listenTransportOrderCreatedMsg(String msg) {
log.info("接收到新增运单的消息 ({})-> {}", Constants.MQ.Queues.TRACK_TRANSPORT_ORDER_CREATED, msg);
// TODO 具体业务逻辑的处理
}
}

View File

@@ -0,0 +1,44 @@
package com.sl.ms.search.service;
import com.sl.ms.search.domain.dto.CourierTaskDTO;
import com.sl.ms.search.domain.dto.CourierTaskPageQueryDTO;
import com.sl.ms.search.entity.CourierTaskEntity;
import com.sl.transport.common.util.PageResponse;
import java.util.List;
/**
* 快递员任务服务接口
**/
public interface CourierTaskService {
/**
* 分页查询
*
* @param pageQueryDTO 分页查询条件
* @return 分页查询结果
*/
PageResponse<CourierTaskDTO> pageQuery(CourierTaskPageQueryDTO pageQueryDTO);
/**
* 新增快递员任务
*
* @param courierTaskDTO 快递员任务
*/
void saveOrUpdate(CourierTaskDTO courierTaskDTO);
/**
* 根据取派件id查询快递员任务
*
* @param id 取派件id
* @return 快递员任务
*/
CourierTaskDTO findById(Long id);
/**
* 根据订单id查询快递员任务
*
* @param orderId 订单id
* @return 快递员任务列表
*/
List<CourierTaskEntity> findByOrderId(Long orderId);
}

View File

@@ -0,0 +1,7 @@
_ ${spring.application.name} ${application.version}
___ | | ___ __ __ _ __ _ __ ___ ___ ___ Port: ${server.port}
/ __|| | _____ / _ \\ \/ /| '_ \ | '__|/ _ \/ __|/ __| Pid: ${pid} Profile(s): ${AnsiColor.GREEN}${spring.profiles.active}${AnsiColor.DEFAULT}
\__ \| ||_____|| __/ > < | |_) || | | __/\__ \\__ \
|___/|_| \___|/_/\_\| .__/ |_| \___||___/|___/ https://sl-express.itheima.net/
|_|

View File

@@ -0,0 +1,22 @@
server:
port: 18099
tomcat:
uri-encoding: UTF-8
threads:
max: 1000
min-spare: 30
spring:
cloud:
nacos:
username: nacos
password: nacos
server-addr: 192.168.150.101:8848
discovery:
namespace: ecae68ba-7b43-4473-a980-4ddeb6157bdc
ip: 192.168.150.1
config:
namespace: ecae68ba-7b43-4473-a980-4ddeb6157bdc
shared-configs: #共享配置
- data-id: shared-spring-rabbitmq.yml
group: SHARED_GROUP
refresh: false

View File

@@ -0,0 +1,21 @@
server:
port: 18099
tomcat:
uri-encoding: UTF-8
threads:
max: 1000
min-spare: 30
spring:
cloud:
nacos:
username: nacos
password: vO5/dZ9,iL
server-addr: nacos-service.yjy-public-slwl-java-prod.svc.cluster.local:8848
discovery:
namespace: 92312ba8-1119-440f-81af-c29618df303b
config:
namespace: 92312ba8-1119-440f-81af-c29618df303b
shared-configs: #共享配置
- data-id: shared-spring-rabbitmq.yml
group: SHARED_GROUP
refresh: false

View File

@@ -0,0 +1,21 @@
server:
port: 18099
tomcat:
uri-encoding: UTF-8
threads:
max: 1000
min-spare: 30
spring:
cloud:
nacos:
username: nacos
password: nacos
server-addr: 192.168.150.101:8848
discovery:
namespace: ecae68ba-7b43-4473-a980-4ddeb6157bdc
config:
namespace: ecae68ba-7b43-4473-a980-4ddeb6157bdc
shared-configs: #共享配置
- data-id: shared-spring-rabbitmq.yml
group: SHARED_GROUP
refresh: false

View File

@@ -0,0 +1,21 @@
server:
port: 18099
tomcat:
uri-encoding: UTF-8
threads:
max: 1000
min-spare: 30
spring:
cloud:
nacos:
username: nacos
password: nacos
server-addr: nacos-service.yjy-public-slwl-java.svc.cluster.local:8848
discovery:
namespace: 92312ba8-1119-440f-81af-c29618df303b
config:
namespace: 92312ba8-1119-440f-81af-c29618df303b
shared-configs: #共享配置
- data-id: shared-spring-rabbitmq.yml
group: SHARED_GROUP
refresh: false

View File

@@ -0,0 +1,23 @@
application:
version: v1.0
logging:
config: classpath:logback-spring.xml
spring:
application:
name: sl-express-ms-search
profiles:
active: local
mvc:
pathmatch:
#解决异常swagger Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException
#因为Springfox使用的路径匹配是基于AntPathMatcher的而Spring Boot 2.6.X使用的是PathPatternMatcher
matching-strategy: ant_path_matcher
sl:
swagger:
package-path: com.sl.ms.search.controller
title: 神领物流 - 搜索微服务接口文档
description: 该微服务完成搜索相关业务。
contact-name: 传智教育·研究院
contact-url: http://www.itcast.cn/
contact-email: yjy@itcast.cn
version: ${application.version}

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--scan: 当此属性设置为true时配置文件如果发生改变将会被重新加载默认值为true。-->
<!--scanPeriod: 设置监测配置文件是否有修改的时间间隔如果没有给出时间单位默认单位是毫秒。当scan为true时此属性生效。默认的时间间隔为1分钟。-->
<!--debug: 当此属性设置为true时将打印出logback内部日志信息实时查看logback运行状态。默认值为false。-->
<configuration debug="false" scan="false" scanPeriod="60 seconds">
<springProperty scope="context" name="appName" source="spring.application.name"/>
<!--文件名-->
<property name="logback.appname" value="${appName}"/>
<!--文件位置-->
<property name="logback.logdir" value="/data/logs"/>
<!-- 定义控制台输出 -->
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} - [%thread] - %-5level - %logger{50} - %msg%n</pattern>
</layout>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
<File>${logback.logdir}/${logback.appname}/${logback.appname}.log</File>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${logback.logdir}/${logback.appname}/${logback.appname}.%d{yyyy-MM-dd}.log.zip</FileNamePattern>
<maxHistory>90</maxHistory>
</rollingPolicy>
<encoder>
<charset>UTF-8</charset>
<pattern>%d [%thread] %-5level %logger{36} %line - %msg%n</pattern>
</encoder>
</appender>
<!--evel:用来设置打印级别大小写无关TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF-->
<!--不能设置为INHERITED或者同义词NULL。默认是DEBUG。-->
<root level="INFO">
<appender-ref ref="stdout"/>
</root>
</configuration>

View File

@@ -0,0 +1,718 @@
package com.sl.ms.search;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SuggestMode;
import co.elastic.clients.elasticsearch._types.query_dsl.Operator;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch._types.query_dsl.TermsQueryField;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.sql.QueryResponse;
import co.elastic.clients.json.JsonData;
import com.sl.ms.search.entity.Person;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* 复杂查询相关
**/
@Slf4j
@SpringBootTest
class ComplexSearchTest {
@Resource
private ElasticsearchClient client;
private static final String INDEX_NAME = "person";
/**
* term查询传参为函数
*
* @throws IOException IO异常
*/
@Test
void searchTerm01() throws IOException {
// 1.构造查询条件,并进行查询(此处search方法传参为lambda函数)
SearchResponse<Person> response = client.search(s -> s
.index(INDEX_NAME)
.query(q -> q
.term(t -> t
.field("name")
.value("张三")))
, Person.class);
// 2.打印查询结果
log.info(response.toString());
}
/**
* term查询传参为对象
*
* @throws IOException IO异常
*/
@Test
void searchTerm02() throws IOException {
// 1.构造查询条件,并进行查询
Query query = new Query.Builder().term(t -> t
.field("name")
.value("张三")).build();
SearchRequest searchRequest = new SearchRequest.Builder()
.index(INDEX_NAME)
.query(query).build();
// 此处search方法传参为SearchRequest对象
SearchResponse<Person> response = client.search(searchRequest, Person.class);
// 2.打印查询结果
log.info(response.toString());
}
/**
* terms查询传参为函数
*
* @throws IOException IO异常
*/
@Test
void searchTerms01() throws IOException {
// 1.构造查询条件,并进行查询
List<FieldValue> values = new ArrayList<>();
values.add(new FieldValue.Builder().stringValue("20").build());
values.add(new FieldValue.Builder().stringValue("21").build());
// 此处search方法传参为lambda函数
SearchResponse<Person> response = client.search(s -> s
.index(INDEX_NAME)
.query(q -> q
.terms(t -> t
.field("age")
.terms(f -> f.value(values))))
, Person.class);
// 2.打印查询结果
log.info(response.toString());
}
/**
* terms查询传参为对象
*
* @throws IOException IO异常
*/
@Test
void searchTerms02() throws IOException {
// 1.构造查询条件,并进行查询
List<FieldValue> values = new ArrayList<>();
values.add(new FieldValue.Builder().stringValue("20").build());
values.add(new FieldValue.Builder().stringValue("21").build());
TermsQueryField termsQueryField = new TermsQueryField.Builder().value(values).build();
Query query = new Query.Builder().terms(t -> t
.field("age")
.terms(termsQueryField)).build();
SearchRequest searchRequest = new SearchRequest.Builder()
.index(INDEX_NAME)
.query(query).build();
// 此处search方法传参为SearchRequest对象
SearchResponse<Person> response = client.search(searchRequest, Person.class);
// 2.打印查询结果
log.info(response.toString());
}
/**
* range查询传参为函数
*
* @throws IOException IO异常
*/
@Test
void searchRange01() throws IOException {
// 此处search方法传参为lambda函数
SearchResponse<Person> response = client.search(s -> s
.index(INDEX_NAME)
.query(q -> q
.range(r -> r
.field("age")
.gte(JsonData.of("20"))
.lt(JsonData.of("40"))))
, Person.class);
log.info(response.toString());
}
/**
* range查询传参为对象
*
* @throws IOException IO异常
*/
@Test
void searchRange02() throws IOException {
// 1.构造查询条件,并进行查询
Query query = new Query.Builder().range(r -> r
.field("age")
.gte(JsonData.of("20"))
.lt(JsonData.of("40"))).build();
SearchRequest searchRequest = new SearchRequest.Builder()
.index(INDEX_NAME)
.query(query).build();
// 此处search方法传参为SearchRequest对象
SearchResponse<Person> response = client.search(searchRequest, Person.class);
// 2.打印查询结果
log.info(response.toString());
}
/**
* match查询传参为函数
* match 查询,对输入内容先分词再查询
*
* @throws IOException IO异常
*/
@Test
void searchMatch01() throws IOException {
// 此处search方法传参为lambda函数
SearchResponse<Person> response = client.search(s -> s
.index(INDEX_NAME)
.query(q -> q
.match(m -> m
.field("address")
.query("昌平区天通苑")))
, Person.class);
log.info(response.toString());
}
/**
* match查询传参为对象
* match 查询,对输入内容先分词再查询
*
* @throws IOException IO异常
*/
@Test
void searchMatch02() throws IOException {
// 1.构造查询条件,并进行查询
Query query = new Query.Builder().match(m -> m
.field("address")
.query("昌平区天通苑")).build();
SearchRequest searchRequest = new SearchRequest.Builder()
.index(INDEX_NAME)
.query(query).build();
// 此处search方法传参为SearchRequest对象
SearchResponse<Person> response = client.search(searchRequest, Person.class);
// 2.打印查询结果
log.info(response.toString());
}
/**
* multi_match查询传参为函数
*
* @throws IOException IO异常
*/
@Test
void searchMultiMatch01() throws IOException {
// 此处search方法传参为lambda函数
SearchResponse<Person> response = client.search(s -> s
.index(INDEX_NAME)
.query(q -> q
.multiMatch(m -> m
.fields("name", "address")
.query("顺义")))
, Person.class);
log.info(response.toString());
}
/**
* multi_match查询传参为对象
*
* @throws IOException IO异常
*/
@Test
void searchMultiMatch02() throws IOException {
// 1.构造查询条件,并进行查询
Query query = new Query.Builder().multiMatch(m -> m
.fields("name", "address")
.query("顺义")).build();
SearchRequest searchRequest = new SearchRequest.Builder()
.index(INDEX_NAME)
.query(query).build();
// 此处search方法传参为SearchRequest对象
SearchResponse<Person> response = client.search(searchRequest, Person.class);
// 2.打印查询结果
log.info(response.toString());
}
/**
* matchPhrase查询传参为函数
*
* @throws IOException IO异常
*/
@Test
void searchMatchPhrase01() throws IOException {
// 此处search方法传参为lambda函数
SearchResponse<Person> response = client.search(s -> s
.index(INDEX_NAME)
.query(q -> q
.matchPhrase(m -> m
.field("address")
.query("金燕龙")))
, Person.class);
log.info(response.toString());
}
/**
* matchPhrase查询传参为对象
*
* @throws IOException IO异常
*/
@Test
void searchMatchPhrase02() throws IOException {
// 1.构造查询条件,并进行查询
Query query = new Query.Builder().matchPhrase(m -> m
.field("address")
.query("金燕龙")).build();
SearchRequest searchRequest = new SearchRequest.Builder()
.index(INDEX_NAME)
.query(query).build();
// 此处search方法传参为SearchRequest对象
SearchResponse<Person> response = client.search(searchRequest, Person.class);
// 2.打印查询结果
log.info(response.toString());
}
/**
* match_all 查询(传参为函数)
*
* @throws IOException IO异常
*/
@Test
void searchMatchAll01() throws IOException {
// 此处search方法传参为lambda函数
SearchResponse<Person> response = client.search(s -> s
.index(INDEX_NAME)
.query(q -> q
.matchAll(m -> m))
, Person.class);
log.info(response.toString());
}
/**
* match_all 查询(传参为对象)
*
* @throws IOException IO异常
*/
@Test
void searchMatchAll02() throws IOException {
// 1.构造查询条件,并进行查询
Query query = new Query.Builder().matchAll(m -> m).build();
SearchRequest searchRequest = new SearchRequest.Builder()
.index(INDEX_NAME)
.query(query).build();
// 此处search方法传参为SearchRequest对象
SearchResponse<Person> response = client.search(searchRequest, Person.class);
// 2.打印查询结果
log.info(response.toString());
}
/**
* query_string查询等同于match
*
* @throws IOException IO异常
*/
@Test
void searchQueryString01() throws IOException {
// 类似 match
SearchResponse<Person> response = client.search(s -> s
.index(INDEX_NAME)
.query(q -> q
.queryString(qs -> qs
.defaultField("address")
.query("昌平区天通苑")))
, Person.class);
log.info(response.toString());
}
/**
* query_string查询等同于match_phrase
*
* @throws IOException IO异常
*/
@Test
void searchQueryString02() throws IOException {
// 类似 match_phrase
SearchResponse<Person> response = client.search(s -> s
.index(INDEX_NAME)
.query(q -> q
.queryString(qs -> qs
.defaultField("address")
.query("\"昌平区天通苑\"")))
, Person.class);
log.info(response.toString());
}
/**
* query_string查询运算符查询
*
* @throws IOException IO异常
*/
@Test
void searchQueryString03() throws IOException {
// 带运算符查询,运算符两边的词不再分词
// 查询同时包含 ”昌平区“ 和 ”金燕龙“ 的文档
SearchResponse<Person> response = client.search(s -> s
.index(INDEX_NAME)
.query(q -> q
.queryString(qs -> qs
.fields("address")
// 如果不写 AND 之间是空格默认是OR
.query("昌平区 AND 天通苑")))
, Person.class);
log.info(response.toString());
}
/**
* query_string查询同上一个
*
* @throws IOException IO异常
*/
@Test
void searchQueryString04() throws IOException {
// 查询同时包含 ”昌平区“ 和 ”金燕龙“ 的文档
SearchResponse<Person> response = client.search(s -> s
.index(INDEX_NAME)
.query(q -> q
.queryString(qs -> qs
.fields("address")
.query("昌平区 金燕龙")
.defaultOperator(Operator.And)))
, Person.class);
log.info(response.toString());
}
/**
* query_string查询复合运用
*
* @throws IOException IO异常
*/
@Test
void searchQueryString05() throws IOException {
// 查询 name 或 address 字段包含"昌平区"和"金燕龙"这两个单词,或者包含"李四"这个单词的文档。
SearchResponse<Person> response = client.search(s -> s
.index(INDEX_NAME)
.query(q -> q
.queryString(qs -> qs
.fields("name", "address")
.query("(昌平区 AND 金燕龙) OR 李四")))
, Person.class);
log.info(response.toString());
}
/**
* simple_query_string 查询
* <p>
* 类似 query_string主要区别如下<p>
* 1、不支持AND OR NOT ,会当做字符处理<p>
* 2、使用 + 代替 AND| 代替OR- 代替 NOT<p>
* 3、term之间默认关系为OR<p>
* 4、会忽略错误的语法
*
* @throws IOException IO异常
*/
@Test
void searchSimpleQueryString() throws IOException {
// 查询同时包含 ”昌平区“ 和 ”金燕龙“ 的文档
SearchResponse<Person> response = client.search(s -> s
.index(INDEX_NAME)
.query(q -> q
.simpleQueryString(sqs -> sqs
.fields("address")
.query("昌平区 AND 天通苑")))
, Person.class);
log.info(response.toString());
}
/**
* 模糊查询(使用match)
*
* @throws IOException IO查询
*/
@Test
void searchFuzzy01() throws IOException {
// 全文查询时使用模糊参数,先分词再计算模糊选项。
SearchResponse<Person> response = client.search(s -> s
.index(INDEX_NAME)
.query(q -> q
.match(m -> m
.field("address")
// 中英文的分词不一样,尽量用英文测试
.query("Pennsylvania Avenue NN")
.fuzziness("1")))
, Person.class);
log.info(response.toString());
}
/**
* 模糊查询使用fuzzy
*
* @throws IOException IO查询
*/
@Test
void searchFuzzy02() throws IOException {
// 使用 fuzzy query对输入不分词直接计算模糊选项。
SearchResponse<Person> response = client.search(s -> s
.index(INDEX_NAME)
.query(q -> q
.fuzzy(f -> f
.field("name")
.value("Joa Biden")
.fuzziness("1")))
, Person.class);
log.info(response.toString());
}
/**
* 组合查询(must)
*
* @throws IOException IO异常
*/
@Test
void searchBool() throws IOException {
// 查询 name 包含 “张三” 且 age 在 [16-40] 之间的文档
SearchResponse<Person> response = client.search(s -> s
.index(INDEX_NAME)
.query(q -> q
.bool(b -> b
.must(q1 -> q1
.match(m -> m
.field("name")
.query("张三"))
)
.must(q2 -> q2
.range(r -> r
.field("age")
.gte(JsonData.of("16"))
.lte(JsonData.of("40")))
)
)
)
, Person.class);
log.info(response.toString());
}
/**
* 组合查询(filter)
*
* @throws IOException IO异常
*/
@Test
void searchFilter() throws IOException {
// 过滤出 name 包含 “张三” 且 age 在 [16-40] 之间的文档,不计算得分
SearchResponse<Person> response = client.search(s -> s
.index(INDEX_NAME)
.query(q -> q
.bool(b -> b
.filter(q1 -> q1
.match(m -> m
.field("name")
.query("张三"))
)
.filter(q2 -> q2
.range(r -> r
.field("age")
.gte(JsonData.of("16"))
.lte(JsonData.of("40")))
)
)
)
, Person.class);
log.info(response.toString());
}
/**
* 聚合查询(求和)
*
* @throws IOException IO异常
*/
@Test
void searchAggs01() throws IOException {
// 求和
SearchResponse<Person> response = client.search(s -> s
.index(INDEX_NAME)
.aggregations("age_sum", a -> a
.sum(sa -> sa
.field("age")))
, Person.class);
log.info(response.toString());
}
/**
* 聚合查询distinct计数
*
* @throws IOException IO异常
*/
@Test
void searchAggs02() throws IOException {
// 类似 select count distinct(age) from person
SearchResponse<Person> response = client.search(s -> s
.index(INDEX_NAME)
.aggregations("age_count", a -> a
.cardinality(ca -> ca.field("age")))
, Person.class);
log.info(response.toString());
}
/**
* 聚合查询(统计)
*
* @throws IOException IO异常
*/
@Test
void searchAggs03() throws IOException {
// 数量、最大、最小、平均、求和
SearchResponse<Person> response = client.search(s -> s
.index(INDEX_NAME)
.aggregations("age_stats", a -> a
.stats(sa -> sa
.field("age")))
, Person.class);
log.info(response.toString());
}
/**
* 聚合查询group统计
*
* @throws IOException IO异常
*/
@Test
void searchAggs04() throws IOException {
// select name,count(*) from person group by name
SearchResponse<Person> response = client.search(s -> s
.index(INDEX_NAME)
.aggregations("name_terms", a -> a
.terms(ta -> ta
.field("name")))
, Person.class);
log.info(response.toString());
}
/**
* 聚合查询(嵌套计数)
*
* @throws IOException IO异常
*/
@Test
void searchAggs05() throws IOException {
// select name,age,count(*) from person group by name,age
SearchResponse<Person> response = client.search(s -> s
.index(INDEX_NAME)
.aggregations("name_terms", a -> a
.terms(ta -> ta
.field("name")
)
.aggregations("age_terms", a1 -> a1
.terms(ta1 -> ta1
.field("age")
))
)
, Person.class);
log.info(response.toString());
}
/**
* 复杂聚合查询
*
* @throws IOException IO异常
*/
@Test
void searchAggs06() throws IOException {
// 类似 select avg(age) from person where name='李四'
SearchResponse<Person> response = client.search(s -> s
.index(INDEX_NAME)
.query(q -> q
.bool(b -> b
.filter(q1 -> q1
.term(t -> t
.field("name")
.value("李四")))))
.aggregations("ave_age", a -> a
.avg(aa -> aa.field("age")))
, Person.class);
log.info(response.toString());
}
/**
* 推荐搜索
* 注意:该测试会报 JsonpMappingException 错误该bug官方会在8.4.1版本修复
*
* @throws IOException IO异常
*/
@Test
void searchSuggest() throws IOException {
SearchResponse<Person> response = client.search(s -> s
.index(INDEX_NAME)
.suggest(suggest -> suggest
.suggesters("address_suggest", fs -> fs
.text("金燕龙")
.term(ts -> ts
.field("address")
.suggestMode(SuggestMode.Always)
.minWordLength(2)
)
)
)
, Person.class);
log.info(response.toString());
}
/**
* 高亮显示
*
* @throws IOException IO异常
*/
@Test
void searchHighlight() throws IOException {
SearchResponse<Person> response = client.search(s -> s
.index(INDEX_NAME)
.query(q -> q
.match(m -> m
.field("address")
.query("昌平区")))
.highlight(h -> h
.preTags("<span color='red'>")
.postTags("</span>")
.fields("address", hf -> hf))
, Person.class);
log.info(response.toString());
}
/**
* SQL 查询
* 注意:该测试会报 JsonpMappingException 错误该bug官方会在8.4.1版本修复
*
* @throws IOException IO异常
*/
@Test
void searchSql() throws IOException {
QueryResponse response = client.sql().query(q -> q
.format("json")
.query("SELECT * FROM \"" + INDEX_NAME + "\" limit 3"));
log.info(response.toString());
}
}

View File

@@ -0,0 +1,398 @@
package com.sl.ms.search;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch.core.*;
import co.elastic.clients.elasticsearch.core.bulk.BulkResponseItem;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.TotalHits;
import co.elastic.clients.elasticsearch.core.search.TotalHitsRelation;
import com.sl.SearchApplication;
import com.sl.ms.search.entity.Person;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* ES文档相关测试
**/
@Slf4j
@SpringBootTest(classes = SearchApplication.class)
class DocumentTest {
@Resource
private ElasticsearchClient client;
private static final String INDEX_NAME = "person";
/**
* 新增或更新文档(传参为函数)
* 注意:这里的更新是全量更新,原来没有的字段会增加,没有数据的字段会删掉
*
* @throws IOException IO异常
*/
@Test
void createOrUpdateDoc01() throws IOException {
// 1.构造对象
Person person = new Person(18, "张三", "18912345678", "北京市昌平区西三旗街道金燕龙办公楼");
// 2.新增或更新文档数据构造函数使用lambda表达式形式
IndexResponse response = client.index(i -> i
.index(INDEX_NAME)
.id("1")
// 此处传参也可以是map
.document(person));
// 3.version为1表示新增大于1表示更新
log.info("Indexed with version " + response.version());
}
/**
* 新增或更新文档(传参为对象)
* 注意:这里的更新是全量更新,原来没有的字段会增加,没有数据的字段会删掉
*
* @throws IOException IO异常
*/
@Test
void createOrUpdateDoc02() throws IOException {
// 1.构造对象
Person person = new Person(18, "张三", "18912345678", "北京市昌平区西三旗街道金燕龙办公楼");
// 2.使用DSL语法创建对象进行新增或更新
IndexRequest<Person> request = IndexRequest.of(i -> i
.index(INDEX_NAME)
.id("2")
// 此处传参也可以是map
.document(person));
IndexResponse response = client.index(request);
// 3.version为1表示新增大于1表示更新
log.info("Indexed with version " + response.version());
}
/**
* 新增文档(使用对象)
*
* @throws IOException IO异常
*/
@Test
void createDoc01() throws IOException {
// 1.构造对象
Person person = new Person(18, "张三", "18912345678", "北京市昌平区西三旗街道金燕龙办公楼");
// 2.新增文档数据若重复新增则会报错错误码409
CreateResponse response = client.create(c -> c
.index(INDEX_NAME)
.id("3")
.document(person));
// 3.响应结果
log.info(response.toString());
}
/**
* 新增文档使用map
*
* @throws IOException IO异常
*/
@Test
void createDoc02() throws IOException {
// 1.数据封装到map
Map<String, Object> doc = new HashMap<>();
doc.put("age", 78);
doc.put("name", "Joe Biden");
doc.put("phone", "1-202-456-1414");
doc.put("address", "1600 Pennsylvania Avenue NW, Washington, DC 20500");
// 2.新增文档数据若重复新增则会报错错误码409
CreateResponse response = client.create(c -> c
.index(INDEX_NAME)
.id("4")
.document(doc));
// 3.响应结果
log.info(response.toString());
}
/**
* 批量新增
*
* @throws IOException IO异常
*/
@Test
void bulkCreateDoc() throws IOException {
// 1.构造数据
List<Person> personList = new ArrayList<>();
for (int i = 0; i < 5; i++) {
Person person = new Person(18 + i, "李四", "18912345678", "北京市顺义区京顺路99号");
personList.add(person);
}
// 2.循环添加新增操作
int i = 5;
BulkRequest.Builder br = new BulkRequest.Builder();
for (Person person : personList) {
String id = String.valueOf(i++);
br.operations(op -> op.index(idx -> idx
.index(INDEX_NAME)
.id(id)
.document(person)));
}
// 3.批量新增
BulkResponse result = client.bulk(br.build());
// 4.如果出现错误,输出所有错误信息
if (result.errors()) {
log.error("Bulk had errors");
for (BulkResponseItem item : result.items()) {
if (item.error() != null) {
log.error(item.error().reason());
}
}
}
}
/**
* 更新文档(使用对象,增量更新)
*
* @throws IOException IO异常
*/
@Test
void updateDoc01() throws IOException {
// 1.构造数据传入为null的字段不会更新
Person person = new Person(20, null, null, null);
// 2.数据更新
UpdateResponse<Person> response = client.update(u -> u
.index(INDEX_NAME)
.id("1")
.doc(person)
, Person.class);
// 3.响应结果
log.info(response.toString());
}
/**
* 更新文档使用map增量更新
*
* @throws IOException IO异常
*/
@Test
void updateDoc02() throws IOException {
// 1.构造数据sex是原来不存在的字段该方法会新增这个字段
Map<String, Object> doc = new HashMap<>();
doc.put("age", 33);
doc.put("sex", "");
// 2.数据更新
UpdateResponse response = client.update(builder -> builder
.index(INDEX_NAME)
.id("1")
.doc(doc)
, Map.class);
// 3.响应结果
log.info(response.toString());
}
/**
* 删除文档
*
* @throws IOException IO异常
*/
@Test
void deleteDoc() throws IOException {
DeleteResponse response = client.delete(d -> d
.index(INDEX_NAME)
.id("1"));
log.info(response.toString());
}
/**
* 条件删除文档(传参为函数)
*
* @throws IOException IO异常
*/
@Test
void deleteByQueryDoc01() throws IOException {
// deleteByQuery方法传参为lambda函数
DeleteByQueryResponse response = client.deleteByQuery(d -> d.index(INDEX_NAME)
.query(q -> q
.match(m -> m
.field("age")
.query("20"))));
log.info(response.toString());
}
/**
* 条件删除文档(传参为对象)
*
* @throws IOException IO异常
*/
@Test
void deleteByQueryDoc02() throws IOException {
// 构造删除条件
Query query = new Query.Builder().match(m -> m
.field("age")
.query("20")).build();
DeleteByQueryRequest searchRequest = new DeleteByQueryRequest.Builder()
.index(INDEX_NAME)
.query(query).build();
// 此处search方法传参为DeleteByQueryRequest对象
DeleteByQueryResponse response = client.deleteByQuery(searchRequest);
log.info(response.toString());
}
/**
* 查询所有文档(用对象接收)
*
* @throws IOException IO异常
*/
@Test
void getDocAll01() throws IOException {
SearchResponse<Person> response = client.search(s -> s.index(INDEX_NAME), Person.class);
log.info(response.toString());
}
/**
* 查询所有文档用map接收
*
* @throws IOException IO异常
*/
@Test
void getDocAll02() throws IOException {
SearchResponse<Map> response = client.search(s -> s.index(INDEX_NAME), Map.class);
log.info(response.toString());
}
/**
* 查询单个文档(使用对象接收)
*
* @throws IOException IO异常
*/
@Test
void getDoc01() throws IOException {
// 1.查询id为2的文档
GetResponse<Person> response = client.get(g -> g.index(INDEX_NAME).id("2"), Person.class);
// 2.判断响应数据是否为空
if (response.found()) {
Person person = response.source();
assert person != null;
log.info("Person name " + person.getName());
} else {
log.info("Person not found");
}
}
/**
* 查询单个文档(使用map接收)
*
* @throws IOException IO异常
*/
@Test
void getDoc02() throws IOException {
// 1.查询id为2的文档
GetResponse<Map> response = client.get(g -> g.index(INDEX_NAME).id("2"), Map.class);
// 2.判断响应数据是否为空
if (response.found()) {
Map person = response.source();
assert person != null;
log.info("Person name " + person.get("name"));
} else {
log.info("Person not found");
}
}
/**
* 简单查询(传入函数)
*
* @throws IOException IO异常
*/
@Test
void simpleSearch01() throws IOException {
// 1.构造查询条件,并进行查询(此处search方法传参为lambda函数)
SearchResponse<Person> response = client.search(s -> s.index(INDEX_NAME)
.query(q -> q
.match(m -> m
.field("name")
.query("张三")))
, Person.class);
// 2.查询结果数量
TotalHits total = response.hits().total();
assert total != null;
// 默认情况下hits.total.value是不确切的命中计数
// 在这种情况下当hits.total.relation的值是eq时hits.total.value的值是准确计数。
// 当hits.total.relation的值是gte时hits.total.value的值是不准确的。
boolean isExactResult = total.relation() == TotalHitsRelation.Eq;
if (isExactResult) {
log.info("There are " + total.value() + " results");
} else {
log.info("There are more than " + total.value() + " results");
}
// 3.解析hit结构输出全部结果
List<Hit<Person>> hits = response.hits().hits();
for (Hit<Person> hit : hits) {
Person person = hit.source();
assert person != null;
log.info("Found person " + person.getName() + ", score " + hit.score());
}
}
/**
* 简单查询(传入对象)
*
* @throws IOException IO异常
*/
@Test
void simpleSearch02() throws IOException {
// 1.构造查询条件,并进行查询
Query query = new Query.Builder().match(m -> m
.field("name")
.query("张三")).build();
SearchRequest searchRequest = new SearchRequest.Builder()
.index(INDEX_NAME)
.query(query).build();
// 此处search方法传参为SearchRequest对象
SearchResponse<Person> response = client.search(searchRequest, Person.class);
// 2.查询结果数量
TotalHits total = response.hits().total();
assert total != null;
// 默认情况下hits.total.value是不确切的命中计数
// 在这种情况下当hits.total.relation的值是eq时hits.total.value的值是准确计数。
// 当hits.total.relation的值是gte时hits.total.value的值是不准确的。
boolean isExactResult = total.relation() == TotalHitsRelation.Eq;
if (isExactResult) {
log.info("There are " + total.value() + " results");
} else {
log.info("There are more than " + total.value() + " results");
}
// 3.解析hit结构输出全部结果
List<Hit<Person>> hits = response.hits().hits();
for (Hit<Person> hit : hits) {
Person person = hit.source();
assert person != null;
log.info("Found person " + person.getName() + ", score " + hit.score());
}
}
}

View File

@@ -0,0 +1,114 @@
package com.sl.ms.search;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.indices.CreateIndexResponse;
import co.elastic.clients.elasticsearch.indices.DeleteIndexResponse;
import co.elastic.clients.elasticsearch.indices.GetIndexResponse;
import co.elastic.clients.elasticsearch.indices.PutMappingResponse;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import java.io.IOException;
/**
* ES索引相关测试
**/
@Slf4j
@SpringBootTest
class IndexTest {
@Resource
private ElasticsearchClient client;
private static final String INDEX_NAME = "person";
/**
* 创建索引
*
* @throws IOException IO异常
*/
@Test
void createIndex() throws IOException {
CreateIndexResponse response = client.indices().create(builder -> builder
// 设置索引分片number_of_shards主分片数默认为1number_of_replicas副本分片数默认为1
// 合理设置索引分片可以提高ES的查询性能
.settings(indexSettingsBuilder -> indexSettingsBuilder.numberOfReplicas("1").numberOfShards("2"))
.mappings(typeMappingBuilder -> typeMappingBuilder
.properties("age", propertyBuilder -> propertyBuilder.integer(integerNumberPropertyBuilder -> integerNumberPropertyBuilder))
.properties("name", propertyBuilder -> propertyBuilder.keyword(keywordPropertyBuilder -> keywordPropertyBuilder))
.properties("phone", propertyBuilder -> propertyBuilder.text(textPropertyBuilder -> textPropertyBuilder.analyzer("whitespace").searchAnalyzer("whitespace")))
.properties("address", propertyBuilder -> propertyBuilder.text(textPropertyBuilder -> textPropertyBuilder.analyzer("ik_max_word").searchAnalyzer("ik_max_word")))
)
.index(INDEX_NAME));
log.info("索引创建是否成功:{}", response.acknowledged());
}
/**
* 修改 _mapping 信息
* 字段可以新增,已有的字段只能修改字段的 search_analyzer 属性。
*
* @throws IOException IO异常
*/
@Test
void modifyIndex() throws IOException {
PutMappingResponse response = client.indices().putMapping(typeMappingBuilder -> typeMappingBuilder
.index(INDEX_NAME)
// 已有字段和之前一样,则不变
.properties("age", propertyBuilder -> propertyBuilder.integer(integerNumberPropertyBuilder -> integerNumberPropertyBuilder))
// 已有字段修改search_analyzer属性
.properties("address", propertyBuilder -> propertyBuilder.text(textPropertyBuilder -> textPropertyBuilder.analyzer("ik_max_word").searchAnalyzer("ik_smart")))
// 新增字段
.properties("sex", propertyBuilder -> propertyBuilder.integer(integerNumberPropertyBuilder -> integerNumberPropertyBuilder))
);
log.info("修改索引是否成功:{}", response.acknowledged());
}
/**
* 删除索引
*
* @throws IOException IO异常
*/
@Test
void deleteIndex() throws IOException {
DeleteIndexResponse response = client.indices().delete(builder -> builder.index(INDEX_NAME));
log.info("删除索引是否成功:{}", response.acknowledged());
}
/**
* 查询索引列表
*
* @throws IOException IO异常
*/
@Test
void getIndex() throws IOException {
// 此处使用 * 也可以
// 同时需要注意,在反序列化时某些索引的属性无法识别,该方法可能会执行失败
GetIndexResponse response = client.indices().get(builder -> builder.index("_all"));
log.info(response.result().toString());
}
/**
* 查询索引是否存在
*
* @throws IOException IO异常
*/
@Test
void existsIndex() throws IOException {
boolean exists = client.indices().exists(e -> e.index(INDEX_NAME)).value();
log.info("person索引是否存在{}", exists);
}
/**
* 查询索引详情
*
* @throws IOException IO异常
*/
@Test
void getIndexDetail() throws IOException {
// 该方法需要查询ES中存在的索引否则会报错
GetIndexResponse response = client.indices().get(builder -> builder.index(INDEX_NAME));
log.info(response.result().toString());
}
}

View File

@@ -0,0 +1,151 @@
package com.sl.ms.search;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.BulkRequest;
import com.sl.ms.oms.api.OrderFeign;
import com.sl.ms.oms.dto.OrderDTO;
import com.sl.ms.search.entity.CourierTaskEntity;
import com.sl.ms.work.api.PickupDispatchTaskFeign;
import com.sl.ms.work.api.TransportOrderFeign;
import com.sl.ms.work.domain.dto.PickupDispatchTaskDTO;
import com.sl.ms.work.domain.dto.TransportOrderDTO;
import com.sl.ms.work.domain.enums.pickupDispatchtask.PickupDispatchTaskStatus;
import com.sl.ms.work.domain.enums.pickupDispatchtask.PickupDispatchTaskType;
import com.sl.transport.common.exception.SLException;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* ES初始化数据
**/
@Slf4j
@SpringBootTest
class InitTest {
@Resource
private ElasticsearchClient client;
@Resource
private OrderFeign orderFeign;
@Resource
private PickupDispatchTaskFeign pickupDispatchTaskFeign;
@Resource
private TransportOrderFeign transportOrderFeign;
@Value("${sl.es.index_name}")
private String indexName;
/**
* 初始化同步数据到es
*/
@Test
void init() throws IOException {
// 1.删除所有文档(不需要删除的话,注释这部分代码)
client.deleteByQuery(d -> d
.index(indexName)
.query(q -> q
.matchAll(m -> m)));
// 2.查询全部取派件任务
List<PickupDispatchTaskDTO> items = pickupDispatchTaskFeign.findAll(null, null, null, null);
// 3.根据订单id列表批量查询订单
List<String> orderIds = items.stream().map(item -> item.getOrderId().toString()).collect(Collectors.toList());
List<OrderDTO> orderDTOS = orderFeign.findByIds(orderIds);
Map<Long, OrderDTO> map = orderDTOS.stream().collect(Collectors.toMap(OrderDTO::getId, dto -> dto));
// 4.数据封装为entity
List<CourierTaskEntity> entities = new ArrayList<>();
for (PickupDispatchTaskDTO item : items) {
// 封装快递员任务对象
CourierTaskEntity courierTaskEntity = this.getCourierTaskEntity(item, map);
// 封装好的对象,添加到快递员任务列表
entities.add(courierTaskEntity);
}
// 5.循环添加新增操作
BulkRequest.Builder br = new BulkRequest.Builder();
for (CourierTaskEntity entity : entities) {
br.operations(op -> op.index(idx -> idx
.index(indexName)
.id(String.valueOf(entity.getId()))
.document(entity)));
}
// 6.批量新增
client.bulk(br.build());
}
/**
* 封装快递员任务对象
*
* @param pickupDispatchTaskDTO 取派件任务
* @return 快递员任务
*/
private CourierTaskEntity getCourierTaskEntity(PickupDispatchTaskDTO pickupDispatchTaskDTO, Map<Long, OrderDTO> map) {
// 1.取派件任务字段复制到快递员任务
CourierTaskEntity courierTaskEntity = BeanUtil.toBean(pickupDispatchTaskDTO, CourierTaskEntity.class);
courierTaskEntity.setTaskType(pickupDispatchTaskDTO.getTaskType().getCode());
courierTaskEntity.setStatus(pickupDispatchTaskDTO.getStatus().getCode());
courierTaskEntity.setIsDeleted(pickupDispatchTaskDTO.getIsDeleted().getCode());
// 2.根据id查询订单
OrderDTO orderDTO = map.get(pickupDispatchTaskDTO.getOrderId());
if (ObjectUtil.isEmpty(orderDTO)) {
String errorMsg = CharSequenceUtil.format("id为{}的取派件任务的订单不存在!", pickupDispatchTaskDTO.getId());
throw new SLException(errorMsg);
}
// 3.设置快递员任务的姓名、电话、地址字段
this.setNameAndPhoneAndAddress(pickupDispatchTaskDTO.getTaskType(), orderDTO, courierTaskEntity);
// 为快递员任务set运单号:取件任务新任务状态和已取消状态不查运单
if (ObjectUtil.equal(PickupDispatchTaskType.DISPATCH, pickupDispatchTaskDTO.getTaskType()) || ObjectUtil.equal(PickupDispatchTaskStatus.COMPLETED, pickupDispatchTaskDTO.getStatus())) {
TransportOrderDTO transportOrderDTO = transportOrderFeign.findByOrderId(pickupDispatchTaskDTO.getOrderId());
courierTaskEntity.setTransportOrderId(transportOrderDTO.getId());// 运单id
}
return courierTaskEntity;
}
/**
* 设置快递员任务的姓名、电话、地址字段
*
* @param taskType 任务类型
* @param orderDTO 订单
* @param courierTaskEntity 快递员任务
*/
private void setNameAndPhoneAndAddress(PickupDispatchTaskType taskType, OrderDTO orderDTO, CourierTaskEntity courierTaskEntity) {
String name;
String phone;
String address;
if (taskType.equals(PickupDispatchTaskType.PICKUP)) {
name = orderDTO.getSenderName();
phone = orderDTO.getSenderPhone();
address = orderDTO.getSenderAddress();
} else {
name = orderDTO.getReceiverName();
phone = orderDTO.getReceiverPhone();
address = orderDTO.getReceiverAddress();
}
courierTaskEntity.setAddress(address);
courierTaskEntity.setName(name);
courierTaskEntity.setPhone(phone);
}
@Test
void setOrderFeign() throws IOException {
List<PickupDispatchTaskDTO> items = pickupDispatchTaskFeign.findAll(null, null, null, null);
System.out.println(items);
}
}

View File

@@ -0,0 +1,36 @@
package com.sl.ms.search.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Person对象
**/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Person {
/**
* 年龄
*/
private Integer age;
/**
* 姓名
*/
private String name;
/**
* 电话
*/
private String phone;
/**
* 地址
*/
private String address;
}