SpringBoot上传文件(使用OSS存储)

上传文件分为同步上传以及异步上传,同步上传表示文件同表单其他信息一同上传。

准备工作

导入依赖

<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>

主启动类

@SpringBootApplication
public class BootApp {

    public static void main(String[] args) {
        SpringApplication.run(BootApp.class, args);
    }
}

建包

创建配置文件以及包等信息:

配置信息

server:
  port: 8088
spring:
  servlet:
    multipart:
      max-file-size: 10485760  # 单个文件大小限制在10M以内
      max-request-size: 104857600 # 单个请求域大小限制在100M以内

添加view-controller

创建com.boot.config.WebControllerConfig

@Configuration
public class WebControllerConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // 首页
        registry.addViewController("/upload/upload_index.html").setViewName("upload_index");
        // 同步上传单个文件
        registry.addViewController("/upload/sync_alone.html").setViewName("sync_alone");
        // 同步上传多个文件
        registry.addViewController("/upload/sync_multi.html").setViewName("sync_multi");
        // 异步上传单个文件
        registry.addViewController("/upload/async_alone.html").setViewName("async_alone");
        // 异步上传多个文件
        registry.addViewController("/upload/async_multi.html").setViewName("async_multi");
    }
}

upload_index.html内容如下:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Spring结合OSS上传文件</title>
</head>
<body>
<article>
    <p><a th:href="@{/upload/sync_alone.html}">同步上传单个文件</a></p>
    <p><a th:href="@{/upload/sync_multi.html}">同步上传多个文件</a></p>
    <p><a th:href="@{/upload/async_alone.html}">异步上传单个文件</a></p>
    <p><a th:href="@{/upload/async_multi.html}">异步上传多个文件</a></p>
</article>
</body>
</html>

引入OSS

开通OSS以及获取AccessId、AccessSecret等略。

引入OSS所需依赖

<dependency>
     <groupId>com.aliyun.oss</groupId>
     <artifactId>aliyun-sdk-oss</artifactId>
     <version>3.10.2</version>
</dependency>

测试使用OSS上传文件

public static void upfile ()
{
    // Endpoint以杭州为例,其它Region请按实际情况填写。
    String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
    // 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录RAM控制台创建RAM账号。
    String accessKeyId = "LTAI5tBxxoU2RjSGAew8Nb3D";
    String accessKeySecret = "jrY8jbAATipWwA657FEFblQqcMbqXy";
    String bucketName = "gwx-test";
    // <yourObjectName>上传文件到OSS时需要指定包含文件后缀在内的完整路径,例如abc/efg/123.jpg。
    String objectName = "a/b/c/hello.txt";
    // Bucket 域名
    String bucketDomain = "https://gwx-test.oss-cn-hangzhou.aliyuncs.com";

    // 创建OSSClient实例。
    OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

    // 上传文件到指定的存储空间(bucketName)并将其保存为指定的文件名称(objectName)。
    String content = "Hello OSS";

    try {
        PutObjectResult putObjectResult = ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(content.getBytes()));
        ResponseMessage response = putObjectResult.getResponse();

        // response为null表示文件上传成功
        if (response != null) {
            System.out.println("出错了:状态码:" + response.getStatusCode() +
                    "错误信息" + response.getErrorResponseAsString());
        } else {
            // 打印下文件可访问路径
            System.out.println(bucketDomain + "/" + objectName);
        }

    } catch (Exception e) {
        System.out.println(e.getMessage());
    }

    // 关闭OSSClient。
    ossClient.shutdown();
}

Spring版上传文件

获取上传的文件域

@RequestMapping("/upload/sync/alone")
public String syncAlone (@RequestParam("name") String name ,@RequestParam("face") MultipartFile face)
{
    System.out.println(face);

    return "sync_alone";
}

MultipartFile常用方法

  • getContentType:获取文件类型,如 image/bmp、 image/jpg、 image/jpeg、image/png
  • getName: 获取文件上传域的名称
  • getOriginalFilename: 获取上传的文件名称
  • getSize:获取上传文件的大小
@RequestMapping("/upload/sync/alone")
public String syncAlone (@RequestParam("name") String name , @RequestParam("face") MultipartFile face)
{
    String contentType = face.getContentType();
    String name1       = face.getName();
    String originalFilename = face.getOriginalFilename();
    long   size = face.getSize();

    System.out.println(contentType);
    System.out.println(name1);
    System.out.println(originalFilename);
    System.out.println(size);

    return "sync_alone";
}

Spring自带的保存文件方法

MultipartFile有一个transferTo方法,可以用来保存文件到指定目录。

@RequestMapping("/upload/sync/alone")
public String syncAlone (@RequestParam("name") String name , @RequestParam("face") MultipartFile face) throws IOException {
    face.transferTo(new File("C:\Users\admin\Desktop\" + face.getOriginalFilename()));

    return "sync_alone";
}

前端展示图片

为了提高用户体验,希望在用户选择好了图片后,就在页面上展示出来。这样,用户可以方便的看到自己选择的图片是否符合自己的期望。

<form th:action="@{/upload/sync/alone}" method="post" enctype="multipart/form-data">
    <p>姓名:<input type="text" name="name" /></p>
    <p>
        <!--用来展示需上传的图片-->
        <img id="faceimg" width="200" />
    </p>
    <p>上传头像:<input type="file" name="face"></p>
    <p><button type="submit">提交</button> </p>
</form>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script>
    $("input[name='face']").change(function (event) {
        // 获取用户选中的文件
        var files = event.target.files;

        // 使用下标0,选择唯一的一个文件
        var file = files[0];

        // 获取URL对象
        var url = window.url || window.webkitURL;

        // 调用url对象的createObjectURL()方法获取当前选中的文件在系统中的路径
        var path = url.createObjectURL(file);

        $("#faceimg").attr("src", path);
    })
</script>

至此,Spring版的文件上传就搞定了。接下来,看Spring如何和OSS结合使用。

Spring结合OSS完成文件上传

为了方便引入和OSS相关的配置,我们将其存放在Spring主配置文件中。然后,再创建一个Property类用来读取OSS相关配置信息。

读取OSS配置

引入依赖:

<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.10.2</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

添加OSS相关配置项:

aliyun:
  oss:
    access-key-id: LTAI5tBxxoU2RjSGAew8Nb3D
    access-key-secret: jrY8jbAATipWwA657FEFblQqcMbqXy
    bucket-domain: https://gwx-test.oss-cn-hangzhou.aliyuncs.com
    bucket-name: gwx-test
    end-point: https://oss-cn-hangzhou.aliyuncs.com

创建OssProperties属性类

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Component
@ConfigurationProperties(prefix = "aliyun.oss")
public class OssProperties {

    private String endPoint;
    private String bucketName;
    private String accessKeyId;
    private String accessKeySecret;
    private String bucketDomain;
}

编写OssUtil类

public class OssUtil {
    private long         maxSize = 1048576; // 1M
    private List<String> allowTypes = new ArrayList<>();

    public OssUtil() {
    }

    public OssUtil(long maxSize, List<String> allowTypes) {
        this.maxSize = maxSize;
        this.allowTypes = allowTypes;
    }

    public long getMaxSize() {
        return maxSize;
    }

    public void setMaxSize(long maxSize) {
        this.maxSize = maxSize;
    }

    public List<String> getAllowTypes() {
        return allowTypes;
    }

    public void setAllowTypes(List<String> allowTypes) {
        this.allowTypes = allowTypes;
    }

    public OssUtil addAllowTypes (String contentType)
    {
        allowTypes.add(contentType);
        return this;
    }

    public String upload (
            String endpoint,
            String accessKeyId,
            String accessKeySecret,
            String bucketName,
            String bucketDomain,
            MultipartFile file,
            OssFileName ossFileName
    )
    {
        if (file.getSize() > maxSize) {
            throw new OssCheckException("the file size is too big");
        }

        if (allowTypes != null && allowTypes.size() > 0 && !allowTypes.contains(file.getContentType())) {
            throw new OssCheckException("file type not allow");
        }

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

       // 设置上传文件的路径及文件名
        String objectName = ossFileName.setName(file.getOriginalFilename());

        try {
            PutObjectResult putObjectResult = ossClient.putObject(bucketName, objectName, file.getInputStream());
            ResponseMessage response        = putObjectResult.getResponse();

            if (response != null) {
                throw new OssException(response.getStatusCode() + ":" + response.getErrorResponseAsString());
            } else {
                return bucketDomain + "/" + objectName;
            }
        } catch (Exception e) {
            throw new OssException(e.getMessage());
        } finally {
            // 关闭OSSClient。
            ossClient.shutdown();
        }
    }

    public String upload (
            String endpoint,
            String accessKeyId,
            String accessKeySecret,
            String bucketName,
            String bucketDomain,
            MultipartFile file
    ) {
        OssFileName ossFileName = new OssFileNameImpl();

        return upload(endpoint, accessKeyId, accessKeySecret, bucketName, bucketDomain, file, ossFileName);
    }
}

该工具类,本人已经测试通过了。

默认文件大小最大不超过是1M,文件类型不进行校验。支持文件大小及文件类型的校验,可自定义设置。

OssUtil ossUtil = new OssUtil();
// 设置最大单个文件上传大小
ossUtil.setMaxSize(1000000000L);  // 单位Byte
// 设置允许的文件类型
ossUtil.addAllowTypes("image/jpeg").addAllowTypes("image/jpg");

此外,还可以自定义文件目录及文件名。默认的实现如下:

public interface OssFileName {

    default String setName (String originalName) {
        // 生成上传文件的目录
        String folderName = new SimpleDateFormat("yyyyMMdd").format(new Date());

        // 生成上传文件在OSS服务器上保存时的文件名
        String fileMainName = UUID.randomUUID().toString().replace("-", "");

        // 从原始文件名中获取文件扩展名
        int index = originalName.lastIndexOf(".");
        String extensionName = "";
        if (index > -1) {
            extensionName = originalName.substring(index);
        }

        // 使用目录、文件主体名称、文件扩展名称拼接得到对象名称
        String objectName = folderName + "/" + fileMainName + extensionName;
        return objectName;
    }
}

可自定义设置目录及文件名:

OssFileName ossFileName = new OssFileName(){
    @Override
    public String setName(String originalName) {
        return originalName;
    }
};

String filePath = ossUtil.upload(
    endPoint,
    accessKeyId,
    accessKeySecret,
    bucketName,
    bucketDomain,
    face,
    ossFileName
);

实例代码

关于OSS上传相关的配置以及工具类都已经就绪,现在我们写几个实例来玩玩。

同步上传单个文件

前端代码:

<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>同步上传单个文件</title>
</head>
<body>
<form th:action="@{/upload/sync/alone}" method="post" enctype="multipart/form-data">
    <p>姓名:<input type="text" name="name" /></p>
    <p>
        <!--用来展示需上传的图片-->
        <img id="faceimg" width="200" />
    </p>
    <p>上传头像:<input type="file" name="face"></p>
    <p><button type="submit">提交</button> </p>
</form>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script>
    $("input[name='face']").change(function (event) {
        // 获取用户选中的文件
        var files = event.target.files;

        // 使用下标0,选择唯一的一个文件
        var file = files[0];

        // 获取URL对象
        var url = window.url || window.webkitURL;

        // 调用url对象的createObjectURL()方法获取当前选中的文件在系统中的路径
        var path = url.createObjectURL(file);

        $("#faceimg").attr("src", path);
    })
</script>
</body>
</html>

后端代码:

@RequestMapping("/upload/sync/alone")
public String syncAlone (@RequestParam("name") String name , @RequestParam("face") MultipartFile face) throws IOException {
    String endPoint        = ossProperties.getEndPoint();
    String accessKeyId     = ossProperties.getAccessKeyId();
    String accessKeySecret = ossProperties.getAccessKeySecret();
    String bucketDomain    = ossProperties.getBucketDomain();
    String bucketName      = ossProperties.getBucketName();

    OssUtil ossUtil = new OssUtil();
    ossUtil.setMaxSize(10485760); // 10M

    ArrayList<String> allowTypes = new ArrayList<>();
    allowTypes.add("image/gif");
    allowTypes.add("image/jpeg");
    allowTypes.add("image/jpg");
    allowTypes.add("image/png");
    ossUtil.setAllowTypes(allowTypes);

    String filePath = ossUtil.upload(endPoint, accessKeyId, accessKeySecret, bucketName, bucketDomain, face);

    ……
}

同步上传多个文件

前端代码:

<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>同步上传多个文件</title>
</head>
<body>
<form th:action="@{/upload/sync/multi}" method="post" enctype="multipart/form-data">
    <p>姓名:<input type="text" name="name" /></p>
    <div id="show-img"></div>
    <p>上传生活照:<input type="file" name="imgs" multiple></p>
    <p><button type="submit">提交</button> </p>
</form>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script>
    $("input[name='imgs']").change(function (event) {
        // 获取用户选中的文件
        var files = event.target.files;

        // 获取URL对象
        var url = window.url || window.webkitURL;

        // 清空原图片列表
        $("#show-img").empty();

        for (var i=0; i < files.length; i++) {
            // 调用url对象的createObjectURL()方法获取当前选中的文件在系统中的路径
            var path = url.createObjectURL(files[i]);
            var html = "<p><img width='200' src='"+ path +"' /></p>";
            $("#show-img").append(html);
        }
    })
</script>
</body>
</html>

后端代码:

@RequestMapping("/upload/sync/multi")
public String syncAlone (@RequestParam("name") String name , @RequestParam("imgs") List<MultipartFile> imgs) throws IOException {
    String endPoint        = ossProperties.getEndPoint();
    String accessKeyId     = ossProperties.getAccessKeyId();
    String accessKeySecret = ossProperties.getAccessKeySecret();
    String bucketDomain    = ossProperties.getBucketDomain();
    String bucketName      = ossProperties.getBucketName();

    OssUtil ossUtil = new OssUtil();
    ossUtil.setMaxSize(10485760); // 10M

    imgs.forEach((file)->{
        ossUtil.upload(endPoint, accessKeyId, accessKeySecret, bucketName, bucketDomain, file);
    });

    ……
}

异步上传单个文件

前端代码:

<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>异步上传单个文件</title>
</head>
<body>
<form >
    <p>姓名:<input type="text" name="name" /></p>
    <p>
        <!--用来展示需上传的图片-->
        <img id="faceimg" width="200" />
    </p>
    <p>上传头像:<input type="file" name="face"></p>
    <p><button type="submit">提交</button> </p>
</form>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script>
    $("input[name='face']").change(function (event) {
        // 获取用户选中的文件
        var files = event.target.files;

        // 使用下标0,选择唯一的一个文件
        var file = files[0];

        var formData = new FormData();
        formData.append("file", file);
        $.ajax({
            type: "post",           //因为是传输文件,所以必须是post
            url: "[[@{/upload/async/alone}]]",
            data: formData,
            dataType:'json',
            processData: false,
            contentType: false,
            success:function (data) {
                $("#faceimg").attr("src", data.src)
            }
        });

    })
</script>
</body>
</html>

后端代码:

@ResponseBody
@RequestMapping("/upload/async/alone")
public Map<String,String> asyncAlone (@RequestParam("file") MultipartFile file)
{
    String endPoint        = ossProperties.getEndPoint();
    String accessKeyId     = ossProperties.getAccessKeyId();
    String accessKeySecret = ossProperties.getAccessKeySecret();
    String bucketDomain    = ossProperties.getBucketDomain();
    String bucketName      = ossProperties.getBucketName();

    OssUtil ossUtil = new OssUtil();
    ossUtil.setMaxSize(10485760); // 10M
    String filePath = ossUtil.upload(endPoint, accessKeyId, accessKeySecret, bucketName, bucketDomain, file);
    HashMap<String, String> map = new HashMap<>();
    map.put("src", filePath);

    return map;
}

异步上传多个文件

异步上传多个文件的后端代码和异步上传单个文件的后端代码是一致的,前端有所不同。

<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>异步上传多个文件</title>
</head>
<body>
<form>
    <p>姓名:<input type="text" name="name" /></p>
    <div id="show-img"></div>
    <p>上传生活照:<input type="file" name="imgs" multiple></p>
    <p><button type="submit">提交</button> </p>
</form>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script>
    $("input[name='imgs']").change(function (event) {
        // 获取用户选中的文件
        var files = event.target.files;
        var length = files.length;
        for (var i=0; i<length; i++) {
            var file = files[i];

            var formData = new FormData();
            formData.append("file", file);
            $.ajax({
                type: "post",           //因为是传输文件,所以必须是post
                url: "[[@{/upload/async/alone}]]",
                data: formData,
                dataType:'json',
                processData: false,
                contentType: false,
                success:function (data) {
                    $("#show-img").append("<p><img src='"+ data.src +"' width='200' /></p>")
                }
            });
        }
    })
</script>
</body>
</html>