Spring Boot实现文件上传案例

wen java案例 1

本文目录导读:

Spring Boot实现文件上传案例

  1. 项目依赖配置 (pom.xml)
  2. 配置文件 (application.yml)
  3. 文件上传控制器
  4. 文件工具类
  5. 上传页面 (HTML)
  6. 主页控制器
  7. 启动类
  8. 测试说明

我来为您提供一个完整的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 
         http://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>2.7.14</version>
        <relativePath/>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>file-upload-demo</artifactId>
    <version>1.0.0</version>
    <name>file-upload-demo</name>
    <description>文件上传下载示例</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>

配置文件 (application.yml)

server:
  port: 8080
spring:
  servlet:
    multipart:
      # 单个文件最大大小
      max-file-size: 10MB
      # 单个请求最大大小
      max-request-size: 100MB
      # 文件上传临时目录
      location: ${java.io.tmpdir}
file:
  # 文件存储路径
  upload-path: D:/uploads/
  # 允许的文件类型
  allowed-types: jpg,jpeg,png,gif,pdf,doc,docx,xls,xlsx,txt
  # 单个文件最大大小(MB)
  max-size: 10

文件上传控制器

package com.example.fileupload.controller;
import com.example.fileupload.util.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@Slf4j
@RestController
@RequestMapping("/file")
public class FileController {
    @Value("${file.upload-path}")
    private String uploadPath;
    @Value("${file.allowed-types}")
    private String allowedTypes;
    /**
     * 单文件上传
     */
    @PostMapping("/upload")
    public Map<String, Object> uploadFile(@RequestParam("file") MultipartFile file) {
        Map<String, Object> result = new HashMap<>();
        try {
            // 检查文件是否为空
            if (file.isEmpty()) {
                result.put("code", 400);
                result.put("message", "文件不能为空");
                return result;
            }
            // 检查文件大小
            long maxSize = 10 * 1024 * 1024; // 10MB
            if (file.getSize() > maxSize) {
                result.put("code", 400);
                result.put("message", "文件大小不能超过10MB");
                return result;
            }
            // 检查文件类型
            String originalFilename = file.getOriginalFilename();
            String extension = StringUtils.getFilenameExtension(originalFilename);
            if (!isAllowedType(extension)) {
                result.put("code", 400);
                result.put("message", "不支持的文件类型: " + extension);
                return result;
            }
            // 生成唯一文件名
            String newFileName = generateFileName(originalFilename);
            // 创建目录(如果不存在)
            Path uploadDir = Paths.get(uploadPath);
            if (!Files.exists(uploadDir)) {
                Files.createDirectories(uploadDir);
            }
            // 保存文件
            Path filePath = uploadDir.resolve(newFileName);
            file.transferTo(filePath.toFile());
            result.put("code", 200);
            result.put("message", "上传成功");
            result.put("data", new HashMap<String, String>() {{
                put("fileName", originalFilename);
                put("newFileName", newFileName);
                put("fileSize", FileUtil.formatFileSize(file.getSize()));
                put("fileUrl", "/file/download/" + newFileName);
            }});
        } catch (Exception e) {
            log.error("文件上传失败", e);
            result.put("code", 500);
            result.put("message", "文件上传失败: " + e.getMessage());
        }
        return result;
    }
    /**
     * 多文件上传
     */
    @PostMapping("/batch/upload")
    public Map<String, Object> batchUpload(@RequestParam("files") MultipartFile[] files) {
        Map<String, Object> result = new HashMap<>();
        if (files.length == 0) {
            result.put("code", 400);
            result.put("message", "请选择要上传的文件");
            return result;
        }
        java.util.List<Map<String, Object>> fileList = new java.util.ArrayList<>();
        int successCount = 0;
        for (MultipartFile file : files) {
            Map<String, Object> fileResult = new HashMap<>();
            try {
                if (file.isEmpty()) {
                    fileResult.put("status", "failed");
                    fileResult.put("message", "文件为空");
                } else {
                    String originalFilename = file.getOriginalFilename();
                    String extension = StringUtils.getFilenameExtension(originalFilename);
                    if (!isAllowedType(extension)) {
                        fileResult.put("status", "failed");
                        fileResult.put("message", "不支持的文件类型: " + extension);
                    } else {
                        // 生成唯一文件名
                        String newFileName = generateFileName(originalFilename);
                        // 保存文件
                        Path uploadDir = Paths.get(uploadPath);
                        if (!Files.exists(uploadDir)) {
                            Files.createDirectories(uploadDir);
                        }
                        Path filePath = uploadDir.resolve(newFileName);
                        file.transferTo(filePath.toFile());
                        fileResult.put("status", "success");
                        fileResult.put("fileName", originalFilename);
                        fileResult.put("newFileName", newFileName);
                        fileResult.put("fileUrl", "/file/download/" + newFileName);
                        successCount++;
                    }
                }
            } catch (Exception e) {
                log.error("文件上传失败: {}", file.getOriginalFilename(), e);
                fileResult.put("status", "failed");
                fileResult.put("message", e.getMessage());
            }
            fileList.add(fileResult);
        }
        result.put("code", 200);
        result.put("message", "上传完成");
        result.put("total", files.length);
        result.put("successCount", successCount);
        result.put("failedCount", files.length - successCount);
        result.put("data", fileList);
        return result;
    }
    /**
     * 文件下载
     */
    @GetMapping("/download/{fileName}")
    public ResponseEntity<Resource> downloadFile(@PathVariable String fileName) {
        try {
            Path filePath = Paths.get(uploadPath).resolve(fileName).normalize();
            Resource resource = new UrlResource(filePath.toUri());
            if (!resource.exists()) {
                return ResponseEntity.notFound().build();
            }
            // 提取原始文件名(如果有的话)
            String originalFileName = getOriginalFileName(fileName);
            return ResponseEntity.ok()
                .contentType(MediaType.parseMediaType("application/octet-stream"))
                .header(HttpHeaders.CONTENT_DISPOSITION, 
                       "attachment; filename=\"" + URLEncoder.encode(originalFileName, "UTF-8") + "\"")
                .body(resource);
        } catch (Exception e) {
            log.error("文件下载失败", e);
            return ResponseEntity.status(500).build();
        }
    }
    /**
     * 在线预览文件
     */
    @GetMapping("/preview/{fileName}")
    public ResponseEntity<Resource> previewFile(@PathVariable String fileName) {
        try {
            Path filePath = Paths.get(uploadPath).resolve(fileName).normalize();
            Resource resource = new UrlResource(filePath.toUri());
            if (!resource.exists()) {
                return ResponseEntity.notFound().build();
            }
            // 根据扩展名判断MIME类型
            String contentType = Files.probeContentType(filePath);
            if (contentType == null) {
                contentType = "application/octet-stream";
            }
            return ResponseEntity.ok()
                .contentType(MediaType.parseMediaType(contentType))
                .body(resource);
        } catch (Exception e) {
            log.error("文件预览失败", e);
            return ResponseEntity.status(500).build();
        }
    }
    /**
     * 删除文件
     */
    @DeleteMapping("/delete/{fileName}")
    public Map<String, Object> deleteFile(@PathVariable String fileName) {
        Map<String, Object> result = new HashMap<>();
        try {
            Path filePath = Paths.get(uploadPath).resolve(fileName).normalize();
            boolean deleted = Files.deleteIfExists(filePath);
            if (deleted) {
                result.put("code", 200);
                result.put("message", "文件删除成功");
            } else {
                result.put("code", 404);
                result.put("message", "文件不存在");
            }
        } catch (Exception e) {
            log.error("文件删除失败", e);
            result.put("code", 500);
            result.put("message", "文件删除失败: " + e.getMessage());
        }
        return result;
    }
    /**
     * 获取文件列表
     */
    @GetMapping("/list")
    public Map<String, Object> getFileList() {
        Map<String, Object> result = new HashMap<>();
        try {
            Path uploadDir = Paths.get(uploadPath);
            if (!Files.exists(uploadDir)) {
                result.put("code", 200);
                result.put("data", new java.util.ArrayList<>());
                return result;
            }
            java.util.List<Map<String, Object>> fileList = new java.util.ArrayList<>();
            Files.list(uploadDir).forEach(path -> {
                if (Files.isRegularFile(path)) {
                    try {
                        Map<String, Object> fileInfo = new HashMap<>();
                        fileInfo.put("fileName", path.getFileName().toString());
                        fileInfo.put("fileSize", FileUtil.formatFileSize(Files.size(path)));
                        fileInfo.put("createTime", Files.getLastModifiedTime(path).toString());
                        fileInfo.put("downloadUrl", "/file/download/" + path.getFileName());
                        fileList.add(fileInfo);
                    } catch (IOException e) {
                        log.error("获取文件信息失败", e);
                    }
                }
            });
            result.put("code", 200);
            result.put("data", fileList);
        } catch (Exception e) {
            log.error("获取文件列表失败", e);
            result.put("code", 500);
            result.put("message", "获取文件列表失败: " + e.getMessage());
        }
        return result;
    }
    /**
     * 检查文件类型是否允许
     */
    private boolean isAllowedType(String extension) {
        if (StringUtils.isEmpty(extension)) {
            return false;
        }
        String[] allowedTypesArray = allowedTypes.split(",");
        for (String type : allowedTypesArray) {
            if (type.trim().equalsIgnoreCase(extension)) {
                return true;
            }
        }
        return false;
    }
    /**
     * 生成唯一文件名
     */
    private String generateFileName(String originalFilename) {
        String extension = StringUtils.getFilenameExtension(originalFilename);
        return UUID.randomUUID().toString().replace("-", "") + "." + extension;
    }
    /**
     * 获取原始文件名(从文件名映射表中获取)
     */
    private String getOriginalFileName(String fileName) {
        // 这里可以查询数据库获取原始文件名
        // 简化版本直接使用文件名
        return fileName;
    }
}

文件工具类

package com.example.fileupload.util;
import java.text.DecimalFormat;
public class FileUtil {
    /**
     * 格式化文件大小
     */
    public static String formatFileSize(long size) {
        if (size <= 0) {
            return "0 B";
        }
        String[] units = {"B", "KB", "MB", "GB", "TB"};
        int digitGroups = (int) (Math.log10(size) / Math.log10(1024));
        return new DecimalFormat("#,##0.#").format(size / Math.pow(1024, digitGroups)) + " " + units[digitGroups];
    }
    /**
     * 检查文件扩展名
     */
    public static boolean checkFileType(String fileName, String[] allowedTypes) {
        String extension = getFileExtension(fileName);
        if (extension == null || extension.isEmpty()) {
            return false;
        }
        for (String type : allowedTypes) {
            if (type.equalsIgnoreCase(extension)) {
                return true;
            }
        }
        return false;
    }
    /**
     * 获取文件扩展名
     */
    public static String getFileExtension(String fileName) {
        if (fileName == null || fileName.isEmpty()) {
            return null;
        }
        int dotIndex = fileName.lastIndexOf(".");
        if (dotIndex >= 0 && dotIndex < fileName.length() - 1) {
            return fileName.substring(dotIndex + 1);
        }
        return null;
    }
    /**
     * 生成唯一文件名
     */
    public static String generateUniqueFileName(String originalFilename) {
        String extension = getFileExtension(originalFilename);
        String timestamp = String.valueOf(System.currentTimeMillis());
        String randomStr = UUID.randomUUID().toString().replace("-", "").substring(0, 8);
        if (extension != null && !extension.isEmpty()) {
            return timestamp + "_" + randomStr + "." + extension;
        } else {
            return timestamp + "_" + randomStr;
        }
    }
}

上传页面 (HTML)

创建 src/main/resources/templates/upload.html:

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">文件上传下载示例</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 20px;
            background-color: #f5f5f5;
        }
        .container {
            max-width: 800px;
            margin: auto;
            background-color: white;
            border-radius: 8px;
            padding: 30px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        h1, h2 {
            color: #333;
        }
        .section {
            margin-bottom: 30px;
            padding: 20px;
            border: 1px solid #ddd;
            border-radius: 5px;
        }
        .form-group {
            margin-bottom: 15px;
        }
        input[type="file"] {
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 4px;
            width: 100%;
            box-sizing: border-box;
        }
        button {
            background-color: #4CAF50;
            color: white;
            padding: 10px 20px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 14px;
            margin-right: 10px;
        }
        button:hover {
            background-color: #45a049;
        }
        button.delete {
            background-color: #f44336;
        }
        button.delete:hover {
            background-color: #da190b;
        }
        .message {
            padding: 10px;
            border-radius: 4px;
            margin-top: 10px;
        }
        .success {
            background-color: #d4edda;
            border-color: #c3e6cb;
            color: #155724;
        }
        .error {
            background-color: #f8d7da;
            border-color: #f5c6cb;
            color: #721c24;
        }
        #fileList {
            margin-top: 15px;
        }
        .file-item {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 10px;
            border: 1px solid #eee;
            margin: 5px 0;
            border-radius: 4px;
        }
        .file-info {
            flex: 1;
        }
        .file-actions a, .file-actions button {
            margin-left: 10px;
        }
        .file-actions a {
            color: #007bff;
            text-decoration: none;
            padding: 5px 10px;
            border: 1px solid #007bff;
            border-radius: 3px;
        }
        .file-actions a:hover {
            background-color: #007bff;
            color: white;
        }
        progress {
            width: 100%;
            height: 20px;
            margin-top: 10px;
        }
        .progress-container {
            display: none;
            margin-top: 10px;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>文件上传下载示例</h1>
        <!-- 单文件上传 -->
        <div class="section">
            <h2>单文件上传</h2>
            <div class="form-group">
                <input type="file" id="singleFile">
            </div>
            <button onclick="uploadSingleFile()">上传文件</button>
            <div class="progress-container" id="singleProgressContainer">
                <progress id="singleProgress" value="0" max="100"></progress>
            </div>
            <div id="singleMessage"></div>
        </div>
        <!-- 多文件上传 -->
        <div class="section">
            <h2>多文件上传</h2>
            <div class="form-group">
                <input type="file" id="multiFiles" multiple>
            </div>
            <button onclick="uploadMultiFiles()">上传文件</button>
            <div class="progress-container" id="multiProgressContainer">
                <progress id="multiProgress" value="0" max="100"></progress>
            </div>
            <div id="multiMessage"></div>
        </div>
        <!-- 文件列表 -->
        <div class="section">
            <h2>已上传文件</h2>
            <button onclick="loadFileList()">刷新列表</button>
            <div id="fileList"></div>
        </div>
    </div>
    <script>
        // 单文件上传
        async function uploadSingleFile() {
            const fileInput = document.getElementById('singleFile');
            const file = fileInput.files[0];
            if (!file) {
                showMessage('single', '请先选择文件', 'error');
                return;
            }
            const formData = new FormData();
            formData.append('file', file);
            showProgress('single', 0);
            try {
                const response = await fetch('/file/upload', {
                    method: 'POST',
                    body: formData
                });
                const result = await response.json();
                showProgress('single', 100);
                if (result.code === 200) {
                    showMessage('single', '上传成功: ' + result.data.fileName, 'success');
                    loadFileList(); // 刷新列表
                } else {
                    showMessage('single', '上传失败: ' + result.message, 'error');
                }
            } catch (error) {
                showMessage('single', '上传失败: ' + error.message, 'error');
            }
            // 重置文件输入
            fileInput.value = '';
        }
        // 多文件上传
        async function uploadMultiFiles() {
            const fileInput = document.getElementById('multiFiles');
            const files = fileInput.files;
            if (files.length === 0) {
                showMessage('multi', '请先选择文件', 'error');
                return;
            }
            const formData = new FormData();
            for (let i = 0; i < files.length; i++) {
                formData.append('files', files[i]);
            }
            showProgress('multi', 0);
            try {
                const response = await fetch('/file/batch/upload', {
                    method: 'POST',
                    body: formData
                });
                const result = await response.json();
                showProgress('multi', 100);
                if (result.code === 200) {
                    showMessage('multi', '上传完成: 成功' + result.successCount + '个, 失败' + result.failedCount + '个', 'success');
                    loadFileList(); // 刷新列表
                } else {
                    showMessage('multi', '上传失败: ' + result.message, 'error');
                }
            } catch (error) {
                showMessage('multi', '上传失败: ' + error.message, 'error');
            }
            // 重置文件输入
            fileInput.value = '';
        }
        // 加载文件列表
        async function loadFileList() {
            const fileListDiv = document.getElementById('fileList');
            try {
                const response = await fetch('/file/list');
                const result = await response.json();
                if (result.code === 200) {
                    const files = result.data;
                    if (files.length === 0) {
                        fileListDiv.innerHTML = '<p>暂无文件</p>';
                        return;
                    }
                    let html = '';
                    files.forEach(file => {
                        html += `
                            <div class="file-item">
                                <div class="file-info">
                                    <strong>${file.fileName}</strong><br>
                                    <small>大小: ${file.fileSize} | 上传时间: ${formatDate(file.createTime)}</small>
                                </div>
                                <div class="file-actions">
                                    <a href="${file.downloadUrl}">下载</a>
                                    <a href="/file/preview/${file.fileName}" target="_blank">预览</a>
                                    <button class="delete" onclick="deleteFile('${file.fileName}')">删除</button>
                                </div>
                            </div>
                        `;
                    });
                    fileListDiv.innerHTML = html;
                } else {
                    fileListDiv.innerHTML = '<p>加载文件列表失败</p>';
                }
            } catch (error) {
                fileListDiv.innerHTML = '<p>加载文件列表失败: ' + error.message + '</p>';
            }
        }
        // 删除文件
        async function deleteFile(fileName) {
            if (!confirm('确定要删除文件: ' + fileName + ' 吗?')) {
                return;
            }
            try {
                const response = await fetch('/file/delete/' + encodeURIComponent(fileName), {
                    method: 'DELETE'
                });
                const result = await response.json();
                if (result.code === 200) {
                    alert('文件删除成功');
                    loadFileList();
                } else {
                    alert('删除失败: ' + result.message);
                }
            } catch (error) {
                alert('删除失败: ' + error.message);
            }
        }
        // 显示消息
        function showMessage(type, message, status) {
            const div = document.getElementById(type + 'Message');
            div.className = 'message ' + (status === 'success' ? 'success' : 'error');
            div.textContent = message;
        }
        // 显示进度条
        function showProgress(type, percent) {
            const container = document.getElementById(type + 'ProgressContainer');
            const progress = document.getElementById(type + 'Progress');
            if (percent === 100) {
                setTimeout(() => {
                    container.style.display = 'none';
                    progress.value = 0;
                }, 1000);
            } else {
                container.style.display = 'block';
                progress.value = percent;
            }
        }
        // 格式化日期
        function formatDate(dateString) {
            const date = new Date(dateString);
            return date.toLocaleString('zh-CN');
        }
        // 页面加载时获取文件列表
        window.onload = function() {
            loadFileList();
        };
    </script>
</body>
</html>

主页控制器

package com.example.fileupload.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class PageController {
    @GetMapping("/")
    public String index() {
        return "upload";
    }
}

启动类

package com.example.fileupload;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class FileUploadApplication {
    public static void main(String[] args) {
        SpringApplication.run(FileUploadApplication.class, args);
    }
}

测试说明

启动应用后,访问 http://localhost:8080 即可看到上传页面。

主要功能:

  1. 单文件上传:支持单个文件的上传
  2. 多文件上传:支持多个文件同时上传
  3. 文件下载:支持文件下载
  4. 文件预览:支持图片、PDF等文件在线预览
  5. 文件删除:支持删除已上传的文件
  6. 文件列表:显示所有已上传的文件

API接口:

  • POST /file/upload - 单文件上传
  • POST /file/batch/upload - 多文件上传
  • GET /file/list - 获取文件列表
  • GET /file/download/{fileName} - 下载文件
  • GET /file/preview/{fileName} - 预览文件
  • DELETE /file/delete/{fileName} - 删除文件

配置说明:

application.yml 中可以配置:

  • 文件上传路径
  • 允许的文件类型
  • 单个文件大小限制
  • 请求总大小限制

这样就完成了Spring Boot的文件上传下载功能,包含了完整的CRUD操作,您可以根据实际需求进行修改和扩展。

抱歉,评论功能暂时关闭!