dev
xiaohuo 8 months ago
parent df48131704
commit 33d3f19c4c
  1. 189
      ALOps_sys_backend/alops-admin/src/main/java/com/alops/web/controller/LlmKnowledgeController/FileController.java
  2. 58
      ALOps_sys_backend/alops-admin/src/main/java/com/alops/web/controller/LlmKnowledgeController/KnowLedgeController.java
  3. 9
      ALOps_sys_backend/alops-admin/src/main/java/com/alops/web/controller/LlmKnowledgeController/QAController.java
  4. 2
      ALOps_sys_backend/alops-chat/src/main/java/com/alops/chat/service/AItokenService.java
  5. 2
      ALOps_sys_backend/alops-system/src/main/resources/mapper/system/EquBaseMapper.xml

@ -0,0 +1,189 @@
package com.alops.web.controller.LlmKnowledgeController;
import java.io.File;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.alops.chat.entity.UploadedFile;
import com.alops.chat.service.UploadedFileService;
import com.alops.common.config.RuoYiConfig;
import com.alops.common.core.controller.BaseController;
import com.alops.common.utils.file.FileUtils;
import com.alops.common.utils.file.FileTypeUtils;
import com.alops.common.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
/**
* 文件下载和预览控制器
*
* @author lingma
*/
@RestController
@RequestMapping("/QandA/knowledge/file")
public class FileController extends BaseController {
private static final Logger log = LoggerFactory.getLogger(FileController.class);
@Autowired
private UploadedFileService uploadedFileService;
/**
* 下载文件
*
* @param fileId 文件ID
* @param response 响应对象
*/
@PreAuthorize("@ss.hasPermi('QandA:knowledge:filedownload')")
@GetMapping("/download/{fileId}")
public void download(@PathVariable Long fileId, HttpServletResponse response, HttpServletRequest request) {
try {
UploadedFile uploadedFile = uploadedFileService.findById(fileId);
if (uploadedFile == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "文件未找到");
return;
}
// 获取文件路径
String filePath = uploadedFile.getFilePath();
// 获取实际文件路径(处理配置文件前缀)
String realPath = RuoYiConfig.getProfile() + filePath.replaceFirst("/profile", "");
// 检查文件是否存在
File file = new File(realPath);
if (!file.exists()) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "文件未找到");
return;
}
// 检查文件是否允许下载
if (!FileUtils.checkAllowDownload(realPath)) {
throw new Exception(StringUtils.format("文件({})非法,不允许下载。", realPath));
}
// 设置下载文件名(从文件路径中提取真实文件名)
String fileName = FileUtils.getName(realPath);
if (StringUtils.isEmpty(fileName)) {
fileName = uploadedFile.getSource();
// 如果source中也不包含扩展名,则尝试添加扩展名
String extension = uploadedFile.getFormat();
if (StringUtils.isNotEmpty(extension) && !fileName.endsWith("." + extension)) {
fileName = fileName + "." + extension;
}
}
// 对文件名进行编码以支持中文
String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString())
.replaceAll("\\+", "%20");
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
response.setHeader("Content-Disposition", "attachment; filename=" + encodedFileName);
FileUtils.writeBytes(realPath, response.getOutputStream());
} catch (Exception e) {
log.error("下载文件失败", e);
try {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "下载文件失败: " + e.getMessage());
} catch (IOException ioException) {
log.error("设置错误响应失败", ioException);
}
}
}
/**
* 预览文件
*
* @param fileId 文件ID
* @param response 响应对象
*/
@PreAuthorize("@ss.hasPermi('QandA:knowledge:filepreview')")
@GetMapping("/preview/{fileId}")
public void preview(@PathVariable Long fileId, HttpServletResponse response, HttpServletRequest request) {
try {
UploadedFile uploadedFile = uploadedFileService.findById(fileId);
if (uploadedFile == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "文件未找到");
return;
}
// 获取文件路径
String filePath = uploadedFile.getFilePath();
// 获取实际文件路径(处理配置文件前缀)
String realPath = RuoYiConfig.getProfile() + filePath.replaceFirst("/profile", "");
// 检查文件是否存在
File file = new File(realPath);
if (!file.exists()) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "文件未找到");
return;
}
// 检查文件是否允许预览
if (!FileUtils.checkAllowDownload(realPath)) {
throw new Exception(StringUtils.format("文件({})非法,不允许预览。", realPath));
}
// 设置Content-Type以便浏览器预览
String fileExtension = FileTypeUtils.getFileType(uploadedFile.getSource());
if (StringUtils.isEmpty(fileExtension)) {
fileExtension = uploadedFile.getFormat();
}
String contentType = getContentTypeByExtension(fileExtension);
if (contentType != null) {
response.setContentType(contentType);
} else {
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
}
FileUtils.writeBytes(realPath, response.getOutputStream());
} catch (Exception e) {
log.error("预览文件失败", e);
try {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "预览文件失败: " + e.getMessage());
} catch (IOException ioException) {
log.error("设置错误响应失败", ioException);
}
}
}
/**
* 根据文件扩展名获取对应的Content-Type
*
* @param extension 文件扩展名
* @return Content-Type字符串
*/
private String getContentTypeByExtension(String extension) {
if (StringUtils.isEmpty(extension)) {
return MediaType.APPLICATION_OCTET_STREAM_VALUE;
}
switch (extension.toLowerCase()) {
case "txt":
return "text/plain;charset=UTF-8";
case "doc":
return "application/msword";
case "docx":
return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
case "pdf":
return "application/pdf";
case "jpg":
case "jpeg":
return "image/jpeg";
case "png":
return "image/png";
case "gif":
return "image/gif";
default:
return MediaType.APPLICATION_OCTET_STREAM_VALUE;
}
}
}

@ -4,13 +4,11 @@ import com.alops.chat.entity.KnowledgeEntity;
import com.alops.chat.entity.UploadedFile; import com.alops.chat.entity.UploadedFile;
import com.alops.chat.service.KnowledgeService; import com.alops.chat.service.KnowledgeService;
import com.alops.chat.service.UploadedFileService; import com.alops.chat.service.UploadedFileService;
import com.alops.common.annotation.Log;
import com.alops.common.constant.HttpStatus; import com.alops.common.constant.HttpStatus;
import com.alops.common.core.controller.BaseController; import com.alops.common.core.controller.BaseController;
import com.alops.common.core.domain.AjaxResult; import com.alops.common.core.domain.AjaxResult;
import com.alops.common.core.page.TableDataInfo; import com.alops.common.core.page.TableDataInfo;
import com.alops.common.config.RuoYiConfig; import com.alops.common.config.RuoYiConfig;
import com.alops.common.enums.BusinessType;
import com.alops.common.utils.file.FileUploadUtils; import com.alops.common.utils.file.FileUploadUtils;
import com.alops.common.utils.file.MimeTypeUtils; import com.alops.common.utils.file.MimeTypeUtils;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
@ -26,14 +24,9 @@ import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.util.HashMap;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* 文件上传控制器 * 文件上传控制器
@ -53,6 +46,36 @@ public class KnowLedgeController extends BaseController {
// 文件存储目录 // 文件存储目录
private static final String UPLOAD_DIR = "uploads/"; private static final String UPLOAD_DIR = "uploads/";
/**
* 根据文件ID获取文件路径供前端下载和预览使用
*
* @param id 文件ID
* @return 文件路径信息
*/
@PreAuthorize("@ss.hasPermi('QandA:knowledge:filedownload')")
@GetMapping("/file-path/{id}")
@ApiOperation("获取文件路径")
public AjaxResult getFilePath(@PathVariable Long id) {
try {
UploadedFile uploadedFile = uploadedFileService.findById(id);
if (uploadedFile == null) {
return AjaxResult.error(HttpStatus.NOT_FOUND, "未找到指定的文件");
}
// 返回文件路径信息
Map<String, Object> result = new HashMap<>();
result.put("filePath", uploadedFile.getFilePath());
result.put("fileName", uploadedFile.getSource());
result.put("fileFormat", uploadedFile.getFormat());
result.put("uploadTime", uploadedFile.getUploadTime());
return AjaxResult.success(result);
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.error("获取文件路径失败: " + e.getMessage());
}
}
/** /**
* 上传并处理文本文档 * 上传并处理文本文档
* *
@ -63,7 +86,6 @@ public class KnowLedgeController extends BaseController {
@PreAuthorize("@ss.hasPermi('QandA:knowledge:uploadfile')") @PreAuthorize("@ss.hasPermi('QandA:knowledge:uploadfile')")
@PostMapping("/upload") @PostMapping("/upload")
@Log(title = "上传并处理文本文档", businessType = BusinessType.INSERT)
@ApiOperation("上传并处理文本文档") @ApiOperation("上传并处理文本文档")
public AjaxResult uploadFile( public AjaxResult uploadFile(
@RequestPart("file") @RequestPart("file")
@ -84,14 +106,14 @@ public class KnowLedgeController extends BaseController {
// 使用优化后的文件上传工具保存文件到本地并记录信息到数据库 // 使用优化后的文件上传工具保存文件到本地并记录信息到数据库
// 上传文件并保持原始文件名,只允许 TEXT_EXTENSION 类型的文件 // 上传文件并保持原始文件名,只允许 TEXT_EXTENSION 类型的文件
String filePath = FileUploadUtils.uploadWithOriginalFilename( String filePath = FileUploadUtils.uploadWithOriginalFilename(
RuoYiConfig.getProfile(), RuoYiConfig.getProfile(),
file, file,
MimeTypeUtils.TEXT_EXTENSION); MimeTypeUtils.TEXT_EXTENSION);
String fileExtension = getFileExtension(file.getOriginalFilename()); String fileExtension = getFileExtension(file.getOriginalFilename());
UploadedFile uploadedFile = new UploadedFile(filePath, source, fileExtension, null); UploadedFile uploadedFile = new UploadedFile(filePath, source, fileExtension, null);
uploadedFile = uploadedFileService.save(uploadedFile); uploadedFile = uploadedFileService.save(uploadedFile);
// 获取保存后的文件ID // 获取保存后的文件ID
Long fileId = uploadedFile.getId(); Long fileId = uploadedFile.getId();
@ -171,7 +193,6 @@ public class KnowLedgeController extends BaseController {
*/ */
@PreAuthorize("@ss.hasPermi('QandA:knowledge:upload')") @PreAuthorize("@ss.hasPermi('QandA:knowledge:upload')")
@PostMapping("/add-knowledge") @PostMapping("/add-knowledge")
@Log(title = "添加知识条目接口", businessType = BusinessType.INSERT)
@ApiOperation("添加知识条目接口") @ApiOperation("添加知识条目接口")
public AjaxResult addKnowledge( public AjaxResult addKnowledge(
@RequestParam String content, @RequestParam String content,
@ -193,7 +214,6 @@ public class KnowLedgeController extends BaseController {
*/ */
@PreAuthorize("@ss.hasPermi('QandA:knowledge:permquery')") @PreAuthorize("@ss.hasPermi('QandA:knowledge:permquery')")
@GetMapping("/permanent-knowledge") @GetMapping("/permanent-knowledge")
@Log(title = "获取所有正式知识条目", businessType = BusinessType.SELECT)
@ApiOperation("获取所有正式知识条目") @ApiOperation("获取所有正式知识条目")
public TableDataInfo getPermanentKnowledge() { public TableDataInfo getPermanentKnowledge() {
startPage(); // 开启分页 startPage(); // 开启分页
@ -208,7 +228,6 @@ public class KnowLedgeController extends BaseController {
*/ */
@PreAuthorize("@ss.hasPermi('QandA:knowledge:filelist')") @PreAuthorize("@ss.hasPermi('QandA:knowledge:filelist')")
@GetMapping("/uploaded-files") @GetMapping("/uploaded-files")
@Log(title = "获取所有上传文档", businessType = BusinessType.SELECT)
@ApiOperation("获取所有上传文档") @ApiOperation("获取所有上传文档")
public TableDataInfo getUploadedFiles() { public TableDataInfo getUploadedFiles() {
startPage(); // 开启分页 startPage(); // 开启分页
@ -226,7 +245,6 @@ public class KnowLedgeController extends BaseController {
*/ */
@PreAuthorize("@ss.hasPermi('QandA:knowledge:update')") @PreAuthorize("@ss.hasPermi('QandA:knowledge:update')")
@PutMapping("/update-knowledge") @PutMapping("/update-knowledge")
@Log(title = "更新知识条目", businessType = BusinessType.INSERT)
@ApiOperation("更新知识条目") @ApiOperation("更新知识条目")
public AjaxResult updateKnowledge( public AjaxResult updateKnowledge(
@RequestParam Long id, @RequestParam Long id,
@ -253,7 +271,6 @@ public class KnowLedgeController extends BaseController {
*/ */
@PreAuthorize("@ss.hasPermi('QandA:knowledge:del')") @PreAuthorize("@ss.hasPermi('QandA:knowledge:del')")
@DeleteMapping("/knowledge/{id}") @DeleteMapping("/knowledge/{id}")
@Log(title = "删除知识条目接口", businessType = BusinessType.DELETE)
@ApiOperation("删除知识条目接口") @ApiOperation("删除知识条目接口")
public AjaxResult deleteKnowledge(@PathVariable Long id) { public AjaxResult deleteKnowledge(@PathVariable Long id) {
try { try {
@ -276,13 +293,12 @@ public class KnowLedgeController extends BaseController {
*/ */
@ApiOperation("删除上传文件信息") @ApiOperation("删除上传文件信息")
@PreAuthorize("@ss.hasPermi('QandA:knowledge:filedel')") @PreAuthorize("@ss.hasPermi('QandA:knowledge:filedel')")
@Log(title = "删除上传文件信息", businessType = BusinessType.DELETE)
@DeleteMapping("/uploaded-files/{id}") @DeleteMapping("/uploaded-files/{id}")
public AjaxResult deleteUploadedFile(@PathVariable Long id) { public AjaxResult deleteUploadedFile(@PathVariable Long id) {
try { try {
// 先删除关联的知识条目 // 先删除关联的知识条目
knowledgeService.deleteKnowledgeByFileId(id); knowledgeService.deleteKnowledgeByFileId(id);
// 再删除文件信息 // 再删除文件信息
uploadedFileService.deleteById(id); uploadedFileService.deleteById(id);
return AjaxResult.success("上传文件信息删除成功"); return AjaxResult.success("上传文件信息删除成功");

@ -1,17 +1,17 @@
package com.alops.web.controller.LlmKnowledgeController; package com.alops.web.controller.LlmKnowledgeController;
import com.alops.chat.DTO.AnswerResponse; import com.alops.chat.DTO.AnswerResponse;
import com.alops.chat.DTO.QuestionRequest; import com.alops.chat.DTO.QuestionRequest;
import com.alops.chat.entity.KnowledgeEntity; import com.alops.chat.entity.KnowledgeEntity;
import com.alops.chat.service.AItokenService; import com.alops.chat.service.AItokenService;
import com.alops.chat.service.KnowledgeService; import com.alops.chat.service.KnowledgeService;
import com.alops.common.annotation.Log;
import com.alops.common.core.domain.AjaxResult; import com.alops.common.core.domain.AjaxResult;
import com.alops.common.enums.BusinessType;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -49,7 +49,6 @@ public class QAController {
@PreAuthorize("@ss.hasPermi('QandA:Aiassistant:ask')") @PreAuthorize("@ss.hasPermi('QandA:Aiassistant:ask')")
@PostMapping("/ask") @PostMapping("/ask")
@ApiOperation("智能问答提问") @ApiOperation("智能问答提问")
@Log(title = "智能问答提问", businessType = BusinessType.INSERT)
public AjaxResult askQuestion(@RequestBody QuestionRequest request) { public AjaxResult askQuestion(@RequestBody QuestionRequest request) {
try { try {
// 检查是否使用假数据模式 // 检查是否使用假数据模式
@ -88,12 +87,12 @@ public class QAController {
List<Map<String, String>> messages = new ArrayList<>(); List<Map<String, String>> messages = new ArrayList<>();
Map<String, String> systemMessage = new HashMap<>(); Map<String, String> systemMessage = new HashMap<>();
systemMessage.put("role", "system"); systemMessage.put("role", "system");
systemMessage.put("content", "你是一个智能运维助手,根据提供的上下文回答问题。如果上下文中没有相关信息,请说明无法基于提供的上下文回答问题。"); systemMessage.put("content", "你是一个智能运维助手,根据提供的上下文回答问题。如果上下文中没有相关信息,请结合自身知识回答用户问题。");
messages.add(systemMessage); messages.add(systemMessage);
Map<String, String> userMessage = new HashMap<>(); Map<String, String> userMessage = new HashMap<>();
userMessage.put("role", "user"); userMessage.put("role", "user");
userMessage.put("content", "上下文信息:\n" + context + "\n\n请根据以上上下文信息回答问题:" + request.getQuestion()); userMessage.put("content", "上下文信息:\n" + context + "\n\n请根据以上上下文信息并且结合自身知识回答问题:" + request.getQuestion());
messages.add(userMessage); messages.add(userMessage);
String answer = AItokenService.generateAnswer(messages); String answer = AItokenService.generateAnswer(messages);

@ -30,7 +30,7 @@ public class AItokenService {
// 使用的模型 // 使用的模型
private static final String EMBEDDING_MODEL = "nomic-embed-text:v1.5"; private static final String EMBEDDING_MODEL = "nomic-embed-text:v1.5";
private static final String GENERATION_MODEL = "qwen3:4b"; private static final String GENERATION_MODEL = "qwen3:8b";
// 是否使用假数据 // 是否使用假数据
@Value("${mock.enabled:false}") @Value("${mock.enabled:false}")

@ -278,7 +278,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</if> </if>
<!-- 精确查询:状态 --> <!-- 精确查询:状态 -->
<if test="status != null and status != ''"> <if test="status != null ">
AND b.status = #{status} AND b.status = #{status}
</if> </if>
</where> </where>

Loading…
Cancel
Save