您的位置:首页 > 其它

Servlet实现带进度条的文件上传

2016-12-16 20:35 323 查看
在上一篇博文中介绍了通过POST的方式上传文件:点击打开链接 ,这一篇文章将介绍如何实现带进度条的上传效果。

工作原理:上传文件的同时,Servlet将上传进度的信息例如文件总长度、已上传文件的多少、传输速率写入Session中。客户端浏览器利用Ajax技术再新开辟一个独立的线程冲Session中获取上传进度信息,并实时显示。Ajax可以不刷新页面获取服务器数据。Session可以看做服务器内存,可以用于存放少量客户信息。上传文件仍然使用Apache开源包:commons-file-1.2.jar和commons-io-2.5.jar包。

上传进度条:通过两个<div>实现:

<style type="text/css">
body,td,div{font-size:12px;font-familly:宋体;}
#progressBar{width:400px;height:12px;background:#FFFFFF;border:1px solid #000000;padding:1px;}
#progressBarItem{width:30%;height:100%;background:#FF0000;}
</style>
<div id="progressBar"><div id="progressBarItem"></div></div>


效果图如下:



监听器:我们需要在文件上传的时候,不断的更新上传状态。commons-fileupload中有ProgressListener接口,只要实现这个接口中的方法:public void update(long bytesRead, long contentLength, int items)。byteRead表示已经上传的字节数,contentLength代表上传的文件的总长度,items表示正在上传第几个文件。添加了监听器之后,就可以计算出文件上传的进度,用进度条实时的显示出来。因此需要把这些数据保存起来,代码中通过一个自定义的Java
bean:UploadStatus类。代码如下:

package com.hello.firstweb.listener;

import org.apache.commons.fileupload.ProgressListener;

import com.hello.firstweb.bean.UploadStatus;

public class UploadListener implements ProgressListener{
private UploadStatus status;
@Override
public void update(long bytesRead, long contentLength, int items) {
// TODO Auto-generated method stub
status.setBytesRead(bytesRead);//已读取数据长度
status.setContentLength(contentLength);//文件总长度
status.setItems(items);//正在保存第几个文件
}
public UploadListener(UploadStatus status){
this.status = status;
}
}

package com.hello.firstweb.bean;

public class UploadStatus {
private long bytesRead;	//已上传的字节数
private long contentLength; //所有文件的总长度
private long startTime = System.currentTimeMillis();
// 开始上传时间,用于计算上传速率
private int items; //正在上传第几个文件
public long getBytesRead() {
return bytesRead;
}
public void setBytesRead(long bytesRead) {
this.bytesRead = bytesRead;
}
public long getContentLength() {
return contentLength;
}
public void setContentLength(long contentLength) {
this.contentLength = contentLength;
}
public long getStartTime() {
return startTime;
}
public void setStartTime(long startTime) {
this.startTime = startTime;
}
public int getItems() {
return items;
}
public void setItems(int items) {
this.items = items;
}

}
Upload中记录了上传开始的时间,用于计算上传速率、估计上传总时间等等。

监听上传进度:处理文件上传的Servlet类为ProgressUploadServlet。与上一篇博文中的UploadServlet类似,仍然使用ServletFileUpload类。监听上传过程需要安装一个监听器,然后把存有上传进度信息的UploadStatus对象放到Session中。上传文件是POST方法,因此需要把代码写到doPost()方法中:

package com.hello.firstweb;
import com.hello.firstweb.bean.UploadStatus;
import com.hello.firstweb.listener.UploadListener;
public class ProgressUploadServlet extends HttpServlet {
// 上传配置
private static final int MEMORY_THRESHOLD   = 1024 * 1024 * 3;  // 3MB
private static final int MAX_FILE_SIZE      = 1024 * 1024 * 40; // 40MB
private static final int MAX_REQUEST_SIZE   = 1024 * 1024 * 50; // 50MB
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
UploadStatus status = new UploadStatus();
UploadListener listener = new UploadListener(status);
request.getSession().setAttribute("uploadStatus",status);
//创建解析工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setRepository(new File(System.getProperty("java.io.tmpdir")));//设置文件缓存目录  //设置文件缓存目录
//创建解析器
ServletFileUpload upload = new ServletFileUpload(factory);
// 设置最大文件上传值
upload.setFileSizeMax(MAX_FILE_SIZE);
// 设置最大请求值 (包含文件和表单数据)
upload.setSizeMax(MAX_REQUEST_SIZE);
upload.setHeaderEncoding("UTF-8");
//解析request得到封装FileItem的list
upload.setProgressListener(listener);

try{
List<FileItem> list = upload.parseRequest(request);
System.out.println("size is "+list.size());
for(FileItem item:list){
if(item.isFormField()){  //表单数据
System.out.println("FormField: "+item.getFieldName()+"="+item.getString());
}else{   //否则就是上传的文件
System.out.println("File:"+item.getName());
String fileName = item.getName();
//最好加上这个,因为有可能只上传一个文件,如果这样的话,其它文件名是
//空的,在写时就会认为它是一个目录而报错
if(fileName == null || "".equals(fileName)){
System.out.println("upload file is null");
continue;
}
File saved = new File("D:\\WebWorkspace\\upload_test",fileName);
saved.getParentFile().mkdirs();
System.out.println("real path:" + saved.getAbsolutePath());
InputStream ins = item.getInputStream();
OutputStream ous = new FileOutputStream(saved);

byte[] tmp = new byte[1024];
int len = -1;
while((len = ins.read(tmp))!=-1){
ous.write(tmp,0,len); //写文件
}
ous.close();
ins.close();
response.getWriter().println("已保存文件:"+saved);
}
}
//把异常改了,这样才会打印准确报错
}catch(FileUploadException e){
System.out.println("Exception!!!!!!!!!!!!!");
System.out.println(e.getMessage());
response.getWriter().println("上传发生错误:"+e.getMessage());
}
}

}
通过ProgressUploadServlet来处理上传文件,就能够获取上传的开始时间、已经上传的字节数、总字节数等等,不过这些信息仍然存放在Session里,需要再写一个程序读取。
读取上传进度:上传文件使用了doPost()方法,但是doGet()方法还没使用,所以可以通过doGet()方法来获取上传的进度信息。

package com.hello.firstweb;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import com.hello.firstweb.bean.UploadStatus;
import com.hello.firstweb.listener.UploadListener;

public class ProgressUploadServlet extends HttpServlet { // 上传配置 private static final int MEMORY_THRESHOLD = 1024 * 1024 * 3; // 3MB private static final int MAX_FILE_SIZE = 1024 * 1024 * 40; // 40MB private static final int MAX_REQUEST_SIZE = 1024 * 1024 * 50; // 50MBpublic void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

response.setHeader("Cache-Control", "no-store"); //禁止浏览器缓存
response.setHeader("Pragrma", "no-cache"); //禁止浏览器缓存
response.setDateHeader("Expires", 0); //禁止浏览器缓存

UploadStatus status = (UploadStatus) request.getSession(true).getAttribute("uploadStatus");//从session中读取上传信息
if(status == null){
response.getWriter().println("没有上传信息");
return;
}

long startTime = status.getStartTime(); //上传开始时间
long currentTime = System.currentTimeMillis(); //现在时间
long time = (currentTime - startTime) /1000 +1;//已经传顺的时间 单位:s

double velocity = status.getBytesRead()/time; //传输速度:byte/s

double totalTime = status.getContentLength()/velocity; //估计总时间
double timeLeft = totalTime -time; //估计剩余时间
int percent = (int)(100*(double)status.getBytesRead()/(double)status.getContentLength()); //百分比
double length = status.getBytesRead()/1024/1024; //已完成数
double totalLength = status.getContentLength()/1024/1024; //总长度 M
String value = percent+"||"+length+"||"+totalLength+"||"+velocity+"||"+time+"||" +totalTime
+"||"+timeLeft+"||"+status.getItems();
response.getWriter().println(value); //输出给浏览器进度条
}

}
至此,使用快捷键Ctrl+N新开子窗口访问ProgressUploadServlet就可以看到文字版的上传仅需,不断的刷新,就能看到不断的更新,但是这样也太不智能了。所以我们还需要一个Ajax程序,自动获取该Servlet返回的数据,将数据显示到进度条上,并且每一秒钟自动更新一次。

显示上传进度:上传文件时,如果不对表单做特别处理,提交表单后会转到另一个页面,造成页面的刷新。而且新页面显示之前,浏览器会因等待而显示白屏。如果上传的文件很大,白屏时间会很长,因此需要对表单做一些特别处理,使提交后的表单内容不变,同时实时显示进度条,知道文件上传技术。

<iframe name=upload_iframe width=0 height=0></iframe>
<form action="servlet/ProgressUploadServlet" method="post" enctype="multipart/form-data" target="upload_iframe" onsubmit="showStatus();">
<input type="file" name="file1" style="width:350px;"><br/>
<input type="file" name="file2" style="width:350px;"><br/>
<input type="file" name="file3" style="width:350px;"><br/>
<input type="file" name="file4" style="width:350px;"><br/>
<input type="text" name="description2" class="text">
<input type="submit" value="开始上传" id="btnSubmit">
</form>
target默认属性为_self。如果target为默认值,则提交后的新页面会在当前窗口显示,造成窗口短暂的白屏。通过在页面内添加一个隐藏的iframe,把target指定为该隐藏的iframe,则提交后的新页面会在iframe内显示,iframe会短暂的白屏,因为iframe是隐藏的,所以我们看不到变化。

注意上传文件需要指定:enctype="multipart/form-data"。另外为表单添加了onsubmit="showStatus();"事件,提交表单后就会执行该函数,显示进度条,并用Ajax读取Session里边的上传进度,实时刷新进度条。showStatus的代码如下:

<script type="text/javascript">
var finished = true;  //上传是否结束
function $(obj){
return document.getElementById(obj);  //快速返回id为obj的HTML对象
}
function showStatus(){
finished = false; //显示进度条
$('status').style.display='block';
$('progressBarItem').style.width="1%";  //进度条初始为1%
$('btnSubmit').disabled = true; //将提交按钮置灰
setTimeout("requestStatus()",1000);//1s钟后执行requestStatus()方法
}
</script>
Ajax能够不刷新页面而改变页面的内容,他的原理是创建一个request,用这个request获取其他页面的内容,并显示到页面上。因为它没有在浏览器中输入新的地址,所以页面不会有刷新,抖动。

Ajax的核心是request,学名为XMLHttpRequest,需要浏览器支持。目前所有的主流浏览器都支持XMLHttpRequest。浏览器端的代码如下:progressUpload.jsp

<%@ page language="java" import="java.util.*" contentType="text/html ;charset=UTF-8"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>My JSP 'uploadProgress.jsp' starting page</title>

<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv ="Content-type" content="text/html ;charset=UTF-8">
<style type="text/css">
body,td,div{font-size:12px;font-familly:宋体;}
#progressBar{width:400px;height:12px;background:#FFFFFF;border:1px solid #000000;padding:1px;}
#progressBarItem{width:30%;height:100%;background:#FF0000;}
</style>
</head>
<body>
<iframe name=upload_iframe width=0 height=0></iframe> <form action="servlet/ProgressUploadServlet" method="post" enctype="multipart/form-data" target="upload_iframe" onsubmit="showStatus();"> <input type="file" name="file1" style="width:350px;"><br/> <input type="file" name="file2" style="width:350px;"><br/> <input type="file" name="file3" style="width:350px;"><br/> <input type="file" name="file4" style="width:350px;"><br/> <input type="text" name="description2" class="text"> <input type="submit" value="开始上传" id="btnSubmit"> </form>

<div id="status" style="display:none;">
上传进度条:
<div id="progressBar"><div id="progressBarItem"></div></div>
<div id="statusInfo"></div>
</div>

<script type="text/javascript">
var finished = true; //上传是否结束
function $(obj){
return document.getElementById(obj); //快速返回id为obj的HTML对象
}
function showStatus(){
finished = false; //显示进度条
$('status').style.display='block';
$('progressBarItem').style.width="1%"; //进度条初始为1%
$('btnSubmit').disabled = true; //将提交按钮置灰
setTimeout("requestStatus()",1000);//1s钟后执行requestStatus()方法
}
function requestStatus(){
if(finished) return;
var req = createRequest(); //获取Ajax请求
req.open("GET","servlet/ProgressUploadServlet"); //设置请求路径
req.onreadystatechange=function(){callback(req);} //请求完毕就执行callback

req.send(null); //发送请求
setTimeout("requestStatus()",1000); //1s后重新请求
}
function createRequest(){
if(new XMLHttpRequest()){ //Netscape浏览器
return new XMLHttpRequest();
}else{ //IE浏览器
try{
return new ActiveXObject("Msxml2.XMLHTTP");
}catch(e){
return new ActiveXObject("Microsoft.XMLHTTP");
}
}
return null;
}

function callback(req){ //刷新进度条
if(req.readyState ==4){ //请求结束后
if(req.status !=200){ //发生错误
debug("发生错误。req.status :"+req.status+"");
return;
}
debug("status.jsp 返回值:"+req.responseText);//显示debug信息
var ss=req.responseText.split("||"); //处理进度信息

$('progressBarItem').style.width=''+ss[0]+'%';
$('statusInfo').innerHTML='已完成百分比:'+ss[0]+'%<br/>已完成数(M):'+ss[1]+'<br />文件总长度(M):'+ss[2]+'<br/>传输速度(K):'+ss[3]+
'<br/>已用时间(s):'+ss[4]+'%<br/>估计总时间(s):'+ss[5]+'%<br/>估计剩余时间(s):'+ss[6]+'%<br/>正在上传第几个文件:'+ss[7];

if(ss[1]==ss[2]){
finished =true;
$('statusInfo').innerHTML +="<br/><br/><br/>上传已完成。";
$('btnSubmit').disabled = false;
}
}
}
function debug(obj){ //显示调试信息
var div = document.createElement("DIV");
div.innerHTML="[debug]:"+obj;
document.body.appendChild(div);
}
</script>
</body>
</html>


运行完的效果图如下:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: