在上一篇《使用fine-uploader上传文件》中,简单介绍了使用fine-uploader进行文件上传。这个插件也支持分块上传,对于大文件,可以同时分成多个块,同时上传,加快文件上传速度。
客户端配置
var elid = "uploader";
var uploadFile = new qq.FineUploaderBasic({
maxConnections: 5, //最多一次上传数量
debug: false, // 开启调试模式
multiple: false, // 多文件上传
button: document.getElementById(elid), //上传按钮
autoUpload: true, //不自动上传则调用uploadStoredFiless方法 手动上传
// 验证上传文件
validation: {
sizeLimit: config.Maxlength * 1000 * 1000,//单位:byte,在Fine-uploader中,1M=1000000
},
// 远程请求地址(相对或者绝对地址)
request: {
endpoint: '/api/common/attachment/upload', //接口地址
inputName: "file" //file对应的key
},
retry: {
enableAuto: true,// defaults to false 自动重试
maxAutoAttempts: 120,//重试120试
autoAttemptDelay: 30//间隔30秒,相当于一小时
},
chunking: { //断点续传
enabled: true,
concurrent: { //同时上传
enabled: true
},
mandatory: false, //强制性的【如果为true,则不论大小,都用分块上传】
paramNames: {
partIndex: "qqpartindex",
partByteOffset: "qqpartbyteoffset",
chunkSize: "qqchunksize",
totalFileSize: "qqtotalfilesize",
totalParts: "qqtotalparts"
},
partSize: 1024*1024, //包大小:单位byte,1M为一个包
success: { //结束后调用的地址
endpoint: '/api/common/attachment/upload'
}
},
callbacks: {
onSubmit: function (fileid, fileName) { //选择了文件
var index1 = fileName.lastIndexOf(".");
var index2 = fileName.length;
var suffix = fileName.substring(index1 + 1, index2).toLowerCase();//后缀名
if(config.Allowed != null){//配置了允许的类型,就做白名单验证
if(!$.inArray(suffix, config.Allowed.split(","))){
top.toastr.warning("上传的附件类型受限");
return false;
}
} else {//否则做黑名单验证
if ($.inArray(suffix, limitExts) > -1) {
top.toastr.warning("上传的附件类型受限");
return false;
}
}
},
onError: function (fileid, name, reason, maybeXhrOrXdr) {
alert("上传出错:"+reason);
uploadFile.cancel(fileid);//如果上传出错,就取消上传
},
//上传完成后
onComplete: function (fileid, fileName, responseJSON) {
if (responseJSON.success) {
alert("文件上传成功")
} else {
alert("文件上传失败,请重试");
}
},
//分块上传
onUploadChunk: function (id, name, chunkData) {
},
//分块完成
onUploadChunkSuccess: function (id, chunkData, responseJSON, xhr) {
}
}
});
启用分块上传
chunking: { //分块上传
enabled: true, //启用
concurrent: { //同时上传
enabled: true
},
mandatory: false, //强制性的【如果为true,则不论大小,都用分块上传】
paramNames: { //分块上传时的参数
partIndex: "qqpartindex",
partByteOffset: "qqpartbyteoffset",
chunkSize: "qqchunksize",
totalFileSize: "qqtotalfilesize",
totalParts: "qqtotalparts"
},
partSize: 1024*1024, //包大小:单位byte,1M为一个包
success: { //分块上传结束后调用的地址
endpoint: '/api/common/attachment/upload'
}
}
其中,有几个需要注意的:
chunking.concurrent.enabled 启用同时上传
chunking.mandatory 是否不管大小,都强制分块上传
chunking.partSize 分块的大小,如果mandatory为false时,小于这个的,则不会分块上传
chunking.success.endpoint 所有分块上传后调用的地址
默认为3个分块上传,如果希望增加,可以调整maxConnections的值。
启用重试
如果考虑网络不稳定的情况,可以使用retry进行重试:
retry: {
enableAuto: true,// defaults to false 自动重试
maxAutoAttempts: 120,//重试120次
autoAttemptDelay: 30//间隔30秒,相当于一小时
}
分块事件
callbacks: {
//分块上传
onUploadChunk: function (id, name, chunkData) {
},
//分块完成
onUploadChunkSuccess: function (id, chunkData, responseJSON, xhr) {
}
}
服务器端
定义DTO接收上传的数据
public class UploadDTO
{
#region 上传的文件
/// <summary>
/// 文件
/// </summary>
public PostedFile file { get; set; }
#endregion
#region 启动分块时参数
#region 启动分块时肯定有的
/// <summary>
/// 文件唯一标识
/// </summary>
public string QQUuid { get; set; }
/// <summary>
/// 文件名称
/// </summary>
public string QQFileName { get; set; }
/// <summary>
/// 文件大小
/// </summary>
public int QQTotalFileSize { get; set; }
/// <summary>
/// 分块总数
/// </summary>
public int QQTotalParts { get; set; }
#endregion
#region 分块上传才有的
/// <summary>
/// 分块序号
/// </summary>
public int? QQPartIndex { get; set; }
/// <summary>
/// 分块偏移量
/// </summary>
public int? QQPartByteOffset { get; set; }
/// <summary>
/// 分块大小
/// </summary>
public int? QQChunkSize { get; set; }
#endregion
#endregion
/// <summary>
/// 当前上传状态
/// </summary>
public UploadType UploadType
{
get
{
if (QQTotalParts == 0) return UploadType.Normal;
else return QQPartIndex.HasValue ? UploadType.ChunkingPart : UploadType.ChunkingComplete;
}
}
}
Fine-Uploader上传有三种场景:
QQTotalParts为0时,是正常上传
QQTotalParts不为0且有QQPartIndex时,是上传的分块;
QQTotalParts不为0但没有QQPartIndex时,是分块上传完成。
UploadType
public enum UploadType
{
/// <summary>
/// 正常上传
/// </summary>
Normal,
/// <summary>
/// 分块上传过程
/// </summary>
ChunkingPart,
/// <summary>
/// 分块上传完成
/// </summary>
ChunkingComplete
}
正常上传
正常上传时,直接处理上传即可:
url = string.Format("/{0}/{1:yyyyMM}/{2}{3}", saveUrl.Trim('/'), DateTime.Now, Guid.NewGuid().ToString().Replace("-", ""), ext);
full = context.Server.MapPath(url);
if (!Directory.Exists(Path.GetDirectoryName(full)))
{
Directory.CreateDirectory(Path.GetDirectoryName(full));
}
dto.file.SaveAs(full);
上传分块
上传分块时,把上传的分块保存到临时目录。
ext = Path.GetExtension(dto.QQFileName);
url = string.Format("/{0}/{1}/{1}_{2}{3}", tempDirectory, Security.MD5Encrypt(dto.QQUuid), dto.QQPartIndex, ext);
full = context.Server.MapPath(url);
if (!Directory.Exists(Path.GetDirectoryName(full)))
{
Directory.CreateDirectory(Path.GetDirectoryName(full));
}
dto.file.SaveAs(full);
分块上传和分块完成时,fine-uploader会有一个QQUuid参数,这个同一个文件参数是相同的,使用它作为一个子目录,即可区分不同的文件上传。这里对QQUuid进行MD5计算,避免不必要的风险。分块上传时,原始的文件名,可通过QQFilename获取。
分块完成
分块完成后,将上传的分块合并为一个文件,删除各个分块。
ext = Path.GetExtension(dto.QQFileName);
url = string.Format("/{0}/{1:yyyyMM}/{2}{3}", saveUrl, DateTime.Now, Guid.NewGuid().ToString().Replace("-", ""), ext);
full = context.Server.MapPath(url);
if (!Directory.Exists(Path.GetDirectoryName(full)))
{
Directory.CreateDirectory(Path.GetDirectoryName(full));
}
#region 合并文件
var key = Security.MD5Encrypt(dto.QQUuid);
var parturl = string.Format("/{0}/{1}", tempDirectory.Trim('/'), key);
var partfull = context.Server.MapPath(parturl);
using (Stream writer = new FileStream(full, FileMode.Create))
{
for (var i = 0; i < dto.QQTotalParts; i++)
{
var fileName = string.Format("{0}_{1}{2}", key, i, ext);
var part = Path.Combine(partfull, fileName);
if (File.Exists(part))
{
using (Stream stream = new FileStream(part, FileMode.Open))
{
stream.CopyTo(writer);
}
}
else
{
return new UploadResponse() { success = false, message = "缺少分块文件:" + fileName };
}
}
}
#endregion
#region 删除临时文件
try
{
Directory.Delete(partfull, true);
}
catch { }
#endregion
完整的服务器端代码,从这里下载:
FineUploader-CShare.rar (2.57 kb)