600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > 使用WebUploader实现图片分片上传

使用WebUploader实现图片分片上传

时间:2022-01-30 09:45:47

相关推荐

使用WebUploader实现图片分片上传

1、添加js和CSS文件

<link rel="stylesheet" type="text/css" href="/resources/css/WebUploader/webuploader.css" /><script type="text/javascript" src="/resources/js/jquery-1.7.min.js"></script><script type="text/javascript" src="/resources/js/WebUploader/webuploader.js"></script><script type="text/javascript" src="/resources/js/WebUploader/md5.js"></script><%String path = request.getContextPath();String basePath = request.getScheme() + "://"+ request.getServerName() + ":" + request.getServerPort()+ path + "/";%><style type="text/css">.itemDel, .itemStop, .itemUpload{margin-left: 15px;color: blue;cursor: pointer;}#theList{width: 80%;min-height: 100px;border: 1px solid red;}#theList .itemStop{display: none;}</style>

2、html代码

<div id="uploader"><ul id="theList"></ul><div id="picker">选择文件</div></div>

3、js代码

<script type="text/javascript">var userInfo = {userId:"kazaff", md5:""}; //用户会话信息var chunkSize = 5 *1024 * 1024; //分块大小var uniqueFileName = null;//文件唯一标识符var md5Mark = null;function getServer(type){ //测试用,根据不同类型的后端返回对应的请求地址return "<%=basePath%>fileUpload";}var backEndUrl = getServer("java");WebUploader.Uploader.register({"before-send-file": "beforeSendFile", "before-send": "beforeSend", "after-send-file": "afterSendFile"}, {beforeSendFile: function(file){//秒传验证var task = new $.Deferred();var start = new Date().getTime();(new WebUploader.Uploader()).md5File(file, 0, 10*1024*1024).progress(function(percentage){console.log(percentage);}).then(function(val){console.log("总耗时: "+((new Date().getTime()) - start)/1000);md5Mark = val;userInfo.md5 = val;$.ajax({type: "POST", url: backEndUrl, data: {status: "md5Check", md5: val}, cache: false, timeout: 1000 //todo 超时的话,只能认为该文件不曾上传过, dataType: "json"}).then(function(data, textStatus, jqXHR){//console.log(data);if(data.ifExist){ //若存在,这返回失败给WebUploader,表明该文件不需要上传task.reject();uploader.skipFile(file);file.path = data.path;UploadComlate(file);}else{task.resolve();//拿到上传文件的唯一名称,用于断点续传uniqueFileName = md5(''+userInfo.userId+file.name+file.type+file.lastModifiedDate+file.size);}}, function(jqXHR, textStatus, errorThrown){ //任何形式的验证失败,都触发重新上传task.resolve();//拿到上传文件的唯一名称,用于断点续传uniqueFileName = md5(''+userInfo.userId+file.name+file.type+file.lastModifiedDate+file.size);});});return $.when(task);}, beforeSend: function(block){//分片验证是否已传过,用于断点续传var task = new $.Deferred();$.ajax({type: "POST", url: backEndUrl, data: {status: "chunkCheck", name: uniqueFileName, chunkIndex: block.chunk, size: block.end - block.start}, cache: false, timeout: 1000 //todo 超时的话,只能认为该分片未上传过, dataType: "json"}).then(function(data, textStatus, jqXHR){if(data.ifExist){ //若存在,返回失败给WebUploader,表明该分块不需要上传task.reject();}else{task.resolve();}}, function(jqXHR, textStatus, errorThrown){ //任何形式的验证失败,都触发重新上传task.resolve();});return $.when(task);}, afterSendFile: function(file){var chunksTotal = 0;if((chunksTotal = Math.ceil(file.size/chunkSize)) > 1){//合并请求var task = new $.Deferred();$.ajax({type: "POST", url: backEndUrl, data: {status: "chunksMerge", name: uniqueFileName, chunks: chunksTotal, ext: file.ext, md5: md5Mark}, cache: false, dataType: "json"}).then(function(data, textStatus, jqXHR){//todo 检查响应是否正常task.resolve();file.path = data.path;UploadComlate(file);}, function(jqXHR, textStatus, errorThrown){task.reject();});return $.when(task);}else{UploadComlate(file);}}});var uploader = WebUploader.create({auto:true //允许自动上传, swf: "/js/WebUploaderUploader.swf" //引用swf文件, server: backEndUrl//后台响应服务器路径, pick:{id:"##picker" //点击触发文件上传的按钮id, multiple:false //是否可选择多个文件(默认为true)} , resize: true //不压缩文件, paste: document.body//指定监听paste事件的容器, disableGlobalDnd: true //是否禁止拖拽, thumb: { //配置生成缩略图的选项width: 200, height: 200, quality: 70, allowMagnify: true, crop: true//, type: "image/jpeg"}, compress: { //配置上传前压缩的图片的选项quality: 100 //图片压缩后比例(只允许jpeg格式), allowMagnify: false//是否允许放大(设为false,保证不失真), crop: false //是否允许裁剪, preserveHeaders: true//是否保留头部meta信息, noCompressIfLarger: true //如果压缩后图片比原图要大,是否选择原图,compressSize: 1024 //小于此值不进行压缩(单位:字节)}, accept: {title: 'Images',extensions: 'gif,jpg,jpeg,bmp,png',mimeTypes: 'image/jpg,image/jpeg,image/png,image/bmp'}, duplicate: true //支持重复上传, prepareNextFile: true//是否允许在文件传输时提前把下一个文件准备好, chunked: true //是否要分片处理, chunkSize: chunkSize//分片数量,默认为2, threads: threads //上传并发数,默认为3, formData: function(){return $.extend(true, {}, userInfo);} //文件上传请求的参数表, fileNumLimit: 10 //验证文件总数量, 超出则不允许加入队列, fileSingleSizeLimit: 1000 * 1024 * 1024//验证文件总大小是否超出限制, 超出则不允许加入队列});uploader.on("fileQueued", function(file){$("#theList").append('<li id="'+file.id+'">' +'<img /><span>'+file.name+'</span><span class="itemUpload">上传</span><span class="itemStop">暂停</span><span class="itemDel">删除</span>' +'<div class="percentage"></div>' +'</li>');var $img = $("#" + file.id).find("img");uploader.makeThumb(file, function(error, src){if(error){$img.replaceWith("<span>不能预览</span>");}$img.attr("src", src);});});$("#theList").on("click", ".itemUpload", function(){uploader.upload();//"上传"-->"暂停"$(this).hide();$(".itemStop").show();});$("#theList").on("click", ".itemStop", function(){uploader.stop(true);//"暂停"-->"上传"$(this).hide();$(".itemUpload").show();});//todo 如果要删除的文件正在上传(包括暂停),则需要发送给后端一个请求用来清除服务器端的缓存文件$("#theList").on("click", ".itemDel", function(){uploader.removeFile($(this).parent().attr("id"));//从上传文件列表中删除$(this).parent().remove();//从上传列表dom中删除});uploader.on("uploadProgress", function(file, percentage){$("#" + file.id + " .percentage").text(percentage * 100 + "%");});function UploadComlate(file){console.log(file);$("#" + file.id + " .percentage").text("上传完毕");$(".itemStop").hide();$(".itemUpload").hide();$(".itemDel").hide();}</script>

4、后台代码

(1)、bean层

package com.bean;public class FileInfo {private String md5;private int chunkIndex;private String size;private String name;private String userId;private String id;private int chunks;private int chunk;private String lastModifiedDate;private String type;private String ext;public FileInfo(){}public FileInfo(String name, String size, int chunkIndex){this.name = name;this.size = size;this.chunkIndex = chunkIndex;}public FileInfo(String userId, String id){this.userId = userId;this.id = id;}public FileInfo(String md5){this.md5 = md5;}public FileInfo(int chunks, int chunk, String userId, String id, String name, String size, String lastModifiedDate, String type){this.userId = userId;this.id = id;this.name = name;this.size = size;this.chunks = chunks;this.chunk = chunk;this.lastModifiedDate = lastModifiedDate;this.type = type;}public FileInfo(String name, int chunks, String ext, String md5){this.name = name;this.chunks = chunks;this.ext = ext;this.md5 = md5;}public String getType() {return type;}public void setType(String type) {this.type = type;}public String getLastModifiedDate() {return lastModifiedDate;}public void setLastModifiedDate(String lastModifiedDate) {this.lastModifiedDate = lastModifiedDate;}public int getChunks() {return chunks;}public void setChunks(int chunks) {this.chunks = chunks;}public int getChunk() {return chunk;}public void setChunk(int chunk) {this.chunk = chunk;}public String getUserId() {return userId;}public void setUserId(String userId) {this.userId = userId;}public String getId() {return id;}public void setId(String id) {this.id = id;}public String getMd5() {return md5;}public void setMd5(String md5) {this.md5 = md5;}public int getChunkIndex() {return chunkIndex;}public void setChunkIndex(int chunkIndex) {this.chunkIndex = chunkIndex;}public String getSize() {return size;}public void setSize(String size) {this.size = size;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getExt() {return ext;}public void setExt(String ext) {this.ext = ext;}}

(2)、Controller层

package com.controller;import com.bean.FileInfo;import com.service.webUploader;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Controller;import org.springframework.ui.ModelMap;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.multipart.MultipartFile;import javax.servlet.http.HttpServletResponse;import java.io.*;@Controller@RequestMapping("/")public class FileUploadController {private final static Logger log = LoggerFactory.getLogger(FileUploadController.class);@Autowiredprivate webUploader wu;@RequestMapping(method = RequestMethod.GET)public String printWelcome(ModelMap model) {model.addAttribute("message", "Hello world!");return "hello";}//大文件上传@RequestMapping(value = "fileUpload", method = RequestMethod.POST)@ResponseBodypublic String fileUpload(String status, FileInfo info, @RequestParam(value = "file", required = false) MultipartFile file){String uploadFolder="D:\tupian\";if(status == null){//文件上传if(file != null && !file.isEmpty()){//验证请求不会包含数据上传,所以避免NullPoint这里要检查一下file变量是否为nulltry {File target = wu.getReadySpace(info, this.uploadFolder);//为上传的文件准备好对应的位置if(target == null){return "{\"status\": 0, \"message\": \"" + wu.getErrorMsg() + "\"}";}file.transferTo(target);//保存上传文件//将MD5签名和合并后的文件path存入持久层,注意这里这个需求导致需要修改webuploader.js源码3170行//因为原始webuploader.js不支持为formData设置函数类型参数,这将导致不能在控件初始化后修改该参数if(info.getChunks() <= 0){if(!wu.saveMd52FileMap(info.getMd5(), target.getName())){log.error("文件[" + info.getMd5() + "=>" + target.getName() + "]保存关系到持久成失败,但并不影响文件上传,只会导致日后该文件可能被重复上传而已");}}return "{\"status\": 1, \"path\": \"" + target.getName() + "\"}";}catch(IOException ex){log.error("数据上传失败", ex);return "{\"status\": 0, \"message\": \"数据上传失败\"}";}}}else{if(status.equals("md5Check")){//秒传验证String path = wu.md5Check(info.getMd5());if(path == null){return "{\"ifExist\": 0}";}else{return "{\"ifExist\": 1, \"path\": \"" + path + "\"}";}}else if(status.equals("chunkCheck")){//分块验证//检查目标分片是否存在且完整if(wu.chunkCheck(this.uploadFolder + "/" + info.getName() + "/" + info.getChunkIndex(), Long.valueOf(info.getSize()))){return "{\"ifExist\": 1}";}else{return "{\"ifExist\": 0}";}}else if(status.equals("chunksMerge")){//分块合并String path = wu.chunksMerge(info.getName(), info.getExt(), info.getChunks(), info.getMd5(), this.uploadFolder);if(path == null){return "{\"status\": 0, \"message\": \"" + wu.getErrorMsg() + "\"}";}return "{\"status\": 1, \"path\": \"" + path + "\", \"message\": \"中文测试\"}";}}log.error("请求参数不完整");return "{\"status\": 0, \"message\": \"请求参数不完整\"}";}}

(3)、Service层

package com.service;import com.bean.FileInfo;import com.util.fileLock;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.context.annotation.Scope;import org.springframework.stereotype.Service;import java.io.*;import java.nio.channels.FileChannel;import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;import java.util.*;import java.util.concurrent.locks.Lock;/*** Created by kazaff on /12/2.*/@Service@Scope("prototype")public class webUploader {private final static Logger log = LoggerFactory.getLogger(webUploader.class);/*** 错误详情*/private String msg;/*** 秒传验证* 根据文件的MD5签名判断该文件是否已经存在** @param key 文件的md5签名* @return 若存在则返回该文件的路径,不存在则返回null*/public String md5Check(String key){//todo 模拟去数据库查找Map<String, String> data = new HashMap<String, String>();data.put("b0201e4d41b2eeefc7d3d355a44c6f5a", "kazaff2.jpg");if(data.containsKey(key)){return data.get(key);}else{return null;}}/*** 分片验证* 验证对应分片文件是否存在,大小是否吻合* @param file 分片文件的路径* @param size 分片文件的大小* @return*/public boolean chunkCheck(String file, Long size){//检查目标分片是否存在且完整File target = new File(file);if(target.isFile() && size == target.length()){return true;}else{return false;}}/*** 分片合并操作* 要点:* > 合并: NIO* > 并发锁: 避免多线程同时触发合并操作* > 清理: 合并清理不再需要的分片文件、文件夹、tmp文件* @param folder 分片文件所在的文件夹名称* @param ext 合并后的文件后缀名* @param chunks 分片总数* @param md5 文件签名* @param path合并后的文件所存储的位置* @return*/public String chunksMerge(String folder, String ext, int chunks, String md5, String path){//合并后的目标文件String target;//检查是否满足合并条件:分片数量是否足够if(chunks == this.getChunksNum(path + "/" + folder)){//同步指定合并的对象Lock lock = fileLock.getLock(folder);lock.lock();try{//检查是否满足合并条件:分片数量是否足够//File[] files = this.getChunks(path + "/" +folder);List<File> files = new ArrayList<File>(Arrays.asList(this.getChunks(path + "/" +folder)));if(chunks == files.size()){//按照名称排序文件,这里分片都是按照数字命名的Collections.sort(files, new Comparator<File>() {@Overridepublic int compare(File o1, File o2) {if(Integer.valueOf(o1.getName()) < Integer.valueOf(o2.getName())){return -1;}return 1;}});//创建合并后的文件File outputFile = new File(path + "/" + this.randomFileName(ext));if(outputFile.exists()){log.error("文件[" + folder + "]随机命名冲突");this.setErrorMsg("文件随机命名冲突");return null;}outputFile.createNewFile();FileChannel outChannel = new FileOutputStream(outputFile).getChannel();//合并FileChannel inChannel;for(File file : files){inChannel = new FileInputStream(file).getChannel();inChannel.transferTo(0, inChannel.size(), outChannel);inChannel.close();//删除分片if(!file.delete()){log.error("分片[" + folder + "=>" + file.getName() + "]删除失败");}}outChannel.close();files = null;//将MD5签名和合并后的文件path存入持久层if(this.saveMd52FileMap(md5, outputFile.getName())){log.error("文件[" + md5 + "=>" + outputFile.getName() + "]保存关系到持久成失败,但并不影响文件上传,只会导致日后该文件可能被重复上传而已");}//清理:文件夹,tmp文件this.cleanSpace(folder, path);return outputFile.getName();}}catch(Exception ex){log.error("数据分片合并失败", ex);this.setErrorMsg("数据分片合并失败");return null;}finally {//解锁lock.unlock();//清理锁对象fileLock.removeLock(folder);}}//去持久层查找对应md5签名,直接返回对应pathtarget = this.md5Check(md5);if(target == null){log.error("文件[签名:" + md5 + "]数据不完整,可能该文件正在合并中");this.setErrorMsg("数据不完整,可能该文件正在合并中");return null;}return target;}/*** 将MD5签名和目标文件path的映射关系存入持久层* @param key md5签名* @param file 文件路径* @return*/public boolean saveMd52FileMap(String key, String file){//todoreturn true;}/*** 为上传的文件创建对应的保存位置* 若上传的是分片,则会创建对应的文件夹结构和tmp文件* @param info 上传文件的相关信息* @param path 文件保存根路径* @return*/public File getReadySpace(FileInfo info, String path){//创建上传文件所需的文件夹if(!this.createFileFolder(path, false)){return null;}String newFileName;//上传文件的新名称//如果是分片上传,则需要为分片创建文件夹if (info.getChunks() > 0) {newFileName = String.valueOf(info.getChunk());String fileFolder = this.md5(info.getUserId() + info.getName() + info.getType() + info.getLastModifiedDate() + info.getSize());if(fileFolder == null){return null;}path += "/" + fileFolder; //文件上传路径更新为指定文件信息签名后的临时文件夹,用于后期合并if(!this.createFileFolder(path, true)){return null;}} else {//生成随机文件名newFileName = this.randomFileName(info.getName());}return new File(path, newFileName);}/*** 清理分片上传的相关数据* 文件夹,tmp文件* @param folder 文件夹名称* @param path上传文件根路径* @return*/private boolean cleanSpace(String folder, String path){//删除分片文件夹File garbage = new File(path + "/" + folder);if(!garbage.delete()){return false;}//删除tmp文件garbage = new File(path + "/" + folder + ".tmp");if(!garbage.delete()){return false;}return true;}/*** 获取指定文件的所有分片* @param folder 文件夹路径* @return*/private File[] getChunks(String folder){File targetFolder = new File(folder);return targetFolder.listFiles(new FileFilter() {@Overridepublic boolean accept(File file) {if (file.isDirectory()) {return false;}return true;}});}/*** 获取指定文件的分片数量* @param folder 文件夹路径* @return*/private int getChunksNum(String folder){File[] filesList = this.getChunks(folder);return filesList.length;}/*** 创建存放上传的文件的文件夹* @param file 文件夹路径* @return*/private boolean createFileFolder(String file, boolean hasTmp){//创建存放分片文件的临时文件夹File tmpFile = new File(file);if(!tmpFile.exists()){try {tmpFile.mkdir();}catch(SecurityException ex){log.error("无法创建文件夹", ex);this.setErrorMsg("无法创建文件夹");return false;}}if(hasTmp){//创建一个对应的文件,用来记录上传分片文件的修改时间,用于清理长期未完成的垃圾分片tmpFile = new File(file + ".tmp");if(tmpFile.exists()){tmpFile.setLastModified(System.currentTimeMillis());}else{try{tmpFile.createNewFile();}catch(IOException ex){log.error("无法创建tmp文件", ex);this.setErrorMsg("无法创建tmp文件");return false;}}}return true;}/*** 为上传的文件生成随机名称* @param originalName 文件的原始名称,主要用来获取文件的后缀名* @return*/private String randomFileName(String originalName){String ext[] = originalName.split("\\.");return UUID.randomUUID().toString() + "." + ext[ext.length-1];}/*** MD5签名* @param content 要签名的内容* @return*/private String md5(String content){StringBuffer sb = new StringBuffer();try{MessageDigest md5 = MessageDigest.getInstance("MD5");md5.update(content.getBytes("UTF-8"));byte[] tmpFolder = md5.digest();for(int i = 0; i < tmpFolder.length; i++){sb.append(Integer.toString((tmpFolder[i] & 0xff) + 0x100, 16).substring(1));}return sb.toString();}catch(NoSuchAlgorithmException ex){log.error("无法生成文件的MD5签名", ex);this.setErrorMsg("无法生成文件的MD5签名");return null;}catch(UnsupportedEncodingException ex){log.error("无法生成文件的MD5签名", ex);this.setErrorMsg("无法生成文件的MD5签名");return null;}}/*** 记录异常错误信息* @param msg 错误详细*/private void setErrorMsg(String msg){this.msg = msg;}/*** 获取错误详细* @return*/public String getErrorMsg(){return this.msg;}}

(4)、util层

package com.util;import org.ponent;import java.util.HashMap;import java.util.Map;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;@Componentpublic class fileLock {private static Map<String, Lock> LOCKS = new HashMap<String, Lock>();public static synchronized Lock getLock(String key){if(LOCKS.containsKey(key)){return LOCKS.get(key);}else{Lock one = new ReentrantLock();LOCKS.put(key, one);return one;}}public static synchronized void removeLock(String key){LOCKS.remove(key);}}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。