1 文件上傳
html中通過<input type="file"/>可以向服務(wù)器上傳文件旬盯。不過后臺(tái)需要手動(dòng)解析請求,比較復(fù)雜,所以可以使用smartupload或apache的fileupload組件進(jìn)行文件的上傳。smartupload據(jù)網(wǎng)友測試辑鲤,在上傳大文件時(shí)不穩(wěn)定,所以還是使用fileupload的吧杠茬,畢竟apache出品月褥。
本例子中使用的jar包:
- commons-fileupload-1.3.2.jar
- commons-io-2.5.jar(fileupload依賴)
前端jsp頁面:
<div class="upload">
<form action="UploadFileServlet" method="POST" enctype="multipart/form-data">
上傳文件:<input type="file" name="uploadFile">
<input type="submit" value="upload">上傳
<input type="reset" value="reset">重置
</form>
</div>
其中有幾個(gè)需要注意的點(diǎn):
- form表單的enctype必須為"multipart/form-data"。
- <input type="file" name="uploadFile"/> 中必須有name屬性瓢喉,因?yàn)樵趂ileupload中會(huì)根據(jù)fieldName解析上傳的文件宁赤。
- method必須為POST方法。
- 如果多文件上傳的話栓票,file類型的name必須為不同的名稱决左。
服務(wù)端需要建立一個(gè)UploadFileServlet來處理請求。
關(guān)鍵的doPost方法:
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
// 首先檢測是否是文件上傳走贪,主要依據(jù)enctype的值來判定
if (!ServletFileUpload.isMultipartContent(request)) {
PrintWriter writer = response.getWriter();
writer.write("Error 不是文件上傳,表單必須包含 enctype='multipart/form-data'");
writer.flush();
writer.close();
return;
}
DiskFileItemFactory factory = new DiskFileItemFactory();
// 設(shè)置在內(nèi)存中的緩存大小哆窿,如果超過了則保存到臨時(shí)文件。
factory.setSizeThreshold(MEMORY_THRESHOLD);
System.err.println(System.getProperty("java.io.tmpdir"));
// 設(shè)置臨時(shí)文件夾的目錄
factory.setRepository(new File(System.getProperty("java.io.tmpdir")));
ServletFileUpload upload = new ServletFileUpload(factory);
// 設(shè)置單個(gè)文件大小的最大值
upload.setFileSizeMax(MAX_FILE_SIZE);
// 設(shè)置上傳文件總量的最大值厉斟,包括所有文件和表單的總和
upload.setSizeMax(MAX_REQUEST_SIZE);
File uploadDir = new File(UPLOAD_PATH);
if (!uploadDir.exists()) {
uploadDir.mkdir();
}
try {
// 解析request
List<FileItem> formItems = upload.parseRequest(request);
if (formItems != null && formItems.size() > 0) {
for (FileItem item : formItems) {
// 如果不是普通的formField則就是上傳的文件
if (!item.isFormField()) {
String fileName = item.getName();
File storeFile = new File(UPLOAD_PATH + File.separator
+ fileName);
System.err.println(storeFile.getAbsolutePath());
item.write(storeFile);
item.delete();
} else {
System.err.println(item.getString());
}
}
}
request.setAttribute("message", "成功");
request.getRequestDispatcher("result.jsp").forward(request,
response);
} catch (Exception e) {
// TODO: handle exception
request.setAttribute("message", e.getMessage());
request.getRequestDispatcher("result.jsp").forward(request,
response);
e.printStackTrace();
return;
}
}
這樣即可實(shí)現(xiàn)文件的上傳。
需要改進(jìn)的地方
- 沒有上傳進(jìn)度
- 限制文件大小
- 限制文件類型
對于第2點(diǎn)强衡,可以利用fileupload的拋出異常解決擦秽。
catch(FileUploadBase.FileSizeLimitExceededException e){
request.setAttribute("message", "單個(gè)文件大小超過限制");
request.getRequestDispatcher("result.jsp").forward(request,
response);
e.printStackTrace();
return;
} catch(FileUploadBase.SizeLimitExceededException e){
request.setAttribute("message", "上傳文件總大小超過限制");
request.getRequestDispatcher("result.jsp").forward(request,
response);
e.printStackTrace();
return;
}
對于第3點(diǎn),可以在頁面用js進(jìn)行校驗(yàn)漩勤。
<form action="UploadFileServlet" method="POST"
enctype="multipart/form-data" onsubmit="return check_file()">
上傳文件:<input type="file" name="uploadFile" id="uploadFile"> <input
type="submit" value="upload">上傳 <input type="reset"
value="reset">重置
</form>
<script>
function check_file() {
var fileName = document.getElementById("uploadFile").value;
var suffix = fileName.substr(fileName.lastIndexOf(".") + 1);
if (suffix !== "exe") {
alert("只能上傳exe文件");
return false;
}
}
</script>
對于第1點(diǎn)感挥,fileupload可以添加監(jiān)聽器,監(jiān)聽上傳進(jìn)度越败。
//設(shè)置上傳進(jìn)度的監(jiān)聽器
ProgressListener progressListener = new ProgressListener() {
public void update(long pBytesRead, long pContentLength, int pItems) {
System.out.println("We are currently reading item " + pItems);
if (pContentLength == -1) {
System.out.println("So far, " + pBytesRead
+ " bytes have been read.");
} else {
System.out.println("So far, " + pBytesRead + " of "
+ pContentLength + " bytes have been read.");
uploadPercent = (double) pBytesRead / pContentLength;
System.err.println(uploadPercent);
}
}
};
upload.setProgressListener(progressListener);
雖然服務(wù)端添加了監(jiān)聽器触幼,可以在console或者Log里打印上傳進(jìn)度,但我們想要的是讓用戶看到上傳進(jìn)度究飞。所以需要把進(jìn)度返回給用戶置谦。
要解決的問題主要有兩個(gè):
- 進(jìn)度信息如何保存
- 前臺(tái)如何獲取
其中的一種方案是利用session。我們將上傳進(jìn)度保存在session里亿傅,前臺(tái)通過ajax方法定時(shí)獲取上傳的進(jìn)度媒峡,因?yàn)槊總€(gè)用戶是一個(gè)session,不同的用戶session不同。這樣當(dāng)不同的用戶同時(shí)上傳文件時(shí)葵擎,依然可以正確的獲得上傳進(jìn)
度谅阿,不會(huì)獲取到其他用戶上傳文件的進(jìn)度。
具體首先有一個(gè)簡單的保存上傳進(jìn)度的實(shí)體類:
public class UploadStatus {
private double percent;
public double getPercent() {
return percent;
}
public void setPercent(double percent) {
this.percent = percent;
}
}
然后有一個(gè)監(jiān)聽的類,實(shí)現(xiàn)了ProgressListener接口
public class UploadListener implements ProgressListener{
private UploadStatus status;
public UploadListener(UploadStatus status) {
// TODO Auto-generated constructor stub
this.status = status;
}
@Override
public void update(long pBytesRead, long pContentLength, int pItems) {
// TODO Auto-generated method stub
double uploadPercent = (double) pBytesRead / pContentLength;
status.setPercent(uploadPercent);
}
}
在doPost方法中:
//設(shè)置上傳進(jìn)度的監(jiān)聽器
UploadStatus status = new UploadStatus();
UploadListener listener = new UploadListener(status);
upload.setProgressListener(listener);
request.getSession(true).setAttribute("uploadStatus", status);
最后在doGet方法中签餐,返回上傳進(jìn)度寓涨。
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
response.setHeader("Cache-Control", "no-store");
response.setDateHeader("Expires", 0);
response.setHeader("Pragrma", "no-cache");
PrintWriter writer = response.getWriter();
UploadStatus status = (UploadStatus)(request.getSession().getAttribute("uploadStatus"));
if(status != null){
writer.write("上傳進(jìn)度:" + status.getPercent());
}else{
writer.write("沒有上傳信息");
}
writer.flush();
writer.close();
}
當(dāng)用戶上傳時(shí)可以訪問doGet方法即可獲取進(jìn)度。
前臺(tái)頁面可以這樣寫:
其中form的target屬性可以防止頁面跳轉(zhuǎn)氯檐。
<body>
<iframe width=0 height=0 name="uploadFrame"></iframe>
<form action="UploadFileServlet" method="POST"
enctype="multipart/form-data" target="uploadFrame" onsubmit="getStatus()">
上傳文件:<input type="file" name="uploadFile" id="uploadFile"> <input
type="submit" value="upload">上傳 <input type="reset"
value="reset">重置
</form>
<span>上傳進(jìn)度:</span><span id="progress"></span>
<script src="jquery-1.11.2.js"></script>
<script>
var finished ;
function check_file() {
var fileName = document.getElementById("uploadFile").value;
var suffix = fileName.substr(fileName.lastIndexOf(".") + 1);
if (suffix !== "exe") {
//alert("只能上傳exe文件");
//return false;
}
finished = false;
return true;
}
function getStatus(){
finished = false;
console.log("finished = " + finished)
showStatus();
}
function showStatus(){
console.log("showstatus finished = " + finished)
if(finished === true) return;
$.ajax({
url:'UploadFileServlet',
type:'GET',
success:function(data){
$('#progress').text(data);
if(data == '1.0'){
finished = true;
}
},
error:function(data){
alert(data);
}
});
setTimeout(showStatus,1000);
}
</script>
</body>
最后 完整的程序UploadFile.zip
除了使用form表單上傳文件這種方式戒良,也可以利用html5的特性使用ajax上傳文件。
下面是主要代碼男摧。
var formData = new FormData();
formData.append('file', $('#input')[0].files[0]);
$.ajax({
url: contextPath + '/file/upload?savedName=' + savedName,
type: 'POST',
data: formData,
cache: false,
contentType: false,
processData: false,
xhr: function () {
var xhr = $.ajaxSettings.xhr();
if (xhr.upload) {
xhr.upload.addEventListener("progress", function (evt) {
var percent = parseInt((evt.loaded / evt.total) * 100) + '%';
console.log(percent);
}, false);
return xhr;
}
}
}).done(function(e){})
首先初始化formData對象蔬墩,然后將真正的file添加到formData里,然后創(chuàng)建一個(gè)ajax請求,type是POST耗拓,
data: formData,
cache: false,
contentType: false,
processData: false,
這幾個(gè)屬性很重要拇颅,具體解釋請百度之。
上傳進(jìn)度則使用如下的方式獲得:
xhr: function () {
var xhr = $.ajaxSettings.xhr();
if (xhr.upload) {
xhr.upload.addEventListener("progress", function (evt) {
var percent = parseInt((evt.loaded / evt.total) * 100) + '%';
console.log(percent);
}, false);
return xhr;
}
}
這里注意這個(gè)是瀏覽器上傳的進(jìn)度乔询,在服務(wù)端還有寫入磁盤的時(shí)間樟插,所以即便達(dá)到100%了服務(wù)端可能也并沒有完全完成保存操作,不過這個(gè)時(shí)間差可以忽略竿刁。
注意如果出于頁面美化的目的黄锤,需要隱藏input框,那么一般會(huì)使用
display:none
這個(gè)css屬性食拜,但由于瀏覽器安全性的限制鸵熟,這種方式無法奏效,這里提供一個(gè)tricky的方法负甸,給input框設(shè)置一個(gè)position:absolute;top:-9999px;
,這樣既可以隱藏input流强,又可以觸發(fā)選擇文件的操作。
2 文件下載
文件下載相對比較簡單
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
String id = request.getParameter("id");
//app.properties 路徑
DFileItem dao = new DFileItem();
String basePath = request.getSession().getServletContext().getRealPath("");
String configPath = basePath+Constants.CONFIG_PATH;
String uploadPath = Configuration.getInstance(configPath).getProperty(Constants.FILE_STORAGE_PATH);
FileItem item = dao.queryFileItem(id);
if(item == null){
PrintWriter writer = response.getWriter();
writer.write("文件已經(jīng)被刪除");
writer.close();
return;
}
String name = item.getFileName();
response.setContentType("application/octet-stream");
String downloadName = URLEncoder.encode(name,"UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-Disposition", "attachment;filename*=utf-8'zh_cn'"+downloadName);
File file = new File(uploadPath+item.getUploadTime());
response.setContentLength((int)file.length());
FileInputStream in = new FileInputStream(file);
OutputStream out = response.getOutputStream();
byte[] buffer = new byte[8192];
int len = 0;
while((len = in.read(buffer)) != -1){
out.write(buffer, 0, len);
}
out.flush();
out.close();
in.close();
}
注意response 的contentType和header的設(shè)置即可呻待。
最終一個(gè)完整的文件上傳下載的例子FileServer