客户端最近项目需要,面临大附件上传的功能,具体研究如下:
实现思路
大附件上传,如何流畅不占用内存,还要支持断点续传,当第一次看到这些需求的时候还是有所顾虑,传统ASP.NET中利用fileupload可以实现上传,但是webconfig中文件大小受限制,即使设置大小了也将面临超时的问题。对于上述情况,WINFORM应该能够很好的解决断点续传大文件,当应用到WEB应用中的时候就很难如此轻松了,因此富客户端思想是很好的选择,决定采用FLEX实现客户端,Webservice实现服务端。
由于ASP.NET默认支持4M大小文件上传,一次需要将需要上传的文件进行分割,客户端分块上传,服务端分块追加。
具体实现
1)、客户端实现
客户端界面设计
< s:Button id ="btnBrower" x ="390" y ="25" label ="浏览..." width ="60" click ="btnBrower_clickHandler(event)" ></ s:Button >
< s:TextInput id ="edFile" x ="21" y ="24" width ="361" enabled ="false" />
< s:Button id ="btnUpload" x ="458" y ="25" label ="开始上传" width ="71" click ="btnUpload_clickHandler(event)" />
< mx:Canvas width ="508" height ="25" backgroundColor ="0Xf1f1f1" x ="21" y ="53" borderStyle ="solid" borderColor ="0Xbbbbbb" >
< mx:Label text ="" fontWeight ="bold" id ="tip_txt" x ="5" y ="4" />
</ mx:Canvas >
< mx:Canvas id ="totalProcess" borderStyle ="solid" x ="22" width ="507" y ="82" height ="13" borderColor ="0X124fc0" backgroundColor ="0xffffff" >
< mx:Canvas backgroundColor ="0X124fc0" backgroundAlpha ="0.5" id ="processBar_Total" width ="0" height ="23" />
</ mx:Canvas >
</ s:Group >
定义Webservice方法,对应服务端,各自有自己的返回事件
< s:operation name = " WriteFile " result = " onResult(event) " fault = " onFault(event) " >
</ s:operation >
< s:operation name = " CheckFile " result = " onCheckResult(event) " fault = " onCheckFault(event) " >
</ s:operation >
< s:operation name = " CopyFile " result = " onCopyResult(event) " fault = " onCopyFault(event) " >
</ s:operation >
</ s:WebService >
浏览需要上传的文件
protected function application1_creationCompleteHandler(event:FlexEvent): void
{
// 注册调用js函数
ExternalInterface.addCallback( " getFileInfo " ,GetFileInfo);
// 初始化文件浏览事件
file.addEventListener(Event.SELECT,onSelect); // 选择文件事件
file.addEventListener(Event.COMPLETE,onComplete); // 文件加载完毕
file.addEventListener(Event.OPEN,onOpen);
btnUpload.enabled = false ;
}
// 浏览文件
private function onSelect(evt:Event): void
{
this .tip_txt.text = "" ;
edFile.text = file.name;
// 浏览完成,开始加载
file.load();
}
// 加载文件完毕
private function onComplete(evt:Event): void
{
this .tip_txt.text = " 加载完毕 " ;
btnUpload.enabled = true ;
}
private function onOpen(evt:Event): void
{
this .tip_txt.text = " 正在加载... " ;
}
校验服务端文件是否存在,不存在则创建,存在则判断是否需要断点续传
private function CheckFile():void
{
//调用webservice方法,传递文件名进行校验
service.CheckFile.send(file.name);
}
private function onCheckResult(event:ResultEvent): void
{
// 获取返回值
var retArray:Array = new Array();
retArray = event.result.toString().split( " , " );
var retMsg:String = retArray[ 0 ].toString(); // 返回文件存在与否消息
var retNum: int = int (retArray[ 1 ].toString()); // 返回文件大小
tip_txt.text = retMsg;
// 判断是否存在文件
if (retNum != 0 )
{
// 若文件已经存在,判断文件是否已经上传完毕
if (retNum == file.data.length)
tip_txt.text = " 文件已上传完毕 " ;
else
{
tip_txt.text = " 准备断点续传 " ;
// 断点续传需要重新计算块数,剩余大小
var Leave: int = file.data.length - retNum;
// 判断剩余情况
if (Leave > blocksize)
{
// 剩余部分分块
var BlockNum2:Number = (Leave / blocksize);
BlockNum = int (BlockNum2);
BlockNumles = int (BlockNum2);
reBlock = Leave % blocksize;
tip_txt.text = " 正在处理... " ;
// 调用上传函数
uploadFile(retNum,blocksize);
}
else
{
// 直接从返回值大小开始,传递剩余部分
BlockNumles = 1 ;
uploadFile(retNum,reBlock);
}
}
}
else
{
// 若文件不存在,则创建,从0开始
var BlockNum1:Number = (file.data.length / blocksize);
BlockNum = int (BlockNum1);
BlockNumles = int (BlockNum1);
reBlock = file.data.length % blocksize;
tip_txt.text = " 正在处理... " ;
uploadFile(retNum,blocksize);
}
}
private function onCheckFault(event:FaultEvent): void
{
Alert.show(event.toString());
}
开始上传
private function uploadFile(begin: int ,end: int ): void
{
// 判断文件大小
if (file.data.length > blocksize)
{
// 读取部分文件,分块上传
fileUpload.writeBytes(file.data,begin,end);
service.WriteFile.send(file.name,fileUpload);
}
else
{
// 直接上传
service.WriteFile.send(file.name,fileUpload);
}
}
// webservice相关事件函数,上传成功
private function onResult( event :ResultEvent): void
{
// 每次上传成功返回值,作为下次传递的开始位置
var begin: int = int ( event .result.toString());
BlockNumles -= 1 ; // 递减
BlockNumadd += 1 ; // 递增
// 清空历史数据
fileUpload.clear();
// 进度条
onProgress(begin,file.data.length);
// 判断剩余块多少,进行不同情况的上传
if (BlockNumles > 0 )
{
uploadFile(begin,blocksize);
}
if (BlockNumles == 0 )
{
uploadFile(begin,file.data.length - begin);
tip_txt.text = " 上传完毕! " ;
tip_txt.text = " 开始扫描文件... " ;
service.CopyFile.send(file.name);
}
}
// 上传失败
private function onFault( event :FaultEvent): void
{
Alert.show( event .toString());
}
进度监视
private function onProgress(Loaded: int ,Total: int ): void
{
processBar_Total.width = (Loaded / Total) * 506 ;
tip_txt.text = " 已上传: " + Loaded + ' / ' + Total;
if (Loaded == Total)
tip_txt.text = " 已上传完毕 " ;
}
上传完毕处理文件,完毕之后需要对文件进行类似处理,在这里是对文件进行重命名。具体在客户端可以体现出来
private function onCopyResult( event :ResultEvent): void
{
tip_txt.text = " 扫描完成 " ;
// 文件上传结束,调用js函数
var f:String = " showButton " ;
var m:String = ExternalInterface.call(f);
trace(m);
}
private function onCopyFault( event :FaultEvent): void
{
Alert.show( event .toString());
}
2)、服务端实现
对应客户端三个方法实现,分别是校验、上传、上传完毕
校验文件,并返回值
[WebMethod]
public string CheckFile( string FileName)
{
string FileSavePath = Server.MapPath( " File/ " ) + FileName;
if ( ! IsExistFile(FileSavePath))
return " 文件不存在,0 " ;
else
{
string FileSize = GetFileSize(FileSavePath).ToString();
return " 文件已存在, " + FileSize;
}
}
#endregion
开始写文件,没有则创建,有则追加
[WebMethod]
public string WriteFile( string FileName, byte [] filestrem)
{
string FileSavePath = Server.MapPath( " File/ " ) + FileName + " .temp " ;
if ( ! IsExistFile(FileSavePath))
{
FileStream fs = new FileStream(FileSavePath, FileMode.Create);
// 获得字节数组
byte [] data = filestrem;
// 开始写入
fs.Write(data, 0 , data.Length);
// 清空缓冲区、关闭流
fs.Flush();
fs.Close();
}
else
{
// 追加文件
using (System.IO.FileStream f = new System.IO.FileStream(FileSavePath, System.IO.FileMode.Append, FileAccess.Write))
{
byte [] b = filestrem;
f.Write(b, 0 , b.Length);
}
}
return GetFileSize(FileSavePath).ToString();
}
#endregion
上传完毕
[WebMethod]
public string CopyFile( string FileName)
{
string FileSavePath = Server.MapPath( " File/ " ) + FileName + " .temp " ;
string FileConvertPath = Server.MapPath( " ConvertFile/ " ) + FileName;
// 如果目标中存在同名文件,则删除
if (IsExistFile(FileConvertPath))
{
DeleteFile(FileConvertPath);
}
// 将文件复制到指定目录
File.Copy(FileSavePath, FileConvertPath);
// 删除原始临时文件
DeleteFile(FileSavePath);
string Path = FileConvertPath;
return Path;
}
#endregion
存在问题
客户端要把文件读取完毕之后,才开始分段上传,如果文件过大,内存玩儿不转那么浏览器将会死掉。需要继续改进,请大家拍砖!!源代码全部奉上,了解flex的可以看看flex部分源码,不了解的可以直接在项目中使用,已经在.NET项目中配置完毕,可直接运行看到效果。本例子仅为beta1.0版本,还在继续修改当中。
效果图: