更新数据同步

This commit is contained in:
2025-11-20 15:31:10 +08:00
parent f8083afbd8
commit 0ba62304c7
3 changed files with 73 additions and 223 deletions

View File

@@ -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<BizFileFolders> childFolders = foldersService.list(childWrapper);
folderTrees.add(new FolderTree(fileFolder, childFolders));
}
QueryWrapper<BizFileFolders> uploadWrapper = new QueryWrapper<>();
uploadWrapper.ne("parent_id", 0);
List<BizFileFolders> 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());
}
}
/**
* 系统管理
*/

View File

@@ -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(); // 强制刷出缓冲区数据
}
}

View File

@@ -198,21 +198,31 @@
<!-- 文件列表区域 -->
<div class="flex-grow overflow-y-auto scrollbar-thin p-4">
<div id="fileList" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
<!-- 文件项1 -->
<div class="bg-white rounded-lg shadow-card p-4 hover:shadow-hover transition-shadow"
<!-- 文件项 - 循环渲染 -->
<div class="bg-white rounded-lg shadow-card p-4 hover:shadow-hover transition-all duration-300 ease-in-out transform hover:-translate-y-1"
th:each="file : ${files}">
<div class="flex items-center mb-3">
<!-- 文件图标与信息区域 -->
<div class="flex items-start mb-3">
<img src="https://p3-doubao-search-sign.byteimg.com/labis/fc16ba2cc6e083aacff2c3629bd48364~tplv-be4g95zd3a-image.jpeg?rk3s=542c0f93&x-expires=1779099593&x-signature=ZOIIEOF4OonT%2B1U3qJMy0ggfOwM%3D"
alt="文档" class="file-icon mr-3">
<div class="flex-grow">
<h3 class="font-medium truncate" th:text="${file.getFileName()}"></h3>
<p class="text-xs text-text-secondary">2023-06-15 | 2.4MB</p>
<div class="flex-grow min-w-0">
<h3 class="font-medium text-gray-800 w-full whitespace-nowrap overflow-hidden text-ellipsis px-1"
th:text="${file.getFileName()}"
th:title="${file.getFileName()}">
</h3>
<p class="text-xs text-gray-500 mt-1 flex items-center justify-between">
<span th:text="${file.getUploadTime()}"></span>
<span th:text="${file.getFileSize()}"></span>
</p>
</div>
</div>
<div class="flex justify-end">
<button class="text-accent hover:text-blue-700 transition-colors"
onclick="downloadFile('project-plan.docx')">
<i class="fa fa-download mr-1"></i> 下载
<!-- 操作按钮区域 -->
<div class="flex justify-end pt-2 border-t border-gray-100">
<button class="text-blue-500 hover:text-blue-700 transition-colors flex items-center px-2 py-1 rounded-md hover:bg-blue-50"
th:onclick="downloadFile([(${file.getFileId()})])">
<i class="fa fa-download mr-1 text-sm"></i>
<span class="text-sm">下载</span>
</button>
</div>
</div>
@@ -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;
// });
}
};
</script>
</body>
</html>