1. 简介

文件上传,是指将本地图片、视频、音频等文件上传到服务器,供其他用户浏览或下载的过程。

前端程序:

<form action="/upload" method="post" enctype="multipart/form-data">  
    姓名: <input type="text" name="username"><br>  
    年龄: <input type="text" name="age"><br>  
    头像: <input type="file" name="image"><br>  
    <input type="submit" value="提交">  
</form>

上传文件的原始 form 表单,要求表单必须具备以下三点(上传文件页面三要素):

  • 表单必须有 file 域,用于选择要上传的文件
  • 表单提交方式必须为 POST
    通常上传的文件会比较大,所以需要使用 POST 提交方式
  • 表单的编码类型 enctype 必须要设置为:multipart/form-data
    默认的编码格式只能上传文件名,不适合传输大型的二进制数据

后端程序:

@RestController
public class UploadController {
    @PostMapping("/upload")
    public Result upload(String username, Integer age, MultipartFile image)  {
        return Result.success();
    }
}
  • 在服务端定义一个 Controller 用来进行文件上传,在 Controller 当中定义一个方法来处理/upload 请求
  • 在定义的方法中接收提交的数据 (方法中的形参名和请求参数的名字保持一致)
    • 用户名:String name
    • 年龄: Integer age
    • 文件: MultipartFile image

Spring 中提供了一个 API:MultipartFile,用来接收上传的文件

后端程序打个断点,以 debug 方式启动项目,打开浏览器输入:http://localhost:8080/upload.html录入数据并提交,通过后端程序控制台可以看到,表单提交的三项数据(姓名、年龄、文件)存放在一个临时目录,分别存储在不同的临时文件中,当程序运行完毕之后,临时文件会自动删除。
所以如果想要实现文件上传,需要将这个临时文件转存到我们的磁盘目录中。

在这里插入图片描述

2. 本地存储

  1. 在服务器本地磁盘上创建 images 目录,用来存储上传的文件(例:E盘创建 images 目录)
  2. 使用 MultipartFile 类提供的 API 方法,把临时文件转存到本地磁盘目录下
String getOriginalFilename(); //获取原始文件名
void transferTo(File dest); //将接收的文件转存到磁盘文件中
long getSize(); //获取文件的大小,单位:字节
byte[] getBytes(); //获取文件内容的字节数组
InputStream getInputStream(); //获取接收到的文件内容的输入流

为保证每次上传文件时文件名都唯一的,可以使用 UUID 获取随机文件名。

@Slf4j
@RestController
public class UploadController {
    @PostMapping("/upload")
    public Result upload(String username, Integer age, MultipartFile image) throws IOException {
        log.info("文件上传:{},{},{}",username,age,image);
        //获取原始文件名
        String originalFilename = image.getOriginalFilename();
        //找到最后一个.的位置截取文件扩展名
        String extname = originalFilename.substring(originalFilename.lastIndexOf("."));
        //使用UUID获取随机文件名+文件扩展名
        String newFileName = UUID.randomUUID().toString()+extname;
        //将文件存储在服务器的磁盘目录
        image.transferTo(new File("E:/images/"+newFileName));
        return Result.success();
    }
}

如果需要上传大文件,可以在 application.properties 进行如下配置:

#配置单个文件最大上传大小  
spring.servlet.multipart.max-file-size=10MB  
​  
#配置单个请求最大上传大小(一次请求可以上传多个文件)  
spring.servlet.multipart.max-request-size=100MB

缺点:

  • 不安全:磁盘如果损坏,所有的文件就会丢失
  • 容量有限:如果存储大量的图片,磁盘空间有限(磁盘不可能无限制扩容)
  • 无法直接访问

为了解决上述问题,通常有两种解决方案:

  • 自己搭建存储服务器,如:fastDFS 、MinIO
  • 使用现成的云服务,如:阿里云,腾讯云,华为云

3. 阿里云 OSS

1.在官网对象存储 OSS_云存储服务_企业数据管理_存储-阿里云 (aliyun.com)注册并实名认证,打开控制台开通 OSS 对象存储服务。

在这里插入图片描述

2.进入 OSS 控制台,创建 Bucket 存储容器,选择本地冗余存储公共读

2ca97c08a1a5049a1.png

3.在 Java 中使用 OSS,参考官方文档
(1)引入依赖

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

如果是 Java 9及以上的版本,还需要引入以下依赖

<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.1</version>
</dependency>
<dependency>
    <groupId>javax.activation</groupId>
    <artifactId>activation</artifactId>
    <version>1.1.1</version>
</dependency>
<!-- no more than 2.3.3-->
<dependency>
    <groupId>org.glassfish.jaxb</groupId>
    <artifactId>jaxb-runtime</artifactId>
    <version>2.3.3</version>
</dependency>

(2)获取 AccessKeyId

在这里插入图片描述

(3)图片上传
在 Java 程序中使用 OSS 服务需要提供 endpoint、accessKeyId、accessKeySecret、bucketName 四个量。前三个用于创建 OSSClient 对象,OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);,最后一个用于确定上传的图片放在哪个 Bucket 下边。
先构造一个 PutObjectRequest 或者 GetObjectRequest,然后通过 ossclient 调用 putObject (put 请求)/ getObject (get 请求)。

// 创建put请求 
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, inputStream); 
// 调用这个put请求,返回请求的result结果 
PutObjectResult result = ossClient.putObject(putObjectRequest);
import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.common.auth.*;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;
import java.io.FileInputStream;
import java.io.InputStream;

public class Demo {
    public static void main(String[] args) throws Exception {
        // Endpoint以华北2(北京)为例,其它Region请按实际情况填写。
        String endpoint = "https://oss-cn-beijing.aliyuncs.com";
        // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。 
        String accessKeyId = "yourAccessKeyId"; 
        String accessKeySecret = "yourAccessKeySecret";
        // 填写Bucket名称,例如examplebucket。
        String bucketName = "examplebucket";
        // 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
        String objectName = "exampledir/exampleobject.txt";// 图片上传之后的名字
        // 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。
        // 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件流。
        String filePath= "D:\\localpath\\examplefile.txt";
        
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        
        try {
            InputStream inputStream = new FileInputStream(filePath);
            // 创建PutObjectRequest对象。
            PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, inputStream);
            // 创建PutObject请求。
            PutObjectResult result = ossClient.putObject(putObjectRequest);
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }
} 

可以修改上述代码定义成阿里云 OSS 上传文件工具类,使用 @Component 注解交给 IOC 容器管理。

import com.aliyun.oss.OSS;  
import com.aliyun.oss.OSSClientBuilder;  
import org.springframework.stereotype.Component;  
import org.springframework.web.multipart.MultipartFile;  
import java.io.InputStream;  
import java.util.UUID;  
  
@Component  
public class AliOssUtil {  
    private String endpoint = "https://oss-cn-beijing.aliyuncs.com";  
    private String prefix = "https://";  
    private String point = ".oss-cn-beijing.aliyuncs.com/";  
    private String accessKeyId = "yourAccessKeyId";  
    private String accessKeySecret = "yourAccessKeySecret";  
    private String bucketName = "examplebucket";  
    
    public String upload(MultipartFile file) throws Exception {  
        // 获取上传文件的输入流  
        InputStream inputStream = file.getInputStream();  
        
        //获取文件原始名  
        String originalFilename = file.getOriginalFilename();  
        //获取文件扩展名  
        String extname = originalFilename.substring(originalFilename.lastIndexOf('.'));  
        //拼接随机名加扩展名作为文件名  
        String newFilename = UUID.randomUUID().toString() + extname;  
        
        // 创建OSSClient实例。  
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);  
        //上传文件到 OSS       
	    ossClient.putObject(bucketName, newFilename, inputStream);  
	    
        //文件访问路径  
        String url = prefix + bucketName +  point + newFilename;  
        
        return url;  
    }  
}
Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐