ES迁移工具,纯手搓,灵活好用效率高

遇到一个场景,需要将原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>
相关推荐
智能化咨询1 天前
(112页PPT)数字化转型制造业企业数据治理平台规划方案(附下载方式)
大数据·运维·人工智能
智慧化智能化数字化方案1 天前
集团财务管控——解读SAP 集团财务管控整体方案【附全文阅读】
大数据·集团财务管控整体方案·大型集团企业财务管理·财务共享与业财融合一体化·财务系统规划设计·财务管理体系·企业财务分析指标
manok1 天前
探索研究:军用领域软件工厂建设核心路径——可信仓库与SBOM驱动的安全高效研发模式
大数据·人工智能·安全·软件工厂
人机与认知实验室1 天前
机器人“拟人化”的演进:融合人机环境生态系统智能的前沿探索
大数据·机器人
老陈头聊SEO1 天前
生成引擎优化(GEO)在内容创作与用户体验提升方面的综合应用与效益分析
其他·搜索引擎·seo优化
中科天工1 天前
如何选择适合的自动化包装解决方案?
大数据·人工智能·智能
song150265372981 天前
PLC电气控制柜 开发上位机软件
大数据
老胡全房源系统1 天前
房产中介管理系统哪一款适合中介?
大数据·人工智能
dear_bi_MyOnly1 天前
数据分析常用操作汇总
大数据·python·数据挖掘·数据分析·学习方法