一、文件上传——简介
文件上传的简介:文件上传是指将本地计算机中的文件传输到网络上的服务器或另一台计算机上的过程。在 Web 开发中,文件上传通常指的是将用户通过 Web 页面提交的文件(如图像、文档、音频、视频等)传输到服务器端的操作。
- 简单来说,文件上传是指将本地图片、视频、音频等文件上传到服务器上,供其他用户浏览或下载的过程。
文件上传通常涉及以下几个主要组件:
- 客户端:指的是文件上传的发起方,通常是用户在 Web 浏览器中通过表单提交文件。
- 服务器端:指的是接收并处理文件上传请求的计算机系统或服务器。
- 上传表单:通过 HTML 表单元素来实现文件上传功能。表单需要设置 enctype 属性为 “multipart/form-data”,并包含一个文件输入框用于选择要上传的文件。
- 文件处理逻辑:服务器端接收到文件上传请求后,需要将上传的文件保存到指定的位置,并可能进行进一步的处理,如文件存储、文件重命名、文件格式验证等。
- 文件上传控制器:在 Web 开发框架中,通常需要编写文件上传的处理逻辑,这些逻辑通常由服务器端的控制器或处理器来处理。
文件上传在 Web 开发中非常常见,常见的应用场景包括但不限于用户头像上传、文件分享、数据备份等。
二、文件上传——入门
文件上传的步骤:
- 先创建一个springboot工程(这个常识了,不展示了)
- 编写一个上传文件的前端html文件
- 编写一个controller
项目结构
文件上传前端的三要素:
- 请求方式是:post
- 需要设置 enctype=“multipart/form-data” 属性以支持文件上传
<form>
元素应该包含一个<input type="file">
元素,用于让用户选择要上传的文件。
编写的html文件:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>文件上传</title> </head> <body> <h2>上传文件</h2> <form action="/upload" method="post" enctype="multipart/form-data"> <input type="file" name="file" id="fileToUpload"> <input type="submit" value="上传" name="submit"> </form> </body> </html>
编写的controller
package com.knife.controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; @RestController public class UploadController { @PostMapping("/upload") public void upload(MultipartFile file){ System.out.println("file == " + file); } }
运行截图:(通过xml配置的路径去访问html文件,然后点击上传文件)
file内容不等于null,说明文件上传成功。
总结:服务端要想接收到从前端页面上传的文件,需要使用到一个api:MultipartFile,通过该api来接收上传的文件。而上传上来的文件是一个临时文件,当文件上传这次请求完成之后,这些临时文件会自动删除。因此,在上传文件的同时,应该把文件保存起来。
三、文件上传——本地存储—第一版
在服务端,接收到上传上来的文件之后,将文件存储在本地服务器磁盘中。
基础实现:在上面的文件上传——入门的基础上,修改一下controller就行。
package com.knife.controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; @RestController public class UploadController { @PostMapping("/upload") public void upload(MultipartFile file) throws IOException { System.out.println("file == " + file); // 获取源文件的文件名 String originalFilename = file.getOriginalFilename(); // 把上传上来的文件存储到磁盘上:transferTo的参数是一个文件 file.transferTo(new File("E:\\images\\"+ originalFilename)); } }
只需把上面的controller改成上面一样,然后运行,然后如果你的路径不对,或者你盘符的目录不存在,就会报错了(因为这里没有对异常处理,只是简单地把异常抛出去了)
最快的解决办法就是,修改本地存储的路径,输入一个存在的目录的路径。
最佳解决办法:如下更改controller
package com.knife.controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; import java.util.UUID; @RestController public class UploadController { @PostMapping("/upload") public void upload(MultipartFile file) throws IOException { System.out.println("file == " + file); // 1. 获取源文件的文件名 String originalFilename = file.getOriginalFilename(); // 定义要上传的路径 String fileUploadPath = "E:\\images\\"+ originalFilename; // 创建要上传的文件 File uploadFile = new File(fileUploadPath); // 判断配置的文件目录是否存在,若不存在则创建一个新的文件目录 File parentFile = uploadFile.getParentFile(); if(!parentFile.exists()) { parentFile.mkdirs(); } System.out.println(uploadFile); file.transferTo(uploadFile); } }
运行:
四、文件上传——本地存储—第二版
本地存储第一版存在的问题:
- 使用原始文件名进行存储时,如果同时上传了文件名一样的文件,那么后面上传的文件会把前面上传的文件给覆盖。
- 解决:保证每一个文件的文件名是唯一的(文件名不能重复)
- 使用uuid工具(通用唯一标识码)——使用生成的uuid码+原始文件名的后缀
不管文件路径的报错的写法(和上面文件上传——本地存储—第一版就只有controller的差别):
package com.knife.controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; import java.util.UUID; @RestController public class UploadController { @PostMapping("/upload") public void upload(MultipartFile file) throws IOException { System.out.println("file == " + file); // 1. 获取源文件的文件名 String originalFilename = file.getOriginalFilename(); // 2. 构造唯一的文件名 -- 使用uuid(通用唯一识别码,长度固定的字符串,而且是唯一的)+ 原始文件文件后缀名 // 2.1. 通过字符串的截取,获取文件的后缀名 int index = originalFilename.lastIndexOf("."); String extname = originalFilename.substring(index); // 2.2 把uuid和文件后缀名合并 String newFileName = UUID.randomUUID().toString() + extname; System.out.println("newFileName = " + newFileName); // 把上传上来的文件存储到磁盘上:(指定一个路径) file.transferTo(new File("E:\\"+ newFileName)); } }
- 解决文件路径的报错的写法(和上面文件上传——本地存储—第一版就只有controller的差别):
package com.knife.controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; import java.util.UUID; @RestController public class UploadController { @PostMapping("/upload") public void upload(MultipartFile file) throws IOException { System.out.println("file == " + file); // 1. 获取源文件的文件名 String originalFilename = file.getOriginalFilename(); // 2. 构造唯一的文件名 -- 使用uuid(通用唯一识别码,长度固定的字符串,而且是唯一的)+ 原始文件文件后缀名 // 2.1. 通过字符串的截取,获取文件的后缀名 int index = originalFilename.lastIndexOf("."); String extname = originalFilename.substring(index); // 2.2 把uuid和文件后缀名合并 String newFileName = UUID.randomUUID().toString() + extname; // 定义要上传的路径 String fileUploadPath = "E:\\images\\"+ newFileName; // 创建要上传的文件 File uploadFile = new File(fileUploadPath); // 判断配置的文件目录是否存在,若不存在则创建一个新的文件目录 File parentFile = uploadFile.getParentFile(); if(!parentFile.exists()) { parentFile.mkdirs(); } System.out.println(uploadFile); // 把上传上来的文件存储到磁盘上:(指定一个路径) file.transferTo(uploadFile); } }
运行:
文件上传大小限制的问题:在配置文件里面进行配置
如果是application.properties则如上图一样配置就行,如果是application.yml,则如下配置即可
application.yml:
server: port: 8088 # 配置访问服务器的端口 spring: servlet: multipart: max-file-size: 10MB # 配置单个文件最大上传大小 max-request-size: 100MB # 配置单个请求最大上传文件的大小(一次可以上传多个文件,即多个文件的总和也算)
- 本地存储存在的问题:
五、文件上传——对象存储——阿里云OOS
五、1、参照官方SDK编写入门程序
- 第一步:在Maven工程中使用OSS Java SDK,只需在pom.xml中加入相应依赖即可。在
<dependencies>
中加入如下内容(参考官方文档的说明):
<dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>3.15.1</version> </dependency>
- 如果使用的是Java 9及以上的版本,则需要添加JAXB相关依赖。添加JAXB相关依赖示例代码如下:
<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>
- 第二步:复制上传文件入门代码(官方文档)官方文档入门程序例子:然后再根据自己的实际情况改写
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以华东1(杭州)为例,其它Region请按实际情况填写。 String endpoint = "https://oss-cn-hangzhou.aliyuncs.com"; // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。 EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider(); // 填写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, credentialsProvider); 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(); } } } }
改写后的入门程序:主要修改自己的endpoint、accessKeyId、bucketName、objectName、filePath
package com.knife; 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 uploadOss{ public static void main(String[] args) throws Exception { // Endpoint:(华北2(北京))请按实际情况填写。 String endpoint = "oss-cn-beijing.aliyuncs.com"; // 根据自己的实际情况填写 // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。 // EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider(); // 使用accessKeyId、accessKeyIdSecret替代。 String accessKeyId = "你自己的密钥id"; // 根据自己的实际情况填写 String accessKeyIdSecret ="你自己的密钥";// 根据自己的实际情况填写 // 填写Bucket名称,例如examplebucket。 String bucketName = "你自己的bucketName";// 根据自己的实际情况填写 // 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。 String objectName = "1.jpg"; //设置我们上传的文件,最终在阿里云上面是什么名字 // 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。 // 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件流。 String filePath= "C:\\Users\\qq351\\Pictures\\Saved Pictures\\1.jpg"; // 创建OSSClient实例。 // OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider); OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId,accessKeyIdSecret); 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(); } } } }
路径获取:
运行就可以传进去了
通过浏览器访问该图片的路径时,会直接下载该图片,但是在页面显示出来挺简单的,就是使用一个imag标签,然后把路径放在src下即可
<image src="https://cangqiongwaimaiknife.oss-cn-beijing.aliyuncs.com/1.jpg">
六、在项目中使用OOS文件上传
- 在项目中,一般会把文件上传的方法封装成一个工具类然后再使用。
在配置文件里面对oss的参数进行配置
#阿里云OSS aliyun: oss: endpoint: https://oss-cn-hangzhou.aliyuncs.com accessKeyId: LTAI4GCH1vX6DKqJWxd6nEuW accessKeySecret: yBshYweHOpqDuhCArrVHwIiBKpyqSL bucketName: web-tlias
属性配置类:(读取配置文件里面的属性)
import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Data @Component @ConfigurationProperties(prefix = "aliyun.oss") public class AliOSSProperties { private String endpoint; private String accessKeyId; private String accessKeySecret; private String bucketName; }
封装工具类:
import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; import java.io.*; import java.util.UUID; /** * 阿里云 OSS 工具类 */ @Component public class AliOSSUtils { @Autowired private AliOSSProperties aliOSSProperties; /** * 实现上传图片到OSS */ public String upload(MultipartFile file) throws IOException { //获取阿里云OSS参数 String endpoint = aliOSSProperties.getEndpoint(); String accessKeyId = aliOSSProperties.getAccessKeyId(); String accessKeySecret = aliOSSProperties.getAccessKeySecret(); String bucketName = aliOSSProperties.getBucketName(); // 获取上传的文件的输入流 InputStream inputStream = file.getInputStream(); // 避免文件覆盖 String originalFilename = file.getOriginalFilename(); String fileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf(".")); //上传文件到 OSS OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); ossClient.putObject(bucketName, fileName, inputStream); //文件访问路径 String url = endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + fileName; // 关闭ossClient ossClient.shutdown(); return url;// 把上传到oss的路径返回 } }
controller类上使用
import com.itheima.pojo.Result; import com.itheima.utils.AliOSSUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; import java.util.UUID; @RestController public class UploadController { @Autowired private AliOSSUtils aliOSSUtils; @PostMapping("/upload") public Result upload(MultipartFile image) throws IOException { log.info("文件上传, 文件名: {}", image.getOriginalFilename()); //调用阿里云OSS工具类进行文件上传 String url = aliOSSUtils.upload(image); log.info("文件上传完成,文件访问的url: {}", url); return Result.success(url); } }