掌控上傳進(jìn)度的AJAX Upload
掌控上傳進(jìn)度的AJAX Upload
cleverpig 發(fā)表于 2007-01-08 11:12:14作者:cleverpig 來源:Matrix
評論數(shù):83 點擊數(shù):5,066 投票總得分:12 投票總?cè)舜?4
關(guān)鍵字:AJAX,upload,monitor
摘要:
2006年底Google了一下AJAX Upload實現(xiàn),結(jié)果沒有發(fā)現(xiàn)很完整的Java實現(xiàn)。碩果僅存的就是TELIO公司的Pierre-Alexandre發(fā)表的《AJAX Upload progress monitor for Commons-FileUpload Example》一文。雖然文中完成Upload工作的是Common-FileUpload組件,但在其代碼中沒有1.2版本所提供的Listener功能,這就對檢測文件上傳情況造成了困難...工具箱
本站收藏作者:cleverpig

AJAX——最酷的“沖浪板”
動機(jī):
2006年底Google了一下AJAX Upload實現(xiàn),結(jié)果沒有發(fā)現(xiàn)很完整的Java實現(xiàn)。碩果僅存的就是TELIO公司的Pierre-Alexandre發(fā)表的《AJAX Upload progress monitor for Commons-FileUpload Example》文中提供的ajax-upload-1.0.war。
雖然上文中完成Upload工作的是Apache的Common-FileUpload組件,但在其代碼中所使用的FileUpload1.1版本并沒有1.2版本所提供的上傳處理Listener功能,這就對檢測文件上傳情況造成了困難。我想正是這個原因致使Pierre-Alexandre使用了DWR+MonitoredDiskFileItem、MonitoredDiskFileItemFactory類(分別繼承DiskFileItem、DiskFileItemFactory)的方式:前者負(fù)責(zé)在web客戶端進(jìn)行Remote Call;后者在進(jìn)行文件數(shù)據(jù)讀取時統(tǒng)計數(shù)據(jù)總量、讀取數(shù)據(jù)量、處理文件總數(shù),并保存于Session中,以供web客戶端通過DWR遠(yuǎn)程調(diào)用UploadMonitor類的getUploadInfo方法進(jìn)行輪詢(Poll)。
從本人觀點出發(fā),Pierre-Alexandre實現(xiàn)的不足之處:
1.沒有用戶取消上傳功能;
2.完全的DWR實現(xiàn),沒有使用Prototype,對于不會使用DWR的開發(fā)者來講有一定的知識局限性,而且由于DWR的個性而造成不便將此實現(xiàn)集成到項目中。
Prototype+Servlet的實現(xiàn):

Prototype+Servlet的Example
所以出于研究Prototype之目的,本人經(jīng)過仔細(xì)思考,嘗試實現(xiàn)了一個Prototype+Servlet的簡單Example。其工作流程很簡單:
1.在Form提交上傳文件Field的同時,使用AJAX周期性地從Servlet輪詢上傳狀態(tài)信息;
2.然后,根據(jù)此信息更新進(jìn)度條和相關(guān)文字,及時反映文件傳輸狀態(tài);
3.如果用戶取消上傳操作,則進(jìn)行相應(yīng)的現(xiàn)場清理工作:刪除已經(jīng)上傳的文件,在Form提交頁面中顯示相關(guān)信息;
4.如果上傳完畢,在Form提交頁面中顯示已經(jīng)上傳的文件內(nèi)容(或鏈接),也可以與一些AJAX SlideShow應(yīng)用結(jié)合在一起。
服務(wù)器端代碼:
Bean序列化/反序列化工作:XmlUnSerializer這個類雖然不能夠通吃任何模樣的Bean,但應(yīng)付一般的Bean、具有Collection類型屬性的Bean和Bean List來講還是夠用的。
{XmlUnSerializer類的核心方法serializeBean和serializeBeanList}:
/**
* 將bean系列化為UTF-8編碼的xml
* @param beanObj
* @return
* @throws IOException
*/
public static String serializeBean(Object beanObj) throws IOException{
…
}
/**
* 將bean列表序列化為UTF-8編碼的xml
* @param beanObj
* @return
* @throws IOException
*/
public static String serializeBeanList(Object beanListObj) throws IOException{
…
}
文件上傳狀態(tài)Bean:使用FileUploadStatus這個類記錄文件上傳狀態(tài),并將其作為服務(wù)器端與web客戶端之間通信的媒介物:通過對這個類對象進(jìn)行XML序列化作為服務(wù)器回應(yīng)發(fā)送給web客戶端,web客戶端使用JavaScript對其進(jìn)行反序列化處理獲得JavaScript版本的文件上傳狀態(tài)對象。
{FileUploadStatus的屬性}:
//上傳總量
private long uploadTotalSize=0;
//讀取上傳總量
private long readTotalSize=0;
//當(dāng)前上傳文件號
private int currentUploadFileNum=0;
//成功讀取上傳文件數(shù)
private int successUploadFileCount=0;
//狀態(tài)
private String status="";
//處理起始時間
private long processStartTime=0l;
//處理終止時間
private long processEndTime=0l;
//處理執(zhí)行時間
private long processRunningTime=0l;
//上傳文件URL列表
private List uploadFileUrlList=new ArrayList();
//取消上傳
private boolean cancel=false;
//上傳base目錄
private String baseDir="";
文件上傳狀態(tài)監(jiān)視工作:使用Common-FileUpload 1.2版本(20070103)。此版本與1.1版的區(qū)別在于提供了能夠監(jiān)視文件上傳情況的ProcessListener接口,使開發(fā)者通過FileUploadBase類對象的setProcessListener方法植入自己的Listener,而且實現(xiàn)這個Listener很簡單。
{FileUploadListener主要方法update}:
/**
* 更新狀態(tài)
* @param pBytesRead 讀取字節(jié)總數(shù)
* @param pContentLength 數(shù)據(jù)總長度
* @param pItems 當(dāng)前正在被讀取的field號
*/
public void update(long pBytesRead, long pContentLength, int pItems){
FileUploadStatus fuploadStatus=BackGroundService.takeOutFileUploadStatusBean(this.session);
logger.debug("當(dāng)前正在處理第" + pItems+"個文件");
fuploadStatus.setUploadTotalSize(pContentLength);
//讀取完成
if (pContentLength == -1) {
logger.debug("讀取完成:讀取了 " + pBytesRead + " bytes.");
fuploadStatus.setStatus("完成對" + pItems+"個文件的讀取:讀取了 " + pBytesRead + " bytes.");
fuploadStatus.setReadTotalSize(pBytesRead);
fuploadStatus.setSuccessUploadFileCount(pItems);
fuploadStatus.setProcessEndTime(System.currentTimeMillis());
fuploadStatus.setProcessRunningTime(fuploadStatus.getProcessEndTime());
//讀取中
} else {
logger.debug("讀取進(jìn)行中:已經(jīng)讀取了 " + pBytesRead + " / " + pContentLength+ " bytes.");
fuploadStatus.setStatus("當(dāng)前正在處理第" + pItems+"個文件:已經(jīng)讀取了 " + pBytesRead + " / " + pContentLength+ " bytes.");
fuploadStatus.setReadTotalSize(pBytesRead);
fuploadStatus.setCurrentUploadFileNum(pItems);
fuploadStatus.setProcessRunningTime(System.currentTimeMillis());
}
BackGroundService.storeFileUploadStatusBean(this.session,fuploadStatus);
}
很清楚,我也把FileUploadStatus這個Bean存取于Session中。
Servlet實現(xiàn):BackGroundService這個Servlet類負(fù)責(zé)接收Form Post數(shù)據(jù)、回應(yīng)狀態(tài)輪詢請求、處理取消文件上傳的請求。盡管可以把這些功能相互分離開來(比如構(gòu)造一個FileUploadManager類),但出于簡單明了、便于閱讀之目的,還是將它們放到Servlet中,只是由不同的方法進(jìn)行分割。
{BackGroundService中的processFileUpload方法用于處理文件上傳請求}:
/**
* 處理文件上傳
* @param request
* @param response
* @throws IOException
* @throws ServletException
*/
private void processFileUpload(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
DiskFileItemFactory factory = new DiskFileItemFactory();
//設(shè)置內(nèi)存閥值,超過后寫入臨時文件
factory.setSizeThreshold(10240000);
//設(shè)置臨時文件存儲位置
factory.setRepository(new File(request.getRealPath("/upload/temp")));
ServletFileUpload upload = new ServletFileUpload(factory);
//設(shè)置單個文件的最大上傳size
upload.setFileSizeMax(10240000);
//設(shè)置整個request的最大size
upload.setSizeMax(10240000);
upload.setProgressListener(new FileUploadListener(request.getSession()));
//保存初始化后的FileUploadStatus Bean
storeFileUploadStatusBean(request.getSession(),initFileUploadStatusBean(request));
String forwardURL="";
try {
List items = upload.parseRequest(request);
//獲得返回url
for(int i=0;i<items.size();i++){
FileItem item=(FileItem)items.get(i);
if (item.isFormField()){
logger.debug("form Field["+item.getFieldName()+"]="+item.getString());
forwardURL=item.getString();
break;
}
}
//處理文件上傳
for(int i=0;i<items.size();i++){
FileItem item=(FileItem)items.get(i);
//取消上傳
if (takeOutFileUploadStatusBean(request.getSession()).getCancel()){
deleteUploadedFile(request);
break;
}
//保存文件
else if (!item.isFormField() && item.getName().length()>0){
String fileName=takeOutFileName(item.getName());
logger.debug("處理文件["+fileName+"]:保存路徑為"
+request.getRealPath(UPLOAD_DIR)+File.separator+fileName);
File uploadedFile = new File(request.getRealPath(UPLOAD_DIR)+File.separator+fileName);
item.write(uploadedFile);
//更新上傳文件列表
FileUploadStatus fUploadStatus=takeOutFileUploadStatusBean(request.getSession());
fUploadStatus.getUploadFileUrlList().add(fileName);
storeFileUploadStatusBean(request.getSession(),fUploadStatus);
Thread.sleep(500);
}
}
} catch (FileUploadException e) {
logger.error("上傳文件時發(fā)生錯誤:"+e.getMessage());
e.printStackTrace();
uploadExceptionHandle(request,"上傳文件時發(fā)生錯誤:"+e.getMessage());
} catch (Exception e) {
// TODO Auto-generated catch block
logger.error("保存上傳文件時發(fā)生錯誤:"+e.getMessage());
e.printStackTrace();
uploadExceptionHandle(request,"保存上傳文件時發(fā)生錯誤:"+e.getMessage());
}
if (forwardURL.length()==0){
forwardURL=DEFAULT_UPLOAD_FAILURE_URL;
}
request.getRequestDispatcher(forwardURL).forward(request,response);
}
{BackGroundService中的responseFileUploadStatusPoll方法用于處理對文件上傳狀態(tài)的輪詢請求}:
/**
* 回應(yīng)上傳狀態(tài)查詢
* @param request
* @param response
* @throws IOException
*/
private void responseFileUploadStatusPoll(HttpServletRequest request,HttpServletResponse response) throws IOException{
response.setContentType("text/xml");
response.setCharacterEncoding("UTF-8");
response.setHeader("Cache-Control", "no-cache");
logger.debug("發(fā)送上傳狀態(tài)回應(yīng)");
response.getWriter().write(XmlUnSerializer.serializeBean(
request.getSession().getAttribute(UPLOAD_STATUS)));
}
{BackGroundService中的processCancelFileUpload方法用于處理取消文件上傳的請求}:
/**
* 處理取消文件上傳
* @param request
* @param response
* @throws IOException
*/
private void processCancelFileUpload(HttpServletRequest request,HttpServletResponse response) throws IOException{
FileUploadStatus fUploadStatus=(FileUploadStatus)request.getSession().getAttribute(UPLOAD_STATUS);
fUploadStatus.setCancel(true);
request.getSession().setAttribute(UPLOAD_STATUS, fUploadStatus);
responseFileUploadStatusPoll(request,response);
}
Web客戶端代碼:

Prototype給開發(fā)者更多的自由選擇
web客戶端使用了基于Prototype的AjaxWrapper類和XMLDomForAjax類,前者實現(xiàn)了對Ajax.Request功能的封裝,而后者實現(xiàn)了對來自服務(wù)器的XML Response的反序列化(反序列化為JavaScript對象)。
為了避免在AjaxWrapper的回調(diào)方法中發(fā)生this被重寫的問題,我使用了ClassUtils類給任何類的每個方法注冊一個對類對象自身引用,詳見《解開JavaScript生命的達(dá)芬奇密碼》和《Prototype.AjaxRequest的調(diào)用堆棧重寫問題》:
{ClassUtils類代碼}:
//類工具
var ClassUtils=Class.create();
ClassUtils.prototype={
_ClassUtilsName:'ClassUtils',
initialize:function(){
},
/**
* 給類的每個方法注冊一個對類對象的自我引用
* @param reference 對類對象的引用
*/
registerFuncSelfLink:function(reference){
for (var n in reference) {
var item = reference[n];
if (item instanceof Function)
item.$ = reference;
}
}
}
{將XML反序列化為JavaScript對象的XMLDomForAjax類代碼}:
var XMLDomForAjax=Class.create();
XMLDomForAjax.prototype={
isDebug:false,
//dom節(jié)點類型常量
ELEMENT_NODE:1,
ATTRIBUTE_NODE:2,
TEXT_NODE:3,
CDATA_SECTION_NODE:4,
ENTITY_REFERENCE_NODE:5,
ENTITY_NODE:6,
PROCESSING_INSTRUCTION_NODE:7,
COMMENT_NODE:8,
DOCUMENT_NODE:9,
DOCUMENT_TYPE_NODE:10,
DOCUMENT_FRAGMENT_NODE:11,
NOTATION_NODE:12,
initialize:function(isDebug){
new ClassUtils().registerFuncSelfLink(this);
this.isDebug=isDebug;
},
/**
* 建立跨平臺的dom解析器
* @param xml xml字符串
* @return dom解析器
*/
createDomParser:function(xml){
// code for IE
if (window.ActiveXObject){
var doc=new ActiveXObject("Microsoft.XMLDOM");
doc.async="false";
doc.loadXML(xml);
}
// code for Mozilla, Firefox, Opera, etc.
else{
var parser=new DOMParser();
var doc=parser.parseFromString(xml,"text/xml");
}
return doc;
},
/**
* 反向序列化xml到j(luò)avascript Bean
* @param xml xml字符串
* @return javascript Bean
*/
deserializedBeanFromXML:function (xml){
var funcHolder=arguments.callee.$;
var doc=funcHolder.createDomParser(xml);
// documentElement總表示文檔的root
var objDomTree=doc.documentElement;
var obj=new Object();
for (var i=0; i<objDomTree.childNodes.length; i++) {
//獲得節(jié)點
var node=objDomTree.childNodes[i];
//取出其中的field元素進(jìn)行處理
if ((node.nodeType==funcHolder.ELEMENT_NODE) && (node.tagName == 'field')) {
var nodeText=funcHolder.getNodeText(node);
if (funcHolder.isDebug){
alert(node.getAttribute('name')+' type:'+node.getAttribute('type')+' text:'+nodeText);
}
var objFieldValue=null;
//如果為列表
if (node.getAttribute('type')=='java.util.List'){
if (objFieldValue && typeof(objFieldValue)=='Array'){
if (nodeText.length>0){
objFieldValue[objFieldValue.length]=nodeText;
}
}
else{
objFieldValue=new Array();
}
}
else if (node.getAttribute('type')=='long'
|| node.getAttribute('type')=='java.lang.Long'
|| node.getAttribute('type')=='int'
|| node.getAttribute('type')=='java.lang.Integer'){
objFieldValue=parseInt(nodeText);
}
else if (node.getAttribute('type')=='double'
|| node.getAttribute('type')=='float'
|| node.getAttribute('type')=='java.lang.Double'
|| node.getAttribute('type')=='java.lang.Float'){
objFieldValue=parseFloat(nodeText);
}
else if (node.getAttribute('type')=='java.lang.String'){
objFieldValue=nodeText;
}
else{
objFieldValue=nodeText;
}
//賦值給對象
obj[node.getAttribute('name')]=objFieldValue;
if (funcHolder.isDebug){
alert(eval('obj.'+node.getAttribute('name')));
}
}
else if (node.nodeType == funcHolder.TEXT_NODE){
if (funcHolder.isDebug){
//alert('TEXT_NODE');
}
}
else if (node.nodeType == funcHolder.CDATA_SECTION_NODE){
if (funcHolder.isDebug){
//alert('CDATA_SECTION_NODE');
}
}
}
return obj;
},
/**
* 獲得dom節(jié)點的text
*/
getNodeText:function (node) {
var funcHolder=arguments.callee.$;
// is this a text or CDATA node?
if (node.nodeType == funcHolder.TEXT_NODE || node.nodeType == funcHolder.CDATA_SECTION_NODE) {
return node.data;
}
var i;
var returnValue = [];
for (i = 0; i < node.childNodes.length; i++) {
//采用遞歸算法
returnValue.push(funcHolder.getNodeText(node.childNodes[i]));
}
return returnValue.join('');
}
}
{AjaxWrapper類的主要方法putRequest和callBackHandler}:
/**
* 以get的方式向server發(fā)送request
* @param url
* @param params
* @param callBackFunction 發(fā)送成功后回調(diào)的函數(shù)或者函數(shù)名
*/
putRequest:function(url,params,callBackFunction){
var funcHolder=arguments.callee.$;
var xmlHttp = new Ajax.Request(url,
{
method: 'get',
parameters: params,
requestHeaders:['my-header-encoding','utf-8'],
onFailure: function(){
alert('對不起,網(wǎng)絡(luò)通訊失敗,請重新刷新!');
},
onSuccess: function(transport){
},
onComplete: function(transport){
funcHolder.callBackHandler.apply(funcHolder,[transport,callBackFunction]);
}
});
},
/**
* 遠(yuǎn)程調(diào)用的回調(diào)處理
* @param transport xmlhttp的transport
* @param callBackFunction 回調(diào)時call的方法,可以是函數(shù)也可以是函數(shù)名
*/
callBackHandler:function(transport,callBackFunction){
var funcHolder=arguments.callee.$;
if(transport.status!=200){
alert("獲得回應(yīng)失敗,請求狀態(tài):"+transport.status);
}
else{
funcHolder.xml_source=transport.responseText;
if (funcHolder.debug_flag)
alert('call callback function');
if (typeof(callBackFunction)=='function'){
if (funcHolder.debug_flag){
alert('invoke callbackFunc');
}
callBackFunction(transport.responseText);
}
else{
if (funcHolder.debug_flag){
alert('evalFunc callbackFunc');
}
new execute().evalFunc(callBackFunction,transport.responseText);
}
if (funcHolder.debug_flag)
alert('end callback function');
}
}
{頁面中主要的JavaScript方法:refreshUploadStatus和startProcess/cancelProcess}:
//刷新上傳狀態(tài)
function refreshUploadStatus(){
var ajaxW = new AjaxWrapper(false);
ajaxW.putRequest(
'./uploadStatus.action',
'uploadStatus=',
function(responseText){
var deserialor=new XMLDomForAjax(false);
var uploadInfo=deserialor.deserializedBeanFromXML(responseText);
var progressPercent = Math.ceil(
(uploadInfo.readTotalSize) / uploadInfo.uploadTotalSize * 100);
$('progressBarText').innerHTML = ' 上傳處理進(jìn)度: '+progressPercent+'% ['+
(uploadInfo.readTotalSize)+'/'+uploadInfo.uploadTotalSize + ' bytes]'+
' 正在處理第'+uploadInfo.currentUploadFileNum+'個文件'+
' 耗時: '+(uploadInfo.processRunningTime-uploadInfo.processStartTime)+' ms';
$('progressStatusText').innerHTML=' 反饋狀態(tài): '+uploadInfo.status;
$('totalProgressBarBoxContent').style.width = parseInt(progressPercent * 3.5) + 'px';
}
);
}
//上傳處理
function startProgress(){
Element.show('progressBar');
$('progressBarText').innerHTML = ' 上傳處理進(jìn)度: 0%';
$('progressStatusText').innerHTML=' 反饋狀態(tài):';
$('uploadButton').disabled = true;
var periodicalExe=new PeriodicalExecuter(refreshUploadStatus,2);
return true;
}
//取消上傳處理
function cancelProgress(){
$('cancelUploadButton').disabled = true;
var ajaxW = new AjaxWrapper(false);
ajaxW.putRequest(
'./uploadStatus.action',
'cancelUpload=true',
//因為form的提交,這可能不會執(zhí)行
function(responseText){
var deserialor=new XMLDomForAjax(false);
var uploadInfo=deserialor.deserializedBeanFromXML(responseText);
$('progressStatusText').innerHTML=' 反饋狀態(tài): '+uploadInfo.status;
if (msgInfo.cancel=='true'){
alert('刪除成功!');
window.location.reload();
};
}
);
}
運行界面:

起始頁面

上傳進(jìn)行中…

上傳完成后的文件列表

用戶取消上傳后顯示的頁面

上傳過程中出錯(上傳文件過大)頁面
源代碼下載:
相關(guān)鏈接:
AJAX Upload progress monitor for Commons-FileUpload Example
Apache Common FileUpload組件
Prototype官方網(wǎng)站
IBM的AJAX SlideShow應(yīng)用
解開JavaScript生命的達(dá)芬奇密碼
Prototype.AjaxRequest的調(diào)用堆棧重寫問題
感謝閱讀此文
請支持cleverpig發(fā)起的

本頁頁面地址:
用戶評論列表
這個方法早想到了,就等common.FileUpload的1.2出來。
讓老外先發(fā)表,我暈哦~
GOVO,這是cleverpig的原創(chuàng),可不是老外的作品哦。
import com.bjinfotech.util.objecttk.*;
沒有,程序無法運行
貌似不錯,抽空試試!!!
有人用AS實現(xiàn)過類似的功能嗎?
to jctr:感謝你的糾錯!我已經(jīng)修改了上面的源代碼下載:直接提供war包,源代碼在WEB-INF/classes目錄中。
正好用上,趕緊試試!
其實ss有想類似的實現(xiàn):http://wiki.springside.org.cn/display/springside/AjaxUpload
不過作者的也不錯,+U.
springside提供的AjaxUpload采用DWR+MVC框架的,而我提供的是比較original的版本,出于簡化的目的單純地使用prototype+jsp,這樣不和任何框架耦合,適用于任何支持Sevlet的java web框架。
在tomcat里上傳中文文件名我最頭疼了,傳是傳的上去,但是點擊后是404錯誤。不知道AJAX Upload有么有辦法解決
tomcat重起后,第一次上傳沒有進(jìn)度條(連接失敗,500,后臺在跑),第二次上傳以后才開始出現(xiàn)進(jìn)度條,cleverpig這問題怎么解決.
瀏覽器關(guān)閉后,第一次就是不會出現(xiàn)進(jìn)度條(連接失敗,500,后臺在跑),第二次開始才出來進(jìn)入條.
還有一個問題,文件上傳時文件的索引不正確,總是從開始處理第2個文件開始,就算是上傳一個文件也是顯示正在處理第2個文件
連接失敗,500,這個問題我也遇到過,莫名其妙的有好了。
問題出現(xiàn)原因是在返回狀態(tài)給客戶端時,到session里去取UPLOAD_STATUS沒有取到,然后再調(diào)用serializeBean方法時出現(xiàn)nullpointexception。
因為在執(zhí)行
Field[] fields=beanObj.getClass().getDeclaredFields();
時beanObj為空。
建議可以在這里做個保護(hù)措施。如果為空怎么怎么樣。
不過session里的UPLOAD_STATUS為什么為空這個原因還沒有找到
取消上傳的功能對小文件來說根本沒有意義,因為上傳小文件時基本上是一閃就過,沒有機(jī)會來取消,呵呵。
是否可以把取消下載這個動作放到所有文件上傳后再讓用戶來選擇取消,如果可以的話能做到在上傳多文件時可以取消其中的任意一個上傳文件那是最好了。
觀察了一下取消下載動作本身就是在所有文件上傳完了之后才生效的,沒有細(xì)究上面的想法在實現(xiàn)上是否可行,只是覺得那樣可能會更方便一點。
謝謝 cleverpig 的原創(chuàng)。難得找到中文的參考資料了,呵呵。
根據(jù)大家的反饋,已經(jīng)完成了AJAX FileUpload的u1版,進(jìn)行了以下fix:
1。支持中文文件下載
2。增加了單個文件的刪除功能
3。增加了AJAX動畫顯示
大家可以從本文的源代碼處或者這里下載
to haidaotao同學(xué):
>>>在tomcat里上傳中文文件名我最頭疼了,傳是傳的上去,但是點擊后是404錯誤。不知道AJAX Upload有么有辦法解決
u1版已經(jīng)使用servlet處理文件下載請求(使用PlainURLEncoder類對文件名進(jìn)行了編解碼),而不是從前的直接鏈接。
to kq1983同學(xué):
關(guān)于進(jìn)度條的顯示問題,完全依靠了AJAX的調(diào)用周期長短(目前代碼中為2秒)。所以也許在2秒內(nèi)第一個文件已經(jīng)被處理完,而AJAX反饋從server回來便會顯示正在處理第2個文件。
to easyrun同學(xué):
關(guān)于處理進(jìn)度的問題請參考上一條。
>>是否可以把取消下載這個動作放到所有文件上傳后再讓用戶來選擇取消,如果可以的話能做到在上傳多文件時可以取消其中的任意一個上傳文件那是最好了。
u1版已經(jīng)增加上傳后的刪除功能。而在上傳多文件時的取消處理時,由于使用fileUpload的解析文件功能無法中斷,所以我是在解析文件后的保存文件的循環(huán)中完成的——刪除所有已經(jīng)上傳的文件、清空session中的文件上傳列表。我想這就是出現(xiàn)你所見到的“文件上傳后才刪除”的原因。
瀏覽器關(guān)閉后,第一次就是不會出現(xiàn)進(jìn)度條(連接失敗,500,后臺在跑),第二次開始才出來進(jìn)入條是因為processFileUpload方法中的request.getSession()取得session和responseFileUploadStatusPoll方法中的request.getSession()取得session不同。
to zengxianhong 同學(xué):
在瀏覽器關(guān)閉后,原來的回話將結(jié)束(在server中也將如此)。這樣在下次打開fileupload.html進(jìn)行上傳時,便會建立新的session,而在processFileUpload方法和responseFileUploadStatusPoll方法中的session都來自于同一個client的session。而且如果起初發(fā)生連接失敗(500),那證明后天可能發(fā)生錯誤(建議使用FireFox的firebug查看一下XMLHTTPResponse的內(nèi)容)。
這樣用Session存取UPLOAD_STATUS的話,因為這個key確定,所以同一時間是能有一個頁面進(jìn)行上傳,要是我想多個頁面同時執(zhí)行上傳動作,請問應(yīng)該怎么樣修改?就是js端怎么能動態(tài)的得到這個Session的key?
這個東東能不能運行啊。
先試試看
to baiyun同學(xué):這需要增加一個server端的seesionManager功能,對session進(jìn)行管理,比如firefox的瀏覽器tab會導(dǎo)致多個頁面共享同一個session,而不是IE那樣的1 session/page。
不用session用cookie存儲上傳狀態(tài)能實現(xiàn)嗎
to zhaow8810同學(xué):
>>不用session用cookie存儲上傳狀態(tài)能實現(xiàn)嗎?
cookie并不具有數(shù)據(jù)存儲的獨立性、隔離性,而session是基于瀏覽器單次會話的存儲體。所以為了將上傳數(shù)據(jù)在server與client之間共享,session是很好的媒介。
但是有些瀏覽器對session的share行為,使得單純使用session并不能達(dá)到精準(zhǔn)的數(shù)據(jù)隔離,因此在下一個版本中將增加SessionManger。
為什么我的運行時,雖然報錯也可以上傳文件成功?
那我的系統(tǒng)就沒用session保存用戶狀態(tài)啊 在集群體環(huán)境下為了多服務(wù)器更好的性能而用cookie保存用戶狀態(tài),這樣減少了session序列化給服務(wù)器的壓力。而在這種環(huán)境下能否集成您提供的ajax上傳文件功能那?
to cleverpig
第一次部署運行時,文件上傳成功,但是從服務(wù)器端得到的消息是500的錯誤。我debug了一下,發(fā)現(xiàn)第一次運行時,processFileUpload方法和responseFileUploadStatusPoll方法中取得的session不是同一個session。第二次之后就沒問題了。
to zhaow8810同學(xué):
>>那我的系統(tǒng)就沒用session保存用戶狀態(tài)啊 在集群體環(huán)境下為了多服務(wù)器更好的性能而用cookie保存用戶狀態(tài),這樣減少了session序列化給服務(wù)器的壓力。
用戶狀態(tài)保存在client也可以,但是建議你使用session cookie。使用session cookie保存用戶信息的話,可以替代server端對session的存取管理,用AJAX將處理狀態(tài)傳送到client,進(jìn)而保存到session cookie。
推薦你一個簡單的session cookie實現(xiàn)。
to zengxianhong同學(xué):
>>第一次部署運行時,文件上傳成功,但是從服務(wù)器端得到的消息是500的錯誤。我debug了一下,發(fā)現(xiàn)第一次運行時...
這個問題我測試時沒有發(fā)現(xiàn),可能和你我的運行環(huán)境差異有關(guān):web容器、servlet api version、瀏覽器版本等。
to cleverpig
我的web容器:apache-tomcat-5.5.17
servlet api version:2.4
瀏覽器版本: firefox_2.0.0.1
IE_6.0.2900
兩個瀏覽器我都測了,都有那種現(xiàn)象。
討論相當(dāng)激烈啊....感謝分享...確實不錯..
我也出現(xiàn) zengxianhong的問題!!刪除的時候也是!
我也遇到同樣的問題 刪文件時報錯
java.lang.NoClassDefFoundError: org/apache/commons/collections/FastHashMap
at org.apache.commons.beanutils.ConvertUtilsBean.<init>(ConvertUtilsBean
.java:125)
to agu
上面的錯誤是缺少commons-collections.jar,和session沒有關(guān)系。
無法下載...............
樓主或哪位能否給我發(fā)一個學(xué)習(xí)學(xué)習(xí),謝謝
ivanw@126.com
當(dāng)上傳文件為100M時,在上傳的過程中,點擊取消。實際上服務(wù)器上并沒有取消。如何才能真正取消?
to zengxianhong同學(xué):
>>第一次部署運行時,文件上傳成功,但是從服務(wù)器端得到的消息是500的錯誤。我debug了一下,發(fā)現(xiàn)第一次運行時...
問題已經(jīng)找到,在某些情況下的確出現(xiàn)ajax xmlHttpRequest和form post的session不一致的現(xiàn)象。
針對上面的問題,我發(fā)布u2版本,進(jìn)行了部分改進(jìn)和提升。
U2's new Feature:
1.使用獨立的UploadSessionManager管理uploadSession,使后者在AJAX和form之間共享。分離原處一處的“處理文件上傳”、“狀態(tài)查詢”任務(wù)到UploadProcessService和FileUploadCommandService這兩個servlet中,實現(xiàn)上傳和查詢的操作分離,提高反應(yīng)速度。
2.使用AJAX join命令,首先發(fā)送AJAX請求,然后進(jìn)行form submit,這樣可以達(dá)到session的一致;
3.增加了MyServletFileUpload類,繼承與ServletFileUpload,并在其parserRequest方法中增加了cancel upload處理;
4.增加在servlet進(jìn)行處理前的上傳文件超限的檢測能力;
5.upload和result頁面完全使用AJAX+DHTML,擺脫了對JSP的依賴。
to haiter同學(xué):
>>當(dāng)上傳文件為100M時,在上傳的過程中,點擊取消。實際上服務(wù)器上并沒有取消。如何才能真正取消?
我沒有想出實現(xiàn)“真正的取消”的辦法,因為form submit是事務(wù)性的,沒法中間取消掉。所以即使后臺已經(jīng)執(zhí)行玩取消操作了,但form仍然要提交完才行。
to cleverpig
知道是什么原因引起的ajax xmlHttpRequest和form post的session不一致的現(xiàn)象嗎?
當(dāng)上傳文件為100M時,在上傳的過程中,點擊取消。服務(wù)器上還在不斷地打印如下信息:說明沒有真正取消。如何才能真正取消?
----
[hirycajulyrosuvydyso]被添加/更新
17:06:51,515 DEBUG FileUploadStatusBeanManager:20 - 使用已建立的UploadSession[hirycajulyrosuvydyso]
to haiter同學(xué):
>>服務(wù)器上還在不斷地打印如下信息:說明沒有真正取消。如何才能真正取消?
這些信息表示uploadsession中的fileUploadStatus被更新,因為已經(jīng)執(zhí)行了取消,所以要更新fileUploadStatus中的信息。雖然終止了對form提交的request的解析處理工作,但web 容器對form提交數(shù)據(jù)的處理仍然要完成。目前沒找到合適的方法實現(xiàn)真正的取消。
to zengxianhong:
>>知道是什么原因引起的ajax xmlHttpRequest和form post的session不一致的現(xiàn)象嗎?
原因還沒有找到,但問題現(xiàn)象很容易再現(xiàn),只需servlet和AJAX、form就可以。
1。首先使用form提交上傳文件(文件足夠大需要執(zhí)行一段時間),然后發(fā)送AJAX請求到servlet,servlet將分配給兩種request不同的session。
2。首先發(fā)送AJAX請求,然后再提交form數(shù)據(jù),servlet將分配給第一次的AJAX請求一個新session,而后的form請求將重用這個session。
u2版,要在lib下加commons-collections.jar
上傳的進(jìn)度不能反應(yīng)出來,輪查時間間隔太短了,有時一部到100%
有時只有2次變化,
我本地上傳3M左右的文件,只有2-3次的變化, 建議進(jìn)度條進(jìn)度變化做細(xì)的點.
感謝你的分享
to chenlb同學(xué):
>>上傳的進(jìn)度不能反應(yīng)出來,輪查時間間隔太短了,有時一部到100%
有時只有2次變化
輪詢時間可以自定義,只要修改fileupload.html頁面中函數(shù)startProgress中:
var periodicalExe=new PeriodicalExecuter(refreshUploadStatus,5);
其中第二個參數(shù)是輪詢周期,目前默認(rèn)是5秒。
to 缺少commons-collections.jar的同學(xué)們:
這個問題將在下一個update版本中修復(fù)。
可以到這里下載本人使用的3.1版:
U3's new Feature:
1.采用單獨的form進(jìn)行file提交,并將其target設(shè)置為隱藏的IFrame,從而實現(xiàn)IFrameIO;
2.集成了先進(jìn)的ClamAV查毒引擎技術(shù),在文件上傳完成前進(jìn)行病毒掃描,及時刪除染毒文件;
3.重構(gòu)了UploadSessionManager、FileUploadStatusBeanManager、UploadSessionImpl、FileUploadStatus等類,去掉了FileUploadStatus類中的URLList屬性,增加了FileUploadStatusList類對其進(jìn)行FileUploadStatus列表進(jìn)行收納、管理。
4.將原來頁面功能合為一頁,實現(xiàn)了One Page One Application。
病毒掃描總是報 文件被感染病毒,
文件被刪除,導(dǎo)致在頁面選擇刪除的時候失敗
to xmlsply同學(xué):
這并不是bug。使用ClamAV進(jìn)行文件查毒,需要下載clamwin.
在安裝后確認(rèn)程序中的clamav.properties配置正確,便可使用查毒功能。如果沒有安裝的話,程序默認(rèn)把上傳文件作為染毒文件對待。
如果使用unix系統(tǒng)的話,請下載clamav,并安裝和配置clamav.properties。
U3版中的clamav.properties支持clamwin在window平臺下的默認(rèn)安裝。
為了增加查毒功能的可選性,更新了部分代碼。
大家可以通過設(shè)置clamav.properties來可選地開關(guān)查毒功能:
#查毒功能使能標(biāo)志
enable=true
#執(zhí)行程序路徑
executeFile=C:/Program Files/ClamWin/bin/clamscan.exe
#病毒代碼庫路徑
libPath=C:/Documents and Settings/All Users/.clamwin/db
新的U3_1版代碼如下:
U3_1 part1
U3_1 part2
我裝了U3版,挺好,謝謝分享
謝謝Cleverpig,很干凈的實現(xiàn),鉆研的精神令人敬佩呀!
為什么有時會出現(xiàn) “對不起,網(wǎng)絡(luò)通訊失敗,請重新刷新!”的警告呢
用兩臺機(jī)器測試在上傳接近100M的文件能成功,但刪除時會出現(xiàn)找不到路徑之類的問題
to ndlgyb同學(xué):
>>為什么有時會出現(xiàn) “對不起,網(wǎng)絡(luò)通訊失敗,請重新刷新!”的警告呢
后臺可能發(fā)生錯誤,請仔細(xì)查看一下日志。
>>用兩臺機(jī)器測試在上傳接近100M的文件能成功,但刪除時會出現(xiàn)找不到路徑之類的問題
兩種可能:
1。后臺可能發(fā)生錯誤,請仔細(xì)查看一下日志。
2。文件正在被別的程序訪問或者已經(jīng)被刪除,可通過瀏覽upload目錄進(jìn)行驗證。
無法下載呀,下載為html樣式,我修改.html為.jar后,為如下文件。2007_01_31_160627_uSLgPxqmcU_bak.rar,2007_01_31_160637_UqKVpbIsNN.rar解包出現(xiàn)錯誤。
請問是什么原因?謝謝
無法下載呀,下載為html樣式,我修改.html為.jar后,為如下文件。2007_01_31_160627_uSLgPxqmcU_bak.rar,2007_01_31_160637_UqKVpbIsNN.rar解包出現(xiàn)錯誤。
請問是什么原因?謝謝
無法下載呀,下載為html樣式,我修改.html為.jar后,為如下文件。2007_01_31_160627_uSLgPxqmcU_bak.rar,2007_01_31_160637_UqKVpbIsNN.rar解包出現(xiàn)錯誤。
請問是什么原因?謝謝
我測試了可以下載的,記住要首先解壓縮2007_01_31_160627_uSLgPxqmcU.rar文件,然后選擇第二個文件2007_01_31_160637_UqKVpbIsNN.rar。
ddddddddddddddddddddddddd
不知道下面這個問題算不算一個bug:
當(dāng)瀏覽一個文件,不上傳,然后點取消,這個時候居然發(fā)現(xiàn)后臺一直在操作。文件沒上傳上去,但是文件列表里居然有剛才瀏覽的文件名。
重新測試的時候居然發(fā)現(xiàn)沒有了。怪。
上傳過程中,如果取消了操作,最好是給于提示,比如正在刪除上傳的文件,刪除完了后提示刪除成功。
對于上傳文件列表中的文件,直接打開,總是報錯,不能查看。
對于上傳文件列表中的文件,直接打開,總是報錯,不能查看。
這里有個進(jìn)度條顯示的,沒用ajax,希望樓主參考,做個完美版本出來。
http://blog.csdn.net/lxzjsj/archive/2006/12/17/1446082.aspx
不錯,寫得不錯jlkjkll
請問:
我的文件放到eclipse里面的時候,他報錯:import junit.framework.TestCase;找不到
還有,我在局域網(wǎng)里其他機(jī)子上可以上傳下載,但是無法直接打開上傳的文件
這是為什么呢?
如果想在上傳后轉(zhuǎn)到一個成功頁面,那就在upload.js中refreshUploadStatus函數(shù)中if (uploadInfo.status=='upload_done'){
寫上要跳轉(zhuǎn)的方法.
如果有人有這個的flash版,請給我一份謝謝。
zhang.kris@gmail.com
u3在tomcat6.0上測試通過沒有問題,但在resin3.0上就一直在初始化的時候循環(huán),不知道是為什么,用的是jdk5.0, u1,u2都無法在tomcat和resin上跑....,不知道樓主有沒有測試過這些情況
確實做的還不錯支持一下
為什么我來是出來這些
09:29:13,718 DEBUG Digester:1262 - New match='web-app/mime-mapping'
09:29:13,718 DEBUG Digester:1273 - Fire begin() for CallMethodRule[methodName=addMimeMapping, paramCount=2, paramTypes={java.lang.String, java.lang.String}]
09:29:13,718 DEBUG Digester:2701 - Pushing params
09:29:13,718 DEBUG sax:932 - characters(
InteractionMessage 這是哪個包的類啊
好文章,現(xiàn)在正在學(xué)習(xí)這方面的.
關(guān)于對文件上傳狀態(tài)Bean的序列化/反序列化工作,用JSON來做是不是更簡單呢?這樣就不用在java和js中寫那么多解析的代碼了
cleverpig , 我如果把fileUpload_single.html文件放進(jìn)里層目錄里,上傳時提示:“對不起,網(wǎng)絡(luò)通訊失敗,請重新刷新”。請問如何解決?謝謝!
cleverpig , 我如果把fileUpload_single.html文件放進(jìn)里層目錄里,上傳時提示:“對不起,網(wǎng)絡(luò)通訊失敗,請重新刷新”。請問如何解決?謝謝!
posted on 2007-08-07 17:02 軒轅 閱讀(2492) 評論(1) 編輯 收藏 所屬分類: java
評論
# re: 掌控上傳進(jìn)度的AJAX Upload 回復(fù) 更多評論
childNodes只有注冊用戶登錄后才能發(fā)表評論。 | ||
![]() |
||
網(wǎng)站導(dǎo)航:
博客園
IT新聞
Chat2DB
C++博客
博問
管理
|
||
相關(guān)文章:
|
||