package com.yitsd.template;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.mapping.*;
import co.elastic.clients.elasticsearch._types.query_dsl.MatchQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.bulk.BulkOperation;
import co.elastic.clients.elasticsearch.core.search.Highlight;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.indices.get_mapping.IndexMappingRecord;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import com.yitsd.annotations.ElasticSearchField;
import com.yitsd.annotations.ElasticSearchIndex;
import com.yitsd.constant.Constants;
import com.yitsd.entity.dto.QueryDto;
import com.yitsd.exception.ElasticSearchException;
import com.yitsd.exception.FieldException;
import com.yitsd.util.StringUtils;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.SSLContexts;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.util.*;
import java.util.stream.Collectors;
@Configuration
public class ElasticSearchTemplate {
private ElasticsearchClient client;
@Value("${elastic-search.host}")
private String host;
@Value("${elastic-search.port}")
private String port;
@Value("${certs.url}")
private String cert;
@PostConstruct
public void init() throws Exception {
RestClient restClient;
if (host.equals("82.157.165.116")) {
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY,
new UsernamePasswordCredentials("elastic", "password"));
Path caCertificatePath = Paths.get(cert);
CertificateFactory factory =
CertificateFactory.getInstance("X.509");
Certificate trustedCa;
try (InputStream is = Files.newInputStream(caCertificatePath)) {
trustedCa = factory.generateCertificate(is);
}
KeyStore trustStore = KeyStore.getInstance("pkcs12");
trustStore.load(null, null);
trustStore.setCertificateEntry("ca", trustedCa);
SSLContextBuilder sslContextBuilder = SSLContexts.custom()
.loadTrustMaterial(trustStore, null);
SSLContext sslContext = sslContextBuilder.build();
RestClientBuilder builder = RestClient.builder(
new HttpHost(host, Integer.parseInt(port), "http"))
.setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder.setSSLContext(sslContext)
.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
.setDefaultCredentialsProvider(credentialsProvider));
restClient = builder.build();
ElasticsearchTransport transport = new RestClientTransport(
restClient, new JacksonJsonpMapper());
client = new ElasticsearchClient(transport);
} else {
restClient = RestClient.builder(new HttpHost(Constants.LOCAL_HOST, 9200))
.build();
}
ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
this.client = new ElasticsearchClient(transport);
}
public List<String> getAllIndex() throws IOException {
Map<String, IndexMappingRecord> result = this.client.indices().getMapping().result();
return new ArrayList<>(result.keySet());
}
public Boolean indexExists(String index) throws IOException {
return this.client.indices().exists(req -> req.index(index)).value();
}
public Boolean deleteIndex(String index) throws IOException {
return this.client.indices().delete(req -> req.index(index)).acknowledged();
}
public <T> Boolean createIndex(Class<T> clazz) throws IOException {
ElasticSearchIndex elasticSearchIndex = clazz.getAnnotation(ElasticSearchIndex.class);
String indexName = elasticSearchIndex.name();
if (indexExists(indexName)) {
String msg = "index:" + indexName + " " + "already exists";
throw new RuntimeException(msg);
}
Field[] declaredFields = clazz.getDeclaredFields();
Map<String, Property> fieldMap = new LinkedHashMap<>();
for (Field field : declaredFields) {
ElasticSearchField annotation = field.getAnnotation(ElasticSearchField.class);
if (annotation == null) {
continue;
}
switch (annotation.type()) {
case text:
doHandleText(fieldMap, field);
break;
case keyword:
doHandleKeyword(fieldMap, field);
break;
case long_:
doHandleLong_(fieldMap, field);
break;
case integer:
doHandleInteger(fieldMap, field);
break;
default:
throw new RuntimeException("类型匹配失败");
}
}
Boolean acknowledged = client.indices()
.create(req -> req.index(indexName)
.mappings(mapping -> mapping.properties(fieldMap)))
.acknowledged();
return acknowledged;
}
public void addDoc(Object obj) throws Exception {
String indexName = getIndexName(obj);
Long id = getId(obj);
client.create(
createReq -> createReq.index(indexName).id(String.valueOf(id)).document(obj)
);
}
public <R> void batchAddDoc(List<R> list) throws Exception {
List<BulkOperation> opts = new ArrayList<>();
for (R obj : list) {
Long id = getId(obj);
String indexName = getIndexName(obj);
BulkOperation opt = new BulkOperation.Builder()
.create(
createReq -> createReq.index(indexName).id(String.valueOf(id)).document(obj)
)
.build();
opts.add(opt);
}
client.bulk(bulk -> bulk.operations(opts));
}
public void deleteDoc(Object obj) throws IOException {
Long id = getId(obj);
String indexName = getIndexName(obj);
client.delete(
del -> del.index(indexName).id(String.valueOf(id))
);
}
public QueryDto page(String field, String value, int pageNum, int pageSize, String indexName) throws IOException {
if (StringUtils.isNull(value)) {
value = "";
}
int from = ((pageNum - 1) * pageSize);
String finalValue = value;
MatchQuery matchQuery = new MatchQuery.Builder()
.field(field)
.query(r -> r.stringValue(finalValue))
.build();
Query query = new Query.Builder()
.match(matchQuery)
.build();
SearchRequest searchRequest = new SearchRequest.Builder()
.index(indexName)
.query(query)
.from(from)
.size(pageSize)
.build();
SearchResponse<Object> search = client.search(searchRequest, Object.class);
List<Object> hits = search.hits()
.hits()
.stream()
.map(Hit::source).
collect(Collectors.toList());
Long total = search.hits().total().value();
return QueryDto.builder()
.total(total)
.list(hits)
.build();
}
public QueryDto queryDoc(String field, String value, String highLightKey) throws IOException {
MatchQuery matchQuery = new MatchQuery.Builder()
.field(field)
.query(FieldValue.of(value))
.build();
Query query = new Query.Builder()
.match(matchQuery)
.build();
Highlight highlight = new Highlight.Builder()
.fields(highLightKey, fields -> fields.preTags("<em>").postTags("</em>"))
.build();
SearchRequest searchRequest = new SearchRequest.Builder()
.query(query)
.highlight(highlight)
.build();
SearchResponse<Object> search = client.search(searchRequest, Object.class);
List<Hit<Object>> hits = search.hits().hits();
List<Object> list = hits.stream().map(Hit::source).collect(Collectors.toList());
List<Map<String, List<String>>> highLight = hits.stream().map(Hit::highlight).collect(Collectors.toList());
return QueryDto.builder()
.list(list)
.highLight(highLight)
.build();
}
public List<Object> queryDoc(String field, String value) throws IOException {
MatchQuery matchQuery = new MatchQuery.Builder()
.field(field)
.query(FieldValue.of(value))
.build();
Query query = new Query.Builder()
.match(matchQuery)
.build();
SearchRequest searchRequest = new SearchRequest.Builder()
.query(query)
.build();
SearchResponse<Object> search = client.search(searchRequest, Object.class);
List<Object> hits = search.hits().hits()
.stream().map(Hit::source).collect(Collectors.toList());
return hits;
}
private Long getId(Object obj) {
long id = 0;
try {
Class<?> clazz = obj.getClass();
Field[] fields = clazz.getDeclaredFields();
Optional<Field> any = Arrays.stream(fields).filter(e -> e.getName().equals("id")).findAny();
Field idField = null;
if (any.isPresent()) {
idField = any.get();
}
assert idField != null;
idField.setAccessible(true);
id = (Long) idField.get(obj);
} catch (IllegalAccessException e) {
throw new FieldException("该类没有id属性");
}
return id;
}
private String getIndexName(Object obj) {
String indexName = null;
try {
Class<?> clazz = obj.getClass();
indexName = clazz.getAnnotation(ElasticSearchIndex.class).name();
} catch (Exception e) {
throw new ElasticSearchException("检查Class是否标有<ElasticSearchIndex>注解");
}
return indexName;
}
private void doHandleText(Map<String, Property> fieldMap, Field field) {
ElasticSearchField annotation = field.getAnnotation(ElasticSearchField.class);
List<String> list = Arrays.asList(annotation.copyTo());
if (list.size() > 0 && StringUtils.isNotEmpty(list.get(0))) {
fieldMap.put(field.getName(), Property.of(property ->
property.text(TextProperty.of(textProperty -> textProperty
.index(annotation.index())
.analyzer(annotation.analyzer().toString())
.copyTo(list)
)
)
)
);
} else {
fieldMap.put(field.getName(), Property.of(property ->
property.text(TextProperty.of(textProperty -> textProperty
.index(annotation.index())
.analyzer(annotation.analyzer().toString())
)
)
)
);
}
}
private void doHandleKeyword(Map<String, Property> fieldMap, Field field) {
ElasticSearchField annotation = field.getAnnotation(ElasticSearchField.class);
List<String> list = Arrays.asList(annotation.copyTo());
if (list.size() > 0 && StringUtils.isNotEmpty(list.get(0))) {
fieldMap.put(field.getName(), Property.of(property ->
property.keyword(KeywordProperty.of(keywordProp -> keywordProp
.index(annotation.index())
.copyTo(list)
)
)
)
);
} else {
fieldMap.put(field.getName(), Property.of(property ->
property.keyword(KeywordProperty.of(keywordProp -> keywordProp
.index(annotation.index())
)
)
)
);
}
}
private void doHandleLong_(Map<String, Property> fieldMap, Field field) {
ElasticSearchField annotation = field.getAnnotation(ElasticSearchField.class);
List<String> list = Arrays.asList(annotation.copyTo());
if (list.size() > 0 && StringUtils.isNotEmpty(list.get(0))) {
fieldMap.put(field.getName(), Property.of(property ->
property.long_(LongNumberProperty.of(longProp -> longProp
.index(annotation.index())
.copyTo(list))
)
)
);
} else {
fieldMap.put(field.getName(), Property.of(property ->
property.long_(LongNumberProperty.of(longProp -> longProp
.index(annotation.index())
)
)
)
);
}
}
private void doHandleInteger(Map<String, Property> fieldMap, Field field) {
ElasticSearchField annotation = field.getAnnotation(ElasticSearchField.class);
List<String> list = Arrays.asList(annotation.copyTo());
if (list.size() > 0 && StringUtils.isNotEmpty(list.get(0))) {
fieldMap.put(field.getName(), Property.of(property ->
property.integer(IntegerNumberProperty.of(integerProp -> integerProp
.index(annotation.index())
.copyTo(list))
)
)
);
} else {
fieldMap.put(field.getName(), Property.of(property ->
property.integer(IntegerNumberProperty.of(integerProp -> integerProp
.index(annotation.index())
)
)
)
);
}
}
}
package com.yitsd.annotations;
import java.lang.annotation.*;
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AggField {
String name() default "";
}
package com.yitsd.annotations;
import com.yitsd.enums.Type;
import com.yitsd.enums.Analyzer;
import java.lang.annotation.*;
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ElasticSearchField {
Type type();
Analyzer analyzer() default Analyzer.standard;
boolean index() default true;
String[] copyTo() default "";
}
package com.yitsd.annotations;
import java.lang.annotation.*;
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ElasticSearchIndex {
String name();
}
package com.yitsd.enums;
public enum Analyzer {
standard("standard"),
ik_smart("ik_smart"),
ik_max_word("ik_max_word")
;
Analyzer(String str) {
}
}
package com.yitsd.enums;
public enum Type {
text("text"),
keyword("keyword"),
long_("long"),
integer("integer");
Type(String str) {
}
}
package com.yitsd.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
@ResponseBody
public class ElasticSearchException extends RuntimeException{
public ElasticSearchException() {
}
public ElasticSearchException(String msg) {
super(msg);
}
}