|
|
@@ -0,0 +1,165 @@
|
|
|
+package org.ruoyi.chat.controller.chat;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import jakarta.servlet.http.HttpServletResponse;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.ruoyi.domain.KnowledgeAttach;
|
|
|
+import org.ruoyi.mapper.KnowledgeAttachMapper;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
+import org.springframework.http.HttpStatus;
|
|
|
+import org.springframework.web.bind.annotation.GetMapping;
|
|
|
+import org.springframework.web.bind.annotation.RequestMapping;
|
|
|
+import org.springframework.web.bind.annotation.RequestParam;
|
|
|
+import org.springframework.web.bind.annotation.RestController;
|
|
|
+
|
|
|
+import java.io.IOException;
|
|
|
+import java.io.InputStream;
|
|
|
+import java.io.OutputStream;
|
|
|
+import java.net.URLEncoder;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+import java.nio.file.Files;
|
|
|
+import java.nio.file.Path;
|
|
|
+import java.nio.file.Paths;
|
|
|
+
|
|
|
+@Slf4j
|
|
|
+@RestController
|
|
|
+@RequestMapping("/file")
|
|
|
+public class FileController {
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ @Value("${file.upload-path}")
|
|
|
+ private String uploadPath;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private KnowledgeAttachMapper attachMapper;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 下载文件(通过 docId)
|
|
|
+ */
|
|
|
+ @GetMapping("/download")
|
|
|
+ public void download(@RequestParam String docId, HttpServletResponse response) {
|
|
|
+ try {
|
|
|
+ // 1. 根据 docId 查询附件信息
|
|
|
+ KnowledgeAttach attach = attachMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<KnowledgeAttach>()
|
|
|
+ .eq(KnowledgeAttach::getDocId, docId)
|
|
|
+ );
|
|
|
+
|
|
|
+ if (attach == null) {
|
|
|
+ response.setStatus(HttpStatus.NOT_FOUND.value());
|
|
|
+ response.getWriter().write("文件记录不存在");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 获取 kid 和文件名
|
|
|
+ String kid = attach.getKid();
|
|
|
+ String docName = attach.getDocName(); // 你这里用的是原始文件名作为存储名
|
|
|
+
|
|
|
+ if (docName == null || docName.isEmpty()) {
|
|
|
+ response.setStatus(HttpStatus.NOT_FOUND.value());
|
|
|
+ response.getWriter().write("文件名信息缺失");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (kid == null || kid.isEmpty()) {
|
|
|
+ response.setStatus(HttpStatus.NOT_FOUND.value());
|
|
|
+ response.getWriter().write("知识库ID(kid)缺失");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ✅ 正确路径:uploadPath/kid/docName
|
|
|
+ Path filePath = Paths.get(uploadPath, kid, docName);
|
|
|
+ if (!Files.exists(filePath)) {
|
|
|
+ log.warn("文件未找到,路径:{}", filePath.toString());
|
|
|
+ response.setStatus(HttpStatus.NOT_FOUND.value());
|
|
|
+ response.getWriter().write("文件在磁盘上未找到");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 探测内容类型
|
|
|
+ String contentType;
|
|
|
+ try {
|
|
|
+ contentType = Files.probeContentType(filePath);
|
|
|
+ } catch (IOException e) {
|
|
|
+ contentType = "application/octet-stream";
|
|
|
+ }
|
|
|
+ if (contentType == null) {
|
|
|
+ contentType = "application/octet-stream";
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 设置响应头(强制下载)
|
|
|
+ response.setContentType(contentType);
|
|
|
+ response.setHeader(
|
|
|
+ "Content-Disposition",
|
|
|
+ "attachment; filename=" + URLEncoder.encode(docName, StandardCharsets.UTF_8)
|
|
|
+ );
|
|
|
+ response.setHeader("Content-Length", String.valueOf(Files.size(filePath)));
|
|
|
+
|
|
|
+ // 5. 输出文件流
|
|
|
+ try (InputStream in = Files.newInputStream(filePath);
|
|
|
+ OutputStream out = response.getOutputStream()) {
|
|
|
+ in.transferTo(out);
|
|
|
+ out.flush();
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (IOException e) {
|
|
|
+ log.error("文件下载失败 - docId: {}", docId, e);
|
|
|
+ try {
|
|
|
+ response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
|
|
|
+ response.getWriter().write("文件下载失败:" + e.getMessage());
|
|
|
+ } catch (IOException ignored) {}
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 【可选】在线预览(不强制下载)
|
|
|
+ */
|
|
|
+ @GetMapping("/view")
|
|
|
+ public void view(@RequestParam String docId, HttpServletResponse response) {
|
|
|
+ try {
|
|
|
+ KnowledgeAttach attach = attachMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<KnowledgeAttach>()
|
|
|
+ .eq(KnowledgeAttach::getDocId, docId)
|
|
|
+ );
|
|
|
+
|
|
|
+ if (attach == null || attach.getKid() == null || attach.getDocName() == null) {
|
|
|
+ response.setStatus(HttpStatus.NOT_FOUND.value());
|
|
|
+ response.getWriter().write("文件不存在");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ Path filePath = Paths.get(uploadPath, attach.getKid(), attach.getDocName());
|
|
|
+ if (!Files.exists(filePath)) {
|
|
|
+ response.setStatus(HttpStatus.NOT_FOUND.value());
|
|
|
+ response.getWriter().write("文件未找到");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ String contentType = Files.probeContentType(filePath);
|
|
|
+ if (contentType == null) contentType = "application/octet-stream";
|
|
|
+
|
|
|
+ // inline 表示尝试在浏览器中打开(如 PDF 可预览)
|
|
|
+ response.setContentType(contentType);
|
|
|
+ response.setHeader(
|
|
|
+ "Content-Disposition",
|
|
|
+ "inline; filename=" + URLEncoder.encode(attach.getDocName(), StandardCharsets.UTF_8)
|
|
|
+ );
|
|
|
+ response.setHeader("Content-Length", String.valueOf(Files.size(filePath)));
|
|
|
+
|
|
|
+ try (InputStream in = Files.newInputStream(filePath);
|
|
|
+ OutputStream out = response.getOutputStream()) {
|
|
|
+ in.transferTo(out);
|
|
|
+ out.flush();
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (IOException e) {
|
|
|
+ log.error("文件预览失败 - docId: {}", docId, e);
|
|
|
+ try {
|
|
|
+ response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
|
|
|
+ response.getWriter().write("预览失败:" + e.getMessage());
|
|
|
+ } catch (IOException ignored) {}
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|