文件上传下载
1、概述
- 文件上传是项目开发中最常见的功能之一,SpringMVC可以很好的支持文件上传,但是SpringMVC上下文中默认没有装配MultipartResolver,因此默认情况下其不能处理文件上传工作。如果想使用Spring的文件上传功能,则需要在上下文中配置MultipartResolver。
- 前端表单要求:为了能上传文件,必须将表单的method设置为post,并将enctype设置为multipart/form-data。只有在这样的情况下,浏览器才会把用户选择的文件以二进制数据发送给服务器。
对表单中的enctype属性做个详细的说明:
- application/x-www=form-urlencoded:默认方式,只处理表单域中的 value 属性值,采用这种编码方式的表单会将表单域中的值处理成 URL 编码方式。
- multipart/form-data:这种编码方式会以二进制流的方式来处理表单数据,这种编码方式会把文件域指定文件的内容也封装到请求参数中,不会对字符编码。form中设置enctype=”multipart/form-data”时会导致参数绑定失败。
- text/plain:除了把空格转换为 “+” 号外,其他字符都不做编码处理,这种方式适用直接通过表单发送邮件。
<form action="" enctype="multipart/form-data" method="post">
<input type="file" name="file"/>
<input type="submit">
</form>
一旦设置了enctype为multipart/form-data,浏览器即会采用二进制流的方式来处理表单数据,而对于文件上传的处理则涉及在服务器端解析原始的HTTP响应。在2003年,Apache Software Foundation发布了开源的Commons FileUpload组件,其很快成为Servlet/JSP程序员上传文件的最佳选择。
- Servlet3.0规范已经提供方法来处理文件上传,但这种上传需要在Servlet中完成。
- 而Spring MVC则提供了更简单的封装。
- Spring MVC为文件上传提供了直接的支持,这种支持是用即插即用的MultipartResolver实现的。
- Spring MVC使用Apache Commons FileUpload技术实现了一个MultipartResolver实现类:
- CommonsMultipartResolver。因此,SpringMVC的文件上传还需要依赖Apache Commons FileUpload的组件
注:前端页面使用 thymeleaf 模板引擎
2、文件上传
- 创建前端页面
<div class="col-lg-6">
<section class="panel">
<header class="panel-heading">
Basic Forms
</header>
<div class="panel-body">
<form role="form" th:action="@{/upload}" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="exampleInputEmail1">邮箱</label>
<input type="email" name="email" class="form-control" id="exampleInputEmail1" placeholder="Enter email">
</div>
<div class="form-group">
<label for="exampleInputPassword1">名字</label>
<input type="text" name="username" class="form-control" id="exampleInputPassword1" placeholder="username">
</div>
<div class="form-group">
<label for="exampleInputFile">头像</label>
<input type="file" name="headerImg" id="exampleInputFile">
</div>
<div class="form-group">
<label for="exampleInputFile">生活照</label>
<input type="file" name="photos" multiple>
</div>
<div class="checkbox">
<label>
<input type="checkbox"> Check me out
</label>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</section>
</div>
- 创建controller
@RequestMapping("/form_layouts")
public String form_layouts() {
return "form/form_layouts";
}
// 文件上传
@RequestMapping("/upload")
public String upload(HttpServletRequest request,
@RequestParam("email") String email,
@RequestParam("username") String username,
@RequestPart("headerImg") MultipartFile headerImg,
@RequestPart("photos") MultipartFile[] photos) throws IOException {
log.info("信息为,邮箱地址:{},名字:{},headerImg:{},photos:{}",
email, username, headerImg.getSize(), photos.length);
// 单文件上传
if (!headerImg.isEmpty()) {
// 获取上传文件的原始名称
String originalFilename = headerImg.getOriginalFilename();
// 设置上传文件的保存地址目录(存放在项目路径下)
String dirPath = "xxx";
// 字符串截取后缀名
String suffix = uploadFile.getOriginalFilename().substring(uploadFile.getOriginalFilename().lastIndexOf('.'));
// 设置上传后的文件名称(拼接)
String newFileName = username + UUID.randomUUID() + "_" + originalFilename;
headerImg.transferTo(new File(dirPath + "/" + newFileName));
}
// 多文件上传
if (photos.length > 0) {
for (MultipartFile photo : photos) {
photo.transferTo(
new File("上传文件的保存目录路径"
+ "/" + username + UUID.randomUUID() + "_" + photo.getOriginalFilename()));
}
}
return "form/form_layouts";
}
- 修改yaml配置文件,设置上传最大值
# 文件上传最大值
spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 100MB
3、文件下载
- 导入依赖
<!--文件下载-->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
- 创建下载页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<a th:href="@{/download(filename=1.jpg)}">英文文件下载</a><br>
<!-- URLEncoder.encode:使用Encoder类的encoder()方法对中文名进行编码 -->
<a th:href="@{/download(filename=小koen同学10.jpg)}">中文文件下载</a>
🐷
<form th:action="@{/download1}" method="post">
<input type="file" name="filename"/><br>
<input type="submit">
</form>
</body>
</html>
- 创建controller
// 跳转到download.html中
@RequestMapping("/toDownload")
public String toDownload(){
return "form/download";
}
// 文件下载
@RequestMapping("/download")
public ResponseEntity<byte[]> fileDownload(HttpServletRequest request, String filename) throws Exception {
// 指定要下载的文件所在策路径
String dirPath = "F:\\idea_workspace\\FrameWork\\springboot-file";
// 创建该文件对象
File file = new File(dirPath + File.separator + filename);
System.out.println(dirPath + File.separator + filename);
System.out.println("分隔符为:" + File.pathSeparator);
// 对文件名编码,防止中文乱码
filename = this.getFileName(request, filename);
// 设置响应头
HttpHeaders headers = new HttpHeaders();
// 通知浏览器以下载的方式打开文件
headers.setContentDispositionFormData("attachment", filename);
// 定义以流的方式下载返回文件的数据(stream:流)
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
// 将该对象读取时按字节传送
byte[] array = FileUtils.readFileToByteArray(file);
// 使用SpringMVC框架的ResponseEntity对象封装返回下载数据
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(array, headers, HttpStatus.OK);
return responseEntity;
}
/**
* 根据浏览器的不同进行编码设置,返回编码后的文件名
*/
public String getFileName(HttpServletRequest request, String filename) throws Exception {
//IE不同版本User-Agent中出现的各种关键词
String[] IEBrowserKeyWords = {"MSIE", "Trident", "Edge"};
//获取请求头代理信息
String userAgent = request.getHeader("User-Agent");
for (String keyWord : IEBrowserKeyWords) {
if (userAgent.contains(keyWord)) {
//IE内核浏览器,统一为UTF-8编码显示
return URLEncoder.encode(filename, "UTF-8");
}
}
//其它浏览器统一为ISO-8859-1编码显示
return new String(filename.getBytes("UTF-8"), "ISO-8859-1");
}
@RequestMapping("/download1")
public ResponseEntity<byte[]> download1(String filename, HttpServletRequest request) throws Exception {
// 确定要下载的文件路径
String dirPath = "F:\\idea_workspace\\FrameWork\\springboot-file";
// 创建该文件对象(谷歌所用)
File file = new File(dirPath + "/" + filename);
//IE、百度
// File file = new File(filename);
System.out.println(file);
//对文件名编码,防止中文乱码
filename = this.getFileName(request,filename);
// 设置响应头
HttpHeaders headers = new HttpHeaders();
// 通知浏览器以下载的方式打开文件
headers.setContentDispositionFormData("attachment", filename);
// 定义以流的方式下载返回文件的数据
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
// 将该对象读取时按字节传送
byte[] array = FileUtils.readFileToByteArray(file);
// 使用SpringMVC框架的ResponseEntity对象封装返回下载数据
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(array, headers, HttpStatus.OK);
return responseEntity;
}
- 解决中文乱码问题
/**
* 根据浏览器的不同进行编码设置,返回编码后的文件名
*/
public String getFileName(HttpServletRequest request, String filename) throws Exception {
//IE不同版本User-Agent中出现的各种关键词
String[] IEBrowserKeyWords = {"MSIE", "Trident", "Edge"};
//获取请求头代理信息
String userAgent = request.getHeader("User-Agent");
for (String keyWord : IEBrowserKeyWords) {
if (userAgent.contains(keyWord)) {
//IE内核浏览器,统一为UTF-8编码显示
return URLEncoder.encode(filename, "UTF-8");
}
}
//其它浏览器统一为ISO-8859-1编码显示
return new String(filename.getBytes("UTF-8"), "ISO-8859-1");
}
4、将文件存储到云服务器
- 阿里云对象存储OSS(Object Storage Service),是一款海量、安全、低成本、高可靠的云存储服务。
- 使用OSS,您可以通过网络随时存储和调用包括文本、图片、音频和视频等在内的各种文件。
- 在我们使用了阿里云OSS对象存储服务之后,我们的项目当中如果涉及到文件上传这样的业务,在前端进行文件上传并请求到服务端时,在服务器本地磁盘当中就不需要再来存储文件了。我们直接将接收到的文件上传到oss,由 oss帮我们存储和管理,同时阿里云的oss存储服务还保障了我们所存储内容的安全可靠。
- 如何在项目当中来使用云服务完成具体的业务功能?
使用第三方服务步骤
★★★非必须充值,我们在这里只是暂用(每个用户阿里云都会给一些流量)
Bucket:存储空间是用户用于存储对象(Object,就是文件)的容器,所有的对象都必须隶属于某个存储空间。
SDK:Software Development Kit 的缩写,软件开发工具包,包括辅助软件开发的依赖(jar包)、代码示例等,都可以叫做SDK。
简单说,sdk中包含了我们使用第三方云服务时所需要的依赖,以及一些示例代码。我们可以参照sdk所提供的示例代码就可以完成入门程序。
1. 注册阿里云
2. 开通对象存储服务(OSS)
搜索找到OSS对象存储服务
第一次访问,需要开通对象存储服务
3. 开通OSS服务之后,就可以进入到阿里云对象存储的控制台,创建Bucket
4. 创建成功
5. 获取AccessKey(秘钥)
- 点进去之后会有一个提示,建议我们创建子用户,我们主账号等价于root,有所有权限,防止泄露
创建子用户
5.1 给新建的用户给权限
6. 地域节点(后面用)
SDK参考文档
- 阿里云oss 对象存储服务的准备工作我们已经完成了,接下来我们就来完成第二步操作:参照官方所提供的sdk示例来编写入门程序。
- 首先我们需要来打开阿里云OSS的官方文档,在官方文档中找到 SDK 的示例代码:
阿里云OSS集成(详见SDK参考文档)
1、方式一
- 引入依赖
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.16.1</version>
</dependency>
- 图片上传工具类
public class AliOSSUtils {
// Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
private static String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
private static String accessKeyId = "yourAccessKeyId";
private static String accessKeySecret = "yourAccessKeySecret";
// 填写Bucket名称,例如examplebucket。
private static String bucketName = "examplebucket";
/**
* 实现上传图片到OSS
*/
public static String upload(MultipartFile file) throws IOException {
// 获取上传的文件的输入流
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();
// 把上传到oss的路径返回
return url;
}
}
- Controller
@PostMapping("/upload")
public Result updateFile(MultipartFile image) throws IOException {
String resultUrl = aliOSSUtils.upload(image);
return Result.success(resultUrl);
}
2、方式二
- 创建工具类
@Data
@Getter
@Setter
public class AliOSSUtils {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketName;
/**
* 实现上传图片到OSS
*/
public String upload(MultipartFile file) throws IOException {
// 获取上传的文件的输入流
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();
// 把上传到oss的路径返回
return url;
}
}
- 自定义配置类
<!--自定义配置类-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
@Configuration
public class AliOSSConfig {
@Bean
@ConfigurationProperties(prefix = "koen-aliyun")
public AliOSSUtils aliOSSUtils(){
return new AliOSSUtils();
}
}
- 配置yaml
koen-aliyun:
endpoint: https://oss-cn-beijing.aliyuncs.com
accessKeyId: yourAccessKeyId
accessKeySecret: yourAccessKeySecret
bucketName: delivery-sys-bucket
- Controller直接调用工具类
@PostMapping("/upload")
public Result upload(MultipartFile imgFile) throws IOException {
String resultUrl = aliOSSUtils.upload(imgFile);
return new Result(true, MessageConst.UPLOAD_SUCCESS, resultUrl);
}
5、向本地存储
- yaml
delivery:
path: D:\..Amajor\data\img
- R
@Data
public class R<T> {
/**
* 编码:1成功,0和其它数字为失败
*/
private Integer code;
/**
* 错误信息
*/
private String msg;
/**
* 数据
*/
private T data;
/**
* 动态数据
*/
private Map map = new HashMap();
public static <T> R<T> success(T object) {
R<T> r = new R<T>();
r.data = object;
r.code = 1;
return r;
}
public static <T> R<T> error(String msg) {
R<T> r = new R<T>();
r.msg = msg;
r.code = 0;
return r;
}
public R<T> add(String key, Object value) {
this.map.put(key, value);
return this;
}
}
- 文件上传
@Slf4j
@RestController
@RequestMapping("/common")
public class CommonController {
//使用配置文件中的属性
//1、Yml文件配置上传路径
@Value("${delivery.path}")
private String basePath;
/**
* 文件上传
* @param file
* @return
*/
@PostMapping("/upload")
//前端文件上传我们接收到 接收文件MultipartFile
public R<String> upload(MultipartFile file){
// 1.yml文件配置上传路径
// 2. 获取文件名和后缀名
String filename = file.getOriginalFilename();
// lastIndexOf:返回字符串最后出现 . 的位置
int index = filename.lastIndexOf(".");
// 获取后缀名
String suffix = filename.substring(index);
// 3. 使用UUID重新生成新文件名
UUID randomUUID = UUID.randomUUID();
String newFileName = randomUUID + suffix;
// 4. 创建图片保存的基本目录
File dir = new File(basePath);
if (!dir.exists()) {
// 创建目录
dir.mkdirs();
}
// 5. 保存临时文件到图片目录
try {
file.transferTo(new File(basePath, newFileName));
} catch (IOException e) {
e.printStackTrace();
}
return R.success(newFileName);
}
}
- 文件下载
/**
* 文件下载
* @param name
* @param response
*/
@GetMapping("/download")
public void dowload(@RequestParam String name, HttpServletResponse response){
try {
File file = new File(basePath, name);
// 1. 定义输入流,通过字节输入流读取文件内容
FileInputStream fileInputStream = new FileInputStream(file);
// 2. 通过response对象设置响应数据格式(image/jpeg)
response.setContentType("image/jpeg");
// 3. 通过response对象,获取到字节输出流
ServletOutputStream outputStream = response.getOutputStream();
// 4. 通过输入流读取文件数据,然后通过输出流写回浏览器
int len = 0;
// 一次读 1024 = 1kb
byte[] bytes = new byte[1024];
while ((len = fileInputStream.read(bytes)) != -1) {
// 通过输入流读取数据,然后通过输出流写回数据
outputStream.write(bytes, 0, len);
outputStream.flush();
}
// 5. 关闭资源
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}