Web大文件断点续传,快来看看吧!
标签:php 上传
示例下载
asp.net:http://www.ncmem.com/download/up6.3/asp.net/up6.3.rar
jsp-oracle:http://www.ncmem.com/download/up6.3/jsp/Uploader6.3Oracle.rar
jsp-mysql:http://www.ncmem.com/download/up6.3/jsp/Uploader6.3MySQL.rar
jsp-sql:http://www.ncmem.com/download/up6.3/jsp/Uploader6.3SQL.rar
php-mysql:http://www.ncmem.com/download/up6.3/php/up6.3.rar
相信很多像我一样的好学的工程师对web的大文件的断点续传都有点儿兴趣,那么今天我们一起来动手实现它.
作为开始,我们先简单了解一下为什么要做断点续传:
传统方式的缺点
大文件上传往往比较耗时,如果采用传统的方式,势必速度慢,用户体验差.
大文件上传过程中,由于种种原因,时常导致上传中断,失败.用户已经成功上传的部分将需要再次重新上传.
上传大文件时,将导致页面卡顿,体验差
不能充分发挥系统的性能.主要是单线程.
断点续传将解决以上问题.
接下来, 我们了解一下在web中实现大文件上传的断点续传的原理.
基本原理
断点续传可以分为两部分:断点和续传。断点是在上传过程中,将一个将要上传的文件分成了多个部分,然后使用多个并发线程进行多个部分的上传, 当某个时间点,由于某种原因, 任务被暂停了, 此时上传暂停的位置就是断点了。此时已经成功上传的部分将会被服务器保存. 续传就是当用户再次继续上传之前未完成的文件时, 系统不会重新上传之前已经成功上传的部分,而是直接从之前暂停的部分开始上传.
断点续传的过程:
在准备上传文件之前,先将大文件分成相同大小的文件块并编号.
然后开启多个线程同时上传多个文件块到服务器.
在发送每个文件块之前,先向服务器查询,该文件块是否已经上传过
如果该文件块已经成功上传,则跳过该文件块的上传.
如果该文件块未上传或者未完全上传,则上传该文件块.
当客户端上传完所有的文件块之后,通知服务器端合并所有的文件块
在了解了基本原理之后, 我们使用使用javaweb来实现它.
提前准备:
up6---- 提供断点续传的前端技术.
jquery----javascript脚本依赖于它
Commons-fileupload----提供文件上传的后端技术
Commons-io----Commons-fileupload依赖于它
说明:以上的文件可在文章开始部分下载
实现:
导入相关文件:
文字版
<link href="js/up6.css" type="text/css" rel="Stylesheet"/>
<script type="text/javascript" src="js/jquery-1.4.min.js"></script>
<script type="text/javascript" src="js/json2.min.js" charset="utf-8"></script>
<script type="text/javascript" src="js/up6.config.js" charset="utf-8" ></script>
<script type="text/javascript" src="js/up6.app.js" charset="utf-8"></script>
<script type="text/javascript" src="js/up6.edge.js" charset="utf-8"></script>
<script type="text/javascript" src="js/up6.file.js" charset="utf-8" ></script>
<script type="text/javascript" src="js/up6.folder.js" charset="utf-8" ></script>
<script type="text/javascript" src="js/up6.js" charset="utf-8" ></script>
核JS代码:
文字版:
<script language="javascript" type="text/javascript">
var cbMgr = new HttpUploaderMgr();
cbMgr.event.md5Complete = function (obj, md5) { /*alert(md5);*/ };
cbMgr.event.fileComplete = function (obj) { /*alert(obj.pathSvr);*/ };
cbMgr.event.addFdError = function (jv) { alert("本地路径不存在:" + jv.path); };
//设置附加字段信息
cbMgr.Config.Fields["test"] = "test";
$(document).ready(function()
{
//console.log(navigator.userAgent);
cbMgr.load_to("FilePanel");
//上传指定文件
$("#btnUpF").click(function () {
var path = $("#filePath").val();
cbMgr.app.addFile({ pathLoc: path });
});
//上传指定目录
$("#btnUpFd").click(function () {
var path = $("#folderPath").val();
cbMgr.app.addFolder({ pathLoc: path });
});
});
</script>
服务端代码:
f_create.jsp
文字版
<%@ page language="java" import="up6.*" pageEncoding="UTF-8"%><%@
page contentType="text/html;charset=UTF-8"%><%@
page import="com.google.gson.Gson" %><%@
page import="up6.*" %><%@
page import="up6.model.*" %><%@
page import="up6.biz.*" %><%@
page import="org.apache.commons.lang.StringUtils" %><%@
page import="java.net.URLDecoder" %><%@
page import="java.net.URLEncoder" %><%
String id = request.getParameter("id");
String md5 = request.getParameter("md5");
String uid = request.getParameter("uid");
String lenLoc = request.getParameter("lenLoc");//数字化的文件大小。12021
String sizeLoc = request.getParameter("sizeLoc");//格式化的文件大小。10MB
String callback = request.getParameter("callback");
String pathLoc = request.getParameter("pathLoc");
pathLoc = PathTool.url_decode(pathLoc);
//参数为空
if ( StringUtils.isBlank(md5)
&& StringUtils.isBlank(uid)
&& StringUtils.isBlank(sizeLoc))
{
out.write(callback + "({\"value\":null})");
return;
}
FileInf fileSvr= new FileInf();
fileSvr.id = id;
fileSvr.uid = Integer.parseInt(uid);
fileSvr.nameLoc = PathTool.getName(pathLoc);
fileSvr.pathLoc = pathLoc;
fileSvr.lenLoc = Long.parseLong(lenLoc);
fileSvr.sizeLoc = sizeLoc;
fileSvr.deleted = false;
fileSvr.md5 = md5;
fileSvr.nameSvr = md5 + "." + PathTool.getExtention(fileSvr.nameLoc);
//所有单个文件均以md5方式存储
PathBuilderMd5 pb = new PathBuilderMd5();
fileSvr.pathSvr = pb.genFile(fileSvr.uid,fileSvr);
fileSvr.pathSvr = fileSvr.pathSvr.replace("\\","/");
DBFile db = new DBFile();
FileInf fileExist = new FileInf();
boolean exist = db.exist_file(md5,fileExist);
//数据库已存在相同文件,且有上传进度,则直接使用此信息
if(exist && fileExist.lenSvr > 1)
{
fileSvr.pathSvr = fileExist.pathSvr;
fileSvr.perSvr = fileExist.perSvr;
fileSvr.lenSvr = fileExist.lenSvr;
fileSvr.complete = fileExist.complete;
db.Add(fileSvr);
}//此文件不存在
else
{
db.Add(fileSvr);
FileBlockWriter fr = new FileBlockWriter();
fr.CreateFile(fileSvr.pathSvr);
}
Gson gson = new Gson();
String json = gson.toJson(fileSvr);
json = URLEncoder.encode(json,"UTF-8");//编码,防止中文乱码
json = json.replace("+","%20");
json = callback + "({\"value\":\"" + json + "\"})";//返回jsonp格式数据。
out.write(json);%>
f_post.jsp
文字版
<%@ page language="java" import="up6.DBFile" pageEncoding="UTF-8"%><%@
page contentType="text/html;charset=UTF-8"%><%@
page import="up6.FileBlockWriter" %><%@
page import="up6.XDebug" %><%@
page import="up6.*" %><%@
page import="org.apache.commons.fileupload.FileItem" %><%@
page import="org.apache.commons.fileupload.FileItemFactory" %><%@
page import="org.apache.commons.fileupload.FileUploadException" %><%@
page import="org.apache.commons.fileupload.disk.DiskFileItemFactory" %><%@
page import="org.apache.commons.fileupload.servlet.ServletFileUpload" %><%@
page import="org.apache.commons.lang.StringUtils" %><%@
page import="java.net.URLDecoder"%><%@
page import="java.util.Iterator"%><%@
page import="java.util.List"%><%/*
此页面负责将文件块数据写入文件中。
*/
String uid = request.getHeader("uid");//
String id = request.getHeader("id");
String md5 = request.getHeader("md5");
String lenSvr = request.getHeader("lenSvr");
String lenLoc = request.getHeader("lenLoc");
String blockOffset = request.getHeader("blockOffset");
String blockSize = request.getHeader("blockSize");
String blockIndex = request.getHeader("blockIndex");
String complete = request.getHeader("complete");
String pathSvr = request.getHeader("pathSvr");
pathSvr = PathTool.url_decode(pathSvr);
//参数为空
if( StringUtils.isBlank( uid )
|| StringUtils.isBlank( id )
|| StringUtils.isBlank( blockOffset )
|| StringUtils.isBlank(pathSvr))
{
XDebug.Output("param is null");
return;
}
// Check that we have a file upload request
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
List files = null;
try
{
files = upload.parseRequest(request);
}
catch (FileUploadException e)
{// 解析文件数据错误
out.println("read file data error:" + e.toString());
return;
}
FileItem rangeFile = null;
// 得到所有上传的文件
Iterator fileItr = files.iterator();
// 循环处理所有文件
while (fileItr.hasNext())
{
// 得到当前文件
rangeFile = (FileItem) fileItr.next();
}
//文件块验证
if(Integer.parseInt(blockSize) == rangeFile.getSize())
{
//保存文件块数据
FileBlockWriter res = new FileBlockWriter();
res.write( Long.parseLong(blockOffset),pathSvr,rangeFile);
rangeFile.delete();
out.write("ok");
}
else
{
rangeFile.delete();
out.write("block size error");
}%>
f_process.jsp
文字版
<%@ page language="java" import="up6.DBFile" pageEncoding="UTF-8"%><%@
page contentType="text/html;charset=UTF-8"%><%@
page import="org.apache.commons.lang.StringUtils" %><%
/*
更新文件进度或文件夹进度,百分比
*/
String id = request.getParameter("id");
String uid = request.getParameter("uid");
String offset = request.getParameter("offset");
String lenSvr = request.getParameter("lenSvr");
String perSvr = request.getParameter("perSvr");
String callback = request.getParameter("callback");
int ret = 0;
if ( !StringUtils.isBlank(id)
&& !StringUtils.isBlank(lenSvr)
&& !StringUtils.isBlank(perSvr))
{
DBFile db = new DBFile();
db.f_process(Integer.parseInt(uid),id,Long.parseLong(offset),Long.parseLong(lenSvr),perSvr);
ret = 1;
}
%><%=callback + "({\"value\":"+ret+"})"%>
f_complete.jsp
文字版
<%@ page language="java" import="up6.*" pageEncoding="UTF-8"%><%@
page contentType="text/html;charset=UTF-8"%><%@
page import="org.apache.commons.lang.StringUtils" %><%
/*
此页面主要用来向数据库添加一条记录。
*/
String md5 = request.getParameter("md5");
String uid = request.getParameter("uid");
String id = request.getParameter("id");
String callback = request.getParameter("callback");//jsonp
//返回值。1表示成功
int ret = 0;
if ( !StringUtils.isBlank(uid)
&& !StringUtils.isBlank(id)
&& !StringUtils.isBlank(md5))
{
DBFile db = new DBFile();
db.UploadComplete(md5);
ret = 1;
}
%><%=callback + "(" + ret + ")"%>
f_list.jsp
文字版
<%@ page language="java" import="up6.*" pageEncoding="UTF-8"%><%@
page contentType="text/html;charset=UTF-8"%><%@
page import="up6.biz.*" %><%@
page import="org.apache.commons.lang.StringUtils" %><%@
page import="java.net.URLEncoder" %><%
/*
获取所有未上传完的文件和文件夹
*/
String uid = request.getParameter("uid");
String cbk = request.getParameter("callback");//jsonp
if( uid.length() > 0 )
{
String json = DBFile.GetAllUnComplete( Integer.parseInt( uid ) );
if( !StringUtils.isBlank(json))
{
json = URLEncoder.encode(json,"utf-8");
json = json.replace("+","%20");
out.write( cbk + "({\"value\":\""+json + "\"})" );
return;
}
}
out.write(cbk + "({\"value\":null})");
%>
f_del.jsp
文字版
<%@ page language="java" import="up6.DBFile" pageEncoding="UTF-8"%><%@
page contentType="text/html;charset=UTF-8"%><%@
page import="org.apache.commons.lang.StringUtils" %><%
/*
此页面主要用来向数据库添加一条记录。
一般在 HttpUploader.js HttpUploader_MD5_Complete(obj) 中调用
更新记录:
2012-05-24 完善
2012-06-29 增加创建文件逻辑,
*/
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
String fid = request.getParameter("id");
String uid = request.getParameter("uid");
String callback = request.getParameter("callback");//jsonp
int ret = 0;
if ( !StringUtils.isBlank(fid)
&& !StringUtils.isBlank(uid))
{
DBFile db = new DBFile();
db.Delete(Integer.parseInt(uid),fid);
ret = 1;
}
%><%= callback + "(" + ret + ")" %>
END...
如果您看到了这里,那么希望您留下您对文章的任何见解看法.