一、概述
文件的上传和下载,一直以来都是开发中必不可少的功能。在没有SpringMVC之前对于文件的上传和下载的操作,一般都是通过Apache 开源组织提供了一个用来处理表单文件上传的一个开源组件( Commons-fileupload )来实现的。现在SpringMVC提供了文件的上传和下载功能,而且使用起来十分简便。
二、SpringMVC的文件的上传和下载
配置Spring-web.xml
文件的上传是通过 MultipartResolver 实现的,所以要实现上传,只要注册相应的 MultipartResolver 即可。
MultipartResolver 的实现类有两个:
-
CommonsMultipartResolver (需要 Apache 的 commons-fileupload 支持,它能在比较旧的 servlet 版本中使用,兼容性好)
-
StandardServletMultipartResolver (不需要第三方 jar 包支持,它使用 servlet 内置的上传功能,但是只能在 Servlet 3 以上的版本使用)
这里我们注册使用第二个,无需配置额外的架包
<!--配置上传下载-->
<bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver"/>
配置web.xml
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/spring-web.xml</param-value>
</init-param>
<!--配置文件的上传和下载-->
<load-on-startup>1</load-on-startup>
<multipart-config>
<!--里面的配置可以在代码中实现,也可已在这里进行配置-->
<!--<location>D:\IdeaWork\ssm_blog\src\main\webapp\images</location>-->
<max-file-size>-1</max-file-size>
<max-request-size>-1</max-request-size>
</multipart-config>
</servlet>
上传文件的后台代码
我们对于文件的上传将会以常用的图片上传为例进行讲解。
//页面跳转
@GetMapping("/up")
public String getIndex(Model model) {
return "upFile";
}
@PostMapping("/up")
public String upFiles(@RequestPart("file") MultipartFile file, HttpServletRequest re,Model model) {
// 获得上传的文件格式
String contextType = file.getContentType();
// 判断文件是否符合要求的类型.
if (!contextTypecontains("image/")) {
model.addAttribute("msg", "只允许上传图片");
return "upFile";
}
// 判断上传的文件大小
if (file.getSize() > 1024 * 1024 * 5) {
model.addAttribute("msg", "文件过大");
return "upFile";
}
try {
// 获取当前类加载的路径
String path = re.getServletContext().getRealPath(File.separator+"img");
File img = new File(path);
// 如果文件夹不存在就创建一个
if(!img.exists()){
img.mkdir();
}
// file.getOriginalFilename()为文件的逻辑名
File fileName= new File(path+File.separator+getNewName(file.getOriginalFilename()));
// 这里是写入文件,最为重要的一段代码
file.transferTo(fileName);
} catch (IOException e) {
model.addAttribute("msg", "写入失败");
}
return "upFile";
}
表单上传
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>${msg}</h1>
<form action="/up" method="post" enctype="multipart/form-data">
<label>文件上传:</label>
<input type="file" name="file"><br/>
<input type="submit" value="提交">
</form>
</body>
</html>
上传图片的新需求
上面我们采取的是表单上传的方式,实际开发中,我们用的更多的是采取异步上传的方式。接下来我们提出以下几点需求:
- 上传的图片左下角加上水印
- 动态显示图片上传百分比
- 动态展示上传的精度
- 压缩图片
后台代码不变,前台实现
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<input type="file" multiple="multiple" id="myFile"/>
<br/>
<canvas id="myCanvas"></canvas>
<br/>
<button id="myBtn">上传</button>
<progress value="0" max="100" id="myPro"></progress>
</body>
<script type="text/javascript" src="../js/jquery.js"></script>
<script>
var myFile = document.getElementById("myFile");
var myBtn = document.getElementById("myBtn");
var myCanvas = document.getElementById("myCanvas");
window.addEventListener("load", () => {
all();
});
function all() {
myFile.addEventListener("change", () => {
$("#myPro").val(0);
loadImg();
});
myBtn.addEventListener("click", () => {
ajax(myFile.files[0]);
});
}
var myImg = new Image();
var pen = myCanvas.getContext("2d");
function loadImg(callback) {
var url = URL.createObjectURL(myFile.files[0]);
myImg.src = url;
//console.log(myImg);
console.log("压缩前:" + myFile.files[0].size);
// 这里是异步
myImg.onload = function () {
URL.revokeObjectURL(url);
// 压缩图片
myCanvas.width = myImg.width / 2;
myCanvas.height = myImg.height / 2;
pen.drawImage(myImg, 0, 0, myImg.width / 2, myImg.height / 2);
pen.font = "30px yahei";
pen.fillText("lzx绘制", (myImg.width / 2) - 100, (myImg.height / 2) - 10);
if (callback) () => callBack();
}
}
function ajax(blob) {
var form = new FormData();
form.append("file", blob);
$.ajax({
url: '/up',
type: 'post',
data: form,
cache: false,
contentType: false, //必须false才会自动加上正确的Content-Type
processData: false, //必须false才会避开jQuery对 formdata 的默认处理
xhr: function () {
var myXhr = $.ajaxSettings.xhr();
if (myXhr.upload) { // check if upload property exists
myXhr.upload.addEventListener('progress', function (e) {
var loaded = e.loaded;//已经上传大小情况
var total = e.total;//附件总大小
var per = Math.floor(100 * loaded / total); //已经上传的百分比
if (e.lengthComputable) {
$("#myPro").val(per);
//清空画板
pen.clearRect(0, 0, myCanvas.width, myCanvas.height);
//从左下角不停地画
pen.globalAlpha = 0.5;
pen.drawImage(myImg, 0, myCanvas.height, myCanvas.width, -myCanvas.height * per/100);
pen.font = "30px yahei";
pen.fillText("lzx绘制", (myImg.width / 2) - 100, (myImg.height / 2) - 10);
//显示百分比
pen.font = "80px yahei";
pen.fillText(per + "%", myCanvas.width / 2 - 20, myCanvas.height / 2 - 20);
}
}, false);
}
return myXhr;
}
});
}
</script>
</html>
文件的下载
文件的下载主要是对字节流的操作,它也是具有一定的套路的,直接复制粘贴使用即可。
这里我们以导出数据为excel表格并进行下载为例,这里我们是使用了poi来实现对表格的操作
service导出表格数据,存入字节流
public byte[] writeExcel(List<Employee> list) throws IOException {
HSSFWorkbook workbook = new HSSFWorkbook();
HSSFSheet sheet = workbook.createSheet("客户信息表");
HSSFRow row1 = sheet.createRow(0);
row1.createCell(0).setCellValue("编号");
row1.createCell(1).setCellValue("姓名");
row1.createCell(2).setCellValue("性别");
row1.createCell(3).setCellValue("学历");
row1.createCell(4).setCellValue("月薪");
HSSFRow row = null;
for (int i = 1; i <= list.size(); i++) {
employee = list.get(i - 1);
row = sheet.createRow(i);
row.createCell(0).setCellValue(employee.getId());
row.createCell(1).setCellValue(employee.getName());
row.createCell(2).setCellValue(employee.getSex());
row.createCell(3).setCellValue(employee.getEducation());
row.createCell(4).setCellValue(String.valueOf(employee.getSalary()));
}
ByteArrayOutputStream stream = new ByteArrayOutputStream();
workbook.write(stream);
return stream.toByteArray();
}
controller下载表格到本地
@GetMapping("/outExcel")
public ResponseEntity outExcel() throws IOException {
// HSSFWorkbook workbook = writeExcel();
// File files = new File( UUID.randomUUID() + ".xls");
// workbook.write(files);
// FileSystemResource file = new FileSystemResource(files);
// 这种方法也是可行的
// HttpHeaders headers = new HttpHeaders();
// headers.setCacheControl("no-cache, no-store, must-revalidate");
// headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
// headers.setContentLength(file.contentLength());
// headers.setContentDispositionFormData("attachment", file.getFilename());
// return ResponseEntity.ok().headers(headers).body(new InputStreamResource(file.getInputStream()));
byte[] file = service.writeExcel(service.listAll());
HttpHeaders headers = new HttpHeaders();
headers.setCacheControl("no-cache, no-store, must-revalidate");
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentLength(file.length);
headers.setContentDispositionFormData("attachment", UUID.randomUUID()+".xls");
return ResponseEntity.ok().headers(headers).body(file);
}
我们发现contoller字节是对byte[]数组,字节流进行操作的。拓展使用起来就十分方便了,我们只需要把要下载的文件转为字节流,存入byte[]数组,再controller中调用即可。