为什么80%的码农都做不了架构师?>>>
近期工作中,需要给公司框架写一个文件上传组件(公司之前的文件上传组件实在是不忍直视)。在网络上Google了一番,最终选择了Plupload上传控件作为插件,下面详细介绍一下开发流程以及踩过的坑。
Plupload是一个相当有特点的文件上传插件,主要功能和特点有:
1.Plupload会自动侦测当前的环境,选择最合适的上传方式,并且会优先使用HTML5的方式。其支持H5,flash、silverlight以及传统的等上传方式,既不会落于潮流之后,也不会对一些低版本或者老系统不兼容的情况出现。
2.支持把大文件切割成小片进行上传,因为有些浏览器对很大的文件比如几G的一些文件无法上传。
3.支持多种后台支持,官方的API以及demo都是PHP的(这也给我在进行Java后台的开发中带来很多痛苦)。
4.支持以拖拽的方式来选取要上传的文件,支持在前端压缩图片,即在图片文件还未上传之前就对它进行压缩
5.可以直接读取原生的文件数据,这样的好处就是例如可以在图片文件还未上传之前就能把它显示在页面上预览
以上的第四点以及第五点在我的demo中并没有体现出来,因为我的功能不需要使用。想要使用的可以自己修改。
前端代码如下:
我的前端文件结构如下:
其中jQuery.tips.js为进度条的插件,看官们可以忽略,红色箭头标记的是你需要引用的文件。可以在http://www.plupload.com/或者直接百度下载。
1.在html页面中绑定一个button作为选择文件的触发器
是的,你没有看错,不需要别的东西,页面怎么布局自己写。
2.JavaScript代码:
//实例化一个上传组件对象
var uploader = new plupload.Uploader({
browse_button: 'browse' //触发文件选择对话框的按钮,为那个元素id
, url: "http://localhost:8888/EmergencyIntegrated/fileUpload.srv "
, flash_swf_url: 'Moxie.swf' //swf文件,当需要使用swf方式进行上传时需要配置该参数
, silverlight_xap_url: 'Moxie.xap' //silverlight文件,当需要使用silverlight方式进行上传时需要配置该参数
, filters: {
mime_types: [ //对上传文件的格式做出限制,title为标识,extensions为允许上传文件的后缀名(配置改这个)
{title: "Image files", extensions: "jpg,gif,png" }
, { title: "Zip files", extensions: "zip" }
, { title: "XLS files", extensions: "xls,xlsx" }
, { title: "Word files", extensions: "txt,doc" }
],
max_file_size: '400mb', //最大只能上传400mb的文件
prevent_duplicates: true //不允许选取重复文件
}
, multipart: true
, unique_names: true//为每个上传的文件生成一个唯一的文件名
});
var completeFiles = [];
//页面初始化
$.page.pageInit = function () {
//获取设置值
$.page.appSettings = {
"attachmentPath": null
};
// 初始化上传组件对象
uploader.init();
};
$.page.pageLoad = function () { };
//添加文件
uploader.bind('FilesAdded', function (uploader, files) {
var data = [];
var gridData = $.page.idM.datagrid1.getData();
var isExist = true;
for (var i = 0; i < files.length; i++) {
var file = files[i];
//当列表中没有文件的时候再添加
for (var j = 0; j < gridData.length; j++) {
if (file.id == gridData[j].fileId) {
isExist = false;
}
}
if (isExist) {
var entity = {
fileId: file.id
, fileName: file.name
, fileSize: convertByteToMb(file.size)
, uploadTime: new Date()
, uploadProgress: ''
};
data.push(entity);
}
};
//设置datagrid数据
$.page.idM.datagrid1.addRows(data);
});
//添加到队列中就上传
uploader.bind('QueueChanged', function (uploader) {
uploader.start();
});
//文件上传进度
uploader.bind('UploadProgress', function (uploader, file) {
var percent = file.percent + '%';
$("#" + file.id).animate({ width: percent }, 1000);
$(".tip").tipsy({ gravity: 's', fade: true });
});
//上传错误
uploader.bind('Error', function (uploader, err) {
switch (err.code) {
case -602:
mini.alert("不允许上传重复的文件!", '提醒');
break;
case -200:
mini.alert("网络错误!", '提醒');
break;
default:
mini.alert("服务器繁忙,请稍后重试.", '提醒');
break;
}
});
//上传完成之后回调,files为已上传的所有文件,id为文件名
uploader.bind('UploadComplete', function (uploader, files) {
if (!fw.fwObject.FWObjectHelper.hasValue(files)) {
return;
};
completeFiles = [];
for (var i = 0; i < files.length; i++) {
var completefile = {};
var file = files[i];
completefile.fileRealName = file.name; //文件的实际名称
completefile.fileSaveName = file.target_name; //文件在服务器上保存名
completefile.fileSavePath = $.page.appSettings.attachmentPath + file.target_name; //文件的的保存路径
completefile.fileId = file.id; //文件的ID(唯一且作为保存在服务器上的文件名)
completefile.fileSize = file.size; //文件尺寸(字节)
completeFiles.push(completefile);
}
});
//删除回调队列中的文件
function del(fileId) {
var id = fileId.id;
var files = uploader.files;
if (fw.fwObject.FWObjectHelper.hasValue(id)) {
//删除上传队列的文件
for (var i = 0; i < files.length; i++) {
var file = files[i];
if (file.id == id) {
uploader.removeFile(file);
}
}
//删除回调队列里的文件
for (var j = 0; j < completeFiles.length; j++) {
var completeFile = completeFiles[j];
if (completeFile.fileId == id) {
completeFiles.splice(j,1);
}
}
var gridData = $.page.idM.datagrid1.getData();
for (var z = 0; z < gridData.length; z++) {
var data = gridData[z];
if (data.fileId == id) {
$.page.idM.datagrid1.removeRow(data);
}
}
}
};
//文件大小由字节装换为MB
function convertByteToMb(byteSize) {
var size = byteSize;
var unitType = ['byte', 'KB', 'MB', 'GB'];
var index = 0;
while (size >= 1024) {
size = size / 1024;
index++;
}
//保留两位小数
var newSize = parseFloat(size);
if (isNaN(newSize)) {
return false;
}
newSize = Math.round(newSize * 100) / 100;
return newSize +' '+ unitType[index];
}
//页面渲染
function datagrid1_Renderer(e) {
var html = '';
switch (e.field) {
case 'fileId':
if (fw.fwObject.FWObjectHelper.hasValue(e.record.fileId)) {
html = '' + e.record.fileId + '';
} else {
html = '--';
};
break;
case 'fileName':
if (fw.fwObject.FWObjectHelper.hasValue(e.record.fileName)) {
html = '' + e.record.fileName + '';
} else {
html = '--';
};
break;
case 'fileSize':
if (fw.fwObject.FWObjectHelper.hasValue(e.record.fileSize)) {
html = '' + e.record.fileSize + '';
} else {
html = '--';
};
break;
case 'uploadTime':
if (fw.fwObject.FWObjectHelper.hasValue(e.record.uploadTime)) {
html = '' + e.record.uploadTime.format('yyyy-MM-dd hh:mm:ss') + '';
} else {
html = '--';
};
break;
case 'uploadProgress':
html = "
break;
case 'delFile':
html = "
break;
}
return html;
};
//日期格式化
Date.prototype.format = function (format) {
var o = {
"M+": this.getMonth() + 1,
"d+": this.getDate(),
"h+": this.getHours(),
"m+": this.getMinutes(),
"s+": this.getSeconds(),
"q+": Math.floor((this.getMonth() + 3) / 3),
"S": this.getMilliseconds()
};
if (/(y+)/.test(format)) {
format = format.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
};
for (var k in o) {
if (new RegExp("(" + k + ")").test(format)) {
format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length));
}
};
return format;
}
以上代码中的pageInit以及一些方法如公司框架内的方法,对功能并无影响。每个方法的功能都是独立的,降低耦合度。基本上拷过去配合注释看一下就可以使用。
注:如下图,在FilesAdded方法中直接uploader.start();是无效的,因为uploader对象中还没有添加进文件(异步问题)。
开始文件上传建议在QueueChanged中执行。
Java后台代码如下(使用servlet作为接受请求的组件):
最坑的就是后台了,找了很多demo都是PHP的,不过最终还是让我找到了合适的解决方案。
使用servlet:
代码如下:
package fw.m.common.servlet;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.UUID;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import com.alibaba.fastjson.JSON;
import com.mlsc.fwConfig.FWConfigHelper;
public class FileUploadServlet extends HttpServlet {
String uploadPath;
private static final long serialVersionUID = -7825355637448948579L;
private static final ResourceBundle bundle = ResourceBundle.getBundle( "config" );
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request,response);
}
@Override
protected void doOptions(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request,response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setCharacterEncoding( "UTF-8" );
//设置支持跨域
response.setHeader("Access-Control-Allow-Origin", "*");
Integer chunk = null; /* 分割块数 */
Integer chunks = null; /* 总分割数 */
String tempFileName = null; /* 临时文件名 */
String newFileName = null; /* 最后合并后的新文件名 */
BufferedOutputStream outputStream = null;
uploadPath = FWConfigHelper.getValue("attachmentPath");
File up = new File( uploadPath );
if ( !up.exists() )
{
up.mkdir();
}
if ( ServletFileUpload.isMultipartContent( request ) )
{
try {
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setSizeThreshold( 1024 );
/* factory.setRepository(new File(repositoryPath));// 设置临时目录 */
ServletFileUpload upload = new ServletFileUpload( factory );
upload.setHeaderEncoding( "UTF-8" );
/* upload.setSizeMax(5 * 1024 * 1024);// 设置附件最大大小,超过这个大小上传会不成功 */
List
for ( FileItem item : items )
{
if ( item.isFormField() ) /* 是文本域 */
{
if ( item.getFieldName().equals( "name" ) )
{
tempFileName = item.getString();
} else if ( item.getFieldName().equals( "chunk" ) )
{
chunk = Integer.parseInt( item.getString() );
} else if ( item.getFieldName().equals( "chunks" ) )
{
chunks = Integer.parseInt( item.getString() );
}
} else { /* 如果是文件类型 */
if ( tempFileName != null )
{
String chunkName = tempFileName;
if ( chunk != null )
{
chunkName = chunk + "_" + tempFileName;
}
File savedFile = new File( uploadPath, chunkName );
item.write( savedFile );
}
}
}
newFileName = UUID.randomUUID().toString().replace( "-", "" )
.concat( "." )
.concat( FilenameUtils.getExtension( tempFileName ) );
if ( chunk != null && chunk + 1 == chunks )
{
outputStream = new BufferedOutputStream(
new FileOutputStream( new File( uploadPath, newFileName ) ) );
/* 遍历文件合并 */
for ( int i = 0; i < chunks; i++ )
{
File tempFile = new File( uploadPath, i + "_" + tempFileName );
byte[] bytes = FileUtils.readFileToByteArray( tempFile );
outputStream.write( bytes );
outputStream.flush();
tempFile.delete();
}
outputStream.flush();
}
Map
m.put( "status", true );
m.put( "fileUrl", bundle.getString( "uploadPath" ) + "/"
+ newFileName );
response.getWriter().write( JSON.toJSONString( m ) );
} catch ( FileUploadException e ) {
e.printStackTrace();
Map
m.put( "status", false );
response.getWriter().write( JSON.toJSONString( m ) );
} catch ( Exception e ) {
e.printStackTrace();
Map
m.put( "status", false );
response.getWriter().write( JSON.toJSONString( m ) );
} finally {
try {
if ( outputStream != null )
outputStream.close();
} catch ( IOException e ) {
e.printStackTrace();
}
}
}
}
@Override
public void init(ServletConfig config) throws ServletException {}
}
坑点:
1.组件的默认上传方式居然是options(没听过没事,哈哈),所以你如果只用doGet()和doPost()是搞不定的,后台根本就接收不到你的请求,加个doOption()吧!
2.跨域问题,如果遇到跨域问题,则直接response文件头到浏览器中就可以解决
对插件的使用如果有疑问可以私我([email protected]),一起讨论。