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>