diff --git a/servers/boe-server-all/src/main/java/com/xboe/config/ElasticSearchIndexInitializer.java b/servers/boe-server-all/src/main/java/com/xboe/config/ElasticSearchIndexInitializer.java index 3c3628c7..c25cb91a 100644 --- a/servers/boe-server-all/src/main/java/com/xboe/config/ElasticSearchIndexInitializer.java +++ b/servers/boe-server-all/src/main/java/com/xboe/config/ElasticSearchIndexInitializer.java @@ -1,22 +1,12 @@ package com.xboe.config; +import com.xboe.module.boecase.service.IElasticSearchIndexService; import lombok.extern.slf4j.Slf4j; -import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; -import org.elasticsearch.action.support.master.AcknowledgedResponse; -import org.elasticsearch.client.RequestOptions; -import org.elasticsearch.client.RestHighLevelClient; -import org.elasticsearch.client.indices.CreateIndexRequest; -import org.elasticsearch.client.indices.CreateIndexResponse; -import org.elasticsearch.client.indices.GetIndexRequest; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.xcontent.XContentType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; -import java.io.IOException; - /** * ElasticSearch索引初始化器 * 在Spring Boot启动完成并监听到配置文件加载完毕后,检查并创建所需的ES索引 @@ -27,8 +17,8 @@ import java.io.IOException; @Component public class ElasticSearchIndexInitializer { - @Autowired(required = false) - private RestHighLevelClient elasticsearchClient; + @Autowired + private IElasticSearchIndexService elasticSearchIndexService; /** * 监听Spring Boot应用启动完成事件 @@ -36,158 +26,12 @@ public class ElasticSearchIndexInitializer { */ @EventListener(ApplicationReadyEvent.class) public void initializeElasticSearchIndices() { - if (elasticsearchClient == null) { - log.warn("ElasticSearch客户端未配置,跳过索引初始化"); - return; - } - - log.info("开始检查和初始化ElasticSearch索引..."); - - try { - // 检查并创建ai_chat_messages索引 - checkAndCreateIndex("ai_chat_messages"); - - log.info("ElasticSearch索引初始化完成"); - } catch (Exception e) { - log.error("ElasticSearch索引初始化失败", e); - } - } - - /** - * 检查索引是否存在,如果不存在则创建 - * - * @param indexName 索引名称 - * @throws IOException IO异常 - */ - private void checkAndCreateIndex(String indexName) throws IOException { - // 检查索引是否存在 - GetIndexRequest getIndexRequest = new GetIndexRequest(indexName); - boolean exists = elasticsearchClient.indices().exists(getIndexRequest, RequestOptions.DEFAULT); - - if (exists) { - log.info("ElasticSearch索引 [{}] 已存在", indexName); - return; - } - - log.info("ElasticSearch索引 [{}] 不存在,开始创建...", indexName); - - // 创建索引 - CreateIndexRequest createIndexRequest = new CreateIndexRequest(indexName); - - // 设置索引配置 - createIndexRequest.settings(Settings.builder() - .put("index.number_of_shards", 1) - .put("index.number_of_replicas", 0) - .put("index.analysis.analyzer.ik_max_word.tokenizer", "ik_max_word") - .put("index.analysis.analyzer.ik_smart.tokenizer", "ik_smart") - ); - - // 设置字段映射 - String mapping = getAiChatMessagesMapping(); - createIndexRequest.mapping(mapping, XContentType.JSON); - - // 执行创建索引请求 - CreateIndexResponse createIndexResponse = elasticsearchClient.indices() - .create(createIndexRequest, RequestOptions.DEFAULT); - - if (createIndexResponse.isAcknowledged()) { - log.info("ElasticSearch索引 [{}] 创建成功", indexName); + String indexName = "ai_chat_messages"; + if (elasticSearchIndexService.checkIndexExists(indexName)) { + log.info("ElasticSearch索引 ai_chat_messages 已存在"); } else { - log.warn("ElasticSearch索引 [{}] 创建可能失败,响应未确认", indexName); + log.info("ElasticSearch索引 ai_chat_messages 不存在,开始创建..."); + elasticSearchIndexService.createIndex(indexName); } } - - /** - * 获取ai_chat_messages索引的字段映射配置 - * 根据项目中的会话消息数据结构规范定义映射 - * - * @return JSON格式的映射配置 - */ - private String getAiChatMessagesMapping() { - return "{\n" + - " \"properties\": {\n" + - " \"conversationId\": {\n" + - " \"type\": \"keyword\",\n" + - " \"index\": true\n" + - " },\n" + - " \"messageId\": {\n" + - " \"type\": \"keyword\",\n" + - " \"index\": true\n" + - " },\n" + - " \"messageType\": {\n" + - " \"type\": \"keyword\",\n" + - " \"index\": true\n" + - " },\n" + - " \"query\": {\n" + - " \"type\": \"text\",\n" + - " \"analyzer\": \"ik_max_word\",\n" + - " \"search_analyzer\": \"ik_smart\",\n" + - " \"fields\": {\n" + - " \"keyword\": {\n" + - " \"type\": \"keyword\",\n" + - " \"ignore_above\": 256\n" + - " }\n" + - " }\n" + - " },\n" + - " \"answer\": {\n" + - " \"type\": \"text\",\n" + - " \"analyzer\": \"ik_max_word\",\n" + - " \"search_analyzer\": \"ik_smart\"\n" + - " },\n" + - " \"caseRefer\": {\n" + - " \"type\": \"nested\",\n" + - " \"properties\": {\n" + - " \"caseId\": {\n" + - " \"type\": \"keyword\",\n" + - " \"index\": true\n" + - " },\n" + - " \"title\": {\n" + - " \"type\": \"text\",\n" + - " \"analyzer\": \"ik_max_word\",\n" + - " \"search_analyzer\": \"ik_smart\"\n" + - " },\n" + - " \"authorName\": {\n" + - " \"type\": \"keyword\",\n" + - " \"index\": true\n" + - " },\n" + - " \"keywords\": {\n" + - " \"type\": \"text\",\n" + - " \"analyzer\": \"ik_max_word\",\n" + - " \"search_analyzer\": \"ik_smart\"\n" + - " },\n" + - " \"content\": {\n" + - " \"type\": \"text\",\n" + - " \"analyzer\": \"ik_max_word\",\n" + - " \"search_analyzer\": \"ik_smart\"\n" + - " }\n" + - " }\n" + - " },\n" + - " \"suggestions\": {\n" + - " \"type\": \"text\",\n" + - " \"analyzer\": \"ik_max_word\",\n" + - " \"search_analyzer\": \"ik_smart\"\n" + - " },\n" + - " \"userId\": {\n" + - " \"type\": \"keyword\",\n" + - " \"index\": true\n" + - " },\n" + - " \"userName\": {\n" + - " \"type\": \"keyword\",\n" + - " \"index\": true\n" + - " },\n" + - " \"timestamp\": {\n" + - " \"type\": \"date\",\n" + - " \"format\": \"yyyy-MM-dd HH:mm:ss||yyyy-MM-dd'T'HH:mm:ss.SSS'Z'||epoch_millis\"\n" + - " },\n" + - " \"createTime\": {\n" + - " \"type\": \"date\",\n" + - " \"format\": \"yyyy-MM-dd HH:mm:ss||yyyy-MM-dd'T'HH:mm:ss.SSS'Z'||epoch_millis\"\n" + - " },\n" + - " \"updateTime\": {\n" + - " \"type\": \"date\",\n" + - " \"format\": \"yyyy-MM-dd HH:mm:ss||yyyy-MM-dd'T'HH:mm:ss.SSS'Z'||epoch_millis\"\n" + - " }\n" + - " }\n" + - "}"; - } } \ No newline at end of file diff --git a/servers/boe-server-all/src/main/java/com/xboe/module/boecase/api/CaseAiChatApi.java b/servers/boe-server-all/src/main/java/com/xboe/module/boecase/api/CaseAiChatApi.java index 2eeeb38e..68d4d4b8 100644 --- a/servers/boe-server-all/src/main/java/com/xboe/module/boecase/api/CaseAiChatApi.java +++ b/servers/boe-server-all/src/main/java/com/xboe/module/boecase/api/CaseAiChatApi.java @@ -5,6 +5,7 @@ import com.xboe.core.JsonResponse; import com.xboe.module.boecase.dto.CaseAiChatDto; import com.xboe.module.boecase.service.ICaseAiChatService; import com.xboe.module.boecase.service.ICaseAiPermissionService; +import com.xboe.module.boecase.service.IElasticSearchIndexService; import com.xboe.module.boecase.vo.CaseAiMessageVo; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -36,6 +37,9 @@ public class CaseAiChatApi extends ApiBaseController { @Autowired private ICaseAiPermissionService caseAiPermissionService; + @Autowired + private IElasticSearchIndexService elasticSearchIndexService; + /** * 聊天 * @param caseAiChatDto @@ -83,4 +87,23 @@ public class CaseAiChatApi extends ApiBaseController { return error("判断失败", e.getMessage()); } } + + /** + * 手动刷新索引 + * @return + */ + @PostMapping("/index/refresh") + public JsonResponse deleteAndCreateEsIndex() { + String indexName = "ai_chat_messages"; + if (elasticSearchIndexService.checkIndexExists(indexName)) { + boolean deleteResult = elasticSearchIndexService.deleteIndex(indexName); + if (deleteResult) { + elasticSearchIndexService.createIndex(indexName); + return success("刷新成功"); + } + } else { + elasticSearchIndexService.createIndex(indexName); + } + return error("刷新失败"); + } } diff --git a/servers/boe-server-all/src/main/java/com/xboe/module/boecase/service/IElasticSearchIndexService.java b/servers/boe-server-all/src/main/java/com/xboe/module/boecase/service/IElasticSearchIndexService.java new file mode 100644 index 00000000..6124c1ab --- /dev/null +++ b/servers/boe-server-all/src/main/java/com/xboe/module/boecase/service/IElasticSearchIndexService.java @@ -0,0 +1,27 @@ +package com.xboe.module.boecase.service; + +/** + * es索引 + */ +public interface IElasticSearchIndexService { + + /** + * 查看索引是否存在 + * @param indexName + * @return + */ + boolean checkIndexExists(String indexName); + + /** + * 创建索引 + * @param indexName + */ + boolean createIndex(String indexName); + + /** + * 删除索引 + * @param indexName + * @return + */ + boolean deleteIndex(String indexName); +} diff --git a/servers/boe-server-all/src/main/java/com/xboe/module/boecase/service/impl/CaseAiChatServiceImpl.java b/servers/boe-server-all/src/main/java/com/xboe/module/boecase/service/impl/CaseAiChatServiceImpl.java index cccfc920..440a29d2 100644 --- a/servers/boe-server-all/src/main/java/com/xboe/module/boecase/service/impl/CaseAiChatServiceImpl.java +++ b/servers/boe-server-all/src/main/java/com/xboe/module/boecase/service/impl/CaseAiChatServiceImpl.java @@ -217,8 +217,8 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService { // 8. 执行HTTP请求 OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(60, TimeUnit.SECONDS) - .writeTimeout(60, TimeUnit.SECONDS) - .readTimeout(60, TimeUnit.SECONDS) + .writeTimeout(600, TimeUnit.SECONDS) + .readTimeout(600, TimeUnit.SECONDS) .build(); EventSource.Factory factory = EventSources.createFactory(client); factory.newEventSource(request, listener); diff --git a/servers/boe-server-all/src/main/java/com/xboe/module/boecase/service/impl/ElasticSearchIndexServiceImpl.java b/servers/boe-server-all/src/main/java/com/xboe/module/boecase/service/impl/ElasticSearchIndexServiceImpl.java new file mode 100644 index 00000000..9092ebe6 --- /dev/null +++ b/servers/boe-server-all/src/main/java/com/xboe/module/boecase/service/impl/ElasticSearchIndexServiceImpl.java @@ -0,0 +1,198 @@ +package com.xboe.module.boecase.service.impl; + +import com.xboe.module.boecase.service.IElasticSearchIndexService; +import lombok.extern.slf4j.Slf4j; +import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; +import org.elasticsearch.action.support.master.AcknowledgedResponse; +import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.client.indices.CreateIndexRequest; +import org.elasticsearch.client.indices.CreateIndexResponse; +import org.elasticsearch.client.indices.GetIndexRequest; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.XContentType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.IOException; + +@Service +@Slf4j +public class ElasticSearchIndexServiceImpl implements IElasticSearchIndexService { + + @Autowired(required = false) + private RestHighLevelClient elasticsearchClient; + + @Override + public boolean checkIndexExists(String indexName) { + if (elasticsearchClient == null) { + log.warn("ElasticSearch客户端未配置"); + return false; + } + // 检查索引是否存在 + GetIndexRequest getIndexRequest = new GetIndexRequest(indexName); + try { + return elasticsearchClient.indices().exists(getIndexRequest, RequestOptions.DEFAULT); + } catch (IOException e) { + log.error("查询ElasticSearch索引时发生异常", e); + return false; + } + } + + @Override + public boolean createIndex(String indexName) { + if (elasticsearchClient == null) { + log.warn("ElasticSearch客户端未配置"); + return false; + } + // 创建索引 + CreateIndexRequest createIndexRequest = new CreateIndexRequest(indexName); + + // 设置索引配置 + createIndexRequest.settings(Settings.builder() + .put("index.number_of_shards", 1) + .put("index.number_of_replicas", 0) + .put("index.analysis.analyzer.ik_max_word.tokenizer", "ik_max_word") + .put("index.analysis.analyzer.ik_smart.tokenizer", "ik_smart") + ); + + // 设置字段映射 + String mapping = getAiChatMessagesMapping(); + createIndexRequest.mapping(mapping, XContentType.JSON); + + // 执行创建索引请求 + CreateIndexResponse createIndexResponse = null; + try { + createIndexResponse = elasticsearchClient.indices() + .create(createIndexRequest, RequestOptions.DEFAULT); + } catch (IOException e) { + log.error("创建ElasticSearch索引时发生异常", e); + return false; + } + + if (createIndexResponse.isAcknowledged()) { + log.info("ElasticSearch索引 [{}] 创建成功", indexName); + return true; + } else { + log.warn("ElasticSearch索引 [{}] 创建可能失败,响应未确认", indexName); + return false; + } + } + + @Override + public boolean deleteIndex(String indexName) { + if (elasticsearchClient == null) { + log.warn("ElasticSearch客户端未配置"); + return false; + } + // 执行删除索引请求 + DeleteIndexRequest deleteRequest = new DeleteIndexRequest(indexName); + try { + AcknowledgedResponse deleteResponse = elasticsearchClient.indices().delete(deleteRequest, RequestOptions.DEFAULT); + if (deleteResponse.isAcknowledged()) { + log.info("成功删除Elasticsearch索引: {}", indexName); + return true; + } else { + log.warn("删除索引 [{}] 未被确认(可能部分节点未响应)", indexName); + return false; + } + } catch (IOException e) { + log.error("删除ElasticSearch索引时发生异常", e); + return false; + } + } + + /** + * 获取ai_chat_messages索引的字段映射配置 + * 根据项目中的会话消息数据结构规范定义映射 + * + * @return JSON格式的映射配置 + */ + private String getAiChatMessagesMapping() { + return "{\n" + + " \"properties\": {\n" + + " \"conversationId\": {\n" + + " \"type\": \"keyword\",\n" + + " \"index\": true\n" + + " },\n" + + " \"messageId\": {\n" + + " \"type\": \"keyword\",\n" + + " \"index\": true\n" + + " },\n" + + " \"messageType\": {\n" + + " \"type\": \"keyword\",\n" + + " \"index\": true\n" + + " },\n" + + " \"query\": {\n" + + " \"type\": \"text\",\n" + + " \"analyzer\": \"ik_max_word\",\n" + + " \"search_analyzer\": \"ik_smart\",\n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\",\n" + + " \"ignore_above\": 256\n" + + " }\n" + + " }\n" + + " },\n" + + " \"answer\": {\n" + + " \"type\": \"text\",\n" + + " \"analyzer\": \"ik_max_word\",\n" + + " \"search_analyzer\": \"ik_smart\"\n" + + " },\n" + + " \"caseRefer\": {\n" + + " \"type\": \"nested\",\n" + + " \"properties\": {\n" + + " \"caseId\": {\n" + + " \"type\": \"keyword\",\n" + + " \"index\": true\n" + + " },\n" + + " \"title\": {\n" + + " \"type\": \"text\",\n" + + " \"analyzer\": \"ik_max_word\",\n" + + " \"search_analyzer\": \"ik_smart\"\n" + + " },\n" + + " \"authorName\": {\n" + + " \"type\": \"keyword\",\n" + + " \"index\": true\n" + + " },\n" + + " \"keywords\": {\n" + + " \"type\": \"text\",\n" + + " \"analyzer\": \"ik_max_word\",\n" + + " \"search_analyzer\": \"ik_smart\"\n" + + " },\n" + + " \"content\": {\n" + + " \"type\": \"text\",\n" + + " \"analyzer\": \"ik_max_word\",\n" + + " \"search_analyzer\": \"ik_smart\"\n" + + " }\n" + + " }\n" + + " },\n" + + " \"suggestions\": {\n" + + " \"type\": \"text\",\n" + + " \"analyzer\": \"ik_max_word\",\n" + + " \"search_analyzer\": \"ik_smart\"\n" + + " },\n" + + " \"userId\": {\n" + + " \"type\": \"keyword\",\n" + + " \"index\": true\n" + + " },\n" + + " \"userName\": {\n" + + " \"type\": \"keyword\",\n" + + " \"index\": true\n" + + " },\n" + + " \"timestamp\": {\n" + + " \"type\": \"date\",\n" + + " \"format\": \"yyyy-MM-dd HH:mm:ss||yyyy-MM-dd'T'HH:mm:ss.SSS'Z'||epoch_millis\"\n" + + " },\n" + + " \"createTime\": {\n" + + " \"type\": \"date\",\n" + + " \"format\": \"yyyy-MM-dd HH:mm:ss||yyyy-MM-dd'T'HH:mm:ss.SSS'Z'||epoch_millis\"\n" + + " },\n" + + " \"updateTime\": {\n" + + " \"type\": \"date\",\n" + + " \"format\": \"yyyy-MM-dd HH:mm:ss||yyyy-MM-dd'T'HH:mm:ss.SSS'Z'||epoch_millis\"\n" + + " }\n" + + " }\n" + + "}"; + } +}