Spring Boot 2.0实现基于Restful风格的文件上传与下载APIs

Stella981
• 阅读 582

Spring Boot 2.0实现基于Restful风格的文件上传与下载APIs

> 文件上传与下载在Web应用中是一个比较常见的功能。在本教程中,我将基于Spring 2.2.6版本实现一个基于Restful风格的文件上传与下载APIs。 > > 基于Spring Boot 2.0实战系列源码已经Push到Github仓库:https://github.com/ramostear/springboot2.0-action 。感兴趣朋友欢迎Star/Fork。

1. 环境

  • JDK: Java 1.8
  • Framework: Spring Boot 2.2.6(Only Using Spring Web MVC)
  • Maven: Maven 3.5.0+
  • IDE: IntelliJ IDEA 2019.2
  • Test: Postman 7.23.0

2. 功能

本教程中,使用Spring 2.2.6实现Restful风格的APIs并提供以下的功能:

  • 1.客户端上传文件到服务端
  • 2.对客户端上传文件大小进行限制(50MB)
  • 3.点击链接地址下载文件
  • 4.获得已上传文件列表(文件名和下载地址)

下面是教程所实现的APIs列表(服务端请求端口默认8080):

请求方式

URL地址

说明

POST

/upload

上传一份文件

GET

/files

获取已上传文件列表

GET

/files/{filename}

根据链接地址下载文件

3.工程结构

Spring Boot 2.0实现基于Restful风格的文件上传与下载APIs

工程目录结构说明如下:

  • 1.config/FileUploadConfiguration.java: 常规组件,主要在重启应用时清理历史文件;
  • 2.controller/FileUploadController.java: 主要的控制器,负责处理文件的上传,下载,浏览等请求;
  • 3.exception/FileUploadExceptionAdvice.java: 全局的异常处理类,提供用户友好的异常提示信息;
  • 4.service/FileStorageService.java: 文件上传接口类,提供存储地址初始化,保存文件,加载文件,清理文件等操作;
  • 5.service/impl/FileStorageServiceImpl.java: 文件上传接口实现类;
  • 6.valueobject/UploadFile.java: 封装了文件名和存储地址的POJO类;
  • 7.valueobject/Message.java: 请求/响应的消息对象;
  • 8.resources/application.yml: 项目配置文件,主要配置了文件上传大小限制;
  • 9.pom.xml:Maven依赖配置文件。

4 创建Spring Boot项目

本教程是基于IntelliJ IDEA创建Spring Boot项目的,你也可以选择自己喜欢的IDE创建项目。创建完项目后,请检查pom.xml文件中是否包含如下配置:

<dependency>
    <groupid>org.springframework.boot</groupid>
    <artifactid>spring-boot-starter-web</artifactid>
</dependency>

> 本教程只使用到Spring Web MVC的功能,因此只需添加spring-boot-starter-web依赖。

4.1 文件上传接口

按照面向接口编程的约定(规范),创建一个用于操作上传文件的接口类FileStorageService.java,并提供相应的方法。

service/FileStorageService.java

package com.ramostear.springboot.uploadfile.service;

import org.springframework.core.io.Resource;
import org.springframework.web.multipart.MultipartFile;

import java.nio.file.Path;
import java.util.stream.Stream;

/**
 * @ClassName FileStorageService
 * @Description TODO
 * @Author 树下魅狐
 * @Date 2020/4/28 0028 18:35
 * @Version since 1.0
 **/
public interface FileStorageService {

    void init();

    void save(MultipartFile multipartFile);

    Resource load(String filename);

    Stream<path> load();

    void clear();

}

> 在启动应用时,先调用clear()方法清理历史文件,再调用init()方法初始化文件上传地址。

4.2 实现文件上传接口

文件上传接口实现类比较简单,这里直接给出代码:

service/impl/FileStorageServiceImpl.java

/**
 * @ClassName FileStorageServiceImpl
 * @Description TODO
 * @Author 树下魅狐
 * @Date 2020/4/28 0028 18:38
 * @Version since 1.0
 **/
@Service("fileStorageService")
public class FileStorageServiceImpl implements FileStorageService {

    private final Path path = Paths.get("fileStorage");


    @Override
    public void init() {
        try {
            Files.createDirectory(path);
        } catch (IOException e) {
            throw new RuntimeException("Could not initialize folder for upload!");
        }
    }

    @Override
    public void save(MultipartFile multipartFile) {
        try {
            Files.copy(multipartFile.getInputStream(),this.path.resolve(multipartFile.getOriginalFilename()));
        } catch (IOException e) {
            throw new RuntimeException("Could not store the file. Error:"+e.getMessage());
        }
    }

    @Override
    public Resource load(String filename) {
        Path file = path.resolve(filename);
        try {
            Resource resource = new UrlResource(file.toUri());
            if(resource.exists() || resource.isReadable()){
                return resource;
            }else{
                throw new RuntimeException("Could not read the file.");
            }
        } catch (MalformedURLException e) {
            throw new RuntimeException("Error:"+e.getMessage());
        }
    }

    @Override
    public Stream<path> load() {
        try {
            return Files.walk(this.path,1)
                    .filter(path -&gt; !path.equals(this.path))
                    .map(this.path::relativize);
        } catch (IOException e) {
            throw new RuntimeException("Could not load the files.");
        }
    }

    @Override
    public void clear() {
        FileSystemUtils.deleteRecursively(path.toFile());
    }
}

> 其中,Files、Path和Paths是java.nio.file提供的类,Resource是org.springframework.core.io包中提供的类。

4.3 定义值对象

本教程中,定义了两个简单的对象UploadFile.java和Message.java,分别封装了上传文件信息和响应消息,代码如下:

valueobject/UploadFile.java

/**
 * @ClassName UploadFile
 * @Description TODO
 * @Author 树下魅狐
 * @Date 2020/4/28 0028 18:48
 * @Version since 1.0
 **/
public class UploadFile {

    private String fileName;
    private String url;

    public UploadFile(String fileName, String url) {
        this.fileName = fileName;
        this.url = url;
    }

    public String getFileName() {
        return fileName;
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }
}

valueobject/Message.java

/**
 * @ClassName Message
 * @Description TODO
 * @Author 树下魅狐
 * @Date 2020/4/28 0028 19:21
 * @Version since 1.0
 **/
public class Message {

    private String message;

    public Message(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

4.4 控制器

在controller包下创建文件上传控制器,用于处理客户端的请求。代码如下:

controller/FileUploadController.java

/**
 * @ClassName FileUploadController
 * @Description TODO
 * @Author 树下魅狐
 * @Date 2020/4/28 0028 18:52
 * @Version since 1.0
 **/
@RestController
public class FileUploadController {

    @Autowired
    FileStorageService fileStorageService;

    @PostMapping("/upload")
    public ResponseEntity<message> upload(@RequestParam("file")MultipartFile file){
        try {
            fileStorageService.save(file);
            return ResponseEntity.ok(new Message("Upload file successfully: "+file.getOriginalFilename()));
        }catch (Exception e){
            return ResponseEntity.badRequest()
                    .body(new Message("Could not upload the file:"+file.getOriginalFilename()));
        }
    }

    @GetMapping("/files")
    public ResponseEntity<list<uploadfile>&gt; files(){
        List<uploadfile> files = fileStorageService.load()
                .map(path -&gt; {
                    String fileName = path.getFileName().toString();
                    String url = MvcUriComponentsBuilder
                            .fromMethodName(FileUploadController.class,
                                    "getFile",
                                    path.getFileName().toString()
                            ).build().toString();
                    return new UploadFile(fileName,url);
                }).collect(Collectors.toList());
        return ResponseEntity.ok(files);
    }

    @GetMapping("/files/{filename:.+}")
    public ResponseEntity<resource> getFile(@PathVariable("filename")String filename){
        Resource file = fileStorageService.load(filename);
        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION,
                        "attachment;filename=\""+file.getFilename()+"\"")
                .body(file);
    }
}

> 在控制器中,使用@RestController组合注解替换了@Controller+@ResponseBody的注解方式,并采用@RequestMapping的快捷方式注解方法。

4.5配置上传文件大小

通常,出于安全和性能考虑,我们需要限定客户端上传文件的大小,本教程限定的文件大小最大为50MB。在application.yml(application.properties)文件中添加如下配置:

application.yml

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

application.properties

spring.servlet.multipart.max-request-size=50MB
spring.servlet.multipart.max-file-size=50MB

> - spring.servlet.multipart.max-request-size=50MB: 单次请求所能上传文件的总文件大小 > - spring.servlet.multipart.max-file-size=50MB:单个文件所能上传的文件大小

4.6 全局异常处理

在控制器中,文件上传过程中可能产生的异常我们使用try-catch语句进行了用户友好处理,但当客户端上传文件大小超过50MB时,应用会抛出MaxUploadSizeExceededException异常信息,我们需要对此异常信息做处理。最简单的方式是使用@ControllerAdvice+@ExceptionHandler组合方式处理异常。在exception包下创建异常处理类,代码如下:

exception/FileUploadExceptionAdvice.java

/**
 * @ClassName FileUploadExceptionAdvice
 * @Description TODO
 * @Author 树下魅狐
 * @Date 2020/4/28 0028 19:10
 * @Version since 1.0
 **/
@ControllerAdvice
public class FileUploadExceptionAdvice extends ResponseEntityExceptionHandler {

    @ExceptionHandler(MaxUploadSizeExceededException.class)
    public ResponseEntity<message> handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e){
       return ResponseEntity.badRequest().body(new Message("Upload file too large."));
    }
}

4.7 初始化文件存储空间

为了在测试时获得干净的测试数据,同时也为了在应用启动后分配好上传文件存储地址,我们需要在config包下创建一个配置类,在应用启动时调用FileStorageService中的clear()方法和init()方法。实现该功能,最快的方式是配置类实现CommandLineRunner接口类的run()方法,代码如下:

config/FileUploadConfiguration.java

@Service
public class FileUploadConfiguration implements CommandLineRunner {

    @Autowired
    FileStorageService fileStorageService;

    @Override
    public void run(String... args) throws Exception {
        fileStorageService.clear();
        fileStorageService.init();
    }
}

> 使用@Autowired注解将FileStorageService注入到FileUploadConfiguration.java中。

5.运行程序并测试

运行Spring Boot应用程序的方式有很多,例如:

  • 1.命令方式:mvn spring-boot:run
  • 2.IntelliJ IDEA:点击IntelliJ IDEA的“Run”按钮
  • 3.main()方法:直接运行主类中的main()方法
  • 4.运行jar包:java -jar springboot-fileupload.jar

选择一种你比较熟悉的方式运行Spring Boot应用程序。当应用程序启动成功后,在项目的根目录会创建一个名为fileStorage的文件夹,该文件夹将用于存放客户端上传的文件。 Spring Boot 2.0实现基于Restful风格的文件上传与下载APIs

5.1 使用Postman对APIs进行测试

应用程序启动成功后,我们使用Postman对应用程序中的APIs进行测试。

$调用/upload接口上传文件:

Spring Boot 2.0实现基于Restful风格的文件上传与下载APIs

$上传一个大小超过50MB的文件

Spring Boot 2.0实现基于Restful风格的文件上传与下载APIs

执行结果: Spring Boot 2.0实现基于Restful风格的文件上传与下载APIs

$检查文件存储文件夹

文件上传成功后,我们可以查看项目根目录下的fileStorage文件夹,检查是否有文件被存储到当中: Spring Boot 2.0实现基于Restful风格的文件上传与下载APIs

$调用/files接口,获取所有已上传文件列表

Spring Boot 2.0实现基于Restful风格的文件上传与下载APIs

/files接口将返回所有已上传的文件信息,我们可以点击其中任意一个链接地址下载文件。在Postman中,可以通过header选项卡查看响应头中文件的详细信息,例如: Spring Boot 2.0实现基于Restful风格的文件上传与下载APIs

> 你也可以复制列表中的链接地址,并在浏览器中访问该地址,浏览器会弹出一个下载询问对话框,点击确定按钮进行下载。

6 总结

本章节介绍了Spring Boot 2.0实现基于Restful风格的文件上传和下载APIs,并使用Postman工具对APIs进行测试,达到了设计的预期结果。你可以通过下面的链接地址获取本次教程的相关源代码。

> Github仓库地址 > > https://github.com/ramostear/springboot2.0-action

如果你在运行本次教程提供的源代码过程中遇到什么问题,请在评论区与我联系。


> 未经允许,请勿转载!</list

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
2年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
Android So动态加载 优雅实现与原理分析
背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这