FLEX+Webservice 大附件上传、断点续传实现
客户端最近项目需要,面临大附件上传的功能,具体研究如下:
实现思路
大附件上传,如何流畅不占用内存,还要支持断点续传,当第一次看到这些需求的时候还是有所顾虑,传统ASP.NET中利用fileupload可以实现上传,但是webconfig中文件大小受限制,即使设置大小了也将面临超时的问题。对于上述情况,WINFORM应该能够很好的解决断点续传大文件,当应用到WEB应用中的时候就很难如此轻松了,因此富客户端思想是很好的选择,决定采用FLEX实现客户端,Webservice实现服务端。
由于ASP.NET默认支持4M大小文件上传,一次需要将需要上传的文件进行分割,客户端分块上传,服务端分块追加。
具体实现
1)、客户端实现
客户端界面设计
<s:Group width="100%" height="100%">
<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>
<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:WebService id="service" wsdl="../Service.asmx?wsdl" useProxy="false">
<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>
<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="正在加载...";
}
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 CheckFile():void
{
//调用webservice方法,传递文件名进行校验
service.CheckFile.send(file.name);
}
//webervice,校验成功
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 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 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 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());
}
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)、服务端实现
对应客户端三个方法实现,分别是校验、上传、上传完毕
校验文件,并返回值
#region 校验文件是否存在
[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 CheckFile(string FileName)
{
string FileSavePath = Server.MapPath("File/") + FileName;
if (!IsExistFile(FileSavePath))
return "文件不存在,0";
else
{
string FileSize = GetFileSize(FileSavePath).ToString();
return "文件已存在," + FileSize;
}
}
#endregion
开始写文件,没有则创建,有则追加
#region 写文件
[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 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
上传完毕
#region
[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
[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版本,还在继续修改当中。
效果图: