From 0ba62304c79981a9fe51bb43074710dea6f79ae4 Mon Sep 17 00:00:00 2001 From: gaoxq <376340421@qq.com> Date: Thu, 20 Nov 2025 15:31:10 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=95=B0=E6=8D=AE=E5=90=8C?= =?UTF-8?q?=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/mini/capi/biz/viewController.java | 24 ++- .../java/com/mini/capi/utils/FileUtils.java | 137 ++++-------------- src/main/resources/templates/file.html | 135 +++-------------- 3 files changed, 73 insertions(+), 223 deletions(-) diff --git a/src/main/java/com/mini/capi/biz/viewController.java b/src/main/java/com/mini/capi/biz/viewController.java index 568d3b4..770fb17 100644 --- a/src/main/java/com/mini/capi/biz/viewController.java +++ b/src/main/java/com/mini/capi/biz/viewController.java @@ -7,15 +7,19 @@ import com.mini.capi.biz.service.*; import com.mini.capi.model.info.FolderTree; import com.mini.capi.model.info.RunInfo; import com.mini.capi.utils.DateUtils; +import com.mini.capi.utils.FileUtils; import com.mini.capi.utils.SqlUtils; import com.mini.capi.utils.vDate; import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.GetMapping; +import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -170,18 +174,32 @@ public class viewController { List childFolders = foldersService.list(childWrapper); folderTrees.add(new FolderTree(fileFolder, childFolders)); } - QueryWrapper uploadWrapper = new QueryWrapper<>(); uploadWrapper.ne("parent_id", 0); List uploadFolder = foldersService.list(uploadWrapper); - - model.addAttribute("files", files); model.addAttribute("uploadFolder", uploadFolder); model.addAttribute("folderTrees", folderTrees); return "file"; } + + /** + * 文件下载 + */ + @GetMapping("/biz/downloadFile") + public void downloadFile(HttpServletResponse response, String fileId) { + try { + BizFiles bizFile = filesService.getById(fileId); + File file = new File(bizFile.getFileName()); + if (file.exists()) { + FileUtils.downloadFile(response, file, bizFile.getFileName()); + } + } catch (Exception e) { + System.err.println(e.getMessage()); + } + } + /** * 系统管理 */ diff --git a/src/main/java/com/mini/capi/utils/FileUtils.java b/src/main/java/com/mini/capi/utils/FileUtils.java index 9292d43..19b37eb 100644 --- a/src/main/java/com/mini/capi/utils/FileUtils.java +++ b/src/main/java/com/mini/capi/utils/FileUtils.java @@ -1,9 +1,13 @@ package com.mini.capi.utils; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.MediaType; import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; import java.io.*; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.util.UUID; @@ -78,116 +82,33 @@ public class FileUtils { } - public static MultipartFile[] getMultipartFiles(String filePath) { - // 1. 校验路径合法性 - if (filePath == null || filePath.trim().isEmpty()) { - throw new IllegalArgumentException("文件路径不能为空"); + /** + * 下载文件核心方法 + */ + public static void downloadFile(HttpServletResponse response, File file, String downloadFileName) throws IOException { + // 1. 校验文件合法性 + if (file == null || !file.exists() || !file.isFile()) { + throw new FileNotFoundException("文件不存在或不是有效文件:" + (file != null ? file.getAbsolutePath() : "null")); } - File dir = new File(filePath); - if (!dir.exists()) { - throw new IllegalArgumentException("目录不存在:" + filePath); + if (!file.canRead()) { + throw new IOException("文件无读取权限:" + file.getAbsolutePath()); } - if (!dir.isDirectory()) { - throw new IllegalArgumentException("路径不是目录:" + filePath); - } - - // 2. 筛选目录下的所有文件(排除子目录) - File[] localFiles = dir.listFiles(File::isFile); - if (localFiles == null || localFiles.length == 0) { - return new MultipartFile[0]; - } - - // 3. 包装本地File为MultipartFile并组装数组 - MultipartFile[] result = new MultipartFile[localFiles.length]; - for (int i = 0; i < localFiles.length; i++) { - File file = localFiles[i]; - result[i] = new MultipartFile() { - @Override - public String getName() { - return file.getName(); // 文件名(与原始文件名一致,满足基础场景) - } - - @Override - public String getOriginalFilename() { - return file.getName(); // 原始文件名(本地文件即自身文件名) - } - - @Override - public String getContentType() { - // 推断文件MIME类型,失败时返回默认二进制类型 - try { - return Files.probeContentType(file.toPath()); - } catch (IOException e) { - return "application/octet-stream"; - } - } - - @Override - public boolean isEmpty() { - return file.length() == 0; // 空文件判断(大小为0) - } - - @Override - public long getSize() { - return file.length(); // 文件大小(字节) - } - - @Override - public byte[] getBytes() throws IOException { - return Files.readAllBytes(file.toPath()); // 读取文件字节数组 - } - - @Override - public InputStream getInputStream() throws IOException { - return Files.newInputStream(file.toPath()); // 获取文件输入流 - } - - @Override - public void transferTo(File dest) throws IOException, IllegalStateException { - Files.copy(file.toPath(), dest.toPath()); // 复制文件到目标路径 - } - }; - } - return result; - } - - public static String getFileType(String fileName) { - if (fileName == null || fileName.isEmpty()) { - return "unknown"; - } - - String extension = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase(); - - switch (extension) { - case "pdf": - return "pdf"; - case "doc": - case "docx": - return "word"; - case "xls": - case "xlsx": - return "excel"; - case "ppt": - case "pptx": - return "ppt"; - case "jpg": - case "jpeg": - case "png": - case "gif": - return "image"; - case "mp4": - case "avi": - case "mov": - return "video"; - case "mp3": - case "wav": - return "audio"; - default: - // 如果没有扩展名,视为文件夹 - if (!fileName.contains(".")) { - return "folder"; - } - return "unknown"; + // 2. 设置响应头(解决中文乱码、触发下载对话框) + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); // 二进制流 + String fileName = downloadFileName != null ? downloadFileName : file.getName(); + // 文件名URLEncoder编码,兼容浏览器中文显示 + String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8); + response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFileName); + response.setContentLengthLong(file.length()); // 设置文件大小(可选,优化体验) + // 3. 流传输文件(try-with-resources自动关闭流) + try (InputStream in = new FileInputStream(file); + OutputStream out = response.getOutputStream()) { + byte[] buffer = new byte[1024 * 8]; // 8KB缓冲区,提升传输效率 + int len; + while ((len = in.read(buffer)) != -1) { + out.write(buffer, 0, len); + } + out.flush(); // 强制刷出缓冲区数据 } } diff --git a/src/main/resources/templates/file.html b/src/main/resources/templates/file.html index 0bee091..35e21c2 100644 --- a/src/main/resources/templates/file.html +++ b/src/main/resources/templates/file.html @@ -198,21 +198,31 @@
- -
+
-
+ +
文档 -
-

-

2023-06-15 | 2.4MB

+
+

+

+

+ + +

-
-
@@ -425,16 +435,10 @@ } // 下载文件 - function downloadFile(fileName) { - showMessage(`正在准备下载 ${fileName}...`); - - // 这里应该是实际的下载API调用 + function downloadFile(fileId) { setTimeout(() => { - // 模拟下载完成 - showMessage(`${fileName} 下载成功`); - - // 实际项目中,这里应该调用后端API进行文件下载 - // 例如:window.location.href = `/api/download/${fileName}`; + showMessage(`下载成功`); + window.open('downloadFile?fileId=' + fileId, '_blank') }, 800); } @@ -730,99 +734,6 @@ toast.classList.add('translate-y-20', 'opacity-0'); }, 3000); } - - // 后端API接口方法(与Spring Boot联调) - const api = { - // 获取文件夹列表 - getFolders: function () { - // 实际项目中,这里应该调用后端API获取文件夹列表 - // return fetch('/api/folders') - // .then(response => response.json()) - // .catch(error => { - // showMessage('获取文件夹列表失败', 'error'); - // console.error(error); - // return []; - // }); - }, - - // 获取文件列表 - getFiles: function (folderId) { - // 实际项目中,这里应该调用后端API获取文件列表 - // return fetch(`/api/folders/${folderId}/files`) - // .then(response => response.json()) - // .catch(error => { - // showMessage('获取文件列表失败', 'error'); - // console.error(error); - // return []; - // }); - }, - - // 创建文件夹 - createFolder: function (name, parentId) { - // 实际项目中,这里应该调用后端API创建文件夹 - // return fetch('/api/folders', { - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json', - // }, - // body: JSON.stringify({ - // name: name, - // parentId: parentId - // }), - // }) - // .then(response => response.json()) - // .catch(error => { - // showMessage('创建文件夹失败', 'error'); - // console.error(error); - // throw error; - // }); - }, - - // 上传文件 - uploadFiles: function (files, folderId) { - // 实际项目中,这里应该调用后端API上传文件 - // const formData = new FormData(); - // files.forEach(file => { - // formData.append('files', file); - // }); - // formData.append('folderId', folderId); - // - // return fetch('/api/upload', { - // method: 'POST', - // body: formData, - // }) - // .then(response => response.json()) - // .catch(error => { - // showMessage('上传文件失败', 'error'); - // console.error(error); - // throw error; - // }); - }, - - // 下载文件 - downloadFile: function (fileId) { - // 实际项目中,这里应该调用后端API下载文件 - // window.location.href = `/api/files/${fileId}/download`; - }, - - // 删除文件 - deleteFile: function (fileId) { - // 实际项目中,这里应该调用后端API删除文件 - // return fetch(`/api/files/${fileId}`, { - // method: 'DELETE', - // }) - // .then(response => { - // if (!response.ok) { - // throw new Error('删除文件失败'); - // } - // }) - // .catch(error => { - // showMessage('删除文件失败', 'error'); - // console.error(error); - // throw error; - // }); - } - };