SpringMVC中的上傳和下載
一、概述
檔案的上傳和下載,一直以來都是開發中必不可少的功能。在沒有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中呼叫即可。