素材上传
This commit is contained in:
@@ -121,3 +121,5 @@ hikari:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,13 +2,17 @@ package com.rj.controller;
|
||||
|
||||
import com.rj.entity.KnowledgeBase;
|
||||
import com.rj.service.IKnowledgeBaseService;
|
||||
import com.rj.service.MinIOService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -20,6 +24,7 @@ import java.util.Map;
|
||||
* @author 李中华 ,spllzh
|
||||
* @since 2025-01-27
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/knowledgeBase")
|
||||
@Tag(name = "知识库管理", description = "知识库管理相关接口")
|
||||
@@ -28,14 +33,35 @@ public class KnowledgeBaseController {
|
||||
@Autowired
|
||||
private IKnowledgeBaseService knowledgeBaseService;
|
||||
|
||||
@Autowired
|
||||
private MinIOService minIOService;
|
||||
|
||||
/**
|
||||
* 新增知识
|
||||
*/
|
||||
@PostMapping("/add")
|
||||
@Operation(summary = "新增知识", description = "添加新的知识信息")
|
||||
public ResponseEntity<Map<String, Object>> addKnowledge(
|
||||
@RequestParam(value = "file", required = false) MultipartFile file,
|
||||
@Parameter(description = "知识信息", required = true)
|
||||
@RequestBody KnowledgeBase knowledgeBase) {
|
||||
@ModelAttribute KnowledgeBase knowledgeBase) {
|
||||
// 如果提供了文件,上传到MinIO并保存路径
|
||||
if (file != null && !file.isEmpty()) {
|
||||
try {
|
||||
log.info("开始上传文件到MinIO,文件名:{}", file.getOriginalFilename());
|
||||
String fileUrl = minIOService.uploadFile(file);
|
||||
log.info("文件上传到MinIO成功,URL: {}", fileUrl);
|
||||
// 将上传的文件URL保存到知识库对象的附件URL字段
|
||||
knowledgeBase.setAttachmentUrl(fileUrl);
|
||||
} catch (Exception e) {
|
||||
log.error("文件上传到MinIO失败: {}", e.getMessage(), e);
|
||||
Map<String, Object> errorResult = new HashMap<>();
|
||||
errorResult.put("success", false);
|
||||
errorResult.put("message", "文件上传失败:" + e.getMessage());
|
||||
return ResponseEntity.internalServerError().body(errorResult);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Object> result = knowledgeBaseService.addKnowledge(knowledgeBase);
|
||||
Boolean success = (Boolean) result.get("success");
|
||||
if (success != null && success) {
|
||||
|
||||
@@ -5,9 +5,16 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.rj.entity.KnowledgeBase;
|
||||
import com.rj.mapper.KnowledgeBaseMapper;
|
||||
import com.rj.service.IKnowledgeBaseService;
|
||||
import com.rj.service.MinIOService;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -22,9 +29,32 @@ import java.util.UUID;
|
||||
* @author 李中华 ,spllzh
|
||||
* @since 2025-01-27
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class KnowledgeBaseServiceImpl extends ServiceImpl<KnowledgeBaseMapper, KnowledgeBase> implements IKnowledgeBaseService {
|
||||
|
||||
@Autowired
|
||||
private MinIOService minIOService;
|
||||
|
||||
/**
|
||||
* 文本文件内容预览的最大字符数
|
||||
*/
|
||||
private static final int MAX_FILE_CONTENT_PREVIEW_LENGTH = 500;
|
||||
|
||||
/**
|
||||
* 默认状态
|
||||
*/
|
||||
private static final String DEFAULT_STATUS = "已发布";
|
||||
|
||||
/**
|
||||
* 支持的文本文件扩展名
|
||||
*/
|
||||
private static final String[] TEXT_FILE_EXTENSIONS = {
|
||||
".txt", ".md", ".json", ".xml", ".csv", ".log",
|
||||
".properties", ".yml", ".yaml", ".html", ".htm",
|
||||
".css", ".js", ".java", ".py", ".sql", ".sh", ".bat"
|
||||
};
|
||||
|
||||
@Override
|
||||
public Map<String, Object> addKnowledge(KnowledgeBase knowledgeBase) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
@@ -47,8 +77,8 @@ public class KnowledgeBaseServiceImpl extends ServiceImpl<KnowledgeBaseMapper, K
|
||||
if (knowledgeBase.getIsTop() == null) {
|
||||
knowledgeBase.setIsTop(false);
|
||||
}
|
||||
if (knowledgeBase.getStatus() == null || knowledgeBase.getStatus().trim().isEmpty()) {
|
||||
knowledgeBase.setStatus("已发布");
|
||||
if (isBlank(knowledgeBase.getStatus())) {
|
||||
knowledgeBase.setStatus(DEFAULT_STATUS);
|
||||
}
|
||||
|
||||
boolean success = this.save(knowledgeBase);
|
||||
@@ -94,33 +124,150 @@ public class KnowledgeBaseServiceImpl extends ServiceImpl<KnowledgeBaseMapper, K
|
||||
@Override
|
||||
public Map<String, Object> getKnowledgeList(Integer current, Integer size, String title,
|
||||
String category, String industry, String creator, String status) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
// 参数验证和默认值设置
|
||||
if (current == null || current < 1) {
|
||||
current = 1;
|
||||
}
|
||||
if (size == null || size < 1) {
|
||||
size = 10;
|
||||
}
|
||||
|
||||
// 构建查询条件
|
||||
LambdaQueryWrapper<KnowledgeBase> queryWrapper = buildQueryWrapper(title, category, industry, creator, status);
|
||||
|
||||
// 执行分页查询
|
||||
Page<KnowledgeBase> page = new Page<>(current, size);
|
||||
LambdaQueryWrapper<KnowledgeBase> queryWrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
// 添加查询条件
|
||||
if (title != null && !title.trim().isEmpty()) {
|
||||
queryWrapper.like(KnowledgeBase::getTitle, title);
|
||||
}
|
||||
if (category != null && !category.trim().isEmpty()) {
|
||||
queryWrapper.eq(KnowledgeBase::getCategory, category);
|
||||
}
|
||||
if (industry != null && !industry.trim().isEmpty()) {
|
||||
queryWrapper.eq(KnowledgeBase::getIndustry, industry);
|
||||
}
|
||||
if (creator != null && !creator.trim().isEmpty()) {
|
||||
queryWrapper.like(KnowledgeBase::getCreator, creator);
|
||||
}
|
||||
if (status != null && !status.trim().isEmpty()) {
|
||||
queryWrapper.eq(KnowledgeBase::getStatus, status);
|
||||
}
|
||||
|
||||
// 排序:置顶优先,然后按排序权重,最后按创建时间倒序
|
||||
queryWrapper.orderByDesc(KnowledgeBase::getUpdateTime);
|
||||
|
||||
Page<KnowledgeBase> knowledgePage = this.page(page, queryWrapper);
|
||||
|
||||
// 处理附件文件内容读取(可选,用于调试)
|
||||
processAttachmentFiles(knowledgePage.getRecords());
|
||||
|
||||
// 构建返回结果
|
||||
return buildSuccessResult(knowledgePage);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("查询知识列表异常", e);
|
||||
return buildErrorResult("查询异常:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建查询条件
|
||||
*
|
||||
* @param title 标题(模糊查询)
|
||||
* @param category 分类
|
||||
* @param industry 行业
|
||||
* @param creator 创建人(模糊查询)
|
||||
* @param status 状态
|
||||
* @return 查询条件包装器
|
||||
*/
|
||||
private LambdaQueryWrapper<KnowledgeBase> buildQueryWrapper(String title, String category,
|
||||
String industry, String creator, String status) {
|
||||
LambdaQueryWrapper<KnowledgeBase> queryWrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
// 标题模糊查询
|
||||
if (isNotBlank(title)) {
|
||||
queryWrapper.like(KnowledgeBase::getTitle, title.trim());
|
||||
}
|
||||
|
||||
// 分类精确查询
|
||||
if (isNotBlank(category)) {
|
||||
queryWrapper.eq(KnowledgeBase::getCategory, category.trim());
|
||||
}
|
||||
|
||||
// 行业精确查询
|
||||
if (isNotBlank(industry)) {
|
||||
queryWrapper.eq(KnowledgeBase::getIndustry, industry.trim());
|
||||
}
|
||||
|
||||
// 创建人模糊查询
|
||||
if (isNotBlank(creator)) {
|
||||
queryWrapper.like(KnowledgeBase::getCreator, creator.trim());
|
||||
}
|
||||
|
||||
// 状态精确查询
|
||||
if (isNotBlank(status)) {
|
||||
queryWrapper.eq(KnowledgeBase::getStatus, status.trim());
|
||||
}
|
||||
|
||||
// 排序:置顶优先,然后按排序权重,最后按更新时间倒序
|
||||
queryWrapper.orderByDesc(KnowledgeBase::getIsTop)
|
||||
.orderByDesc(KnowledgeBase::getSortOrder)
|
||||
.orderByDesc(KnowledgeBase::getUpdateTime);
|
||||
|
||||
return queryWrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理附件文件内容读取(用于调试和日志记录)
|
||||
* 注意:此操作可能影响性能,生产环境建议移除或改为异步处理
|
||||
*
|
||||
* @param knowledgeList 知识库列表
|
||||
*/
|
||||
private void processAttachmentFiles(List<KnowledgeBase> knowledgeList) {
|
||||
if (knowledgeList == null || knowledgeList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
knowledgeList.forEach(knowledgeBase -> {
|
||||
String attachmentUrl = knowledgeBase.getAttachmentUrl();
|
||||
if (isNotBlank(attachmentUrl)) {
|
||||
readAndLogAttachmentContent(knowledgeBase.getId(), attachmentUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取并记录附件文件内容
|
||||
*
|
||||
* @param knowledgeId 知识库ID
|
||||
* @param attachmentUrl 附件URL
|
||||
*/
|
||||
private void readAndLogAttachmentContent(String knowledgeId, String attachmentUrl) {
|
||||
try {
|
||||
log.debug("知识库ID: {}, 附件URL: {}", knowledgeId, attachmentUrl);
|
||||
|
||||
// 从URL中提取对象名(文件名)
|
||||
String objectName = extractObjectNameFromUrl(attachmentUrl);
|
||||
if (isBlank(objectName)) {
|
||||
log.warn("无法从URL中提取对象名: {}", attachmentUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug("从MinIO读取文件: {}", objectName);
|
||||
|
||||
// 只处理文本文件
|
||||
if (!isTextFile(objectName)) {
|
||||
log.debug("文件 {} 不是文本文件,跳过内容读取", objectName);
|
||||
return;
|
||||
}
|
||||
|
||||
// 从MinIO下载文件
|
||||
InputStream inputStream = minIOService.downloadFile(objectName);
|
||||
if (inputStream == null) {
|
||||
log.warn("无法从MinIO下载文件: {}", objectName);
|
||||
return;
|
||||
}
|
||||
|
||||
// 读取文件内容预览
|
||||
String fileContent = readTextFileContent(inputStream, MAX_FILE_CONTENT_PREVIEW_LENGTH);
|
||||
log.info("知识库ID: {}, 文件内容预览 (前{}字符):\n{}",
|
||||
knowledgeId, MAX_FILE_CONTENT_PREVIEW_LENGTH, fileContent);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("读取MinIO文件内容失败,知识库ID: {}, 错误: {}", knowledgeId, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建成功结果
|
||||
*
|
||||
* @param knowledgePage 分页结果
|
||||
* @return 结果Map
|
||||
*/
|
||||
private Map<String, Object> buildSuccessResult(Page<KnowledgeBase> knowledgePage) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("success", true);
|
||||
result.put("message", "查询成功");
|
||||
result.put("data", knowledgePage.getRecords());
|
||||
@@ -128,13 +275,42 @@ public class KnowledgeBaseServiceImpl extends ServiceImpl<KnowledgeBaseMapper, K
|
||||
result.put("current", knowledgePage.getCurrent());
|
||||
result.put("size", knowledgePage.getSize());
|
||||
result.put("pages", knowledgePage.getPages());
|
||||
} catch (Exception e) {
|
||||
result.put("success", false);
|
||||
result.put("message", "查询异常:" + e.getMessage());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建错误结果
|
||||
*
|
||||
* @param message 错误消息
|
||||
* @return 结果Map
|
||||
*/
|
||||
private Map<String, Object> buildErrorResult(String message) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("success", false);
|
||||
result.put("message", message);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断字符串是否不为空
|
||||
*
|
||||
* @param str 字符串
|
||||
* @return 是否不为空
|
||||
*/
|
||||
private boolean isNotBlank(String str) {
|
||||
return str != null && !str.trim().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断字符串是否为空
|
||||
*
|
||||
* @param str 字符串
|
||||
* @return 是否为空
|
||||
*/
|
||||
private boolean isBlank(String str) {
|
||||
return str == null || str.trim().isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> updateKnowledge(KnowledgeBase knowledgeBase) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
@@ -347,4 +523,99 @@ public class KnowledgeBaseServiceImpl extends ServiceImpl<KnowledgeBaseMapper, K
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从MinIO URL中提取对象名(文件名)
|
||||
* MinIO URL格式通常是: http://endpoint/bucket/objectName 或 http://endpoint/bucket/objectName?参数
|
||||
*
|
||||
* @param url MinIO文件URL
|
||||
* @return 对象名,如果提取失败返回null
|
||||
*/
|
||||
private String extractObjectNameFromUrl(String url) {
|
||||
if (url == null || url.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// 去掉查询参数
|
||||
String urlWithoutParams = url;
|
||||
if (url.contains("?")) {
|
||||
urlWithoutParams = url.substring(0, url.indexOf("?"));
|
||||
}
|
||||
|
||||
// 按"/"分割URL,获取最后一个部分作为对象名
|
||||
// MinIO URL格式: http://endpoint/bucket/objectName
|
||||
String[] parts = urlWithoutParams.split("/");
|
||||
if (parts.length > 0) {
|
||||
// 获取最后一个非空部分作为对象名
|
||||
for (int i = parts.length - 1; i >= 0; i--) {
|
||||
if (parts[i] != null && !parts[i].trim().isEmpty()) {
|
||||
return parts[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
log.error("从URL提取对象名失败: {}", url, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断文件是否为文本文件
|
||||
*
|
||||
* @param fileName 文件名
|
||||
* @return 是否为文本文件
|
||||
*/
|
||||
private boolean isTextFile(String fileName) {
|
||||
if (isBlank(fileName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String lowerFileName = fileName.toLowerCase();
|
||||
for (String ext : TEXT_FILE_EXTENSIONS) {
|
||||
if (lowerFileName.endsWith(ext)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取文本文件内容
|
||||
*
|
||||
* @param inputStream 文件输入流
|
||||
* @param maxLength 最大读取长度(字符数)
|
||||
* @return 文件内容(限制长度)
|
||||
*/
|
||||
private String readTextFileContent(InputStream inputStream, int maxLength) {
|
||||
if (inputStream == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
StringBuilder content = new StringBuilder();
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
|
||||
String line;
|
||||
int totalLength = 0;
|
||||
while ((line = reader.readLine()) != null && totalLength < maxLength) {
|
||||
if (totalLength + line.length() > maxLength) {
|
||||
// 如果加上这一行会超过限制,只读取部分内容
|
||||
int remaining = maxLength - totalLength;
|
||||
content.append(line, 0, remaining);
|
||||
content.append("\n...(内容已截断)");
|
||||
break;
|
||||
}
|
||||
content.append(line).append("\n");
|
||||
totalLength += line.length() + 1; // +1 for newline
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("读取文件内容失败: {}", e.getMessage(), e);
|
||||
return "读取文件内容失败: " + e.getMessage();
|
||||
}
|
||||
|
||||
return content.toString();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user