-
2019-07-02 21:26:30
webuploader
源代码设计博大精深,具有工匠精神,本文分析webuploader
源代码总体流程和一些重点难点,webuploader
采用模块机制,比较复杂,模块编程和异步加载现在几乎已经成为历史,本文忽略wiget
组件机制和flash
部分的源码分析。先看
webuploader
的总体程序结构:(function( root, factory ) { // 直接执行匿名函数,传参,root就是window,factory就是模块构造函数 var modules = {}, _require = function( deps, callback ) { //获取依赖模块,调用callback,传递依赖模块,callback调setModule( id, factory, arguments ) var args, len, i; // 如果deps不是数组,则直接返回指定module if ( typeof deps === 'string' ) { return getModule( deps ); } else { args = []; for( len = deps.length, i = 0; i < len; i++ ) { args.push( getModule( deps[ i ] ) ); } return callback.apply( null, args ); } } _define = function( id, deps, factory ) { // 调require _require( deps || [], function() { setModule( id, factory, arguments ); }); } setModule = function( id, factory, args ) { // 执行模块构造函数factory,返回对象保存到modules[] var module = { exports: factory }, returned; if ( typeof factory === 'function' ) { args.length || (args = [ _require, module.exports, module ]); returned = factory.apply( null, args ); returned !== undefined && (module.exports = returned); } modules[ id ] = module.exports; } getModule = function( id ) {} exportsTo = function( obj ) { // obj是引用所有模块集合,此函数是修改引用对象之后再返回引用对象,修改什么呢?就是把模块名中的xxx/路径转换为.xxx namespace var key, host, parts, part, last, ucFirst; // make the first character upper case. ucFirst = function( str ) { //把首字母变为大写 return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 )); }; for ( key in modules ) { // 循环处理modules[]中每一个模块,key就是模块名,以runtime/runtime为例 host = obj; // 每次循环开始时host重新引用obj if ( !modules.hasOwnProperty( key ) ) { continue; } parts = key.split('/'); //如果模块名有/xxx这样的路径,就取路径名到数组 parts=['runtime','runtime'] last = ucFirst( parts.pop() ); // 从parts取出(去掉)最后一部分,第一个字母变大写,数组变为['Runtime'],下面只循环一次,只有一个路径 while( (part = ucFirst( parts.shift() )) ) { // 如果模块名有路径,循环处理每一个部分,第一个字母大写 host[ part ] = host[ part ] || {}; // 修改属性是修改引用的obj的属性,第一次循环obj.Runtime={} host = host[ part ]; // host重新赋值,不再引用之前的对象,而是引用host[part]对象也就是obj的一个属性。第一次循环host重新引用obj.Runtime } host[ last ] = modules[ key ]; //就是设置obj.Runtime.Runtime=modules['runtime/runtime'],这样就把moudles[]中的路径名变成了obj[]中的namespace,比如在modules[]中有一个模块runtime/runtime,那么在obj[]中就建立一个Runtime.Runtime模块,也就是建立obj.Runtime.Runtime模块,第一个Runtime是namespace,没有其它意义,第二个Runtime是属性名/模块名,其值是模块对象或函数。 } return obj; //返回修改之后的模块集合,如果以为函数是修改host但却返回obj那就理解错了。 } makeExport = function( dollar ) { root.__dollar = dollar; return exportsTo( factory( root, _define, _require ) ); //在这里执行factory函数产生各个模块 } root.WebUploader = makeExport(); //把模块集合存储到全局对象window.WebUploader做为应用程序可以访问的api })( window, function( window, define, require ) { //匿名函数即为factory函数,构造各个模块保存到modules[],最终返回webuploader全局对象含所有模块。 。。。 return require('webuploader'); });
webuploader
调用方式:uploader = WebUploader.create(opts) // 调用WebUploader.create这个api创建new Uploader实例 Base.create = Uploader.create = function( opts ) { return new Uploader( opts ); };
Uploader
构造函数:function Uploader( opts ) { this.options = $.extend( true, {}, Uploader.options, opts ); this._init( this.options ); }
之后写应用代码调用
uploader
实例的方法,比如:uploader.addButton({ id: '#filePicker2', label: '继续添加' });
再比如:
stats = uploader.getStats(); //获取上传统计数据
点击按钮开始上传这个事件绑定是在应用程序
upload.js
中设置的:$upload.on('click', function () { // $upload就是上传按钮元素对象。 if (state === 'ready') { uploader.upload(); // upload()方法就是执行request('start-upload',args),就是执行start-upload命令函数 Uploader.prototype[upload] = function () { return this.request('start-upload', arguments); // start-upload命令函数 startUpload: function (file) { // 点击"开始上传"按钮执行startupload,开始上传第一步是getfile,并不是真正开始上传 if (file) { } else { // 执行这儿,获取文件,改变文件在queue里面的状态从inited->queued $.each(me.request('get-files', [Status.INITED]), function () { //request返回获取的文件,再循环处理每一个文件 this.setStatus(Status.QUEUED); // this代表循环项(获取的文件对象) setStatus: function (status, text) { this.trigger('statuschange', status, prevStatus); //处理文件/queue/stats } }); Base.nextTick(me.__tick); // 延迟执行__tick,uploader实例初始化时定义了__tick,就是执行_tick绑定uploader实例 this.__tick = Base.bindFn(this._tick, this); //bindfn返回function(){_tick.apply(this,args)},__tick就是执行_tick绑定作用域 me.owner.trigger('startUpload'); //开始上传时要进行一些相关的状态数据处理,应用代码也可以绑定这些事件进行一些状态处理 _startSend: function (block) { promise = me.request('before-send', block, function () { // 先执行before-send命令,再执行回调,before-send命令不存在,因此就是延迟一秒执行callback(传入的匿名函数) if (file.getStatus() === Status.PROGRESS) { me._doSend(block); // dosend是真正的上传代码,之前都是预处理,预处理还涉及到统计和进度数据处理,非常复杂。 } }); _tick: function () { // 异步调度执行_startsend me._startSend(val); // 做上传操作。 _doSend: function (block) { // 从startupload开始经过多次延迟异步调度才执行到这儿真正开始上传,封装层次非常多非常细致复杂 // 开始发送。 tr.appendBlob(opts.fileVal, block.blob, file.name); tr.append(data); tr.setRequestHeader(headers); tr.send(); // 底层用xhr = new XMLHttpRequest()上传文件
命令的定义:
$.each({ upload: 'start-upload', stop: 'stop-upload', 。。。 }, function( fn, command ) { Uploader.prototype[ fn ] = function() { return this.request( command, arguments ); }; });
调用命令函数的方式,比如:
promise = me.request('before-send', block, function () { //先执行before-send命令函数,再执行匿名函数(回调),并且返回promise,根据命令函数执行结果resolve返回的promise对象 some code。。。 }); promise.fail(function () {} //注册失败回调, 如果失败则执行回调
执行命令函数的
request
代码是webuploader
源代码的精华之一,非常复杂高深,下面就分析一下:request: function (apiName, args, callback) { //apiName比如是"start-upload" for (; i < len; i++) { // 相当于假定每个组件都有apiName函数,要把每个组件的apiName函数都执行一遍 widget = widgets[i]; rlt = widget.invoke(apiName, args); // invoke就是调用执行函数,这里就是执行apiName对应的函数并传递widget组件为作用域 } // 如果有callback,则用异步方式。 if (callback || dfds.length) { // callback就是调用request时传入的匿名函数 return promise[key](function () { // 这个匿名函数就是为了延迟执行下一个then(callback),相当于setTimeout()的作用 var deferred = Base.Deferred(), args = arguments; if (args.length === 1) { args = args[0]; } setTimeout(function () { deferred.resolve(args); }, 1); return deferred.promise(); })[callback ? key : 'done'](callback || Base.noop); //这个return语句超级复杂,它其实就是promise.then(fn).then(callback),由于promise是when返回的,因此是已经resolved的,会立即执行第一个then(),fn代码resolve返回的promise才执行下一个then(callback)里面的callback,也就是延迟执行callback。 //这个return语句的意思就是,在执行完apiName函数之后,再延迟执行callback,并返回promise对象,这样在调用request之后还可以写比如promise.fail(fn),继续下一个异步处理。 } else { return rlts[0]; //最简单的情况就是返回执行apiName函数的结果数据,比如get-file执行完就是返回获取到的文件对象WUfile。 } },
webuploader
的功能模块有一部分用widget
组件方式,还有一部分用new
实例方式,html5runtime
用new
实例方式实现。至于
trigger/on
那是webuploader
自己设计的一套逻辑事件机制,用于异步调度执行相关过程,因为在操作过程中,会有几十个上百个异步过程需要调度执行,需要同步,比如过程2要保证在过程1之后执行。tick
也是延迟/异步调度方法。webuploader
博大精深,本文只是一个粗浅的分析,抱着学习人家对象编程技术的态度,文中错误不妥之处欢迎指正交流,呵呵。更多相关内容 -
php + WebUploader实现图片批量上传功能
2021-01-02 19:22:32一.webuploader webuploader主要用来做文件的上传,支持批量上传和... webuploader上传原理 1. PHP+HTML表单上传文件 在讲这个之前,需要先了解一下php的文件上传方式,上传分两个部分 先通过html创建表单,在表单 -
WebUploader大文件上传支持切片上传
2021-01-19 16:06:56前言:因自己负责的项目(jetty内嵌启动的SpringMvc)中需要实现文件上传,而自己对java文件上传这一块未接触过,且对 Http 协议较模糊,故这次采用渐进的方式来学习文件上传的原理与实践。该博客重在实践。 一. ...前言:因自己负责的项目(jetty内嵌启动的SpringMvc)中需要实现文件上传,而自己对java文件上传这一块未接触过,且对 Http 协议较模糊,故这次采用渐进的方式来学习文件上传的原理与实践。该博客重在实践。
一. Http协议原理简介
HTTP是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。它于1990年提出,经过几年的使用与发展,得到不断地完善和扩展。目前在WWW中使用的是HTTP/1.0的第六版,HTTP/1.1的规范化工作正在进行之中,而且HTTP-NG(Next Generation of HTTP)的建议已经提出。
简单来说,就是一个基于应用层的通信规范:双方要进行通信,大家都要遵守一个规范,这个规范就是HTTP协议。
1.特点:
(1)支持客户/服务器模式。
(2)简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
(3)灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。
(4)无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
(5)无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。
注意:其中(4)(5)是面试中常用的面试题。虽然HTTP协议(应用层)是无连接,无状态的,但其所依赖的TCP协议(传输层)却是常连接、有状态的,而TCP协议(传输层)又依赖于IP协议(网络层)。
2.HTTP消息的结构
(1)Request 消息分为3部分,第一部分叫请求行, 第二部分叫http header消息头, 第三部分是body正文,header和body之间有个空行, 结构如下图
(2)Response消息的结构, 和Request消息的结构基本一样。 同样也分为三部分,第一部分叫request line状态行, 第二部分叫request header消息体,第三部分是body正文, header和body之间也有个空行, 结构如下图
下面是使用Fiddler捕捉请求baidu的Request消息机构和Response消息机构:
因为没有输入任何表单信息,故request的消息正文为空,大家可以找一个登录的页面试试看。
先到这里,HTTP协议的知识网上很丰富,在这里就不再熬述了。
二. 文件上传的三种实现
1. Jsp/servlet 实现文件上传
这是最常见也是最简单的方式
(1)实现文件上传的Jsp页面
(2)负责接文件的FileUploadServlet
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
// @WebServlet(name = "FileLoadServlet", urlPatterns = {"/fileload"})
public class FileLoadServlet extends HttpServlet {
private static Logger logger = Logger.getLogger(FileLoadServlet.class);
private static final long serialVersionUID = 1302377908285976972L;
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
logger.info("------------ FileLoadServlet ------------");
if (request.getContentLength() > 0) {
InputStream inputStream = null;
FileOutputStream outputStream = null;
try {
inputStream = request.getInputStream();
// 给新文件拼上时间毫秒,防止重名
long now = System.currentTimeMillis();
File file = new File("c:/", "file-" + now + ".txt");
file.createNewFile();
outputStream = new FileOutputStream(file);
byte temp[] = new byte[1024];
int size = -1;
while ((size = inputStream.read(temp)) != -1) { // 每次读取1KB,直至读完
outputStream.write(temp, 0, size);
}
logger.info("File load success.");
} catch (IOException e) {
logger.warn("File load fail.", e);
request.getRequestDispatcher("/fail.jsp").forward(request, response);
} finally {
outputStream.close();
inputStream.close();
}
}
request.getRequestDispatcher("/succ.jsp").forward(request, response);
}
}
FileUploadServlet的配置,推荐采用servlet3.0注解的方式更方便
<servlet>
<servlet-name>FileLoadServlet</servlet-name>
<servlet-class>com.juxinli.servlet.FileLoadServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>FileLoadServlet</servlet-name>
<url-pattern>/fileload</url-pattern>
</servlet-mapping>
(3)运行效果
点击"submit"
页面转向文件上传成功的页面,再去C盘看看,发现多了一个文件:file-1433417127748.txt,这个就是刚上传的文件
我们打开看看,发现和原来的文本有些不一样
结合前面讲的HTTP协议的消息结构,不难发现这些文本就是去掉"请求头"后的"Request消息体"。所以,如果要得到与上传文件一致的文本,还需要一些字符串操作,这些就留给大家了。
另外,大家可以试试一个Jsp页面上传多个文件,会有不一样的精彩哦o(∩_∩)o ,不解释。
2. 模拟Post请求/servlet 实现文件上传
刚才我们是使用Jsp页面来上传文件,假如客户端不是webapp项目呢,显然刚才的那种方式有些捉襟见衬了。
这里我们换种思路,既然页面上通过点击可以实现文件上传,为何不能通过HttpClient来模拟浏览器发送上传文件的请求呢。关于HttpClient ,大家可以自己去了解。
(1)还是这个项目,启动servlet服务
(2)模拟请求的FileLoadClient
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.multipart.FilePart;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import org.apache.commons.httpclient.methods.multipart.Part;
import org.apache.log4j.Logger;
public class FileLoadClient {
private static Logger logger = Logger.getLogger(FileLoadClient.class);
public static String fileload(String url, File file) {
String body = "{}";
if (url == null || url.equals("")) {
return "参数不合法";
}
if (!file.exists()) {
return "要上传的文件名不存在";
}
PostMethod postMethod = new PostMethod(url);
try {
// FilePart:用来上传文件的类,file即要上传的文件
FilePart fp = new FilePart("file", file);
Part[] parts = { fp };
// 对于MIME类型的请求,httpclient建议全用MulitPartRequestEntity进行包装
MultipartRequestEntity mre = new MultipartRequestEntity(parts, postMethod.getParams());
postMethod.setRequestEntity(mre);
HttpClient client = new HttpClient();
// 由于要上传的文件可能比较大 , 因此在此设置最大的连接超时时间
client.getHttpConnectionManager().getParams() .setConnectionTimeout(50000);
int status = client.executeMethod(postMethod);
if (status == HttpStatus.SC_OK) {
InputStream inputStream = postMethod.getResponseBodyAsStream();
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
StringBuffer stringBuffer = new StringBuffer();
String str = "";
while ((str = br.readLine()) != null) {
stringBuffer.append(str);
}
body = stringBuffer.toString();
} else {
body = "fail";
}
} catch (Exception e) {
logger.warn("上传文件异常", e);
} finally {
// 释放连接
postMethod.releaseConnection();
}
return body;
}
public static void main(String[] args) throws Exception {
String body = fileload("http://localhost:8080/jsp_upload-servlet/fileload", new File("C:/1111.txt"));
System.out.println(body);
}
}
(3)在Eclipse中运行FileLoadClient程序来发送请求,运行结果:
<html><head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /></head><body><h2>File upload success</h2><a href="index.jsp">return</a></body></html>
打印了:文件上传成功的succ.jsp页面
有没有发现什么,是不是和前面Jsp页面上传的结果类似?对的,还是去掉"请求头"后的"Request消息体"。
这种方式也很简单,负责接收文件的FileUploadServlet没有变,只要在客户端把文件读取到流中,然后模拟请求servlet就行了。
3.模拟Post请求/Controller(SpringMvc)实现文件上传
终于到第三种方式了,主要难点在于搭建maven+jetty+springmvc环境,接收文件的service和模拟请求的客户端 和上面相似。
(1)模拟请求的FileLoadClient未变
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.multipart.FilePart;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import org.apache.commons.httpclient.methods.multipart.Part;
import org.apache.log4j.Logger;
public class FileLoadClient {
private static Logger logger = Logger.getLogger(FileLoadClient.class);
public static String fileload(String url, File file) {
String body = "{}";
if (url == null || url.equals("")) {
return "参数不合法";
}
if (!file.exists()) {
return "要上传的文件名不存在";
}
PostMethod postMethod = new PostMethod(url);
try {
// FilePart:用来上传文件的类,file即要上传的文件
FilePart fp = new FilePart("file", file);
Part[] parts = { fp };
// 对于MIME类型的请求,httpclient建议全用MulitPartRequestEntity进行包装
MultipartRequestEntity mre = new MultipartRequestEntity(parts, postMethod.getParams());
postMethod.setRequestEntity(mre);
HttpClient client = new HttpClient();
// 由于要上传的文件可能比较大 , 因此在此设置最大的连接超时时间
client.getHttpConnectionManager().getParams() .setConnectionTimeout(50000);
int status = client.executeMethod(postMethod);
if (status == HttpStatus.SC_OK) {
InputStream inputStream = postMethod.getResponseBodyAsStream();
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
StringBuffer stringBuffer = new StringBuffer();
String str = "";
while ((str = br.readLine()) != null) {
stringBuffer.append(str);
}
body = stringBuffer.toString();
} else {
body = "fail";
}
} catch (Exception e) {
logger.warn("上传文件异常", e);
} finally {
// 释放连接
postMethod.releaseConnection();
}
return body;
}
public static void main(String[] args) throws Exception {
String body = fileload("http://localhost:8080/fileupload/upload", new File("C:/1111.txt"));
System.out.println(body);
}
}
(2)servlet换为springMvc中的Controller
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping("/fileupload")
public class FileUploadService {
private Logger logger = Logger.getLogger(FileUploadService.class);
@RequestMapping(consumes = "multipart/form-data", value = "/hello", method = RequestMethod.GET)
public void hello(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.getWriter().write("Hello, jetty server start ok.");
}
@RequestMapping(consumes = "multipart/form-data", value = "/upload", method = RequestMethod.POST)
public void uploadFile(HttpServletRequest request, HttpServletResponse response) throws IOException {
String result = "";
if (request.getContentLength() > 0) {
InputStream inputStream = null;
FileOutputStream outputStream = null;
try {
inputStream = request.getInputStream();
// 给新文件拼上时间毫秒,防止重名
long now = System.currentTimeMillis();
File file = new File("c:/", "file-" + now + ".txt");
file.createNewFile();
outputStream = new FileOutputStream(file);
byte temp[] = new byte[1024];
int size = -1;
while ((size = inputStream.read(temp)) != -1) { // 每次读取1KB,直至读完
outputStream.write(temp, 0, size);
}
logger.info("File load success.");
result = "File load success.";
} catch (IOException e) {
logger.warn("File load fail.", e);
result = "File load fail.";
} finally {
outputStream.close();
inputStream.close();
}
}
response.getWriter().write(result);
}
}
(3)启动jetty的核心代码,在Eclipse里面右键可以启动,也可以把项目打成jar报启动
import org.apache.log4j.Logger;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.webapp.WebAppContext;
public class Launcher
{
private static Logger logger = Logger.getLogger(Launcher.class);
private static final int PORT = 8080;
private static final String WEBAPP = "src/main/webapp";
private static final String CONTEXTPATH = "/";
private static final String DESCRIPTOR = "src/main/webapp/WEB-INF/web.xml";
/*
* 创建 Jetty Server,指定其端口、web目录、根目录、web路径
* @param port
* @param webApp
* @param contextPath
* @param descriptor
* @return Server
*/
public static Server createServer(int port, String webApp, String contextPath, String descriptor) {
Server server = new Server();
//设置在JVM退出时关闭Jetty的钩子
//这样就可以在整个功能测试时启动一次Jetty,然后让它在JVM退出时自动关闭
server.setStopAtShutdown(true);
ServerConnector connector = new ServerConnector(server);
connector.setPort(port);
//解决Windows下重复启动Jetty不报告端口冲突的问题
//在Windows下有个Windows + Sun的connector实现的问题,reuseAddress=true时重复启动同一个端口的Jetty不会报错
//所以必须设为false,代价是若上次退出不干净(比如有TIME_WAIT),会导致新的Jetty不能启动,但权衡之下还是应该设为False
connector.setReuseAddress(false);
server.setConnectors(new Connector[]{connector});
WebAppContext webContext = new WebAppContext(webApp, contextPath);
webContext.setDescriptor(descriptor);
// 设置webapp的位置
webContext.setResourceBase(webApp);
webContext.setClassLoader(Thread.currentThread().getContextClassLoader());
server.setHandler(webContext);
return server;
}
/**
* 启动jetty服务
*/
public void startJetty() {
final Server server = Launcher.createServer(PORT, WEBAPP, CONTEXTPATH, DESCRIPTOR);
try {
server.start();
server.join();
} catch (Exception e) {
logger.warn("启动 jetty server 失败", e);
System.exit(-1);
}
}
public static void main(String[] args) {
(new Launcher()).startJetty();
// jetty 启动后的测试url
// http://localhost:8080/fileupload/hello
}
}
springMvc的配置不贴了,大家可以下载源码下来看。
(4)运行效果
上传包含1W个文件的文件夹,正常
大型文件续传功能正常 。
文件批量上传正常
服务器中已经根据日期+GUID生成了目录
数据库中也有记录
后端代码逻辑大部分是相同的,目前能够支持MySQL,Oracle,SQL。在使用前需要配置一下数据库,可以参考我写的这篇文章:http://blog.ncmem.com/wordpress/2019/08/12/java-http%E5%A4%A7%E6%96%87%E4%BB%B6%E6%96%AD%E7%82%B9%E7%BB%AD%E4%BC%A0%E4%B8%8A%E4%BC%A0/
欢迎入群一起讨论:374992201
-
百度WebUploader之实现文件上传与下载
2021-04-02 15:59:04最近遇见一个需要上传超大大文件的需求,调研了七牛和腾讯云的切片分段上传功能,因此在此整理前端大文件上传相关功能的实现。 在某些业务中,大文件上传是一个比较重要的交互场景,如上传入库比较大的Excel表格...最近遇见一个需要上传超大大文件的需求,调研了七牛和腾讯云的切片分段上传功能,因此在此整理前端大文件上传相关功能的实现。
在某些业务中,大文件上传是一个比较重要的交互场景,如上传入库比较大的Excel表格数据、上传影音文件等。如果文件体积比较大,或者网络条件不好时,上传的时间会比较长(要传输更多的报文,丢包重传的概率也更大),用户不能刷新页面,只能耐心等待请求完成。
下面从文件上传方式入手,整理大文件上传的思路,并给出了相关实例代码,由于PHP内置了比较方便的文件拆分和拼接方法,因此服务端代码使用PHP进行示例编写。
本文相关示例代码位于github上,主要参考
聊聊大文件上传
大文件切割上传
文件上传的几种方式
首先我们来看看文件上传的几种方式。
普通表单上传
使用PHP来展示常规的表单上传是一个不错的选择。首先构建文件上传的表单,并指定表单的提交内容类型为enctype="multipart/form-data",表明表单需要上传二进制数据。
然后编写index.php上传文件接收代码,使用move_uploaded_file方法即可(php大法好…)
form表单上传大文件时,很容易遇见服务器超时的问题。通过xhr,前端也可以进行异步上传文件的操作,一般由两个思路。
文件编码上传
第一个思路是将文件进行编码,然后在服务端进行解码,之前写过一篇在前端实现图片压缩上传的博客,其主要实现原理就是将图片转换成base64进行传递
varimgURL = URL.createObjectURL(file);
ctx.drawImage(imgURL, 0, 0);
// 获取图片的编码,然后将图片当做是一个很长的字符串进行传递
vardata= canvas.toDataURL( "image/jpeg", 0.5);
在服务端需要做的事情也比较简单,首先解码base64,然后保存图片即可
$imgData = $_REQUEST[ 'imgData'];
$base64 = explode( ',', $imgData)[ 1];
$img = base64_decode($base64);
$url = './test.jpg';
if(file_put_contents($url, $img)) {
exit(json_encode( array(
url => $url
)));
}
base64编码的缺点在于其体积比原图片更大(因为Base64将三个字节转化成四个字节,因此编码后的文本,会比原文本大出三分之一左右),对于体积很大的文件来说,上传和解析的时间会明显增加。
更多关于base64的知识,可以参考Base64笔记。
除了进行base64编码,还可以在前端直接读取文件内容后以二进制格式上传
// 读取二进制文件
functionreadBinary(text){
vardata = newArrayBuffer(text.length);
varui8a = newUint8Array(data, 0);
for( vari = 0; i < text.length; i++){
ui8a[i] = (text.charCodeAt(i) & 0xff);
}
console.log(ui8a)
}
varreader = newFileReader;
reader. = function{
readBinary( this.result) // 读取result或直接上传
}
// 把从input里读取的文件内容,放到fileReader的result字段里
reader.readAsBinaryString(file);
formData异步上传
FormData对象主要用来组装一组用 发送请求的键/值对,可以更加灵活地发送Ajax请求。可以使用FormData来模拟表单提交。
letfiles = e.target.files // 获取input的file对象
letformData = newFormData;
formData.append( 'file', file);
axios.post(url, formData);
服务端处理方式与直接form表单请求基本相同。
iframe无刷新页面
在低版本的浏览器(如IE)上,xhr是不支持直接上传formdata的,因此只能用form来上传文件,而form提交本身会进行页面跳转,这是因为form表单的target属性导致的,其取值有
_self,默认值,在相同的窗口中打开响应页面
_blank,在新窗口打开
_parent,在父窗口打开
_top,在最顶层的窗口打开
framename,在指定名字的iframe中打开
如果需要让用户体验异步上传文件的感觉,可以通过framename指定iframe来实现。把form的target属性设置为一个看不见的iframe,那么返回的数据就会被这个iframe接受,因此只有该iframe会被刷新,至于返回结果,也可以通过解析这个iframe内的文本来获取。
functionupload{
varnow = + newDate
varid = 'frame'+ now
$( "body").append( `<iframe style="display:none;" name="${id}" id="${id}" />`);
var$form = $( "#myForm")
$form.attr({
"action": '/index.php',
"method": "post",
"enctype": "multipart/form-data",
"encoding": "multipart/form-data",
"target": id
}).submit
$( "#"+id).on( "load", function{
varcontent = $( this).contents.find( "body").text
try{
vardata = JSON.parse(content)
} catch(e){
console.log(e)
}
})
}
大文件上传
现在来看看在上面提到的几种上传方式中实现大文件上传会遇见的超时问题,
表单上传和iframe无刷新页面上传,实际上都是通过form标签进行上传文件,这种方式将整个请求完全交给浏览器处理,当上传大文件时,可能会遇见请求超时的情形
通过fromData,其实际也是在xhr中封装一组请求参数,用来模拟表单请求,无法避免大文件上传超时的问题
编码上传,我们可以比较灵活地控制上传的内容
大文件上传最主要的问题就在于:在同一个请求中,要上传大量的数据,导致整个过程会比较漫长,且失败后需要重头开始上传。试想,如果我们将这个请求拆分成多个请求,每个请求的时间就会缩短,且如果某个请求失败,只需要重新发送这一次请求即可,无需从头开始,这样是否可以解决大文件上传的问题呢?
综合上面的问题,看来大文件上传需要实现下面几个需求
支持拆分上传请求(即切片)
支持断点续传
支持显示上传进度和暂停上传
接下来让我们依次实现这些功能,看起来最主要的功能应该就是切片了。
文件切片
参考: 大文件切割上传
编码方式上传中,在前端我们只要先获取文件的二进制内容,然后对其内容进行拆分,最后将每个切片上传到服务端即可。
在Java中,文件FIle对象是Blob对象的子类,Blob对象包含一个重要的方法slice,通过这个方法,我们就可以对二进制文件进行拆分。
下面是一个拆分文件的示例,对于up6来说开发者不需要关心拆分的细节,由控件帮助实现,开发者只需要关心业务逻辑即可。
控件上传的时候会为每一个文件块数据添加相关的信息,开发者在服务端接收到数据后可以自已进行处理。
服务器接收到这些切片后,再将他们拼接起来就可以了,下面是PHP拼接切片的示例代码
对于up6来说,开发人员不需要进行拼接,up6已经提供了示例代码,已经实现了这个逻辑。
保证唯一性,控件会为每一个文件块添加信息,如块索引,块MD5,文件MD5
断点续传
up6自带续传功能,up6在服务端已经保存了文件的信息,在客户端也保存了文件的进度信息。在上传时控件会自动加载文件进度信息,开发者不需要关心这些细节。在文件块的处理逻辑中只需要根据文件块索引来识别即可。
此时上传时刷新页面或者关闭浏览器,再次上传相同文件时,之前已经上传成功的切片就不会再重新上传了。
服务端实现断点续传的逻辑基本相似,只要在getUploadSliceRecord内部调用服务端的查询接口获取已上传切片的记录即可,因此这里不再展开。
此外断点续传还需要考虑切片过期的情况:如果调用了mkfile接口,则磁盘上的切片内容就可以清除掉了,如果客户端一直不调用mkfile的接口,放任这些切片一直保存在磁盘显然是不可靠的,一般情况下,切片上传都有一段时间的有效期,超过该有效期,就会被清除掉。基于上述原因,断点续传也必须同步切片过期的实现逻辑。
续传效果
上传进度和暂停
通过xhr.upload中的progress方法可以实现监控每一个切片上传进度。
上传暂停的实现也比较简单,通过xhr.abort可以取消当前未完成上传切片的上传,实现上传暂停的效果,恢复上传就跟断点续传类似,先获取已上传的切片列表,然后重新发送未上传的切片。
由于篇幅关系,上传进度和暂停的功能这里就先不实现了。
实现效果:
小结
目前社区已经存在一些成熟的大文件上传解决方案,如七牛SDK,腾讯云SDK等,也许并不需要我们手动去实现一个简陋的大文件上传库,但是了解其原理还是十分有必要的。
本文首先整理了前端文件上传的几种方式,然后讨论了大文件上传的几种场景,以及大文件上传需要实现的几个功能
通过Blob对象的slice方法将文件拆分成切片
整理了服务端还原文件所需条件和参数,演示了PHP将切片还原成文件
通过保存已上传切片的记录来实现断点续传
还留下了一些问题,如:合并文件时避免内存溢出、切片失效策略、上传进度暂停等功能,并没有去深入或一一实现,继续学习吧
后端代码逻辑大部分是相同的,目前能够支持MySQL,Oracle,SQL。在使用前需要配置一下数据库,可以参考我写的这篇文章:http://blog.ncmem.com/wordpress/2019/08/12/java-http%E5%A4%A7%E6%96%87%E4%BB%B6%E6%96%AD%E7%82%B9%E7%BB%AD%E4%BC%A0%E4%B8%8A%E4%BC%A0/
欢迎入群一起讨论:374992201 -
webuploader 大文件上传
2019-05-06 17:50:16最近项目遇到大文件上传出现超时情况,Google了下发现百度旗下提供了webuploader(官网:http://fex.baidu.com/webuploader/ )文件上传插件很不错。采用大文件切割上传的方法。原理就是把大文件切割用多线程上传,...最近项目遇到大文件上传出现超时情况,Google了下发现百度旗下提供了webuploader(官网:http://fex.baidu.com/webuploader/ )文件上传插件很不错。采用大文件切割上传的方法。原理就是把大文件切割用多线程上传,然后后台进行文件合并,很方便。下面说实现过程。参考过网上前辈的一些经验再结合自己的实际。下面直接贴代码
1、简单的文件上传
- 前端页面
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <link rel="stylesheet" type="text/css" href="../js/webuploader.css" ></link> <script type="text/javascript" src="../js/jquery.js"></script> <script type="text/javascript" src="../js/webuploader.js"></script> </head> <body> <!-- 2.创建页面元素 --> <div id="upload"> <div id="thelist" class="uploader-list"></div> <div id="filePicker">文件上传</div> </div> <!-- 3.添加js代码 --> <script type="text/javascript"> var fileArray = []; var guid = WebUploader.Base.guid(); var uploader = WebUploader.create( { swf:"../js/Uploader.swf", server:"/file/saveUpload", pick:"#filePicker", auto:true, // 分块上传设置 // 是否分块 chunked:true, // 每块文件大小(默认5M) chunkSize:5*1024*1024, // 开启几个并非线程(默认3个) threads:3, // 在上传当前文件时,准备好下一个文件 prepareNextFile:true, formData:{"guid":guid} } ); //点击上传之前调用的方法 uploader.on("uploadStart", function (file) { var paramOb = {"guid": guid, "filedId": file.source.ruid} uploader.options.formData.guid = guid; fileArray.push(paramOb); }); //文件成功、失败处理 uploader.on('uploadSuccess', function (file) { // var successDuid = file.source.ruid; var chunks = file.blocks.length; var realFileName = file.name; var folder = file.id; $.post("/file/merge", {"guid": guid,"chunks":chunks,"realFileName":realFileName,"folder":folder}, function (data, status) { alert("文件上传成功!") }); }); uploader.on('uploadError', function (file) { $('#' + file.id).find('p.state').text('上传出错'); }); </script> </body> </html>
- 后端代码
import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Controller; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartHttpServletRequest; import java.io.*; import java.util.Map; @Slf4j @Controller @RequestMapping(value = "/file") public class BigFileController { @RequestMapping(value = "/upload", method = RequestMethod.GET) public String upload(Map<String, Object> model) { return "upload"; } @RequestMapping(value = "/uploadSimple", method = RequestMethod.GET) public String uploadSimple(Map<String, Object> model) { return "uploadSimple"; } @RequestMapping(value = "saveUpload") @ResponseBody public void upload(MultipartHttpServletRequest request) { String guid = request.getParameter("guid"); MultiValueMap<String, MultipartFile> multiFileMap = request.getMultiFileMap(); MultipartFile multipartFile = multiFileMap.getFirst("file"); try { InputStream in = multipartFile.getInputStream(); String chunk = request.getParameter("chunk")==null?"0":request.getParameter("chunk"); String path = "D:\\bigFile" + File.separator + request.getParameter("id")+File.separator+guid; File exist = new File(path); if (!exist.exists()) { exist.mkdirs(); } OutputStream out = new FileOutputStream(path + File.separator + chunk + "_" + request.getParameter("name")); byte[] bytes = new byte[1024]; int len = -1; while ((len = in.read(bytes)) != -1) { out.write(bytes, 0, len); } out.close(); in.close(); } catch (IOException e) { e.printStackTrace(); } } /** * 文件合并 * @param guid * @param realFileName * @param chunks * @param folder */ @RequestMapping(value = "merge",method = RequestMethod.POST) @ResponseBody public void merge(String guid,String realFileName,int chunks,String folder ) { String path = "D:\\bigFile"+File.separator+folder+File.separator+guid; try { FileOutputStream fileOutputStream = new FileOutputStream(path+File.separator+realFileName); byte[] buf = new byte[1024]; for(int i=0;i<chunks;i++){ File tempFile = new File(path+File.separator+i+"_"+realFileName); InputStream inputStream = new FileInputStream(tempFile); int len = 0; while((len=inputStream.read(buf))!=-1){ fileOutputStream.write(buf,0,len); } inputStream.close(); //删除临时文件 tempFile.delete(); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
- 页面效果
2、完善的文件上传
2.1. 前端页面
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <link rel="stylesheet" type="text/css" href="../js/webuploader.css" ></link> <script type="text/javascript" src="../js/jquery.js"></script> <script type="text/javascript" src="../js/webuploader.js"></script> </head> <body> <!-- 2.创建页面元素 --> <div id="uploader" class="wu-example"> <!--用来存放文件信息--> <div id="thelist" class="uploader-list"></div> <div class="btns"> <div id="filePicker">选择文件</div> <button id="ctlBtn" class="btn btn-default">开始上传</button> </div> </div> <!-- 3.添加js代码 --> <script type="text/javascript"> var fileArray = []; var guid = WebUploader.Base.guid(); var $list = $("#thelist"); var $btn = $("#ctlBtn"); var timer; var state = 'pending'; // 上传文件初始化 var uploader = WebUploader.create({ // swf文件路径 swf:"../js/Uploader.swf", // 文件接收服务端。 server:"/file/saveUpload", // 选择文件的按钮。可选。 // 内部根据当前运行是创建,可能是input元素,也可能是flash. pick: '#filePicker', // 不压缩image, 默认如果是jpeg,文件上传前会压缩一把再上传! resize: false, chunked:true, duplicate: true, // 每块文件大小(默认5M) chunkSize:5*1024*1024, // 开启几个并非线程(默认3个) threads:3, // 在上传当前文件时,准备好下一个文件 prepareNextFile:true, formData:{"guid":guid} }); // 当有文件被添加进队列的时候 uploader.on( 'fileQueued', function( file ) { $list.append( '<div id="' + file.id + '" class="item">' + '<h4 class="info">' + file.name + '</h4>' + '<p class="state">等待上传...</p>' + '</div>' ); }); //点击上传之前调用的方法 uploader.on("uploadStart", function (file) { var paramOb = {"guid": guid, "filedId": file.source.ruid} uploader.options.formData.guid = guid; fileArray.push(paramOb); }); // 文件上传过程中创建进度条实时显示。 uploader.on( 'uploadProgress', function( file, percentage ) { var $li = $( '#'+file.id ), $percent = $li.find('.progress .progress-bar'); // 避免重复创建 if ( !$percent.length ) { $percent = $('<div class="progress progress-striped active">' + '<div class="progress-bar" role="progressbar" style="width: 0%">' + '</div>' + '</div>').appendTo( $li ).find('.progress-bar'); } $li.find('p.state').text('上传中'); $percent.css( 'width', percentage * 100 + '%' ); }); //文件成功、失败处理 uploader.on('uploadSuccess', function (file) { // var successDuid = file.source.ruid; var chunks = file.blocks.length; var realFileName = file.name; var folder = file.id; clearInterval(timer); $.post("/file/merge", {"guid": guid,"chunks":chunks,"realFileName":realFileName,"folder":folder}, function (data, status) { // alert("文件上传成功!") }); $( '#'+file.id ).find('p.state').text('已上传'); }); uploader.on('uploadError', function (file) { $('#' + file.id).find('p.state').text('上传出错'); }); uploader.on( 'uploadComplete', function( file ) { $( '#'+file.id ).find('.progress').fadeOut(); }); $btn.on('click', function () { if (state === 'uploading') { uploader.stop(); } else { uploader.upload(); timer = setInterval(function () { var useTime = parseInt($("#useTime").html()); useTime = useTime + 1; $("#useTime").html(useTime); }, 1000); } }); </script> </body> </html>
2.2.后端代码
import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Controller; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartHttpServletRequest; import java.io.*; import java.util.Map; @Slf4j @Controller @RequestMapping(value = "/file") public class BigFileController { @RequestMapping(value = "/upload", method = RequestMethod.GET) public String upload(Map<String, Object> model) { return "upload"; } @RequestMapping(value = "/uploadSimple", method = RequestMethod.GET) public String uploadSimple(Map<String, Object> model) { return "uploadSimple"; } @RequestMapping(value = "saveUpload") @ResponseBody public void upload(MultipartHttpServletRequest request) { String guid = request.getParameter("guid"); MultiValueMap<String, MultipartFile> multiFileMap = request.getMultiFileMap(); MultipartFile multipartFile = multiFileMap.getFirst("file"); try { InputStream in = multipartFile.getInputStream(); String chunk = request.getParameter("chunk")==null?"0":request.getParameter("chunk"); String path = "D:\\bigFile" + File.separator + request.getParameter("id")+File.separator+guid; File exist = new File(path); if (!exist.exists()) { exist.mkdirs(); } OutputStream out = new FileOutputStream(path + File.separator + chunk + "_" + request.getParameter("name")); byte[] bytes = new byte[1024]; int len = -1; while ((len = in.read(bytes)) != -1) { out.write(bytes, 0, len); } out.close(); in.close(); } catch (IOException e) { e.printStackTrace(); } } /** * 文件合并 * @param guid * @param realFileName * @param chunks * @param folder */ @RequestMapping(value = "merge",method = RequestMethod.POST) @ResponseBody public void merge(String guid,String realFileName,int chunks,String folder ) { String path = "D:\\bigFile"+File.separator+folder+File.separator+guid; try { FileOutputStream fileOutputStream = new FileOutputStream(path+File.separator+realFileName); byte[] buf = new byte[1024]; for(int i=0;i<chunks;i++){ File tempFile = new File(path+File.separator+i+"_"+realFileName); InputStream inputStream = new FileInputStream(tempFile); int len = 0; while((len=inputStream.read(buf))!=-1){ fileOutputStream.write(buf,0,len); } inputStream.close(); //删除临时文件 tempFile.delete(); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
2.3.展示效果
注意:如果进度条不显示,很多情况下应该是样式的问题。覆盖下webuploader.css的样式即可。
webuploader.css样式代码如下.webuploader-container { position: relative; } .webuploader-element-invisible { position: absolute !important; clip: rect(1px 1px 1px 1px); /* IE6, IE7 */ clip: rect(1px,1px,1px,1px); } .webuploader-pick { position: relative; /display: inline-block;/ cursor: pointer; background: #047a0a; padding: 5px 8px; color: #fff; text-align: center; border-radius: 3px; overflow: hidden; } .webuploader-pick-hover { background: #047a0a; } .webuploader-pick-disable { opacity: 0.6; pointer-events:none; } /**/ element.style { opacity: 0; width: 100%; height: 100%; display: block; cursor: pointer; background: rgb(255, 255, 255); } picker { display: inline-block; line-height: 1.428571429; position: relative; top: 12px; } .wu-example { position: relative; padding: 45px 15px 15px; margin: 15px 0; background-color: #fafafa; box-shadow: inset 0 3px 6px rgba(0, 0, 0, .05); border-color: #e5e5e5 #eee #eee; border-style: solid; border-width: 1px 0; } .wu-example:after { content: "示例"; position: absolute; top: 15px; left: 15px; font-size: 12px; font-weight: bold; color: #bbb; text-transform: uppercase; letter-spacing: 1px; } .uploader-list { width: 100%; overflow: hidden; } .file-item { float: left; position: relative; margin: 0 20px 20px 0; padding: 4px; } .file-item .error { position: absolute; top: 4px; left: 4px; right: 4px; background: red; color: white; text-align: center; height: 20px; font-size: 14px; line-height: 23px; } .file-item .info { position: absolute; left: 4px; bottom: 4px; right: 4px; height: 20px; line-height: 20px; text-indent: 5px; background: rgba(0, 0, 0, 0.6); color: white; overflow: hidden; white-space: nowrap; text-overflow : ellipsis; font-size: 12px; z-index: 10; } .upload-state-done:after { content:"示例"; font-family: FontAwesome; font-style: normal; font-weight: normal; line-height: 1; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; font-size: 32px; position: absolute; bottom: 0; right: 4px; color: #4cae4c; z-index: 99; } .file-item .progress { position: absolute; right: 4px; bottom: 4px; height: 3px; left: 4px; height: 4px; overflow: hidden; z-index: 15; margin:0; padding: 0; border-radius: 0; background: transparent; } .file-item .progress span { display: block; overflow: hidden; width: 0; height: 100%; background: #d14 url(progress.png) repeat-x; -webit-transition: width 200ms linear; -moz-transition: width 200ms linear; -o-transition: width 200ms linear; -ms-transition: width 200ms linear; transition: width 200ms linear; -webkit-animation: progressmove 2s linear infinite; -moz-animation: progressmove 2s linear infinite; -o-animation: progressmove 2s linear infinite; -ms-animation: progressmove 2s linear infinite; animation: progressmove 2s linear infinite; -webkit-transform: translateZ(0); } @-webkit-keyframes progressmove { 0% { background-position: 0 0; } 100% { background-position: 17px 0; } } @-moz-keyframes progressmove { 0% { background-position: 0 0; } 100% { background-position: 17px 0; } } @keyframes progressmove { 0% { background-position: 0 0; } 100% { background-position: 17px 0; } } .progress { height: 20px; margin-bottom: 20px; overflow: hidden; background-color: #f5f5f5; border-radius: 4px; -webkit-box-shadow: inset 0 1px 2px rgba(0,0,0,0.1); box-shadow: inset 0 1px 2px rgba(0,0,0,0.1); } .progress.active .progress-bar { -webkit-animation: progress-bar-stripes 2s linear infinite; animation: progress-bar-stripes 2s linear infinite; } .progress-striped .progress-bar { background-image: linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent); background-size: 40px 40px; } .progress-bar { background-image: -webkit-linear-gradient(top,#428bca 0,#3071a9 100%); background-image: linear-gradient(to bottom,#428bca 0,#3071a9 100%); background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff3071a9',GradientType=0); } .progress-bar { float: left; height: 100%; font-size: 12px; line-height: 20px; color: #fff; text-align: center; background-color: #428bca; box-shadow: inset 0 -1px 0 rgba(0,0,0,0.15); transition: width .6s ease; } .btn-default { text-shadow: 0 1px 0 #fff; background-image: -webkit-linear-gradient(top,#fff 0,#e0e0e0 100%); background-image: linear-gradient(to bottom,#fff 0,#e0e0e0 100%); background-repeat: repeat-x; } .btn { display: inline-block; padding: 6px 12px; margin-bottom: 0; font-size: 14px; font-weight: normal; line-height: 1.428571429; text-align: center; white-space: nowrap; vertical-align: middle; cursor: pointer; border: 1px solid transparent; border-radius: 4px; user-select: none; }
-
js WebUploader 分片上传
2020-05-28 16:57:14前言:因自己负责的项目(jetty内嵌启动的SpringMvc)中需要实现文件上传,而自己对java文件上传这一块未接触过,且对 Http 协议较模糊,故这次采用渐进的方式来学习文件上传的原理与实践。该博客重在实践。 一. ... -
C#实现大文件上传功能(二)---webuploader上传
2018-10-05 11:27:18近些时候,处理项目的时候发现如果用户上传大文件的时候使用HtmlInputFile通过httppostfilebase 来实现上传,如果文件较小的话较小的话,上传没有问题(4M),较大通过修改Web.Config的配置 <httpRuntime ... -
百度WebUploader实现大文件分片上传的方法
2021-03-18 09:49:59要求操作便利,一次选择多个文件和文件夹进行上传; 支持PC端全平台操作系统,Windows,Linux,Mac 支持文件和文件夹的批量下载,断点续传。刷新页面后继续传输。关闭浏览器后保留进度信息。 支持文件夹批量上传下载... -
百度WebUploader实现大文件上传
2020-08-11 13:43:08总结一下大文件分片上传...所以我们本地在上传的时候,要将大文件进行分片,比如分成1024*1024B,即将大文件分成1M的片进行上传,服务器在接收后,再将这些片合并成原始文件,这就是分片的基本原理。断点续传要求本地要 -
webuploader+上传文件夹
2019-08-23 16:37:06在web项目中上传文件夹现在已经成为了一个主流的需求。在OA,或者企业ERP系统中都有类似的需求。上传文件夹并且保留层级结构能够对用户行成很好的引导,用户使用起来也更方便。能够提供更高级的应用支撑。 数据表... -
WebUploader大文件(视频)上传解决方案
2021-01-26 11:55:18第一点:Java代码实现文件上传 FormFile file = manform.getFile(); String newfileName =null; String newpathname =null; String fileAddre ="/numUp"; try { InputStream stream = file.getInputStream();... -
php WebUploader 分片上传
2020-05-28 15:11:47核心原理: 该项目核心就是文件分块上传。前后端要高度配合,需要双方约定好一些数据,才能完成大文件分块,我们在项目中要重点解决的以下问题。 *如何分片; *如何合成一个文件; *中断了从哪个分片开始。 ... -
WebUploader实现大文件上传下载
2020-08-20 10:07:19要求操作便利,一次选择多个文件和文件夹进行上传; 支持PC端全平台操作系统,Windows,Linux,Mac 支持文件和文件夹的批量下载,断点续传。刷新页面后继续传输。关闭浏览器后保留进度信息。 支持文件夹批量上传下载... -
WebUploader大文件上传支持切割上传
2021-01-19 17:51:54form表单属性enctype的必须是multipart/form-data 提供input type=”file”类的上传输入域 大致实现原理:当enctype的值是multipart/form-data时,浏览器会把每个表单项进行分割,分割成不同的部件,以boundary的值... -
百度WebUploader大文件上传详解及实例代码
2021-04-13 15:00:32要求操作便利,一次选择多个文件和文件夹进行上传; 支持PC端全平台操作系统,Windows,Linux,Mac 支持文件和文件夹的批量下载,断点续传。刷新页面后继续传输。关闭浏览器后保留进度信息。 支持文件夹批量上传下载... -
php+WebUploader图片批量上传
2021-04-08 11:35:33一.webuploaderwebuploader主要用来做文件的上传,支持批量上传和图片预览,图片预览是将图片生成base64数据直接在标签中使用,所以能够达到的效果是未真正上传图片可以先看到上传的效果。更多具体的介绍,可以上... -
WebUploader 大文件或视频上传
2018-02-27 11:39:58WebUploader 上传东西其实没啥好说的,原理更上传图片一样,只不过文件要大一点。废话就不多说,上代码://html <div id="uploader" class="wu-example"> <!--用来存放... -
使用webuploader组件实现大文件分片上传,断点续传
2020-12-20 12:09:21组件简介webuploader:是一个以HTML5为主, Flash为辅的文件上传组件,采用大文件分片/并发上传的方式,极大地提高了文件上传的效率,同时兼容多种浏览器版本;2. 项目背景简介本篇文章的背景,是在上一篇文章(《无... -
WebUploader上传超大文件和断点续传思路
2021-10-26 16:00:24我们平时经常做的是上传文件,上传文件夹与上传文件类似,但也有一些不同之处,这次做了上传文件夹就记录下以备后用。 这次项目的需求: 支持大文件的上传和续传,要求续传支持所有浏览器,包括ie6,ie7,ie8,ie9,... -
vue WebUploader 分片上传
2020-05-29 09:26:30项目要支持大文件上传功能,经过讨论,初步将文件上传大小控制在20G内,因此自己需要在项目中进行文件上传部分的调整和配置,自己将大小都以20G来进行限制。 PC端全平台支持,要求支持Windows,Mac,Linux 支持所有... -
Vue2.0结合webuploader实现文件分片上传
2019-07-08 21:21:00ue项目中遇到了大文件分片上传的问题,之前用过webuploader,索性就把Vue2.0与webuploader结合起来使用,封装了一个vue的上传组件,使用起来也比较舒爽。 上传就上传吧,为什么搞得那么麻烦,用分片上传? 分片... -
webuploader如何上传文件夹
2021-09-06 17:10:19前言:因自己负责的项目(jetty内嵌启动的SpringMvc)中需要实现文件上传,而自己对java文件上传这一块未接触过,且对 Http 协议较模糊,故这次采用渐进的方式来学习文件上传的原理与实践。该博客重在实践。 一. ... -
webuploader上传文件夹控件
2021-08-03 10:00:27要求操作便利,一次选择多个文件和文件夹进行上传; 支持PC端全平台操作系统,Windows,Linux,Mac 支持文件和文件夹的批量下载,断点续传。刷新页面后继续传输。关闭浏览器后保留进度信息。 支持文件夹批量上传下载... -
大文件上传vue+WebUploader
2020-09-27 16:02:09大文件上传vue+WebUploader 说说我实习前端开发的时候用的大文件上传,前端原本项目用的是element自带的el-upload文件上传,确实很方便,element把数据上传成功,失败,上传中等等的监听事件都已经封装好了,文件... -
百度 WebUploader 分块上传
2020-06-01 17:05:21前言:因自己负责的项目(jetty内嵌启动的SpringMvc)中需要实现文件上传,而自己对java文件上传这一块未接触过,且对 Http 协议较模糊,故这次采用渐进的方式来学习文件上传的原理与实践。该博客重在实践。 一. ... -
WebUploader上传超大文件和断点续传总结
2021-10-28 09:43:23要求操作便利,一次选择多个文件和文件夹进行上传; 支持PC端全平台操作系统,Windows,Linux,Mac 支持文件和文件夹的批量下载,断点续传。刷新页面后继续传输。关闭浏览器后保留进度信息。 支持文件夹批量上传下载... -
SpringMVC WebUploader 分片上传
2020-05-28 14:13:55最近遇见一个需要上传超大大文件的需求,调研了七牛和腾讯云的切片分段上传功能,因此在此整理前端大文件上传相关功能的实现。 在某些业务中,大文件上传是一个比较重要的交互场景,如上传入库比较大的Excel表格... -
webuploader上传文件夹实例
2021-08-03 16:07:38前言:因自己负责的项目(jetty内嵌启动的SpringMvc)中需要实现文件上传,而自己对java文件上传这一块未接触过,且对 Http 协议较模糊,故这次采用渐进的方式来学习文件上传的原理与实践。该博客重在实践。 一. ...