全部学科
Python全栈
python
NodeJS全栈
nodejs
小程序首页
📅 2026-05-18 6 分钟 ✍️ juanwangdev

Spring MVC 单文件上传实现

单文件上传是文件处理的基础功能,掌握其实现方式是理解多文件上传的前提。

基本单文件上传

Controller实现

Java
@RestController
@RequestMapping("/api/upload")
public class UploadController {

    @Value("${upload.path}")
    private String uploadPath;

    @PostMapping("/single")
    public String upload(@RequestParam("file") MultipartFile file) {
        if (file.isEmpty()) {
            throw new IllegalArgumentException("上传文件为空");
        }

        String fileName = generateFileName(file);
        File destFile = new File(uploadPath + fileName);

        try {
            file.transferTo(destFile);
            return fileName;
        } catch (IOException e) {
            throw new RuntimeException("文件上传失败", e);
        }
    }

    private String generateFileName(MultipartFile file) {
        return UUID.randomUUID().toString() + getExtension(file);
    }

    private String getExtension(MultipartFile file) {
        String originalName = file.getOriginalFilename();
        return originalName != null && originalName.contains(".")
            ? originalName.substring(originalName.lastIndexOf("."))
            : "";
    }
}

配置上传路径

YAML
upload:
  path: /data/upload/

spring:
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 10MB

文件上传表单

HTML表单

HTML
<!DOCTYPE html>
<html>
<head>
    <title>文件上传</title>
</head>
<body>
    <form action="/api/upload/single" method="post" enctype="multipart/form-data">
        <input type="file" name="file">
        <button type="submit">上传</button>
    </form>
</body>
</html>

enctype必须设置为multipart/form-data。

AJAX文件上传

JavaScript
function uploadFile() {
    const fileInput = document.querySelector('input[type="file"]');
    const file = fileInput.files[0];

    if (!file) {
        alert('请选择文件');
        return;
    }

    const formData = new FormData();
    formData.append('file', file);

    fetch('/api/upload/single', {
        method: 'POST',
        body: formData
    })
    .then(response => response.text())
    .then(fileName => {
        console.log('上传成功: ' + fileName);
    })
    .catch(error => {
        console.error('上传失败: ' + error);
    });
}

文件名策略

UUID唯一命名

Java
private String generateUUIDFileName(MultipartFile file) {
    return UUID.randomUUID().toString() + getExtension(file);
}

时间戳命名

Java
private String generateTimestampFileName(MultipartFile file) {
    String timestamp = LocalDateTime.now()
        .format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
    return timestamp + "_" + UUID.randomUUID().toString().substring(0, 8)
        + getExtension(file);
}

保留原始文件名(加UUID防重名)

Java
private String generateOriginalFileName(MultipartFile file) {
    String originalName = file.getOriginalFilename();
    String uuid = UUID.randomUUID().toString().substring(0, 8);
    return uuid + "_" + originalName;
}

按日期分目录存储

Java
@PostMapping("/with-date-path")
public String uploadWithDatePath(@RequestParam("file") MultipartFile file) {
    String datePath = LocalDateTime.now()
        .format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));

    String fullPath = uploadPath + datePath;
    File directory = new File(fullPath);

    if (!directory.exists()) {
        directory.mkdirs();
    }

    String fileName = UUID.randomUUID() + getExtension(file);
    File destFile = new File(fullPath + "/" + fileName);

    try {
        file.transferTo(destFile);
        return datePath + "/" + fileName;
    } catch (IOException e) {
        throw new RuntimeException("上传失败", e);
    }
}

InputStream方式处理

Java
@PostMapping("/stream")
public String uploadStream(@RequestParam("file") MultipartFile file) {
    String fileName = UUID.randomUUID() + getExtension(file);
    String filePath = uploadPath + fileName;

    try (InputStream inputStream = file.getInputStream();
         FileOutputStream outputStream = new FileOutputStream(filePath)) {

        byte[] buffer = new byte[4096];
        int bytesRead;

        while ((bytesRead = inputStream.read(buffer)) != -1) {
            outputStream.write(buffer, 0, bytesRead);
        }

        return fileName;
    } catch (IOException e) {
        throw new RuntimeException("上传失败", e);
    }
}

文件信息响应

Java
@Data
@AllArgsConstructor
class UploadResponse {
    private String fileName;
    private String originalName;
    private Long size;
    private String url;
}

@PostMapping("/info")
public UploadResponse uploadWithInfo(@RequestParam("file") MultipartFile file) {
    String fileName = UUID.randomUUID() + getExtension(file);
    String filePath = uploadPath + fileName;

    try {
        file.transferTo(new File(filePath));
    } catch (IOException e) {
        throw new RuntimeException("上传失败", e);
    }

    return new UploadResponse(
        fileName,
        file.getOriginalFilename(),
        file.getSize(),
        "/download/" + fileName
    );
}

文件类型校验

Java
@PostMapping("/validate")
public String uploadWithValidate(@RequestParam("file") MultipartFile file) {
    // 校验文件为空
    if (file.isEmpty()) {
        throw new ValidationException("文件为空");
    }

    // 校验文件大小
    if (file.getSize() > 10 * 1024 * 1024) {
        throw new ValidationException("文件大小超过10MB限制");
    }

    // 校验文件类型
    String contentType = file.getContentType();
    if (!isAllowedType(contentType)) {
        throw new ValidationException("不允许的文件类型: " + contentType);
    }

    // 校验文件扩展名
    String extension = getExtension(file);
    if (!isAllowedExtension(extension)) {
        throw new ValidationException("不允许的文件扩展名: " + extension);
    }

    return saveFile(file);
}

private boolean isAllowedType(String contentType) {
    Set<String> allowedTypes = Set.of(
        "image/jpeg", "image/png", "image/gif",
        "application/pdf"
    );
    return allowedTypes.contains(contentType);
}

private boolean isAllowedExtension(String extension) {
    Set<String> allowedExts = Set.of(".jpg", ".jpeg", ".png", ".gif", ".pdf");
    return allowedExts.contains(extension.toLowerCase());
}

异常处理

Java
@RestControllerAdvice
public class UploadExceptionHandler {

    @ExceptionHandler(MaxUploadSizeExceededException.class)
    public ResponseEntity<Map<String, Object>> handleMaxSize(
            MaxUploadSizeExceededException e) {

        Map<String, Object> response = new HashMap<>();
        response.put("code", 400);
        response.put("message", "文件大小超过限制");

        return ResponseEntity.badRequest().body(response);
    }

    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<Map<String, Object>> handleIllegalArgument(
            IllegalArgumentException e) {

        Map<String, Object> response = new HashMap<>();
        response.put("code", 400);
        response.put("message", e.getMessage());

        return ResponseEntity.badRequest().body(response);
    }
}

文件存储到云服务

Java
@Service
public class CloudStorageService {

    @Autowired
    private OSSClient ossClient;

    @Value("${oss.bucket}")
    private String bucket;

    public String uploadToOSS(MultipartFile file) {
        String fileName = UUID.randomUUID() + getExtension(file);

        try {
            ossClient.putObject(bucket, fileName, file.getInputStream());
            return ossClient.generatePresignedUrl(bucket, fileName,
                new Date(System.currentTimeMillis() + 3600 * 1000)).toString();
        } catch (IOException e) {
            throw new RuntimeException("OSS上传失败", e);
        }
    }
}

RESTful上传接口

Java
@RestController
@RequestMapping("/api/files")
public class FileController {

    @PostMapping
    public UploadResponse upload(@RequestParam("file") MultipartFile file) {
        return fileService.upload(file);
    }

    @GetMapping("/{id}")
    public ResponseEntity<Resource> download(@PathVariable Long id) {
        return fileService.download(id);
    }

    @DeleteMapping("/{id}")
    public void delete(@PathVariable Long id) {
        fileService.delete(id);
    }
}

上传进度回调

Java
@PostMapping("/progress")
public String uploadWithProgress(
        @RequestParam("file") MultipartFile file,
        @RequestParam("uploadId") String uploadId) {

    long totalSize = file.getSize();
    long processed = 0;

    try (InputStream inputStream = file.getInputStream();
         FileOutputStream outputStream = new FileOutputStream(uploadPath + UUID.randomUUID())) {

        byte[] buffer = new byte[4096];
        int bytesRead;

        while ((bytesRead = inputStream.read(buffer)) != -1) {
            outputStream.write(buffer, 0, bytesRead);
            processed += bytesRead;

            // 更新进度(存储到Redis等)
            updateProgress(uploadId, processed, totalSize);
        }

        return "上传完成";
    } catch (IOException e) {
        throw new RuntimeException("上传失败", e);
    }
}

要点总结

  • @RequestParam接收MultipartFile
  • enctype="multipart/form-data"是必须的
  • transferTo()方法直接保存文件
  • UUID命名避免文件名冲突
  • 按日期分目录便于管理
  • 校验文件大小、类型、扩展名

📝 发现内容有误?点击此处直接编辑

← 上一篇 Spring MVC 拦截器配置(Java配置方式)
下一篇 → Spring MVC 多文件上传实现
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库