利用 Spring Boot 实现一个文件上传和下载功能的系统
项目的目录总体结构

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.12</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.jd</groupId>
<artifactId>HandleFileUpload</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>HandleFileUpload</name>
<description>Demo project for Spring Boot</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
添加配置信息application.yml
# 配置Spring框架的文件上传属性
spring:
servlet:
multipart:
# 设置单个文件的最大大小为10MB
max-file-size: 10MB
# 设置整个HTTP请求的最大大小为10MB,限制一次上传的总文件大小
max-request-size: 10MB
# 启用文件上传功能
enabled: true
# 设置文件上传的目录
file:
upload-dir: D:/uploads
# 配置服务器端口
server:
port: 8081
# 配置日志级别
logging:
level:
# 设置特定包的日志级别为DEBUG,以便捕获文件上传处理过程中的调试信息
com.jd.com.jd.HandleFileUpload: DEBUG
dao层的UploadFileResponse
package com.jd.HandleFileUpload.dao;
/**
* 文件上传响应类
* 用于封装文件上传后的相关信息,包括文件名、下载URI、文件类型和文件大小
*/
public class UploadFileResponse {
// 文件名
private String fileName;
// 文件下载的URI
private String downloadUri;
// 文件类型
private String fileType;
// 文件大小,以字节为单位
private long size;
/**
* 构造函数
* 用于创建UploadFileResponse对象,并初始化文件名、下载URI、文件类型和文件大小
*
* @param fileName 文件名
* @param downloadUri 文件下载的URI
* @param fileType 文件类型
* @param size 文件大小,以字节为单位
*/
public UploadFileResponse(String fileName, String downloadUri, String fileType, long size) {
this.fileName = fileName;
this.downloadUri = downloadUri;
this.fileType = fileType;
this.size = size;
}
// Getters and Setters
/**
* 获取文件名
*
* @return 文件名
*/
public String getFileName() {
return fileName;
}
/**
* 设置文件名
*
* @param fileName 文件名
*/
public void setFileName(String fileName) {
this.fileName = fileName;
}
/**
* 获取文件下载的URI
*
* @return 文件下载的URI
*/
public String getDownloadUri() {
return downloadUri;
}
/**
* 设置文件下载的URI
*
* @param downloadUri 文件下载的URI
*/
public void setDownloadUri(String downloadUri) {
this.downloadUri = downloadUri;
}
/**
* 获取文件类型
*
* @return 文件类型
*/
public String getFileType() {
return fileType;
}
/**
* 设置文件类型
*
* @param fileType 文件类型
*/
public void setFileType(String fileType) {
this.fileType = fileType;
}
/**
* 获取文件大小
*
* @return 文件大小,以字节为单位
*/
public long getSize() {
return size;
}
/**
* 设置文件大小
*
* @param size 文件大小,以字节为单位
*/
public void setSize(long size) {
this.size = size;
}
}
service层FileStorageService
package com.jd.HandleFileUpload.service;
import com.jd.HandleFileUpload.config.FileStorageConfig;
import com.jd.HandleFileUpload.exception.FileStorageException;
import com.jd.HandleFileUpload.exception.MyFileNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 文件存储服务类,负责处理文件上传、检索、删除等操作
*/
@Service
public class FileStorageService {
// 存储文件的位置
private final Path fileStorageLocation;
/**
* 构造函数,初始化文件存储位置
*
* @param fileStorageConfig 文件存储配置,包含上传目录等信息
*/
@Autowired
public FileStorageService(FileStorageConfig fileStorageConfig) {
// 获取配置中的上传目录,并转换为绝对路径
this.fileStorageLocation = Paths.get(fileStorageConfig.getUploadDir())
.toAbsolutePath().normalize();
try {
// 尝试创建上传目录,如果目录已存在则不执行任何操作
Files.createDirectories(this.fileStorageLocation);
} catch (Exception ex) {
// 如果创建目录失败,则抛出异常
throw new FileStorageException("Could not create the directory where the uploaded files will be stored.", ex);
}
}
/**
* 存储上传的文件
*
* @param file 要存储的文件
* @return 存储的文件名
* @throws FileStorageException 如果文件名包含无效字符或文件无法存储
*/
public String storeFile(MultipartFile file) {
// 规范化文件名,去除路径中的冗余信息
String fileName = StringUtils.cleanPath(file.getOriginalFilename());
try {
// 检查文件名是否包含无效字符
if (fileName.contains("..")) {
throw new FileStorageException("Sorry! Filename contains invalid path sequence " + fileName);
}
// 将文件复制到目标位置,如果文件已存在则替换
Path targetLocation = this.fileStorageLocation.resolve(fileName);
Files.copy(file.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING);
return fileName;
} catch (IOException ex) {
// 如果文件无法存储,则抛出异常
throw new FileStorageException("Could not store file " + fileName + ". Please try again!", ex);
}
}
/**
* 加载文件作为资源
*
* @param fileName 要加载的文件名
* @return 文件对应的资源对象
* @throws MyFileNotFoundException 如果文件未找到
* @throws MalformedURLException 如果文件路径无效
*/
public Resource loadFileAsResource(String fileName) {
try {
// 获取文件路径并创建资源对象
Path filePath = this.fileStorageLocation.resolve(fileName).normalize();
Resource resource = new UrlResource(filePath.toUri());
if (resource.exists()) {
return resource;
} else {
// 如果文件不存在,则抛出异常
throw new MyFileNotFoundException("File not found " + fileName);
}
} catch (MalformedURLException ex) {
// 如果文件路径无效,则抛出异常
throw new MyFileNotFoundException("File not found " + fileName, ex);
}
}
/**
* 加载所有文件名
*
* @return 文件名列表
* @throws FileStorageException 如果无法加载文件
*/
public List<String> loadAllFiles() {
try (Stream<Path> paths = Files.walk(this.fileStorageLocation, 1)) {
// 遍历上传目录,筛选出所有常规文件并收集文件名
return paths
.filter(Files::isRegularFile)
.map(Path::getFileName)
.map(Path::toString)
.collect(Collectors.toList());
} catch (IOException ex) {
// 如果无法加载文件,则抛出异常
throw new FileStorageException("Could not load files", ex);
}
}
/**
* 删除文件
*
* @param fileName 要删除的文件名
* @return 如果文件被成功删除则返回true,否则返回false
* @throws FileStorageException 如果无法删除文件
*/
public boolean deleteFile(String fileName) {
try {
// 获取文件路径并尝试删除文件
Path filePath = this.fileStorageLocation.resolve(fileName).normalize();
return Files.deleteIfExists(filePath);
} catch (IOException ex) {
// 如果无法删除文件,则抛出异常
throw new FileStorageException("Could not delete file " + fileName, ex);
}
}
}
controller层FileController
package com.jd.HandleFileUpload.controller;
import com.jd.HandleFileUpload.dao.UploadFileResponse;
import com.jd.HandleFileUpload.service.FileStorageService;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* 文件控制器类
* 提供文件上传、下载、列表和删除等接口
*/
@RestController
@RequestMapping("/api/files")
public class FileController {
// 日志记录器
private static final Logger logger = LoggerFactory.getLogger(FileController.class);
// 文件存储服务
@Autowired
private FileStorageService fileStorageService;
/**
* 单个文件上传接口
*
* @param file 上传的文件对象
* @return 返回包含文件信息的响应对象
*/
@PostMapping("/upload")
public UploadFileResponse uploadFile(@RequestParam("file") MultipartFile file) {
// 存储文件并获取文件名
String fileName = fileStorageService.storeFile(file);
// 构建文件下载链接
String fileDownloadUri = ServletUriComponentsBuilder.fromCurrentContextPath()
.path("/api/files/download/")
.path(fileName)
.toUriString();
// 创建并返回响应对象
return new UploadFileResponse(fileName, fileDownloadUri,
file.getContentType(), file.getSize());
}
/**
* 多个文件上传接口
*
* @param files 上传的多个文件对象
* @return 返回包含每个文件信息的响应对象列表
*/
@PostMapping("/uploadMultiple")
public List<UploadFileResponse> uploadMultipleFiles(@RequestParam("files") MultipartFile[] files) {
// 遍历所有文件并分别上传
return Arrays.asList(files)
.stream()
.map(this::uploadFile)
.collect(Collectors.toList());
}
/**
* 文件下载接口
*
* @param fileName 要下载的文件名
* @param request HTTP请求对象,用于获取上下文信息
* @return 返回封装了文件资源的响应实体
*/
@GetMapping("/download/{fileName:.+}")
public ResponseEntity<Resource> downloadFile(@PathVariable String fileName, HttpServletRequest request) {
// 加载文件为资源对象
Resource resource = fileStorageService.loadFileAsResource(fileName);
// 尝试获取文件MIME类型
String contentType = null;
try {
contentType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath());
} catch (IOException ex) {
logger.info("无法确定文件类型。");
}
// 如果未获取到MIME类型,则使用默认类型
if (contentType == null) {
contentType = "application/octet-stream";
}
// 构造并返回响应实体
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
.body(resource);
}
/**
* 获取所有文件名列表
*
* @return 返回文件名列表
*/
@GetMapping("/list")
public List<String> listFiles() {
// 从服务层加载所有文件名
return fileStorageService.loadAllFiles();
}
/**
* 删除指定文件
*
* @param fileName 要删除的文件名
* @return 返回操作结果的响应实体
*/
@DeleteMapping("/delete/{fileName:.+}")
public ResponseEntity<String> deleteFile(@PathVariable String fileName) {
// 执行文件删除操作
boolean deleted = fileStorageService.deleteFile(fileName);
// 根据删除结果返回对应的HTTP响应
if (deleted) {
return ResponseEntity.ok("文件删除成功: " + fileName);
} else {
return ResponseEntity.notFound().build();
}
}
}
config包的配置操作FileStorageConfig
package com.jd.HandleFileUpload.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* 文件存储配置类
* 用于获取文件上传目录的配置
*/
@Component
public class FileStorageConfig {
// 从应用配置中获取的文件上传目录路径
@Value("${file.upload-dir}")
private String uploadDir;
/**
* 获取文件上传目录的路径
*
* @return 文件上传目录的路径
*/
public String getUploadDir() {
return uploadDir;
}
/**
* 设置文件上传目录的路径
*
* @param uploadDir 文件上传目录的路径
*/
public void setUploadDir(String uploadDir) {
this.uploadDir = uploadDir;
}
}
exception异常处理
FileStorageException.java
/**
* 自定义文件存储异常类
* 用于在文件上传或管理过程中出现错误时抛出
* 它提供了与异常相关联的详细消息,以及可选的原始异常(cause)
*/
package com.jd.HandleFileUpload.exception;
public class FileStorageException extends RuntimeException {
/**
* 构造一个带有详细消息的FileStorageException
*
* @param message 异常的详细消息
*/
public FileStorageException(String message) {
super(message);
}
/**
* 构造一个带有详细消息和原始异常的FileStorageException
*
* @param message 异常的详细消息
* @param cause 异常的原始原因
*/
public FileStorageException(String message, Throwable cause) {
super(message, cause);
}
}
MyFileNotFoundException
package com.jd.HandleFileUpload.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
/**
* 当文件未找到时抛出的异常类
*
* 该类继承自RuntimeException,用于处理文件上传过程中遇到的文件不存在的情况
* 使用@ResponseStatus注解,当抛出此类异常时,返回HTTP 404状态码
*/
@ResponseStatus(HttpStatus.NOT_FOUND)
public class MyFileNotFoundException extends RuntimeException {
/**
* 构造一个MyFileNotFoundException实例,包含详细的错误信息
*
* @param message 错误信息,描述文件未找到的情况
*/
public MyFileNotFoundException(String message) {
super(message);
}
/**
* 构造一个MyFileNotFoundException实例,包含详细的错误信息和原始异常
*
* @param message 错误信息,描述文件未找到的情况
* @param cause 原始异常,导致文件未找到的底层原因
*/
public MyFileNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}
前端页面代码
index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<!-- 页面元信息 -->
<meta charset="UTF-8">
<!-- 设置视口以支持响应式布局 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 页面标题 -->
<title>文件上传和下载</title>
<!-- 引入外部样式表 -->
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- 页面主容器 -->
<div class="container">
<!-- 页面主标题 -->
<h1>文件管理系统</h1>
<!-- 单文件上传区域 -->
<section class="upload-section">
<!-- 子标题 -->
<h2>单文件上传</h2>
<!-- 用于上传单个文件的表单 -->
<form id="singleUploadForm" enctype="multipart/form-data">
<!-- 文件输入与上传按钮布局容器 -->
<div class="file-input-wrapper">
<!-- 文件选择框 -->
<input type="file" id="singleFileUpload" name="file" required>
<!-- 提交按钮 -->
<button type="submit">上传</button>
</div>
</form>
<!-- 上传结果展示区域 -->
<div id="singleFileUploadResult"></div>
</section>
<!-- 多文件上传区域 -->
<section class="upload-section">
<!-- 子标题 -->
<h2>多文件上传</h2>
<!-- 用于上传多个文件的表单 -->
<form id="multipleUploadForm" enctype="multipart/form-data">
<!-- 文件输入与上传按钮布局容器 -->
<div class="file-input-wrapper">
<!-- 文件选择框(支持多选) -->
<input type="file" id="multipleFileUpload" name="files" multiple required>
<!-- 提交按钮 -->
<button type="submit">批量上传</button>
</div>
</form>
<!-- 批量上传结果展示区域 -->
<div id="multipleFileUploadResult"></div>
</section>
<!-- 文件列表展示区域 -->
<section class="file-list-section">
<!-- 子标题 -->
<h2>文件列表</h2>
<!-- 刷新按钮 -->
<button id="refreshBtn">刷新列表</button>
<!-- 文件列表动态加载区域 -->
<div id="fileList"></div>
</section>
</div>
<!-- 引入前端交互脚本 -->
<script src="app.js"></script>
</body>
</html>
style.css
/* 全局样式重置 */
* {
margin: 0; /* 清除默认外边距 */
padding: 0; /* 清除默认内边距 */
box-sizing: border-box; /* 设置盒模型为 border-box,包含 padding 和 border 在 width/height 内 */
}
/* 页面主体样式 */
body {
font-family: 'Arial', sans-serif; /* 使用 Arial 字体,备选无衬线字体 */
background-color: #f5f5f5; /* 设置浅灰色背景 */
color: #333; /* 设置文字颜色为深灰色 */
line-height: 1.6; /* 增加行高,提升可读性 */
}
/* 页面内容容器 */
.container {
max-width: 800px; /* 最大宽度限制为 800px */
margin: 0 auto; /* 居中显示 */
padding: 20px; /* 内边距 */
}
/* 主标题样式 */
h1 {
text-align: center; /* 居中对齐 */
color: #2c3e50; /* 深蓝色标题 */
margin-bottom: 30px; /* 底部留白 */
font-size: 2.5em; /* 加大字号 */
}
/* 子标题样式 */
h2 {
color: #34495e; /* 稍浅的蓝色标题 */
margin-bottom: 15px; /* 底部留白 */
border-bottom: 2px solid #3498db; /* 添加蓝色下划线 */
padding-bottom: 5px; /* 下划线下方留白 */
}
/* 单文件上传区域样式 */
.upload-section {
background: white; /* 白色背景 */
padding: 25px; /* 内边距 */
margin-bottom: 25px; /* 底部留白 */
border-radius: 10px; /* 圆角 */
box-shadow: 0 2px 10px rgba(0,0,0,0.1); /* 添加轻微阴影,增强立体感 */
}
/* 文件输入和按钮的布局容器 */
.file-input-wrapper {
display: flex; /* 使用 Flex 布局 */
gap: 10px; /* 子元素之间间距 */
align-items: center; /* 垂直居中 */
margin-bottom: 15px; /* 底部留白 */
}
/* 文件选择框样式 */
input[type="file"] {
flex: 1; /* 自动扩展以填充可用空间 */
padding: 10px; /* 内边距 */
border: 2px dashed #3498db; /* 蓝色虚线边框 */
border-radius: 5px; /* 圆角 */
background: #ecf0f1; /* 浅灰色背景 */
}
/* 按钮通用样式 */
button {
padding: 12px 25px; /* 内边距 */
background: #3498db; /* 蓝色背景 */
color: white; /* 白色文字 */
border: none; /* 移除默认边框 */
border-radius: 5px; /* 圆角 */
cursor: pointer; /* 鼠标悬停变为手形 */
font-size: 14px; /* 字号 */
transition: background 0.3s; /* 添加背景过渡动画 */
}
/* 按钮悬停效果 */
button:hover {
background: #2980b9; /* 悬停时更深的蓝色 */
}
/* 文件列表区域样式 */
.file-list-section {
background: white; /* 白色背景 */
padding: 25px; /* 内边距 */
border-radius: 10px; /* 圆角 */
box-shadow: 0 2px 10px rgba(0,0,0,0.1); /* 阴影效果 */
}
/* 刷新按钮样式 */
#refreshBtn {
margin-bottom: 20px; /* 底部留白 */
background: #27ae60; /* 绿色背景 */
}
#refreshBtn:hover {
background: #229954; /* 悬停时更深的绿色 */
}
/* 每个文件条目样式 */
.file-item {
display: flex; /* 使用 Flex 布局 */
justify-content: space-between; /* 左右内容拉开 */
align-items: center; /* 垂直居中 */
padding: 10px; /* 内边距 */
margin: 5px 0; /* 上下间隔 */
background: #f8f9fa; /* 浅灰色背景 */
border-radius: 5px; /* 圆角 */
border-left: 4px solid #3498db; /* 左侧蓝色条,突出视觉层次 */
}
/* 文件名样式 */
.file-name {
flex: 1; /* 自动扩展 */
font-weight: 500; /* 稍粗字体 */
}
/* 文件操作按钮组 */
.file-actions {
display: flex; /* 使用 Flex 布局 */
gap: 10px; /* 按钮之间间距 */
}
/* 下载按钮样式 */
.download-btn {
background: #27ae60; /* 绿色背景 */
padding: 5px 15px; /* 小尺寸内边距 */
font-size: 12px; /* 更小字号 */
}
.download-btn:hover {
background: #229954; /* 悬停更深绿色 */
}
/* 删除按钮样式 */
.delete-btn {
background: #e74c3c; /* 红色背景 */
padding: 5px 15px; /* 小尺寸内边距 */
font-size: 12px; /* 更小字号 */
}
.delete-btn:hover {
background: #c0392b; /* 悬停更深红色 */
}
/* 提示信息通用样式 */
.result {
margin-top: 15px; /* 顶部留白 */
padding: 10px; /* 内边距 */
border-radius: 5px; /* 圆角 */
}
/* 成功提示样式 */
.success {
background: #d4edda; /* 浅绿色背景 */
color: #155724; /* 深绿色文字 */
border: 1px solid #c3e6cb; /* 浅绿边框 */
}
/* 错误提示样式 */
.error {
background: #f8d7da; /* 浅红背景 */
color: #721c24; /* 深红文字 */
border: 1px solid #f5c6cb; /* 浅红边框 */
}
/* 进度提示样式 */
.progress {
background: #d1ecf1; /* 浅蓝背景 */
color: #0c5460; /* 深蓝文字 */
border: 1px solid #bee5eb; /* 浅蓝边框 */
}
app.js
// 工具函数:在指定的元素中显示操作结果信息,并在5秒后自动清除
function showResult(elementId, message, type) {
const element = document.getElementById(elementId);
// 使用模板字符串插入带样式的提示框
element.innerHTML = `<div class="result ${type}">${message}</div>`;
// 5秒后自动清除提示信息
setTimeout(() => {
element.innerHTML = '';
}, 5000);
}
// 单文件上传功能
document.getElementById('singleUploadForm').addEventListener('submit', function (e) {
e.preventDefault(); // 阻止表单默认提交行为
// 获取文件输入框和选中的文件
const fileInput = document.getElementById('singleFileUpload');
const file = fileInput.files[0];
// 如果没有选择文件,显示错误提示并返回
if (!file) {
showResult('singleFileUploadResult', '请选择文件', 'error');
return;
}
// 创建 FormData 对象用于封装上传数据
const formData = new FormData();
formData.append('file', file); // 添加文件字段
// 显示上传进度提示
showResult('singleFileUploadResult', '上传中...', 'progress');
// 发送 POST 请求到服务器上传文件
fetch('/api/files/upload', {
method: 'POST',
body: formData
})
.then(response => response.json()) // 解析响应为 JSON
.then(data => {
// 显示成功消息,并格式化文件大小为 KB
showResult('singleFileUploadResult',
`文件上传成功: ${data.fileName} (${(data.size / 1024).toFixed(2)} KB)`, 'success');
fileInput.value = ''; // 清空文件选择框
loadFileList(); // 刷新文件列表
})
.catch(error => {
console.error('Error:', error); // 打印错误日志
showResult('singleFileUploadResult', '上传失败: ' + error.message, 'error'); // 显示错误提示
});
});
// 多文件上传功能
document.getElementById('multipleUploadForm').addEventListener('submit', function (e) {
e.preventDefault(); // 阻止表单默认提交行为
// 获取多文件输入框和所有选中的文件
const fileInput = document.getElementById('multipleFileUpload');
const files = fileInput.files;
// 如果没有选择任何文件,显示错误提示并返回
if (files.length === 0) {
showResult('multipleFileUploadResult', '请选择文件', 'error');
return;
}
// 创建 FormData 并将多个文件添加进去
const formData = new FormData();
for (let i = 0; i < files.length; i++) {
formData.append('files', files[i]); // 每个文件作为一个条目
}
// 显示批量上传进度提示
showResult('multipleFileUploadResult', '批量上传中...', 'progress');
// 发送 POST 请求到批量上传接口
fetch('/api/files/uploadMultiple', {
method: 'POST',
body: formData
})
.then(response => response.json()) // 解析响应为 JSON
.then(data => {
// 提取所有上传成功的文件名并拼接成字符串
const fileNames = data.map(file => file.fileName).join(', ');
// 显示成功消息
showResult('multipleFileUploadResult',
`${data.length} 个文件上传成功: ${fileNames}`, 'success');
fileInput.value = ''; // 清空文件选择框
loadFileList(); // 刷新文件列表
})
.catch(error => {
console.error('Error:', error); // 打印错误日志
showResult('multipleFileUploadResult', '批量上传失败: ' + error.message, 'error'); // 显示错误提示
});
});
// 加载文件列表函数
function loadFileList() {
// 向服务器请求文件列表
fetch('/api/files/list')
.then(response => response.json()) // 解析响应为 JSON
.then(files => {
const fileListDiv = document.getElementById('fileList');
// 如果没有文件,显示“暂无文件”提示
if (files.length === 0) {
fileListDiv.innerHTML = '<p>暂无文件</p>';
return;
}
// 将每个文件名渲染为 HTML 元素
const fileListHTML = files.map(fileName => `
<div class="file-item">
<span class="file-name">${fileName}</span>
<div class="file-actions">
<button class="download-btn" onclick="downloadFile('${fileName}')">下载</button>
<button class="delete-btn" onclick="deleteFile('${fileName}')">删除</button>
</div>
</div>
`).join(''); // 将数组转换为字符串
// 插入到页面中
fileListDiv.innerHTML = fileListHTML;
})
.catch(error => {
console.error('Error loading file list:', error); // 打印错误日志
// 显示加载失败提示
document.getElementById('fileList').innerHTML = '<p class="error">加载文件列表失败</p>';
});
}
// 下载文件函数
function downloadFile(fileName) {
// 构造下载链接
const downloadUrl = `/api/files/download/${encodeURIComponent(fileName)}`;
// 创建一个临时的 <a> 标签来触发下载
const link = document.createElement('a');
link.href = downloadUrl;
link.download = fileName; // 设置下载时的文件名
document.body.appendChild(link);
link.click(); // 模拟点击
document.body.removeChild(link); // 移除标签
}
// 删除文件函数
function deleteFile(fileName) {
// 弹出确认框让用户确认是否删除
if (!confirm(`确定要删除文件 "${fileName}" 吗?`)) {
return;
}
// 发送 DELETE 请求到服务器删除文件
fetch(`/api/files/delete/${encodeURIComponent(fileName)}`, {
method: 'DELETE'
})
.then(response => {
if (response.ok) {
alert('文件删除成功'); // 删除成功提示
loadFileList(); // 刷新文件列表
} else {
alert('文件删除失败'); // 删除失败提示
}
})
.catch(error => {
console.error('Error deleting file:', error); // 打印错误日志
alert('文件删除失败: ' + error.message); // 显示错误提示
});
}
// 绑定刷新按钮事件监听器
document.getElementById('refreshBtn').addEventListener('click', loadFileList);
// 页面加载完成后自动加载文件列表
document.addEventListener('DOMContentLoaded', loadFileList);
posted on 2025-06-17 14:33 Indian_Mysore 阅读(72) 评论(0) 收藏 举报
浙公网安备 33010602011771号