遇到一个场景,需要将原es集群的数据,迁移到新的es集群,在找了许多网上的方法之后,觉得繁琐,于是决定自己写个接口,读取原es集群的数据,写入新的es集群。
受网络带宽限制,2M/s 。
设计的是自定义传入索引名称(多个索引用逗号隔开),可批量同步。同步完之后可通知。保留了原索引的分片数等参数设置。


java
application.yml的内容:
server:
port: 2026
# ==================== 旧 Elasticsearch 集群配置 ====================
elasticsearch:
old:
host: xxx.xxx.xxx.xxx
port: 9200
username: username
password: *********
scheme: http
# ==================== 新 Elasticsearch 集群配置 ====================
new:
host: xx.xxx.xx.xxx
port:
username: username
password: **********
scheme: https
service的内容:
package com.inca.service;
import com.inca.constant.Constant;
import com.inca.tool.ToolUtils;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.elasticsearch.ElasticsearchStatusException;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.ClearScrollRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchScrollRequest;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.client.IndicesClient;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.client.indices.GetIndexResponse;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.Scroll;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springblade.core.tool.utils.StringPool;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Service
public class EsMigrationService {
// 定义全局锁对象(按索引名加锁),防止多线程执行导致重复通知
private final Map<String, Object> indexLocks = new ConcurrentHashMap<>();
private final RestHighLevelClient oldRestHighLevelClient;
private final RestHighLevelClient newRestHighLevelClient;
// 构造器注入,指定 Bean 名称,避免混淆
public EsMigrationService(
@Qualifier("oldRestHighLevelClient") RestHighLevelClient oldRestHighLevelClient,
@Qualifier("newRestHighLevelClient") RestHighLevelClient newRestHighLevelClient) {
this.oldRestHighLevelClient = oldRestHighLevelClient;
this.newRestHighLevelClient = newRestHighLevelClient;
}
public void migrateAllIndices(String indexNameStr) {
List<String> indices = Arrays.asList(indexNameStr.split(StringPool.COMMA));
for (String index : indices) {
try {
log.info("========== 开始迁移索引: {} ==========", index);
migrateSingleIndex(index);
log.info("========== 索引: {} 迁移完成 ==========", index);
} catch (Exception e) {
log.error("迁移索引: {} 失败!", index, e);
}
}
log.info("所有索引迁移任务结束!");
}
private void migrateSingleIndex(String indexName) throws IOException {
Object lock = indexLocks.computeIfAbsent(indexName, k -> new Object());
synchronized (lock){
if (isIndexMigrated(indexName)) {
log.info("索引: {} 已完成迁移,跳过执行", indexName);
return;
}
// 1. 复制索引结构
long l = System.currentTimeMillis();
boolean b = copyStruct(indexName);
// 2. 复制索引数据
boolean b1 = copyIndexData(indexName);
long l1 = System.currentTimeMillis();
// 3. 标记索引已迁移(防止重复执行)
markIndexMigrated(indexName, true);
// 4. 通知(非必需)
Map<String, Boolean> map = new HashMap<>();
map.put(indexName, b&&b1);
ToolUtils.sendQiWeChat(map,(l1-l)/1000,null,null,new ArrayList<>(),"Es迁移");
}
}
private boolean copyStruct(String indexName){
// ========== 1. 新集群索引存在性检查 ==========
boolean newIndexExists = false;
boolean copyStructureResult = false;
IndicesClient newIndicesClient = newRestHighLevelClient.indices();
try {
GetIndexRequest checkNewReq = new GetIndexRequest(indexName);
// 7.10.2 配置IndicesOptions(禁止忽略不存在的索引)
IndicesOptions indicesOptions = IndicesOptions.fromOptions(
false, // ignoreUnavailable
false, // allowNoIndices
false, // expandToOpenIndices
false, // expandToClosedIndices
false, // expandToHiddenIndices
false, // allowAliasesToMultipleIndices
true, // forbidClosedIndices
true, // ignoreAliases
false // ignoreThrottled
);
checkNewReq.indicesOptions(indicesOptions);
// 捕获IOException + ElasticsearchStatusException
newIndicesClient.get(checkNewReq, RequestOptions.DEFAULT);
newIndexExists = true;
log.warn("索引 [{}] 已存在于新集群,跳过结构创建", indexName);
return true;
} catch (ElasticsearchStatusException e) {
if (e.status() != RestStatus.NOT_FOUND) {
log.error("检查新集群索引 [{}] 时发生非404异常", indexName, e);
return copyStructureResult;
}
// 404说明新集群无索引,继续执行
} catch (IOException e) {
log.error("检查新集群索引 [{}] 时发生IO异常", indexName, e);
return copyStructureResult;
}
// ========== 2. 旧集群读取结构(修复Settings遍历 + 捕获IOException) ==========
IndicesClient oldIndicesClient = oldRestHighLevelClient.indices();
GetIndexResponse oldIndexResp = null;
try {
GetIndexRequest getOldReq = new GetIndexRequest(indexName);
oldIndexResp = oldIndicesClient.get(getOldReq, RequestOptions.DEFAULT);
} catch (ElasticsearchStatusException e) {
log.error("索引 [{}] 不存在于旧集群,无法迁移", indexName, e);
return copyStructureResult;
} catch (IOException e) {
log.error("读取旧集群索引 [{}] 结构时发生IO异常", indexName, e);
return copyStructureResult;
}
// ========== 3. 7.10.2 正确遍历Settings ==========
org.elasticsearch.common.settings.Settings oldSettings = oldIndexResp.getSettings().get(indexName);
Map<String, String> cleanSettingsMap = new HashMap<>();
// 7.10.2 用keys()遍历Settings,再通过get()取值
for (String key : oldSettings.keySet()) {
String value = oldSettings.get(key);
// 过滤系统参数 + 保留业务配置
if (!Constant.SYSTEM_INDEX_SETTINGS.contains(key)
&& key.startsWith("index.")
//集群节点数相同,ES 版本一致,且先创建结构,再迁数据,所以保留分片数
// && !key.equals("index.number_of_shards")
) {
cleanSettingsMap.put(key, value);
}
}
// ========== 4. 新集群创建索引 ==========
try {
CreateIndexRequest createReq = new CreateIndexRequest(indexName);
// 传入过滤后的settings
if (!cleanSettingsMap.isEmpty()) {
createReq.settings(cleanSettingsMap);
}
// 提取mapping
String mappingJson = oldIndexResp.getMappings().get(indexName).source().toString();
createReq.mapping(mappingJson, XContentType.JSON);
// 执行创建
newIndicesClient.create(createReq, RequestOptions.DEFAULT);
log.info("成功在新集群创建索引 [{}] 的结构", indexName);
copyStructureResult = true;
} catch (ElasticsearchStatusException e) {
if (e.status() == RestStatus.BAD_REQUEST && e.getMessage().contains("resource_already_exists_exception")) {
log.warn("索引 [{}] 已存在于新集群,跳过创建", indexName);
} else {
log.error("创建索引 [{}] 结构失败(ES状态异常)", indexName, e);
}
} catch (IOException e) {
log.error("创建索引 [{}] 结构失败(IO异常)", indexName, e);
}
return copyStructureResult;
}
/**
* 复制索引数据
* @param indexName
* @throws IOException
*/
private boolean copyIndexData(String indexName) throws IOException {
final Scroll scroll = new Scroll(TimeValue.timeValueMinutes(10L));
boolean dataResult = false;
SearchRequest searchRequest = new SearchRequest(indexName);
searchRequest.scroll(scroll);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
searchSourceBuilder.sort("_doc");//按文档物理位置读取,最快的Scroll方式
searchSourceBuilder.size(Constant.BATCH_SIZE);
// searchSourceBuilder.timeout(TimeValue.timeValueMinutes(10L));
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = oldRestHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
String scrollId = searchResponse.getScrollId();
long totalHits = searchResponse.getHits().getTotalHits().value;
long processedHits = 0;
log.info("开始迁移索引 [{}] 的数据,共 {} 条。", indexName, totalHits);
while (true) {
BulkRequest bulkRequest = new BulkRequest();
for (var hit : searchResponse.getHits().getHits()) {
IndexRequest indexRequest = new IndexRequest(indexName);
indexRequest.id(hit.getId()); // 保留原始文档ID
indexRequest.source(hit.getSourceAsString(), XContentType.JSON);
bulkRequest.add(indexRequest);
}
if (bulkRequest.numberOfActions() > 0) {
BulkResponse bulkResponse = newRestHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
if (bulkResponse.hasFailures()) {
log.error("批量写入数据失败: {}", bulkResponse.buildFailureMessage());
}
processedHits += bulkRequest.numberOfActions();
log.info("索引 [{}] 已处理 {} / {} 条数据。", indexName, processedHits, totalHits);
}
// 检查是否还有下一页数据
if (searchResponse.getHits().getHits().length == 0) {
break;
}
// 准备下一次滚动查询
SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId);
scrollRequest.scroll(scroll);
searchResponse = oldRestHighLevelClient.scroll(scrollRequest, RequestOptions.DEFAULT);
scrollId = searchResponse.getScrollId();
}
// 清理 Scroll 上下文
ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
clearScrollRequest.addScrollId(scrollId);
oldRestHighLevelClient.clearScroll(clearScrollRequest, RequestOptions.DEFAULT);
log.info("索引 [{}] 的数据迁移完成。", indexName);
dataResult = true;
return dataResult;
}
private final Set<String> migratedIndices = Collections.synchronizedSet(new HashSet<>());
private boolean isIndexMigrated(String indexName) {
return migratedIndices.contains(indexName);
}
private void markIndexMigrated(String indexName, boolean migrated) {
if (migrated) {
migratedIndices.add(indexName);
} else {
migratedIndices.remove(indexName);
}
}
}
配置类的内容:
package com.inca.config;
import com.inca.constant.Constant;
import lombok.Data;
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.client.config.RequestConfig;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.ssl.SSLContexts;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.annotation.PreDestroy;
import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
@Configuration
public class ElasticsearchConfig {
private RestHighLevelClient oldRestHighLevelClient;
private RestHighLevelClient newRestHighLevelClient;
@Bean
@ConfigurationProperties(prefix = "elasticsearch.old")
public EsProperties oldEsProperties() {
return new EsProperties();
}
@Bean
@ConfigurationProperties(prefix = "elasticsearch.new")
public EsProperties newEsProperties() {
return new EsProperties();
}
@Bean(name = "oldRestHighLevelClient")
public RestHighLevelClient oldRestHighLevelClient() {
oldRestHighLevelClient = createClient(oldEsProperties());
return oldRestHighLevelClient;
}
@Primary
@Bean(name = "newRestHighLevelClient")
public RestHighLevelClient newRestHighLevelClient() {
newRestHighLevelClient = createClient(newEsProperties());
return newRestHighLevelClient;
}
private RestHighLevelClient createClient(EsProperties properties) {
// 1. 认证配置
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
if (properties.getUsername() != null && properties.getPassword() != null) {
credentialsProvider.setCredentials(AuthScope.ANY,
new UsernamePasswordCredentials(properties.getUsername(), properties.getPassword()));
}
// 2. 配置请求超时
RequestConfig.Builder requestConfigBuilder = RequestConfig.custom()
.setConnectTimeout(30000) // 连接超时:30秒
.setSocketTimeout(60000) // 读取超时:60秒
.setConnectionRequestTimeout(30000); // 连接池获取连接超时:30秒
RequestConfig requestConfig = requestConfigBuilder.build();
// 3. 构建HttpHost(核心:使用配置的scheme)
HttpHost httpHost = new HttpHost(
properties.getHost(),
properties.getPort(),
properties.getScheme()
);
// 4. 构建RestClientBuilder
RestClientBuilder builder = RestClient.builder(httpHost)
// 修正RequestConfig回调类型
.setRequestConfigCallback(builder1 -> {
builder1.setConnectTimeout(requestConfig.getConnectTimeout());
builder1.setSocketTimeout(requestConfig.getSocketTimeout());
builder1.setConnectionRequestTimeout(requestConfig.getConnectionRequestTimeout());
return builder1;
})
// 配置HttpClient(认证+连接池+HTTPS SSL处理)
.setHttpClientConfigCallback(httpClientBuilder -> {
// 基础配置:认证+连接池+长连接
httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
httpClientBuilder.setMaxConnTotal(200);
httpClientBuilder.setMaxConnPerRoute(50);
httpClientBuilder.setKeepAliveStrategy((response, context) -> 30000);
httpClientBuilder.setDefaultRequestConfig(requestConfig);
// HTTPS场景下跳过SSL证书验证(自签证书专用,生产需替换为信任证书)
if (Constant.HTTPS.equalsIgnoreCase(properties.getScheme())) {
try {
SSLContext sslContext = SSLContexts.custom()
// 信任所有证书(临时方案,生产需加载合法证书)
.loadTrustMaterial(null, (chain, authType) -> true)
.build();
httpClientBuilder.setSSLContext(sslContext);
// 禁用主机名验证(自签证书常见问题)
httpClientBuilder.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE);
} catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
throw new RuntimeException("配置HTTPS SSL上下文失败", e);
}
}
return httpClientBuilder;
});
return new RestHighLevelClient(builder);
}
@PreDestroy
public void destroyClients() {
try {
if (oldRestHighLevelClient != null) {
oldRestHighLevelClient.close();
}
if (newRestHighLevelClient != null) {
newRestHighLevelClient.close();
}
} catch (IOException e) {
throw new RuntimeException("关闭ES Client连接池失败", e);
}
}
@Data
public static class EsProperties {
private String host;
private int port;
private String username;
private String password;
private String scheme; // 新增:http/https
}
}
常量类的内容:
package com.inca.constant;
import java.util.Arrays;
/**
* @author zhaozq
* @date 2026/1/6
* @description 全局常量
*/
public class Constant {
// 服务名称
public static final String ES_MIGRATE_APP = "es-migrate";
// 每批滚动查询的数据量
public static final int BATCH_SIZE = 1000;
// 企业微信机器人地址:
public static final String QiWeChat_URL = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=d0gh436d-d78c-4855-96c5-00a137b678d1";
// 请求协议
public static final String HTTPS = "https";
// 系统设置黑名单,作用:索引设置参数中,不同步以下参数。
public static final java.util.Set<String> SYSTEM_INDEX_SETTINGS = new java.util.HashSet<>(Arrays.asList(
// 索引标识
"index.creation_date",
"index.uuid",
"index.provided_name",
"index.id",
// 版本相关
"index.version",
"index.version.created",
"index.version.upgraded",
// 存储/路径
"index.path.data",
"index.path.stats",
// 内部状态
"index.shutdown",
"index.translog",
"index.read_only",
"index.read_only_allow_delete",
"index.blocks",
// 系统预留
"index.system",
"index.hidden"
));
}
控制层的内容:
package com.inca.controller;
import com.inca.service.EsMigrationService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author:zhaozq
* @Date:2026-1-6
* @Desc: es数据迁移丢数据 补偿
*/
@RestController
@RequestMapping("/es")
@Slf4j
public class EsController {
@Autowired
private EsMigrationService migrationService;
/**
* ES数据迁移
* 入参形式:index_name_1,index_name_2,...
* @param indexNameStr 索引
* @date:2026-1-6
* @author:zhaozq
*/
@GetMapping("esMove")
public void esMove(String indexNameStr) {
migrationService.migrateAllIndices(indexNameStr);
}
}
工具类的内容:
package com.inca.tool;
import com.inca.constant.Constant;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.tool.utils.StringPool;
import java.util.List;
import java.util.Map;
/**
* @author zhaozq
* @Date 2026-1-6
* @Description: 时间转换工具
*/
@Slf4j
public class ToolUtils {
/**
* @description: 将秒转换为时分秒,如:66秒转换为1分钟6秒 3666秒转换为1小时1分钟6秒
* @author: zhaozq
* @date: 2026-1-6
**/
public static String convertSecondsToMinutes(long totalSeconds) {
long hours = totalSeconds / 3600;
long minutes = (totalSeconds % 3600) / 60;
long seconds = totalSeconds % 60;
StringBuilder result = new StringBuilder("耗时:");
if (hours > 0) {
result.append(hours).append("小时");
}
if (minutes > 0) {
result.append(minutes).append("分钟");
}
if (seconds > 0 || result.length() == 0) { // 如果结果为空,则表示只有秒数
result.append(seconds).append("秒");
}
return result.toString().concat(StringPool.NEWLINE);
}
public static void sendQiWeChat(Map<String, Boolean> map, Long time, String beginDate, String endDate, List<String> tableNameList, String title){
String content = ToolUtils.convertSecondsToMinutes(time);
String result = StringPool.EMPTY;
if(!map.isEmpty()){
for (Map.Entry<String, Boolean> m : map.entrySet()) {
result+=m.getKey()+StringPool.COLON+m.getValue().toString() +StringPool.COMMA;
}
result = result.replaceAll(".$", StringPool.EMPTY);
content += result;
}
String color = "info";
if(result.contains("false")){
color = "warning";
}
String reqJson = "{\n" +
" \"markdown\": {\n" +
" \"content\": \"> " +
"通知标题:<font color=\\\"info\\\">"+title+"</font>\\n> " +
"执行结果:<font color=\\\""+color+"\\\">"+content+"</font>\\n" +
"\"\n" +
" },\n" +
" \"msgtype\": \"markdown\"\n" +
"}";
String body = cn.hutool.http.HttpRequest.post(Constant.QiWeChat_URL).body(reqJson).timeout(5000).execute().body();
log.info("请求结果:\n"+body);
}
}
启动类的内容:
package com.inca;
import cn.hutool.extra.spring.EnableSpringUtil;
import com.inca.constant.Constant;
import org.springblade.core.cloud.feign.EnableBladeFeign;
import org.springblade.core.launch.BladeApplication;
import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling;
@EnableBladeFeign(basePackages = {"org.springblade", "com.cqyy","com.inca"})
@SpringCloudApplication
@ComponentScan(basePackages = {"org.springblade","com.inca.**"})
@EnableRabbit
@EnableSpringUtil
@EnableScheduling
public class EsMigrateApplication {
public static void main(String[] args) {
BladeApplication.run(Constant.ES_MIGRATE_APP, EsMigrateApplication.class, args);
}
}
项目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>
<groupId>com.inca</groupId>
<artifactId>es-migrate</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<properties>
<bladex.project.version>2.6.2.RELEASE</bladex.project.version>
<java.version>1.8</java.version>
<maven.plugin.version>3.8.1</maven.plugin.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<elasticsearch.version>7.10.2</elasticsearch.version>
<spring.boot.version>2.3.12.RELEASE</spring.boot.version>
<spring.cloud.version>Hoxton.SR11</spring.cloud.version>
<spring.platform.version>Cairo-SR8</spring.platform.version>
</properties>
<modules>
<module>es-migrate-service</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.spring.platform</groupId>
<artifactId>platform-bom</artifactId>
<version>${spring.platform.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 新增:管控ES客户端版本,和你的集群对齐 -->
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>${elasticsearch.version}</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>${elasticsearch.version}</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>${elasticsearch.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-service-common</artifactId>
<version>2.6.2.RELEASE</version>
</dependency>
<!-- 核心:引入Spring Data ES,并排除默认的ES依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
<exclusions>
<exclusion>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
</exclusion>
<exclusion>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 显式引入版本的ES核心包 -->
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
</dependency>
<!-- 显式引入版本的HighLevel客户端 -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<!-- 解决依赖传递问题:手动引入rest-client -->
<exclusions>
<exclusion>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
</exclusion>
<exclusion>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 显式引入版本的RestClient -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
</dependency>
</dependencies>
<build>
<finalName>${project.name}</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<configuration>
<fork>true</fork>
<finalName>${project.build.finalName}</finalName>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<!--suppress UnresolvedMavenProperty -->
<copy overwrite="true"
tofile="${session.executionRootDirectory}/target/${project.artifactId}.jar"
file="${project.build.directory}/${project.artifactId}.jar" />
</tasks>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<includeSystemScope>true</includeSystemScope>
</configuration>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven.plugin.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>UTF-8</encoding>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>aliyun-repos</id>
<url>https://maven.aliyun.com/nexus/content/groups/public/</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>