素材上传

This commit is contained in:
zhonghua1
2025-12-24 22:59:54 +08:00
parent 7753523fb7
commit 6a5d6accb4
3 changed files with 335 additions and 36 deletions

View File

@@ -121,3 +121,5 @@ hikari:

View File

@@ -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) {

View File

@@ -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();
}
}