package xiaozhi.modules.knowledge.rag.impl;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.AbstractResource;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.multipart.MultipartFile;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.exception.RenException;
import xiaozhi.common.page.PageData;
import xiaozhi.modules.knowledge.dto.KnowledgeFilesDTO;
import xiaozhi.modules.knowledge.dto.dataset.DatasetDTO;
import xiaozhi.modules.knowledge.dto.document.ChunkDTO;
import xiaozhi.modules.knowledge.dto.document.RetrievalDTO;
import xiaozhi.modules.knowledge.dto.document.DocumentDTO;
import xiaozhi.modules.knowledge.rag.KnowledgeBaseAdapter;
import xiaozhi.modules.knowledge.rag.RAGFlowClient;
/**
* RAGFlow知识库适配器实现
*
* 重构说明 (Refactoring Note):
* 本类已升级为使用 {@link RAGFlowClient} 统一处理 HTTP 通信。
* 解决了旧代码中 Timeout 缺失、Error Handling 分散的问题。
*
*/
@Slf4j
public class RAGFlowAdapter extends KnowledgeBaseAdapter {
private static final String ADAPTER_TYPE = "ragflow";
private Map config;
private ObjectMapper objectMapper;
// Client 实例,初始化时创建
private RAGFlowClient client;
public RAGFlowAdapter() {
this.objectMapper = new ObjectMapper();
}
@Override
public String getAdapterType() {
return ADAPTER_TYPE;
}
@Override
public void initialize(Map config) {
this.config = config;
validateConfig(config);
String baseUrl = getConfigValue(config, "base_url", "baseUrl");
String apiKey = getConfigValue(config, "api_key", "apiKey");
// 初始化 Client,默认超时 30s,可通过 config 扩展
int timeout = 30;
Object timeoutObj = getConfigValue(config, "timeout", "timeout");
if (timeoutObj != null) {
try {
timeout = Integer.parseInt(timeoutObj.toString());
} catch (Exception e) {
log.warn("解析超时配置失败,使用默认值 30s");
}
}
this.client = new RAGFlowClient(baseUrl, apiKey, timeout);
log.info("RAGFlow适配器初始化完成,Client已就绪");
}
@Override
public boolean validateConfig(Map config) {
if (config == null) {
throw new RenException(ErrorCode.RAG_CONFIG_NOT_FOUND);
}
String baseUrl = getConfigValue(config, "base_url", "baseUrl");
String apiKey = getConfigValue(config, "api_key", "apiKey");
if (StringUtils.isBlank(baseUrl)) {
throw new RenException(ErrorCode.RAG_API_ERROR_URL_NULL);
}
if (StringUtils.isBlank(apiKey)) {
throw new RenException(ErrorCode.RAG_API_ERROR_API_KEY_NULL);
}
if (apiKey.contains("你")) {
throw new RenException(ErrorCode.RAG_API_ERROR_API_KEY_INVALID);
}
if (!baseUrl.startsWith("http://") && !baseUrl.startsWith("https://")) {
throw new RenException(ErrorCode.RAG_API_ERROR_URL_INVALID);
}
return true;
}
/**
* 辅助方法:支持多种键名获取配置(兼容 camelCase 和 snake_case)
*/
private String getConfigValue(Map config, String snakeKey, String camelKey) {
if (config.containsKey(snakeKey)) {
return (String) config.get(snakeKey);
}
if (config.containsKey(camelKey)) {
return (String) config.get(camelKey);
}
return null;
}
/**
* 辅助方法:确保 Client 已初始化
*/
private RAGFlowClient getClient() {
if (this.client == null) {
// 尝试重新初始化
if (this.config != null) {
initialize(this.config);
} else {
throw new RenException(ErrorCode.RAG_CONFIG_NOT_FOUND, "适配器未初始化"); // 应该抛出 RuntimeException
}
}
return this.client;
}
private RenException convertToRenException(Exception e) {
if (e instanceof RenException) {
return (RenException) e;
}
return new RenException(ErrorCode.RAG_API_ERROR, e.getMessage());
}
@Override
public PageData getDocumentList(String datasetId, DocumentDTO.ListReq req) {
try {
log.info("=== [RAGFlow] 获取文档列表: datasetId={} ===", datasetId);
// 使用 Jackson 将 DTO 转为 Map 作为查询参数
Map params = objectMapper.convertValue(req, new TypeReference