1.什么是jquery-file-upload
jQuery File Upload是一个非常优秀的上传组件,主要使用了XHR作为上传方式,并且利用了相当多的现代浏览器功能,所以可以实现诸如批量上传、超大文件上传、图片预览、拖拽上传、上传进度显示、跨域上传等功能。
美中不足的是jQuery File Upload的默认UI比较复杂,集成了全部功能,让jQuery File Upload的定制变得比较繁琐。同时jquery-file-upload官网上对angualrjs集成没有相关的API,doc 使得jquery-file-upload在angualrjs中难度极为复杂,网上关于相关集成的文章少之又少,故在此介绍。
2.jquery-file-upload 与angualrJs 集成效果图
批量上传
拖拽上传
进度条
自动播放功能
3.相关实现
1.需要的js库
query-file-upload 方面
预览效果依赖blueimp 的 gallery库 相关资源如下:
为了预览效果的实现还需要load-image.all.min.js和canvas-to-blob.min.js这两个js 可以从网上下载
相关资源整合: http://pan.baidu.com/s/1slN6Jox
2.在app中引入相关js css
在app中引入相关module
3.html的编写
<div >
<h1>文件上传</h1>
<!-- The file upload form used as target for the file upload widget -->
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">注意事项</h3>
</div>
<div class="panel-body">
<ul>
<li>单个图片上传大小限制为<strong>999 KB</strong> (default file size is unlimited).</li>
<li>前选择对应格式的图片上传 (<strong>JPG, GIF, PNG</strong>)</li>
</ul>
</div>
</div>
<form id="fileupload"
method="POST"
data-ng-controller="uploadController";
enctype="multipart/form-data"
data-file-upload="options"
data-ng-class="{'fileupload-processing': processing() || loadingFiles}">
<!-- The fileupload-buttonbar contains buttons to add/delete files and start/cancel the upload -->
<div class="row fileupload-buttonbar">
<div class="col-lg-5">
<!-- The fileinput-button span is used to style the file input field as button -->
<span class="btn btn-success fileinput-button" ng-class="{disabled: disabled}">
<i class="glyphicon glyphicon-plus"></i>
<span>添加附件</span>
<input type="file" name="files[]" ng-show="options.maxNumberOfFiles==1" ng-disabled="disabled">
<input type="file" name="files[]" multiple ng-show="!options.maxNumberOfFiles" ng-disabled="disabled">
</span>
<button type="button" class="btn btn-primary start" data-ng-click="submit()">
<i class="glyphicon glyphicon-upload"></i>
<span>全部开始</span>
</button>
<button type="button" class="btn btn-danger cancel" data-ng-click="destoryAll()">
<i class="glyphicon glyphicon glyphicon-trash"></i>
<span>全部删除</span>
</button>
<!-- The global file processing state -->
<span class="fileupload-process"></span>
</div>
<!-- The global progress state -->
<div class="col-lg-4 fade" data-ng-class="{in: active()}">
<!-- The global progress bar -->
<div class="progress progress-striped active" data-file-upload-progress="progress()"><div class="progress-bar progress-bar-success" data-ng-style="{width: num + '%'}"></div></div>
<!-- The extended global progress state -->
<div class="progress-extended"> </div>
</div>
<div class="col-lg-3">
<button type="button" class="btn btn-warning" ng-click="cancelUpload()" style="float:right">
<i class="glyphicon glyphicon-ban-circle"></i>
<span>取消</span>
</button>
<button type="button" class="btn btn-info" ng-click="saveUpload()" ng-disabled="saveStatus" style="float:right;margin-right:5px">
<i class="glyphicon glyphicon-save"></i>
<span>保存</span>
</button>
</div>
</div>
<!-- The table listing the files available for upload/download -->
<table class="table table-striped files ng-cloak">
<tr data-ng-repeat="file in queue" data-ng-class="{'processing': file.$processing()}">
<td data-ng-switch data-on="!!file.thumbnailUrl">
<div class="preview" data-ng-switch-when="true">
<a data-ng-href="{{file.url}}" title="{{file.name}}" download="{{file.name}}" data-gallery>
<img data-ng-src="{{file.thumbnailUrl}}" alt="" width="80" height="80">
</a>
</div>
<div class="preview" data-ng-switch-default data-file-upload-preview="file"></div>
</td>
<td>
<p class="name" data-ng-switch data-on="!!file.url">
<span data-ng-switch-when="true" data-ng-switch data-on="!!file.thumbnailUrl">
<a data-ng-switch-when="true" data-ng-href="{{file.url}}" title="{{file.name}}" download="{{file.name}}" data-gallery>{{file.name}}</a>
<a data-ng-switch-default data-ng-href="{{file.url}}" title="{{file.name}}" download="{{file.name}}">{{file.name}}</a>
</span>
<span data-ng-switch-default>{{file.name}}</span>
</p>
<strong data-ng-show="file.error" class="error text-danger">{{file.error}}</strong>
</td>
<td>
<p class="size">{{file.size | formatFileSize}}</p>
<div class="progress progress-striped active fade" data-ng-class="{pending: 'in'}[file.$state()]" data-file-upload-progress="file.$progress()"><div class="progress-bar progress-bar-success" data-ng-style="{width: num + '%'}"></div></div>
</td>
<td style="text-align:right">
<button type="button" class="btn btn-primary start" data-ng-click="file.$submit()" data-ng-hide="!file.$submit || options.autoUpload || file.status" data-ng-disabled="file.$state() == 'pending' || file.$state() == 'rejected'">
<i class="glyphicon glyphicon-upload"></i>
<span>开始</span>
</button>
<button type="button" class="btn btn-warning cancel" data-ng-click="file.$cancel()" data-ng-hide="!file.$cancel || file.status">
<i class="glyphicon glyphicon-ban-circle"></i>
<span>取消</span>
</button>
<button type="button" class="btn btn-danger destroy" ng-click="destory(file)" data-ng-hide="!file.status">
<i class="glyphicon glyphicon-trash"></i>
<span>删除</span>
</button>
</td>
</tr>
</table>
</form>
<br>
</div>
<!-- The blueimp Gallery widget -->
<div id="blueimp-gallery" class="blueimp-gallery blueimp-gallery-controls" data-filter=":even">
<div class="slides"></div>
<h3 class="title"></h3>
<a class="prev">‹</a>
<a class="next">›</a>
<a class="close">×</a>
<a class="play-pause"></a>
<ol class="indicator"></ol>
</div>
4.controller的编写
var page=angular.module('upload.view',[]);
page.config(['$httpProvider', 'fileUploadProvider',
function ($httpProvider, fileUploadProvider) {
delete $httpProvider.defaults.headers.common['X-Requested-With'];
fileUploadProvider.defaults.redirect = window.location.href.replace(
/\/[^\/]*$/,
'/cors/result.html?%s'
);
angular.extend(fileUploadProvider.defaults, {
disableImageResize: /Android(?!.*Chrome)|Opera/.test(window.navigator.userAgent),
maxFileSize: 999000,
acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i,
maxNumberOfFiles :10
});
}
]);
page.controller('uploadController', [ '$scope', '$rootScope', '$http', 'RoleService', '$state', '$stateParams',
'$window', function($scope, $rootScope, $http, RoleService, $state, $stateParams, $window) {
var url = '/servlet/UploadServlet';
$scope.options = {
url : url,
dataType : 'json',
autoUpload : false,
acceptFileTypes : /(\.|\/)(gif|jpe?g|png)$/i,
previewCrop : true,
maxNumberOfFiles :1
};
$scope.queue=[];
$scope.fileList=[];
$scope.saveStatus=true;
$scope.$on('fileuploadadd', function(event, data) {
if($scope.options.maxNumberOfFiles==1){
$scope.queue=[];
}
});
$scope.$on('fileuploaddone', function(event, data) {
if(data.result.length==1){
var uploadSuccessName=data.result[0].name;
var uploadSuccessFileName=data.result[0].fileName;
var uploadSuccessStatus=data.result[0].status;
var uploadSuccessFileId=data.result[0].fileId;
for(var i=0;i<$scope.queue.length;i++){
if($scope.queue[i].name==uploadSuccessName){
$scope.queue[i].url="/servlet/DownLoadServlet?f="+uploadSuccessFileName;
$scope.queue[i].thumbnailUrl="/servlet/DownLoadServlet?f="+uploadSuccessFileName;
$scope.queue[i].status=uploadSuccessStatus;
$scope.queue[i].fileId=uploadSuccessFileId;
$scope.saveStatus=false;
}
}
}
for(var i=0;i<$scope.queue.length;i++){
if($scope.queue[i].status==1){
$scope.saveStatus=false;
}else{
$scope.saveStatus=true;
break;
}
}
});
$scope.destoryAll=function(){
$scope.clear($scope.queue);
}
$scope.saveUpload=function(){
$scope.fileList=[];
for(var i=0;i<$scope.queue.length;i++){
if($scope.queue[i].status){
$scope.fileList.push($scope.queue[i]);
}
}
$scope.closeThisDialog($scope.fileList);
}
$scope.cancelUpload=function(){
$scope.closeThisDialog();
}
$scope.destory=function(file){
$scope.clear(file);
}
}]);
UploadServlet
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException{
// 得到上传文件的保存目录,将上传的文件存放于WEB-INF目录下,不允许外界直接访问,保证上传文件的安全
String savePath = this.getServletContext().getRealPath("/WEB-INF/upload");
File file = new File(savePath);
// 判断上传文件的保存目录是否存在
if (!file.exists() && !file.isDirectory()) {
System.out.println(savePath + "目录不存在,需要创建");
// 创建目录
file.mkdirs();
}
List<FileMeta> files = new LinkedList<FileMeta>();
// 1. 使用Apache的FileUpload上传文件
files.addAll(MultipartRequestHandler.getInstance().uploadByApacheFileUpload(request, savePath));
// Remove some files
while (files.size() > 20) {
files.remove(0);
}
// 2.设置响应类型的JSON
response.setContentType("application/json");
// 3. 转换 List<FileMeta> 为 JSON 格式
ObjectMapper mapper = new ObjectMapper();
// 4. 发送结果到客户端
mapper.writeValue(response.getOutputStream(), files);
}
MultipartRequestHandler
public List<FileMeta> uploadByApacheFileUpload(HttpServletRequest request,String savePath) throws IOException, ServletException{
List<FileMeta> files = new LinkedList<FileMeta>();
// 1. 检查要求有多文件内容
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
FileMeta temp = null;
// 2. If yes (it has multipart "files")
if(isMultipart){
// 2.1 Apache的FileUpload类实例化
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setSizeThreshold(1024*100);//设置缓冲区的大小为100KB,如果不指定,那么缓冲区的大小默认是10KB
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setHeaderEncoding("UTF-8");
//设置上传单个文件的大小的最大值,目前是设置为1024*1024*5字节,也就是5MB
upload.setFileSizeMax(1024*1024*5);
//设置上传文件总量的最大值,最大值=同时上传的多个文件的大小的最大值的和,目前设置为100MB
upload.setSizeMax(1024*1024*100);
// 2.2 解析请求
try {
// 2.3 得到所有上传FileItem
List<FileItem> items = upload.parseRequest(request);
// 2.4 遍历每一个FileItem
for(FileItem item:items){
// 2.5 如果FileItem不是文件类型
if (item.isFormField()) {
} else {
// 2.7 创建filemeta对象
temp = new FileMeta();
temp.setContent(item.getInputStream());
temp.setFileType(item.getContentType());
temp.setSize(item.getSize()/1024+ "Kb");
saveFile(temp,item,savePath);
temp.setStatus("1");
String fileId=saveAttachment(temp); //保存到数据库
temp.setFileId(fileId);
files.add(temp);
}
}
} catch (FileUploadException e) {
e.printStackTrace();
}
}
return files;
}
private void saveFile(FileMeta temp,FileItem item,String savePath){
String filename=item.getName();
//注意:不同的浏览器提交的文件名是不一样的,有些浏览器提交上来的文件名是带有路径的,如: c:\a\b\1.txt,
//而有些只是单纯的文件名,如:1.txt
filename = filename.substring(filename.lastIndexOf("\\")+1);
temp.setName(filename);
//得到上传文件的扩展名
// String fileExtName = filename.substring(filename.lastIndexOf(".")+1);
//得到文件保存的名称 UUID
String saveFilename=makeFileName(filename);
temp.setFileName(saveFilename);
//得到文件的保存目录 hash打散
String realSavePath = makePath(saveFilename, savePath);
try {
//获取item中的上传文件的输入流
InputStream in = item.getInputStream();
//创建一个文件输出流
FileOutputStream out = new FileOutputStream(realSavePath + "\\" + saveFilename);
//创建一个缓冲区
byte buffer[] = new byte[1024];
//判断输入流中的数据是否已经读完的标识
int len = 0;
//循环将输入流读入到缓冲区当中,(len=in.read(buffer))>0就表示in里面还有数据
while((len=in.read(buffer))>0){
//使用FileOutputStream输出流将缓冲区的数据写入到指定的目录(savePath + "\\" + filename)当中
out.write(buffer, 0, len);
}
//关闭输入流 关闭输出流 删除处理文件上传时生成的临时文件
in.close();
out.flush();
out.close();
item.delete();
} catch (Exception e) {
temp.setError("上传失败,文件类型错误/超过上传大小限制");
e.printStackTrace();
}
}
private String saveAttachment(FileMeta temp){
Attachment attachment=new Attachment();
attachment.setAttachmentId(temp.getFileName());
attachment.setName(temp.getName());
attachment.setFileType(temp.getFileType());
attachment.setSize(temp.getSize());
String id=attachmentService.saveAttachment(attachment);
return id;
}
/**
* @Method: makeFileName
* @Description: 生成上传文件的文件名,文件名以:uuid+"_"+文件的原始名称
*/
private static String makeFileName(String filename) { // 2.jpg
// 为防止文件覆盖的现象发生,要为上传文件产生一个唯一的文件名
return UUID.randomUUID().toString() + "_" + filename;
}
/**
* 为防止一个目录下面出现太多文件,要使用hash算法打散存储
*/
private static String makePath(String filename, String savePath) {
// 得到文件名的hashCode的值,得到的就是filename这个字符串对象在内存中的地址
int hashcode = filename.hashCode();
int dir1 = hashcode & 0xf; // 0--15
int dir2 = (hashcode & 0xf0) >> 4; // 0-15
// 构造新的保存目录
String dir = savePath + "\\" + dir1 + "\\" + dir2; // upload\2\3 upload\3\5
// File既可以代表文件也可以代表目录
File file = new File(dir);
// 如果目录不存在
if (!file.exists()) {
// 创建目录
file.mkdirs();
}
return dir;
}
FileMeta
@JsonIgnoreProperties({"content"})
public class FileMeta {
private String fileName;
private String fileType;
private String size;
private String thumbnailUrl;
private String url;
private String name;
private String error;
private String status;
private InputStream content;
private String fileId;
public String getFileId() {
return fileId;
}
public void setFileId(String fileId) {
this.fileId = fileId;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getFileType() {
return fileType;
}
public void setFileType(String fileType) {
this.fileType = fileType;
}
public InputStream getContent() {
return content;
}
public void setContent(InputStream content) {
this.content = content;
}
public String getSize() {
return size;
}
public void setSize(String size) {
this.size = size;
}
public String getThumbnailUrl() {
return thumbnailUrl;
}
public void setThumbnailUrl(String thumbnailUrl) {
this.thumbnailUrl = thumbnailUrl;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
原理:上传成功后服务器需向客户端返回上传成功的files 方便jquery-file-upload进行解析 以此判断上传状态 进行下载 预览等后续操作
文件下载Servlet
package com.apusic.justice.wx.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 下载文件Servlet
*
* @author zhaoyang
*
*/
public class DownLoadServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 得到要下载的文件名
request.setCharacterEncoding("UTF-8");
String fileName = request.getParameter("f"); // 23239283-92489-QQ图片.jpg
// 上传的文件都是保存在/WEB-INF/upload目录下的子目录当中
String fileSaveRootPath = this.getServletContext().getRealPath("/WEB-INF/upload");
// 通过文件名找出文件的所在目录
String path = findFileSavePathByFileName(fileName, fileSaveRootPath);
// 得到要下载的文件
File file = new File(path + "\\" + fileName);
// 如果文件不存在
if (!file.exists()) {
return;
}
// 处理文件名
String realname = fileName.substring(fileName.indexOf("_") + 1);
// 设置响应头,控制浏览器下载该文件
response.setHeader("content-disposition", "attachment;filename=" + URLEncoder.encode(realname, "UTF-8"));
// 读取要下载的文件,保存到文件输入流
FileInputStream in = new FileInputStream(path + "\\" + fileName);
// 创建输出流
OutputStream out = response.getOutputStream();
// 创建缓冲区
byte buffer[] = new byte[1024];
int len = 0;
// 循环将输入流中的内容读取到缓冲区当中
while ((len = in.read(buffer)) > 0) {
// 输出缓冲区的内容到浏览器,实现文件下载
out.write(buffer, 0, len);
}
// 关闭文件输入流
in.close();
// 关闭输出流
out.close();
}
/**
* @Method: findFileSavePathByFileName
* @Description: 通过文件名和存储上传文件根目录找出要下载的文件的所在路径
*/
public String findFileSavePathByFileName(String filename, String saveRootPath) {
int hashcode = filename.hashCode();
int dir1 = hashcode & 0xf; // 0--15
int dir2 = (hashcode & 0xf0) >> 4; // 0-15
String dir = saveRootPath + "\\" + dir1 + "\\" + dir2; // upload\2\3 upload\3\5
File file = new File(dir);
if (!file.exists()) {
// 创建目录
file.mkdirs();
}
return dir;
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
web.xml
<servlet>
<servlet-name>UploadServlet</servlet-name>
<servlet-class>com.a_1.a_1.wx.util.UploadServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UploadServlet</servlet-name>
<url-pattern>/servlet/UploadServlet</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>DownLoadServlet</servlet-name>
<servlet-class>com.a_1.a_1.wx.util.DownLoadServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DownLoadServlet</servlet-name>
<url-pattern>/servlet/DownLoadServlet</url-pattern>
</servlet-mapping>