前言
在这个互联网已经普及的社会,文件的上传是必不可的了,可以说,只要你用过电子产品,那就必然接触并上传过文件,那不然你玩微信啊,陌陌啥的,不上传个帅一点的图片,多发几个高大上的朋友圈,也聊不到妹子啊…
哈哈,开玩笑的,毕竟撩妹子这种事情,靠这些花里胡哨的东西可是行不通的,得靠脸… 还得有钱…
切入正题,我们今天要讲得内容就是如何使用SpringMVC来实现文件得上传和下载
SpringMVC的文件上传
那么此次我们还是通过一个小案例来给大家演示文件上传及下载啦,首先我们先看文件上传的步骤
第一步:导包
第二步:创建一个简单得文件上传页面
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>
<a href="../marco/attachment/queryAllAttachment.action">查询所有文件列表</a>
</h1>
<h1>文件上传</h1>
<hr>
<form action="attachment/addAttachment.action" method="post" enctype="multipart/form-data">
请选择文件:<input type="file" name="multiFile"><br>
请选择文件:<input type="file" name="multiFile"><br>
请选择文件:<input type="file" name="multiFile"><br>
<input type="submit" value="提交">
</form>
<img alt="" src="../marco/attachment/downloadFile.action?id=13">
</body>
</html>
第三步:配置二进制流程解析
在学习SpringMVC之前,如果需要进行文件的传输,将文件转换为流之后成功被Controller控制端接收,需要在Controller上添加@MultipartConfig注解,通过getParts()方法获取流对象,并解析文件流的内容,非常复杂,而SpringMVC帮我们提供了一个内置的解析文件的类CommonsMultipartResolver,以下就是该类的xml配置
<!-- 实例化二进制流解析器 -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 指定文件上传过程中提交的数据库的编码 -->
<property name="defaultEncoding" value="UTF-8"></property>
<!-- 指定上传文件的保存的临时目录 -->
<property name="uploadTempDir" value="/upload"></property>
<!-- 指定上传的文件的最大大小 -->
<property name="maxUploadSize" value="1024000000"></property>
</bean>
需要注意的一点是,配置CommonsMultipartResolver的时候,必须要加上id=“multipartResolver”,否则文件上传不会生效,而且multipartResolver这个名字可不是乱取的哦,大家查看DispatcherServlet的时候可以找到CommonsMultipartResolver这个类,他的属性名就是multipartResolver,而DispatcherServlet正是通过这个名称搜索到这个类。
第四步:定义文件上传控制器处理上传请求
之前我们在页面上定义的input框中的name是multiFile,因此在接收文件数据源的时候名字也应该是multiFile,否则后台是接受不到文件流的。Attachement是我单独定义的一个类,主要用于存储文件的信息(包含文件名,文件大小,以及文件存放的路径) 到后台,为了突出本章节的重点,这里的JDBC操作的代码我就不放出来啦~
@Controller
@RequestMapping("attachment")
public class FileController {
@Autowired
private AttachementService attachementService;
/**
* 文件上传
* @throws IOException
* @throws IllegalStateException
*/
@RequestMapping("addAttachment")
public String addAttachment(MultipartFile[] multiFile) throws Exception {
// 文件上传的父目录
String parentPath = AppFileUtils.PATH;
// 得到当前日期作为文件夹名称
String dirName = RandomUtils.getCurrentDateForString();
// 构造文件夹对象
File dirFile = new File(parentPath, dirName);
if (!dirFile.exists()) {
dirFile.mkdirs();// 创建文件夹
}
for (MultipartFile multipartFile : multiFile) {
// 得到文件原名
String oldName = multipartFile.getOriginalFilename();
//得到文件类型
String contenttype=multipartFile.getContentType();
//得到文件大小
Double size = (double) multipartFile.getSize();
// 根据文件原名得到新名
String newName = RandomUtils.createFileNameUseTime(oldName);
File dest = new File(dirFile, newName);
multipartFile.transferTo(dest);
AttachementVo attachementVo=new AttachementVo();
attachementVo.setCreatetime(new Date());
attachementVo.setOldname(oldName);
attachementVo.setContenttype(contenttype);
attachementVo.setSize(size);
//路径为文件夹的名称+新的名称
attachementVo.setPath(dirName+"/"+newName);
//保存数据到数据库
attachementService.addAttachment(attachementVo);
}
return "success.jsp";
}
}
另外AppFileUtils和RandomUtils是我单独定义的两个工具类,RandomUtils是为了重新组装文件的名称,比较简单,网上一搜一大把
public class RandomUtils {
private static SimpleDateFormat sdf1=new SimpleDateFormat("yyyy-MM-dd");
private static SimpleDateFormat sdf2=new SimpleDateFormat("yyyyMMddHHmmssSSS");
private static Random random=new Random();
/**
* 得到当前日期
*/
public static String getCurrentDateForString() {
return sdf1.format(new Date());
}
/**
* 生成文件名使用时间+4位随机数
* @param suffix 文件名称
*/
public static String createFileNameUseTime(String fileName) {
String fileSuffix=fileName.substring(fileName.lastIndexOf("."),fileName.length());
String time=sdf2.format(new Date());
Integer num=random.nextInt(9000)+1000;
return time+num+fileSuffix;
}
/**
* 生成文件名使用UUID
* @param suffix 文件名称
*/
public static String createFileNameUseUUID(String fileName) {
String fileSuffix=fileName.substring(fileName.lastIndexOf("."),fileName.length());
return UUID.randomUUID().toString().replace("-", "").toUpperCase()+fileSuffix;
}
}
AppFileUtil是为了方便获取文件存储的路径,我这里设置的路径是F:\upload,大家可能在这里会有疑问,因为这一点和我们之前在Servlet项目中讲到的不太一样,我们之前是将文件存储在webapps目录下,打个比方,我们在webapps下新建一个目录为userImg,并且将文件存储在userImg中,那么我们的网络访问路径就是127.0.0.1:8080/userImg/avatar.jpg,通过网络地址是可以直接访问到这张图片的。
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class AppFileUtils {
/**
* 得到文件上传的路径
*/
public static String PATH;
static {
InputStream stream = AppFileUtils.class.getClassLoader().getResourceAsStream("file.properties");
Properties properties=new Properties();
try {
properties.load(stream);
PATH=properties.getProperty("path");
} catch (IOException e) {
e.printStackTrace();
}
}
}
但是实际开发中我们存储的不仅仅是图片,可能是文件,我们如果需要将文件下载下来的话,那么这种方式就行不通啦,而且还变相的存在着安全隐患,因为它直接的暴露了我们服务器和具体文件夹的信息。
因此综合考虑,我这里将存储的路径改到了其他的盘符,那么问题又来了,如果说将文件存储在webapps下我们还可以通过网络地址访问到,那我放在其他盘符下怎么访问的到呢?那就跟着我往下走啦~
第五步:文件下载
这里就是我们的重点啦,我们之前将文件的路径存在了Attachement对象中,那么当页面发出请求,给到所需要下载或者浏览的文件的id,就可以获取到文件的子路径(比方说全路径是 F:\upload\2019-07-24\xxxx.JPG,那么子路径就是upload\2019-07-24\xxxx.JPG),通过这种方式存储的话避免了我们文件夹被暴露的风险。
那当我们拿到了文件的子路径之后,就可以拼接文件的全路径啦。
/**
* 下载的方法
*/
@RequestMapping("downloadFile")
public ResponseEntity<Object> downloadFile(AttachementVo attachementVo,HttpServletResponse response){
//1,根据ID查询文件
Attachment attachment=this.attachementService.queryAttachmentById(attachementVo.getId());
//2,得到文件的相对路径
String path=attachment.getPath();
//3,拿到文件的老名字
String oldName=attachment.getOldname();
return AppFileUtils.downloadFile(response, path, oldName);
}
拿到全路径之后,我这里封装了一个downloadFile的方法,通过FileUtils.readFileToByteArray(file)可以获取到文件的流对象,然后封装为ResponseEntity对象后,页面通过一个a标签就可以下载这个流对象也就是相对应的文件或者图片啦
<a href="downloadFile.action?id=1">
/**
* 文件下载
* @param response
* @param path
* @param oldName
* @return
*/
public static ResponseEntity<Object> downloadFile(HttpServletResponse response, String path, String oldName) {
//4,使用绝对路径+相对路径去找到文件对象
File file=new File(AppFileUtils.PATH,path);
//5,判断文件是否存在
if(file.exists()) {
try {
try {
//如果名字有中文 要处理编码
oldName=URLEncoder.encode(oldName, "UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
//把file转成一个bytes
byte [] bytes=FileUtils.readFileToByteArray(file);
HttpHeaders header=new HttpHeaders();
//封装响应内容类型(APPLICATION_OCTET_STREAM 响应的内容不限定)
header.setContentType(MediaType.APPLICATION_OCTET_STREAM);
//设置下载的文件的名称
header.setContentDispositionFormData("attachment", oldName);
//创建ResponseEntity对象
ResponseEntity<Object> entity=
new ResponseEntity<Object>(bytes, header, HttpStatus.CREATED);
return entity;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}else {
PrintWriter out;
try {
out = response.getWriter();
out.write("文件不存在");
out.flush();
out.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
那同理,如果我们保存的是一张图片的话,通过一个img标签就可以访问到相对应的图片啦
<img alt="" src="../marco/attachment/downloadFile.action?id=13">
大家赶紧试试吧~