This commit is contained in:
yangshb
2024-03-05 11:39:57 +08:00
parent c9553fdd65
commit f6d1d354e6
1984 changed files with 175140 additions and 2 deletions
@@ -0,0 +1,70 @@
<?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>com.cf.imes</groupId>
<artifactId>cf-framework</artifactId>
<version>${revision}</version>
</parent>
<packaging>jar</packaging>
<artifactId>cf-spring-boot-starter-elasticsearch</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<elasticsearch.version>8.8.1</elasticsearch.version>
<jakarta-json.version>2.1.1</jakarta-json.version>
</properties>
<dependencies>
<dependency>
<groupId>com.cf.imes</groupId>
<artifactId>cf-common</artifactId>
<scope>provided</scope>
</dependency>
<!-- Spring 核心 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>${elasticsearch.version}</version>
<exclusions>
<exclusion>
<groupId>jakarta.json</groupId>
<artifactId>jakarta.json-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>jakarta.json</groupId>
<artifactId>jakarta.json-api</artifactId>
<version>${jakarta-json.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.cf.imes</groupId>
<artifactId>cf-spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,100 @@
package com.cf.imes.framework.es.config;
import co.elastic.clients.elasticsearch.ElasticsearchAsyncClient;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import com.cf.imes.framework.es.core.service.ESDocumentService;
import com.cf.imes.framework.es.core.service.ESDocumentServiceImpl;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.util.StringUtils;
/**
* @author there
*/
@AutoConfiguration
@ConditionalOnClass(ElasticsearchClient.class)
@EnableConfigurationProperties(EsProperties.class)
public class ChenfengElasticsearchAutoConfiguration {
//超时时间设置
public static final int DEFAULT_CONNECT_TIMEOUT_MILLIS = 10000;
public static final int DEFAULT_SOCKET_TIMEOUT_MILLIS = 300000;
public static final int DEFAULT_CONNECT_REQUEST_TIMEOUT_MILLIS = 1000;
/**
* 同步方式
*
*/
@Bean
public ElasticsearchClient elasticsearchClient(EsProperties properties) {
return new ElasticsearchClient(getTransport(properties.getUris()));
}
/**
* 异步方式
*
*/
@Bean
public ElasticsearchAsyncClient elasticsearchAsyncClient(EsProperties properties) {
return new ElasticsearchAsyncClient(getTransport(properties.getUris()));
}
@Bean
private ESDocumentService esDocumentService(ElasticsearchClient elasticsearchClient, ElasticsearchAsyncClient elasticsearchAsyncClient) {
return new ESDocumentServiceImpl(elasticsearchClient, elasticsearchAsyncClient);
}
/**
* 获取客户端 RestClientTransport
*/
private RestClientTransport getTransport(String hosts){
HttpHost[] httpHosts = toHttpHost(hosts);
RestClient restClient = getRestClient(httpHosts);
return new RestClientTransport(restClient, new JacksonJsonpMapper());
}
/**
* 获取客户端RestClient
* @param httpHosts http数组
*/
private RestClient getRestClient(HttpHost[] httpHosts){
return RestClient.builder(httpHosts).setRequestConfigCallback(requestConfigBuilder -> {
requestConfigBuilder.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT_MILLIS);
requestConfigBuilder.setSocketTimeout(DEFAULT_SOCKET_TIMEOUT_MILLIS);
requestConfigBuilder.setConnectionRequestTimeout(DEFAULT_CONNECT_REQUEST_TIMEOUT_MILLIS);
return requestConfigBuilder;
}).build();
}
/**
* 解析配置的字符串hosts,转为HttpHost对象数组
*/
private HttpHost[] toHttpHost(String hosts) {
if (!StringUtils.hasLength(hosts)) {
throw new RuntimeException("invalid elasticsearch configuration. elasticsearch.hosts不能为空!");
}
// 多个IP逗号隔开
String[] hostArray = hosts.split(",");
HttpHost[] httpHosts = new HttpHost[hostArray.length];
HttpHost httpHost;
for (int i = 0; i < hostArray.length; i++) {
String[] strings = hostArray[i].split(":");
httpHost = new HttpHost(strings[0], Integer.parseInt(strings[1]), "http");
httpHosts[i] = httpHost;
}
return httpHosts;
}
}
@@ -0,0 +1,20 @@
package com.cf.imes.framework.es.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* es环境配置
* @author there
*/
@ConfigurationProperties(prefix = "spring.elasticsearch")
public class EsProperties {
public String getUris() {
return uris;
}
public void setUris(String uris) {
this.uris = uris;
}
private String uris;
}
@@ -0,0 +1 @@
package com.cf.imes.framework.es.config;
@@ -0,0 +1,59 @@
package com.cf.imes.framework.es.core.convert;
import lombok.extern.slf4j.Slf4j;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Date;
/**
* @author there
*/
@Slf4j
public class LocalDateTimeEsConverter implements PropertyValueConverter {
/**
* Converts a property value to an elasticsearch value. If the converter cannot convert the value, it must return a
* String representation.
*
* @param value the value to convert, must not be {@literal null}
* @return The elasticsearch property value, must not be {@literal null}
*/
@Override
public Object write(Object value) {
if(value instanceof LocalDateTime) {
LocalDateTime localDateTime = (LocalDateTime)value;
ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, ZoneId.of("GMT+8"));
return zonedDateTime.toInstant().toString();
}
return value;
}
/**
* Converts an elasticsearch property value to a property value.
*
* @param value the elasticsearch property value to convert, must not be {@literal null}
* @return The converted value, must not be {@literal null}
*/
@Override
public Object read(Object value) {
try {
String strDateTime = value.toString();
Instant instant = null;
if(value.getClass().getName().contains("Long")){
long longTime = (Long) value;
Date date = new Date(longTime);
instant =date.toInstant();
}
else{
instant = Instant.parse(strDateTime);
}
LocalDateTime localDateTime = LocalDateTime.ofInstant(instant,ZoneId.of("GMT+8"));
return localDateTime;
}catch (Exception e){
log.error(e.getMessage(),e);
return null;
}
}
}
@@ -0,0 +1,21 @@
package com.cf.imes.framework.es.core.convert;
public interface PropertyValueConverter {
/**
* Converts a property value to an elasticsearch value. If the converter cannot convert the value, it must return a
* String representation.
*
* @param value the value to convert, must not be {@literal null}
* @return The elasticsearch property value, must not be {@literal null}
*/
Object write(Object value);
/**
* Converts an elasticsearch property value to a property value.
*
* @param value the elasticsearch property value to convert, must not be {@literal null}
* @return The converted value, must not be {@literal null}
*/
Object read(Object value);
}
@@ -0,0 +1,19 @@
package com.cf.imes.framework.es.core.convert;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Documented
@Inherited
public @interface ValueConverter {
/**
* Defines the class implementing the {@link PropertyValueConverter} interface. If this is a normal class, it must
* provide a default constructor with no arguments. If this is an enum and thus implementing a singleton by enum it
* must only have one enum value.
*
* @return the class to use for conversion
*/
Class<? extends PropertyValueConverter> value();
}
@@ -0,0 +1,30 @@
package com.cf.imes.framework.es.core.dal;
import com.cf.imes.framework.es.core.valid.UpdateGroup;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotBlank;
import java.util.Date;
import static com.cf.imes.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
import static com.cf.imes.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
/**
* @author there
*/
@Data
public class ESDocument {
@NotBlank(groups = UpdateGroup.class, message = "文档ID不能空")
public String id;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
private Date createTime;
private Long creator;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
private Date updateTime;
private Long updater;
private Long organId;
}
@@ -0,0 +1 @@
package com.cf.imes.framework.es.core;
@@ -0,0 +1,102 @@
package com.cf.imes.framework.es.core.service;
import cn.hutool.json.JSONObject;
import co.elastic.clients.elasticsearch._types.Result;
import co.elastic.clients.elasticsearch.core.BulkResponse;
import co.elastic.clients.elasticsearch.core.IndexResponse;
import com.cf.imes.framework.es.core.dal.ESDocument;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
/**
* 公用es服务接口
*/
public interface ESDocumentService {
/**
* 新增一个文档,此种方式若发现没有索引,会自动创建一个索引
* @param idxName 索引名
* @param idxId 索引id
* @param document 文档对象
*/
<T> IndexResponse createByFluentDSL(String idxName, String idxId, ESDocument document) throws Exception;
/**
* 新增一个文档,此种方式若发现没有索引,会自动创建一个索引
* @param idxName 索引名
* @param idxId 索引id
* @param document 文档对象
*/
<T> IndexResponse createByBuilderPattern(String idxName, String idxId, ESDocument document) throws Exception;
/**
* 用JSON字符串创建文档,此种方式若发现没有索引,会自动创建一个索引
* @param idxName 索引名
* @param idxId 索引id
* @param jsonContent json字符串
*/
IndexResponse createByJson(String idxName, String idxId, String jsonContent) throws Exception;
/**
* 异步新增文档,此种方式若发现没有索引,会自动创建一个索引
* @param idxName 索引名
* @param idxId 索引id
* @param document 文档
* @param action 操作
*/
<T> void createAsync(String idxName, String idxId, T document, BiConsumer<IndexResponse, Throwable> action);
/**
* 批量增加文档
* @param idxName 索引名
* @param documents 要增加的对象集合
* @return 批量操作的结果
*/
<T> BulkResponse bulkCreate(String idxName, List<T> documents) throws Exception;
/**
* 根据文档id查找文档
* @param idxName 索引名
* @param docId 文档id
* @return Object类型的查找结果
*/
<T> T getById(String idxName, String docId ,Class<T> tClass) throws IOException;
/**
*
* @param idxName 索引名称
* @param docId 文档id
* @param tClass 返回的类型
* @param map 修改内容的map
*/
<T> Result updateById(String idxName, String docId, Class<T> tClass, Map<String,Object> map) throws IOException;
/**
* 根据文档id查找文档,返回类型是ObjectNode
* @param idxName 索引名
* @param docId 文档id
* @return ObjectNode类型的查找结果
*/
JSONObject getObjectNodeById(String idxName, String docId) throws IOException;
/**
* 根据文档id删除文档
* @param idxName 索引名
* @param docId 文档id
* @return Object类型的查找结果
*/
Boolean deleteById(String idxName, String docId) throws IOException;
/**
* 批量删除文档
* @param idxName 索引名
* @param docIds 要删除的文档id集合
*/
BulkResponse bulkDeleteByIds(String idxName, List<String> docIds) throws Exception;
}
@@ -0,0 +1,215 @@
package com.cf.imes.framework.es.core.service;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import co.elastic.clients.elasticsearch.ElasticsearchAsyncClient;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.Result;
import co.elastic.clients.elasticsearch.core.*;
import com.cf.imes.framework.es.core.dal.ESDocument;
import com.cf.imes.framework.security.core.LoginUser;
import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils;
import java.io.IOException;
import java.io.StringReader;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
/**
* @author there
*/
public class ESDocumentServiceImpl implements ESDocumentService{
//同步客户端
private final ElasticsearchClient elasticsearchClient;
// 异步客户端
private final ElasticsearchAsyncClient elasticsearchAsyncClient;
private Snowflake snowflake = IdUtil.getSnowflake();
public ESDocumentServiceImpl (ElasticsearchClient elasticsearchClient, ElasticsearchAsyncClient elasticsearchAsyncClient) {
this.elasticsearchClient = elasticsearchClient;
this.elasticsearchAsyncClient = elasticsearchAsyncClient;
}
@Override
public <T> IndexResponse createByFluentDSL(String idxName, String idxId, ESDocument document) throws Exception {
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
document.setCreator(loginUser.getId());
document.setUpdater(loginUser.getId());
document.setOrganId(loginUser.getOrganId());
document.setCreateTime(new Date());
document.setUpdateTime(new Date());
if(StrUtil.isBlank(document.getId())) {
document.setId(snowflake.nextIdStr());
}
return elasticsearchClient.index(idx -> idx
.index(idxName)
.id(document.getId())
.document(document));
}
/**
* BuilderPattern 方式创建文档
* @param idxName 索引名
* @param idxId 索引id
* @param document 文档对象
*/
@Override
public <T> IndexResponse createByBuilderPattern(String idxName, String idxId, ESDocument document) throws Exception {
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
document.setCreator(loginUser.getId());
document.setUpdater(loginUser.getId());
document.setOrganId(loginUser.getOrganId());
document.setCreateTime(new Date());
document.setUpdateTime(new Date());
IndexRequest.Builder<Object> indexReqBuilder = new IndexRequest.Builder<>();
indexReqBuilder.index(idxName);
if(StrUtil.isBlank(idxId)) {
idxId = snowflake.nextIdStr();
}
indexReqBuilder.id(idxId);
indexReqBuilder.document(document);
return elasticsearchClient.index(indexReqBuilder.build());
}
/**
* json方式创建文档
* @param idxName 索引名
* @param idxId 索引id
* @param jsonContent json字符串
*/
@Override
public IndexResponse createByJson(String idxName, String idxId, String jsonContent) throws Exception {
if(StrUtil.isBlank(idxId)) {
idxId = snowflake.nextIdStr();
}
String finalIdxId = idxId;
return elasticsearchClient.index(i -> i
.index(idxName)
.id(finalIdxId)
.withJson(new StringReader(jsonContent))
);
}
/**
* 异步方式创建文档
* @param idxName 索引名
* @param idxId 索引id
* @param document 文档
* @param action 操作
*/
@Override
public <T> void createAsync(String idxName, String idxId, T document, BiConsumer<IndexResponse, Throwable> action) {
elasticsearchAsyncClient.index(idx -> idx
.index(idxName)
.id(idxId)
.document(document)
).whenComplete(action);
}
/**
* 批量方式创建文档
* @param idxName 索引名
* @param documents 要增加的对象集合
*/
@Override
public <T> BulkResponse bulkCreate(String idxName, List<T> documents) throws Exception {
BulkRequest.Builder br = new BulkRequest.Builder();
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
documents.forEach(document ->{
ESDocument esDocument = (ESDocument) document;
if(StrUtil.isBlank(esDocument.getId())) {
esDocument.setId(snowflake.nextIdStr());
}
esDocument.setUpdater(loginUser.getId());
esDocument.setUpdateTime(new Date());
br.operations(op -> op.index(idx -> idx
.index(idxName)
.id(esDocument.getId().toString())
.document(esDocument)));
});
return elasticsearchClient.bulk(br.build());
}
/**
*
* @param idxName 索引名称
* @param docId 文档id
* @param tClass 返回的类型
* @param map 修改内容的map
* Map<String, Object> map = new HashMap<>();
* map.put("age", 35);
* 把年龄改成35
*/
@Override
public <T> Result updateById(String idxName, String docId, Class<T> tClass, Map<String,Object> map) throws IOException {
UpdateResponse<T> response = elasticsearchClient.update(e -> e.index(idxName).id(docId).doc(map), tClass);
return response.result();
}
/**
* 文档id查询信息
* @param idxName 索引名
* @param docId 文档id
*/
@Override
public <T> T getById(String idxName, String docId,Class<T> tClass) throws IOException {
GetResponse<T> response = elasticsearchClient.get(g -> g
.index(idxName)
.id(docId),
tClass);
return response.found() ? response.source() : null;
}
/**
* 根据索引名称和文档id查询ObjectNode
* @param idxName 索引名
* @param docId 文档id
*/
@Override
public JSONObject getObjectNodeById(String idxName, String docId) throws IOException {
GetResponse<JSONObject> response = elasticsearchClient.get(g -> g
.index(idxName)
.id(docId),
JSONObject.class);
return response.found() ? response.source() : null;
}
/**
* 单条输出
* @param idxName 索引名
* @param docId 文档id
*/
@Override
public Boolean deleteById(String idxName, String docId) throws IOException {
DeleteResponse delete = elasticsearchClient.delete(d -> d
.index(idxName)
.id(docId));
return delete.forcedRefresh();
}
/**
* 批量删除
* @param idxName 索引名
* @param docIds 要删除的文档id集合
*/
@Override
public BulkResponse bulkDeleteByIds(String idxName, List<String> docIds) throws Exception {
BulkRequest.Builder br = new BulkRequest.Builder();
// 将每一个对象都放入builder中
docIds.forEach(id -> br
.operations(op -> op
.delete(d -> d
.index(idxName)
.id(id))));
return elasticsearchClient.bulk(br.build());
}
}
@@ -0,0 +1,10 @@
package com.cf.imes.framework.es.core.valid;
import javax.validation.groups.Default;
/**
* @author there
* 新增组
*/
public interface CreateGroup extends Default {
}
@@ -0,0 +1,10 @@
package com.cf.imes.framework.es.core.valid;
import javax.validation.groups.Default;
/**
* @author there
* 修改组
*/
public interface UpdateGroup extends Default {
}
@@ -0,0 +1 @@
package com.cf.imes.framework.es;
@@ -0,0 +1 @@
com.cf.imes.framework.es.config.ChenfengElasticsearchAutoConfiguration