java實現大文件分片上傳

java實現大文件分片上傳

在項目中用到了大文件上傳功能,最初從網上參考了一些代碼來實現,但是最終的上傳效果不是很好,速度比較慢。

之前的上傳思路是:

  1. 前端利用webUploader分片大文件
  2. 後端接收各個分片後的小文件
  3. 接收完一個大文件的所有分片文件後,合併這些文件爲大文件

現在的思路是:

  1. 前端利用webUploader分片大文件

  2. 後端接收各個分片後的小文件

  3. 將接收到的小文件直接存到目標文件夾

  4. 需要注意的是,一定要按分片順序來存儲小文件,不然最後生成的

    文件和原文件不一樣,會被損壞

代碼實現:

後端:

MediaUploadInfoController.java

/**
 * Copyright &copy; 2012-2016 <a href="https://github.com/thinkgem/jeesite">JeeSite</a> All rights reserved.
 */
package com.thinkgem.jeesite.modules.uploadfiles.web;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.thinkgem.jeesite.common.response.Code;
import com.thinkgem.jeesite.common.response.Result;
import com.thinkgem.jeesite.common.utils.IdGen;
import com.thinkgem.jeesite.modules.mediafileinfo.entity.MediaFileInfo;
import com.thinkgem.jeesite.modules.mediafileinfo.service.MediaFileInfoService;
import com.thinkgem.jeesite.modules.sys.entity.Company;
import com.thinkgem.jeesite.modules.sys.entity.MediaType;
import com.thinkgem.jeesite.modules.sys.entity.User;
import com.thinkgem.jeesite.modules.sys.service.MediaSysCompanyService;
import com.thinkgem.jeesite.modules.sys.service.TypeService;
import com.thinkgem.jeesite.modules.sys.utils.UserUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.thinkgem.jeesite.common.config.Global;
import com.thinkgem.jeesite.common.persistence.Page;
import com.thinkgem.jeesite.common.web.BaseController;
import com.thinkgem.jeesite.common.utils.StringUtils;
import com.thinkgem.jeesite.modules.uploadfiles.entity.MediaUploadInfo;
import com.thinkgem.jeesite.modules.uploadfiles.service.MediaUploadInfoService;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * 上傳文件信息Controller
 * @author admin
 * @version 2018-07-11
 */
@Controller
@RequestMapping(value = "${adminPath}/uploadfiles/mediaUploadInfo")
public class MediaUploadInfoController extends BaseController {

   @Autowired
   private MediaUploadInfoService mediaUploadInfoService;
   @Autowired
   private MediaSysCompanyService mediaSysCompanyService;
   @Autowired
   private TypeService typeService;
   @Autowired
   private MediaFileInfoService mediaFileInfoService;
   
   @ModelAttribute
   public MediaUploadInfo get(@RequestParam(required=false) String id) {
      MediaUploadInfo entity = null;
      if (StringUtils.isNotBlank(id) && !StringUtils.contains(id, "WU_FILE")){
         entity = mediaUploadInfoService.get(id);
      }
      if (entity == null){
         entity = new MediaUploadInfo();
      }
      return entity;
   }
   
// @RequiresPermissions("uploadfiles:mediaUploadInfo:view")
   @RequestMapping(value = {"list", ""})
   public String list(MediaUploadInfo mediaUploadInfo, HttpServletRequest request, HttpServletResponse response, Model model) {
      //todo 獲得用戶
      User user = UserUtils.getUser();
      mediaUploadInfo.setCreateBy(user);
      Page<MediaUploadInfo> page = mediaUploadInfoService.findPage(new Page<MediaUploadInfo>(request, response), mediaUploadInfo);
      model.addAttribute("page", page);
      return "modules/uploadfiles/mediaUploadInfoList";
   }

// @RequiresPermissions("uploadfiles:mediaUploadInfo:view")
   @RequestMapping(value = "form")
   public String form(MediaUploadInfo mediaUploadInfo, Model model, @RequestParam(required = false) String rootId) {
      User user = UserUtils.getUser();
      String companyId = user.getCompany().getId();
      Company company = mediaSysCompanyService.get(Integer.valueOf(companyId));
      List<MediaType> typeList = typeService.getLastLevelTypes(rootId);
      model.addAttribute("UUID", IdGen.uuid());
      model.addAttribute("typeList", typeList);
      model.addAttribute("company", company);
      model.addAttribute("mediaUploadInfo", mediaUploadInfo);
      if ("1".equals(rootId))
         return "modules/uploadfiles/mediaUploadInfoForm";
      else
         return "modules/uploadfiles/vedioUploadInfoForm";
   }

   @RequiresPermissions("uploadfiles:mediaUploadInfo:edit")
   @RequestMapping(value = "save")
   public String save(MediaUploadInfo mediaUploadInfo, Model model, RedirectAttributes redirectAttributes) {
      if (!beanValidator(model, mediaUploadInfo)){
         return form(mediaUploadInfo, model, null);
      }
      mediaUploadInfoService.save(mediaUploadInfo);
      addMessage(redirectAttributes, "保存文件成功");
      return "redirect:"+Global.getAdminPath()+"/uploadfiles/mediaUploadInfo/?repage";
   }
   
   @RequiresPermissions("uploadfiles:mediaUploadInfo:edit")
   @RequestMapping(value = "delete")
   public String delete(MediaUploadInfo mediaUploadInfo, RedirectAttributes redirectAttributes) {
      mediaUploadInfoService.delete(mediaUploadInfo);
      addMessage(redirectAttributes, "刪除文件成功");
      return "redirect:"+Global.getAdminPath()+"/uploadfiles/mediaUploadInfo/?repage";
   }


   /**
    * 分片上傳文件
    *
    * @param mediaUploadInfo MediaUploadInfo實體
    * @param mediaFileInfo MediaFileInfo實體
    * @param uploadCounts 上傳的文件總數量
    * @param UUID 作爲media_upload_info表中數據主鍵
    * @param isCover 是否是封面,圖片上傳時用於判定封面圖片
    * @param isVideo 是否是視頻文件
    * @param chunks 文件總的分片數量
    * @param chunk 當前分片
    * @param name 名字
    * @param request 請求
    * @param file 文件
    * @param md5Value 文件MD5值,用於多文件上傳時,判定是不是相同文件
    * @return json信息
    */
   @RequestMapping("/sliceUploadFiles")
   @ResponseBody
   public Map<String, Object> sliceUploadFiles(MediaUploadInfo mediaUploadInfo,
                                    MediaFileInfo mediaFileInfo,
                                    int uploadCounts,
                                    String UUID,
                                    Boolean isCover,
                                    Boolean isVideo,
                                    String chunks,
                                    String chunk,
                                    String name,
                                    HttpServletRequest request,
                                    MultipartFile file,
                                    String md5Value) {
      try {
         return mediaUploadInfoService.sliceUploadFiles(mediaUploadInfo, mediaFileInfo, uploadCounts, UUID, isCover, isVideo, chunks, chunk, name, request, file, md5Value);
      } catch (Exception e) {
         return Result.Error(Code.SYSTEM_ERROR);
      }
   }

   /**
    * 視頻上傳封面上傳
    *
    * @param cover
    * @param UUID
    * @param mediaUploadInfo
    * @return
    */
   @RequestMapping("/coverUpload")
   @ResponseBody
   public Map<String, Object> coverUpload(HttpServletRequest request, MultipartFile file, String UUID, MediaUploadInfo mediaUploadInfo, String md5Value) {
      return mediaUploadInfoService.coverUpload(request, file, UUID, mediaUploadInfo, md5Value);
   }

   /**
    * 批量刪除文件
    *
    * @param ids 多個文件ID,用“,”隔開
    * @return
    */
   @RequestMapping("/batchDelete")
   @ResponseBody
   @Transactional
   public Map<String, Object> batchDelete(String ids){
      String[] strings = ids != null ? ids.split(",") : null;
      if (strings != null && strings.length > 0){
         List<String> pkIds = Arrays.asList(strings);
         //批量刪除可以被刪除的文件信息,審覈通過的不能刪除
         Integer count = mediaUploadInfoService.batchDelete(pkIds);
         Integer count1 = mediaFileInfoService.batchDelete(pkIds);
         if (count == 0 && count1 == 0){
            return Result.Success("您選擇的文件因爲已經審覈通過,不能刪除!");
         }else {
            return Result.Success("您選擇的文件刪除成功!");
         }
      }else {
         return Result.Error(Code.PARAM_ERROR);
      }
   }

}

MediaUploadInfoService.java

/**
 * Copyright &copy; 2012-2016 <a href="https://github.com/thinkgem/jeesite">JeeSite</a> All rights reserved.
 */
package com.thinkgem.jeesite.modules.uploadfiles.service;

import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.thinkgem.jeesite.common.config.Global;
import com.thinkgem.jeesite.common.persistence.Page;
import com.thinkgem.jeesite.common.response.Code;
import com.thinkgem.jeesite.common.response.Result;
import com.thinkgem.jeesite.common.service.CrudService;
import com.thinkgem.jeesite.common.utils.FileUtils;
import com.thinkgem.jeesite.common.utils.IdGen;
import com.thinkgem.jeesite.common.utils.UploadFiles.OperatingFileUtil;
import com.thinkgem.jeesite.modules.mediafileinfo.entity.MediaFileInfo;
import com.thinkgem.jeesite.modules.mediafileinfo.service.MediaFileInfoService;
import com.thinkgem.jeesite.modules.sys.entity.MediaType;
import com.thinkgem.jeesite.modules.sys.entity.User;
import com.thinkgem.jeesite.modules.sys.service.TypeService;
import com.thinkgem.jeesite.modules.sys.utils.UserUtils;
import com.thinkgem.jeesite.modules.uploadfiles.dao.MediaUploadInfoDao;
import com.thinkgem.jeesite.modules.uploadfiles.entity.MediaUploadInfo;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 上傳文件信息Service
 * @author admin
 * @version 2018-07-11
 */
@Service
@Transactional(readOnly = false)
public class MediaUploadInfoService extends CrudService<MediaUploadInfoDao, MediaUploadInfo> {

   //上傳文件根目錄
   private static final String BASEDIR = Global.getConfig("uploadFile.baseDir");
   private static final String TEMPDIR = "temp";
   private static final String DEST_PATH = "destPath";
   private static final String SAVE_PATH = "savePath";
   private static final String MEDIA_TYPE = "media_type";

   /**
    * 線程安全Map,存放media_file_type表內容,不同時候上傳,從數據庫讀取到的該表數據有變化,
    * 所以以各次上傳讀取到的數據爲準,即使在上傳過程中數據有變化,key組成:media_type_${UUID}_${md5Value},
    * 其中UUID爲各次上傳的UUID,md5Value代表每個文件的md5值
    */
   private static Map<String, List<MediaType>> MEDIA_TYPE_MAP = new ConcurrentHashMap<>();
   /**
    * 線程安全Map,key存放media_file_type對應id(即文件類型ID) + _ + ${UUID} + _ + ${md5Value},其中UUID爲各次上傳的UUID,
    * 如果 MEDIA_TYPE_MAP 中的value變化,則該key的value也相應變化,所以用UUID來標識
    * value存放從樹根節點一直到該文件類型ID對應節點所經過的所有節點,用來組成存放文件的路徑
    */
   private static Map<String, Object> FATHER_AND_SON_MAP = new ConcurrentHashMap<>();
   /**
    * 線程安全Map,key組成:destPath/savePath_${UUID}_${md5Value},其中UUID爲各次上傳的UUID,每次上傳的目標路徑不同
    */
   private static Map<String, Object> PATH_MAP = new ConcurrentHashMap<>();
   /**
    * 上傳個數計數器,key代表一次上傳的UUID,value是這次上傳的文件個數計數器
    */
   private static Map<String, AtomicInteger> uploadCountMap = new ConcurrentHashMap<>();

   private static Map<String, AtomicInteger> chunkCountMap = new ConcurrentHashMap<>();

   //上傳封面圖片長
   private static final int hight = 350;
   //上傳封面圖片寬
   private static final int width = 500;

   @Autowired
   private TypeService typeService;

   public MediaUploadInfo get(String id) {
      return super.get(id);
   }
   
   public List<MediaUploadInfo> findList(MediaUploadInfo mediaUploadInfo) {
      return super.findList(mediaUploadInfo);
   }
   
   public Page<MediaUploadInfo> findPage(Page<MediaUploadInfo> page, MediaUploadInfo mediaUploadInfo) {
      return super.findPage(page, mediaUploadInfo);
   }
   
   @Transactional(readOnly = false)
   public void save(MediaUploadInfo mediaUploadInfo) {
      super.save(mediaUploadInfo);
   }
   
   @Transactional(readOnly = false)
   public void delete(MediaUploadInfo mediaUploadInfo) {
      super.delete(mediaUploadInfo);
   }

   @Autowired
   private MediaFileInfoService mediaFileInfoService;

   /**
    * 分片上傳文件
    *
    * @param mediaUploadInfo MediaUploadInfo實體
    * @param mediaFileInfo MediaFileInfo實體
    * @param uploadCounts 該批次上傳的文件總數量
    * @param UUID 作爲media_upload_info表中數據主鍵
    * @param isCover 是否是封面,圖片上傳時用於判定封面圖片
    * @param isVideo 是否是視頻文件
    * @param chunks 文件總的分片數量
    * @param chunk 當前分片
    * @param name 名字
    * @param request 請求
    * @param file 文件
    * @param md5Value 文件MD5值,用於多文件上傳時,判定是不是相同文件
    * @return json信息
    */
    public Map<String,Object> sliceUploadFiles(MediaUploadInfo mediaUploadInfo,
                                    MediaFileInfo mediaFileInfo,
                                    int uploadCounts,
                                    String UUID,
                                    Boolean isCover,
                                    Boolean isVideo,
                                    String chunks,
                                    String chunk,
                                    String name,
                                    HttpServletRequest request,
                                    MultipartFile file, String md5Value) {


      //目標文件夾
      String destPath, savePath;
      //文件擴展名,包括".",例如".mp4"
      String ext = name.substring(name.lastIndexOf("."));
      //文件真實擴展名,不包括"."
      String realExt = ext.substring(ext.lastIndexOf(".") + 1);
      //工程根目錄,最後不包括分隔符
      String realPath = OperatingFileUtil.getProjectPath(request);
      //文件最終保存名字前綴,文件最終保存名字
      String prefixNewName,newName;
      //父類型list,用於生成保存文件路徑
      List<String> parentTypeList;
      //獲取日期,用於目標文件夾目錄創建
      Date date = new Date();
      SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd");
      String today = format.format(date);
      if (PATH_MAP.get(Joiner.on("_").join(DEST_PATH, UUID, md5Value)) == null
            || PATH_MAP.get(Joiner.on("_").join(SAVE_PATH, UUID, md5Value)) == null
            || FATHER_AND_SON_MAP.get(Joiner.on("_").join(mediaUploadInfo.getUploadType(), UUID, md5Value)) == null){
         logger.debug(Thread.currentThread().getName() + "開始計算路徑");
         //獲取該節點所有父類型,用於目標文件夾目錄創建
         parentTypeList = this.getParentTypeList(mediaUploadInfo, UUID, md5Value);
         try {
            destPath = this.getLinuxFilePath(realPath+BASEDIR, today, parentTypeList, mediaUploadInfo.getUploadType(), UUID);
            //開頭不包括"/"
            savePath = this.getLinuxFilePath(BASEDIR, today, parentTypeList, mediaUploadInfo.getUploadType(), UUID).substring(1);
         }catch (NullPointerException | ClassNotFoundException e){
            logger.error("拼接上傳文件路徑異常!", e);
            return Result.Error(Code.SYSTEM_ERROR);
         }
         PATH_MAP.putIfAbsent(Joiner.on("_").join(DEST_PATH, UUID, md5Value), destPath);
         PATH_MAP.putIfAbsent(Joiner.on("_").join(SAVE_PATH, UUID, md5Value), savePath);
         logger.debug(Thread.currentThread().getName() + "結束計算路徑");
      }else {
         logger.debug(Thread.currentThread().getName() + "開始獲取路徑");
         destPath = (String) PATH_MAP.get(Joiner.on("_").join(DEST_PATH, UUID, md5Value));
         savePath = (String) PATH_MAP.get(Joiner.on("_").join(SAVE_PATH, UUID, md5Value));
         parentTypeList = (List<String>) FATHER_AND_SON_MAP.get(Joiner.on("_").join(mediaUploadInfo.getUploadType(), UUID, md5Value));
         logger.debug(Thread.currentThread().getName() + "結束獲取路徑");
      }
      //合併後文件的名字前綴
      prefixNewName = md5Value+UUID;
      //合併後文件的名字
      newName = prefixNewName + ext;

      int chunkUploadCount;
      // 將文件分片保存到文件夾裏,按分片順序保存,輪詢到自己的順序再保存
      while (true){
         logger.debug(Thread.currentThread().getName() + "輪詢開始");
         if (chunkCountMap.get(Joiner.on("_").join(UUID, md5Value)) == null){
            logger.debug(Thread.currentThread().getName() + "給chunkCountMap設置初始值開始");
            AtomicInteger uploadCount = new AtomicInteger(0);
            chunkCountMap.putIfAbsent(Joiner.on("_").join(UUID, md5Value), uploadCount);
            logger.debug(Thread.currentThread().getName() + "給chunkCountMap設置初始值結束");
             //如果chunkCountMap裏面該文件已經上傳的個數,與該分片相差1,則輪到該分片存儲
         }else if (Integer.valueOf(chunk) - chunkCountMap.get(Joiner.on("_").join(UUID, md5Value)).intValue() + 1 == 1){
            logger.debug(Thread.currentThread().getName() + "開始保存文件" + "chunk = " + chunk);
            if(!OperatingFileUtil.saveFile(destPath, newName, file)){
               return Result.Success("分片文件保存失敗!");
            }
            // 分片上傳成功總數增加1
            chunkUploadCount = OperatingFileUtil.incrAndGet(Joiner.on("_").join(UUID, md5Value), chunkCountMap);
            logger.debug(Thread.currentThread().getName() + "保存文件結束" + "chunk = " + chunk);
            break;
         }
      }

      //如果所有分片沒有上傳完成,則直接返回
      if (!OperatingFileUtil.isAllUploaded(chunkUploadCount, chunks, md5Value, uploadCountMap)){
         return Result.Success("上傳成功!");
      }
      //等待所有分片文件上傳完成,存儲文件信息
      try {
         //計數器計數
         int countAfterIncr = OperatingFileUtil.incrAndGet(UUID, uploadCountMap);
         //封面進行壓縮後保存的文件名
         String coverName = IdGen.uuid() + ext;
         //查詢該批次是否有上傳信息
         MediaUploadInfo info = this.get(UUID);
         //說明是這個批次不是第一次上傳,只有視頻的封面存在這種情況
         if (info != null){
            if (isCover){
               //刪除原來壓縮的封面圖片
               String filePath = this.getLinuxFilePath(realPath, "/", info.getUploadCover());
               FileUtils.deleteFile(filePath);
               //刪除數據庫中保存的封面信息
               List<String> paths = Splitter.on("/").splitToList(info.getUploadCover());
               String fileName = paths.get(paths.size() - 1);
               String id = fileName.substring(0, fileName.indexOf("."));
               MediaFileInfo mediaFileInfo1 = new MediaFileInfo();
               mediaFileInfo1.setDelFlag(1);
               mediaFileInfo1.setFileId(id);
               mediaFileInfoService.delete(mediaFileInfo1);
            }
            //用來更新info
            mediaUploadInfo.setUploadId(info.getUploadId());
         }

         //如果是封面
         if (isCover) {
            boolean state;
            //封面進行壓縮,如果是視頻封面不保存原文件,直接壓縮覆蓋原文件,如果是圖片封面不能覆蓋
            if (isVideo){
               state = this.reduceImg(destPath + newName, destPath + newName, width, hight, null);
               mediaUploadInfo.setUploadCover(savePath + newName);
            }
            else{
               state = this.reduceImg(destPath + newName, destPath + coverName, width, hight, null);
               mediaUploadInfo.setUploadCover(savePath + coverName);
            }
            if (!state){
               //還原uploadCountMap裏面上傳次數
               uploadCountMap.get(UUID).decrementAndGet();
               Code.SYSTEM_ERROR.setMes("上傳失敗!");
               return Result.Error(Code.SYSTEM_ERROR);
            }
            mediaUploadInfo.setUploadParentType(parentTypeList.get(parentTypeList.size()-1));
            this.saveFileInfo(UUID, mediaUploadInfo);
         } else { //對於視頻走下面邏輯
            if (countAfterIncr == uploadCounts){
               mediaUploadInfo.setUploadParentType(parentTypeList.get(parentTypeList.size()-1));
               this.saveFileInfo(UUID, mediaUploadInfo);
            }
         }

         //保存單個文件信息
         mediaFileInfo.setFileId(prefixNewName);
         mediaFileInfo.setFileName(name);
         mediaFileInfo.setFileSuffix(realExt);
         mediaFileInfo.setFileAttach(UUID);
         mediaFileInfo.setFileUrl(savePath + newName);
         mediaFileInfo.setFileNum(String.valueOf(System.currentTimeMillis()));
         mediaFileInfo.setFileSize(String.valueOf(getFileSize(destPath + newName) / 1024)); //單位KB
         mediaFileInfo.setIsNewRecord(true);
         mediaFileInfoService.save(mediaFileInfo);
         //清除緩存的信息
         if (countAfterIncr == uploadCounts){
            uploadCountMap.remove(UUID);
            MEDIA_TYPE_MAP.remove(Joiner.on("_").join(MEDIA_TYPE, UUID, md5Value));
            FATHER_AND_SON_MAP.remove(Joiner.on("_").join(mediaUploadInfo.getUploadType(), UUID, md5Value));
            PATH_MAP.remove(Joiner.on("_").join(DEST_PATH, UUID, md5Value));
            PATH_MAP.remove(Joiner.on("_").join(SAVE_PATH, UUID, md5Value));
         }
      } catch (SQLException e) {
         logger.error("MediaUploadInfoService類sliceUploadFile方法保存文件信息異常!", e);
         return Result.Error(Code.SYSTEM_ERROR);
      } catch (ClassNotFoundException e) {
         logger.error("拼接上傳文件路徑異常!", e);
         return Result.Error(Code.SYSTEM_ERROR);
      }catch (Exception e){
         logger.error("系統異常!", e);
         return Result.Error(Code.SYSTEM_ERROR);
      }
      return Result.Success("上傳成功!");
    }

   /**
    * 獲得文件大小
    * @param file
    * @return
    */
   private long getFileSize(File file){
       if (file.isFile()){
          return file.length();
      }
      return 0L;
   }

   /**
    * 通過文件名獲得文件大小
    * @param fileName 文件絕對路徑
    * @return 文件大小
    */
   private long getFileSize(String fileName){
       File file = new File(fileName);
       return getFileSize(file);
   }

   /**
    * 計數器進行計數並且返回增加後的值
    *
    * @param key ConcurrentHashMap中的key值
    * @return 增加後的值
    */
   private int incrAndGet(String key){
      int countAfterIncr;
      if (uploadCountMap.get(key) == null){
         AtomicInteger uploadCount = new AtomicInteger(0);
         /**
          * putIfAbsent方法可以不覆蓋原來key對應的value,
          * 如果key對應的value不存在(新的entry),那麼會向uploadCountMap中添加該鍵值對,並返回null,
          * 如果已經存在,那麼不會覆蓋已有的值,直接返回已經存在的值,
          * 即使剛開始兩個線程同時進來,也不會導致兩個線程互相覆蓋
          * 所以多個線程執行這句代碼都相當於執行了一次
          */
         uploadCountMap.putIfAbsent(key, uploadCount);
      }
      countAfterIncr = uploadCountMap.get(key).incrementAndGet();
      return countAfterIncr;
   }

   /**
    * 得到某個類型的所有父類型list
    *
    * @param mediaUploadInfo
    * @param UUID
    * @param md5Value
    * @return
    */
    private List<String> getParentTypeList(MediaUploadInfo mediaUploadInfo, String UUID, String md5Value){
      List<MediaType> list;
      if (MEDIA_TYPE_MAP.get(Joiner.on("_").join(MEDIA_TYPE, UUID, md5Value)) == null){
//       logger.debug(Thread.currentThread().getName() + "開始計算MEDIA_TYPE_MAP");
         list = typeService.findAllListFront();
         MEDIA_TYPE_MAP.putIfAbsent(Joiner.on("_").join(MEDIA_TYPE, UUID, md5Value), list);
//       logger.debug(Thread.currentThread().getName() + "結束計算MEDIA_TYPE_MAP");
      }else {
//       logger.debug(Thread.currentThread().getName() + "開始獲取MEDIA_TYPE_MAP裏面的list");
         list = MEDIA_TYPE_MAP.get(Joiner.on("_").join(MEDIA_TYPE, UUID, md5Value));
//       logger.debug(Thread.currentThread().getName() + "結束獲取MEDIA_TYPE_MAP裏面的list");
      }
      StringBuffer pids = new StringBuffer();
      //父類型id的list
      List<String> parentTypeList = new ArrayList<>();
      if (!FATHER_AND_SON_MAP.containsKey(Joiner.on("_").join(mediaUploadInfo.getUploadType(), UUID, md5Value))){
//       logger.debug(Thread.currentThread().getName() + "開始計算FATHER_AND_SON_MAP");
         MediaType mediaType = typeService.getType(mediaUploadInfo.getUploadType());
         typeService.findPids(list, mediaType, pids);
         String[] strs = StringUtils.isNotBlank(pids.toString()) ? pids.toString().split(",") : null;
         if (strs != null){
            parentTypeList = Arrays.asList(strs);
         }
         FATHER_AND_SON_MAP.putIfAbsent(Joiner.on("_").join(mediaUploadInfo.getUploadType(), UUID, md5Value), parentTypeList);
//       logger.debug(Thread.currentThread().getName() + "結束計算FATHER_AND_SON_MAP");
      }else {
//       logger.debug(Thread.currentThread().getName() + "開始獲取FATHER_AND_SON_MAP裏面parentTypeList");
         parentTypeList = (List<String>) FATHER_AND_SON_MAP.get(Joiner.on("_").join(mediaUploadInfo.getUploadType(), UUID, md5Value));
//       logger.debug(Thread.currentThread().getName() + "結束獲取FATHER_AND_SON_MAP裏面parentTypeList");
      }
      return parentTypeList;
   }

   /**
    * 拼接Linux下文件路徑
    *
    * @param path 多個表示路徑的字符串,第一個字符串首字母可以是"/",或者是單純字符串,不帶"/"
    * @return 拼接成的文件路徑
    */
   @SuppressWarnings("unchecked")
   private String getLinuxFilePath(Object... path) throws ClassNotFoundException {
       if (path == null || path.length == 0) {
         throw new NullPointerException("path 不能爲空!");
      }
//    StringBuilder destPath = new StringBuilder("/");
      StringBuilder destPath = new StringBuilder();
      for (Object item : path) {
         if (item instanceof String){
            if (StringUtils.isBlank(item.toString())){
               throw new NullPointerException("path 不能爲空或者空字符串!");
            }
            destPath.append(item.toString()).append("/");
         }else if (item instanceof List<?>){
            for (String s : (List<String>)item) {
               if (StringUtils.isBlank(s)){
                  throw new NullPointerException("path 不能爲空或者空字符串!");
               }
               destPath.append(s).append("/");
            }
         }else
            throw new ClassNotFoundException("該方法只支持String或者String集合的參數!");
      }
      String result = destPath.toString();
      return result.charAt(1) != "/".charAt(0) ? result : result.substring(1);
   }


   /**
    * 保存信息到media_upload_info表
    *
    * @param UUID 主鍵
    * @param mediaUploadInfo 實體
    * @throws SQLException
    */
   private void saveFileInfo(String UUID, MediaUploadInfo mediaUploadInfo) throws SQLException {
      //未審覈
      mediaUploadInfo.setToExamineStatus("0");
      //允許下載
      mediaUploadInfo.setIsDownload("0");
      User user = UserUtils.getUser();
      mediaUploadInfo.setSubjectionCompany(user.getCompany().getId());
      if (mediaUploadInfo.getUploadId() == null){
         mediaUploadInfo.setUploadId(UUID);
         mediaUploadInfo.setIsNewRecord(true);
         this.save(mediaUploadInfo);
      }else
         this.update(mediaUploadInfo);
   }

   /**
    * 通過主鍵獲得能夠被刪除的文件信息
    *
    * @param pkIds 主鍵集合
    * @return
    */
   public List<MediaUploadInfo> findCanDelete(List<String> pkIds) {
      return dao.findCanDelete(pkIds);
   }

   /**
    * 批量刪除能被刪除的文件信息
    *
    * @param pkIds 主鍵集合
    */
   public Integer batchDelete(List<String> pkIds) {
      return dao.batchDelete(pkIds);
   }


   /**
    * 視頻上傳中上傳封面圖片
    *
    * @param file 文件
    * @param UUID 主鍵
    * @param mediaUploadInfo 實體類,主要包含封面圖片存儲地址
    * @return json信息
    * @deprecated
    */
   public Map<String,Object> coverUpload(HttpServletRequest request, MultipartFile file, String UUID, MediaUploadInfo mediaUploadInfo, String md5Value) {
      //目標文件夾
      String destPath, savePath;
      Date date = new Date();
      SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd");
      //獲取日期,用於目標文件夾目錄創建
      String today = format.format(date);
      List<String> parentTypeList = this.getParentTypeList(mediaUploadInfo, UUID, md5Value);
      //工程根目錄,不包括最後一個分隔符
      String realPath = OperatingFileUtil.getProjectPath(request);
      try {
         destPath = this.getLinuxFilePath(realPath+BASEDIR, today, parentTypeList, mediaUploadInfo.getUploadType(), UUID);
         //存相對路徑
         savePath = this.getLinuxFilePath(BASEDIR, today, parentTypeList, mediaUploadInfo.getUploadType(), UUID).substring(1);
      }catch (NullPointerException | ClassNotFoundException e){
         logger.error("MediaUploadInfoService類sliceUploadFile方法執行異常!", e);
         return Result.Error(Code.SYSTEM_ERROR);
      }

      String name = file.getOriginalFilename();
      //文件擴展名,包括".",例如".mp4"
      String ext = name.substring(name.lastIndexOf("."));
      //文件真實擴展名,不包括"."
      String realExt = ext.substring(ext.lastIndexOf(".") + 1);
      //文件最終保存名字前綴
      String prefixNewName = IdGen.uuid();
      //文件最終保存名字
      String newName = prefixNewName + ext;
      //保存文件到目標文件夾
      if(!OperatingFileUtil.saveFile(destPath, newName, file)){
         Code.SYSTEM_ERROR.setMes("上傳失敗!請重新上傳");
         return Result.Error(Code.SYSTEM_ERROR);
      }

      try {
         //查詢該批次是否有上傳信息
         MediaUploadInfo info = this.get(UUID);
         //說明是這個批次不是第一次上傳
         if (info != null){
            //刪除原來壓縮的封面圖片
            String filePath = this.getLinuxFilePath(realPath, "/", info.getUploadCover());
            FileUtils.deleteFile(filePath);
            //用來更新info
            mediaUploadInfo.setUploadId(info.getUploadId());
         }

         //封面進行壓縮
         boolean state = this.reduceImg(destPath + newName, destPath + newName, width, hight, null);
         if (!state){
            Code.SYSTEM_ERROR.setMes("上傳失敗!");
            return Result.Error(Code.SYSTEM_ERROR);
         }
         //更新文件信息
         mediaUploadInfo.setUploadCover(savePath + newName);
         this.saveFileInfo(UUID, mediaUploadInfo);
      }catch (SQLException e){
         logger.error("MediaUploadInfoService類coverUpload方法保存文件信息異常!", e);
         Code.SYSTEM_ERROR.setMes("封面圖片保存數據庫失敗!");
         return Result.Error(Code.SYSTEM_ERROR);
      } catch (ClassNotFoundException e) {
         logger.error("拼接上傳文件路徑異常!", e);
         return Result.Error(Code.SYSTEM_ERROR);
      }
      return Result.Success("封面圖片上傳成功!");
   }


   /**
    * 指定圖片寬度和高度和壓縮比例對圖片進行壓縮
    *
    * @param imgsrc 源圖片地址
    * @param imgdist 目標圖片地址
    * @param widthdist 壓縮後圖片的寬度
    * @param heightdist 壓縮後圖片的高度
    * @param rate 壓縮的比例
    */
   public boolean reduceImg(String imgsrc, String imgdist, int widthdist, int heightdist, Float rate) {
      try {
         File srcfile = new File(imgsrc);
         // 檢查圖片文件是否存在
         if (!srcfile.exists()) {
            System.out.println("文件不存在");
         }
         // 如果比例不爲空則說明是按比例壓縮
         if (rate != null && rate > 0) {
            //獲得源圖片的寬高存入數組中
            int[] results = getImgWidthHeight(srcfile);
            if (results == null || results[0] == 0 || results[1] == 0) {
               return false;
            } else {
               //按比例縮放或擴大圖片大小,將浮點型轉爲整型
               widthdist = (int) (results[0] * rate);
               heightdist = (int) (results[1] * rate);
            }
         }
         // 開始讀取文件並進行壓縮
         Image src = ImageIO.read(srcfile);

         // 構造一個類型爲預定義圖像類型之一的 BufferedImage
         BufferedImage tag = new BufferedImage((int) widthdist, (int) heightdist, BufferedImage.TYPE_INT_RGB);

         //繪製圖像  getScaledInstance表示創建此圖像的縮放版本,返回一個新的縮放版本Image,按指定的width,height呈現圖像
         //Image.SCALE_SMOOTH,選擇圖像平滑度比縮放速度具有更高優先級的圖像縮放算法。
         tag.getGraphics().drawImage(src.getScaledInstance(widthdist, heightdist, Image.SCALE_SMOOTH), 0, 0, null);

         //創建文件輸出流
         FileOutputStream out = new FileOutputStream(imgdist);
         //將圖片按JPEG壓縮,保存到out中
//       JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
//       encoder.encode(tag);
         String formatName = imgdist.substring(imgdist.lastIndexOf(".") + 1);
         ImageIO.write(tag, formatName, out);
         //關閉文件輸出流
         out.close();
      } catch (Exception ef) {
         ef.printStackTrace();
         return false;
      }
      return true;
   }


   public static int[] getImgWidthHeight(File file) {
      InputStream is = null;
      BufferedImage src = null;
      int result[] = {0,0};
      try {
         // 獲得文件輸入流
         is = new FileInputStream(file);
         // 從流裏將圖片寫入緩衝圖片區
         src = ImageIO.read(is);
         result[0] =src.getWidth(null); // 得到源圖片寬
         result[1] =src.getHeight(null);// 得到源圖片高
         is.close();  //關閉輸入流
      } catch (Exception ef) {
         ef.printStackTrace();
      }
      return result;
   }


   public static void main(String[] args) {
//    String uuid = IdGen.uuid();
//    System.out.println(uuid);
//    String srcPath = "C:\\nfs\\sources\\uploadFiles\\2018\\07\\30\\0\\1\\494b2b8ffead4eebb7f4d25991a7a66e\\5d35ab59efd0456abe85865f1c14061d\\d262e2bdb6c04ad4a28ad3b5494d6390.jpg";
//    String destPath = "C:\\nfs\\sources\\uploadFiles\\2018\\07\\30\\0\\1\\494b2b8ffead4eebb7f4d25991a7a66e\\5d35ab59efd0456abe85865f1c14061d\\1.jpg";
//    File srcfile = new File(srcPath);
//    File distfile = new File(destPath);
//
//    System.out.println("壓縮前圖片大小:" + srcfile.length());
//    reduceImg(srcPath, destPath, 500, 350, null);
//    System.out.println("壓縮後圖片大小:" + distfile.length());
//    for (int i = 0; i < 100; ++i){
//       Thread thread = new Thread(() -> System.out.println(incrAndGet("a")));
//       thread.start();
//    }

   }
}

OperatingFileUtil.java

package com.thinkgem.jeesite.common.utils.UploadFiles;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.nio.channels.FileChannel;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
public class OperatingFileUtil {
    //上傳個數計數器,key代表一次上傳的UUID,value是這次上傳的文件個數計數器
    private static Map<String, AtomicInteger> uploadCountMap = new ConcurrentHashMap<>();

    /**
     * 取得tomcat中的webapps目錄 如:/home/apache-tomcat-8.0.45/webapps
     * @param request 請求
     * @return 真實路徑
     */
    public static String getRealPath(HttpServletRequest request) {
        String realPath = request.getSession().getServletContext().getRealPath(File.separator);
        realPath = realPath.substring(0, realPath.length() - 1);
        int aString = realPath.lastIndexOf(File.separator);
        realPath = realPath.substring(0, aString);
        return realPath;
    }

    /**
     * 取得tomcat中的項目目錄 如:/home/apache-tomcat-8.0.45/webapps/ysp
     * @param request 請求
     * @return 真實路徑
     */
    public static String getProjectPath(HttpServletRequest request) {
        String realPath = request.getSession().getServletContext().getRealPath(File.separator);
        int aString = realPath.lastIndexOf(File.separator);
        realPath = realPath.substring(0, aString);
        return realPath;
    }


    /**
     * 保存文件到指定路徑
     *
     * @param savePath 保存的路徑
     * @param fileFullName 文件名字,包括擴展名
     * @param file 文件
     * @return true:保存成功,false:保存失敗
     */
    public static boolean saveFile(String savePath, String fileFullName, MultipartFile file) {
        // 判斷文件夾是否存在,不存在就創建一個
        File fileDirectory = new File(savePath);
        if (!fileDirectory.exists()) {
            fileDirectory.mkdirs();
        }

        File uploadFile = new File(savePath + fileFullName);
        byte[] data = new byte[0];
        try {
            data = readInputStream(file.getInputStream());
        } catch (IOException e) {
            log.error("文件" + (savePath+fileFullName) +"讀入失敗", e);
            return false;
        }
        // 創建輸出流
        try (FileOutputStream outStream = new FileOutputStream(uploadFile, true)) {// 寫入數據
            outStream.write(data);
            outStream.flush();
        } catch (FileNotFoundException e) {
            log.error("文件" + (savePath+fileFullName) +"未找到", e);
            return false;
        } catch (IOException e) {
            log.error("寫入數據異常,寫入路徑爲:" + (savePath+fileFullName), e);
            return false;
        }
        return uploadFile.exists();
    }


    /**
     * 讀取輸入流到byte[]中
     *
     * @param in 輸入流
     * @return 讀取到的byte數組
     * @throws IOException 異常
     */
    private static byte[] readInputStream(InputStream in) throws IOException {
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        // 創建一個Buffer字符串
        byte[] buffer = new byte[1024];
        // 每次讀取的字符串長度,如果爲-1,代表全部讀取完畢
        int len;
        // 使用一個輸入流從buffer裏把數據讀取出來
        while ((len = in.read(buffer)) != -1) {
            // 用輸出流往buffer裏寫入數據,中間參數代表從哪個位置開始讀,len代表讀取的長度
            outStream.write(buffer, 0, len);
        }
        // 關閉輸入流
        in.close();
        // 把outStream裏的數據寫入內存
        return outStream.toByteArray();
    }

    /**
     * 計數器進行計數並且返回增加後的值
     *
     * @param key ConcurrentHashMap中的key值
     * @return 增加後的值
     */
    public static int incrAndGet(String key, Map<String, AtomicInteger> map){
        int countAfterIncr;
        if (map.get(key) == null){
            AtomicInteger uploadCount = new AtomicInteger(0);
            /**
             * putIfAbsent方法可以不覆蓋原來key對應的value,
             * 如果key對應的value不存在(新的entry),那麼會向uploadCountMap中添加該鍵值對,並返回null,
             * 如果已經存在,那麼不會覆蓋已有的值,直接返回已經存在的值,
             * 即使剛開始兩個線程同時進來,也不會導致兩個線程互相覆蓋
             * 所以多個線程執行這句代碼都相當於執行了一次
             */
            map.putIfAbsent(key, uploadCount);
        }
        countAfterIncr = map.get(key).incrementAndGet();
        return countAfterIncr;
    }

    /**
     * 是否全部上傳完成
     *
     * @param md5 MD5值
     * @param chunks 分片總數
     * @return true:一個文件所有分片上傳成功,false:一個文件不是所有分片上傳成功
     */
    public static boolean isAllUploaded(int uploadCount, String chunks, String md5Value, Map<String, AtomicInteger> map) {
        if (uploadCount == Integer.parseInt(chunks)){
            map.remove(md5Value);
            return true;
        }else
            return false;
    }


    /**
     * 把輸入流保存到目標文件夾
     *
     * @param inputStream 輸入流
     * @param filePath 目標文件夾
     * @param newName 新名字
     * @return true:保存成功,false:保存失敗
     */
    public static boolean saveStreamToFile(SequenceInputStream inputStream, String filePath, String newName) {
        boolean result = true;
        File fileDirectory = new File(filePath);
        if (!fileDirectory.exists()) {
            if (!fileDirectory.mkdirs()) {
                log.error("保存文件的文件夾創建失敗!路徑爲:[" + fileDirectory + "]");
                return false;
            }
        }

      /* 創建輸出流,寫入數據,合併分塊 */
        byte[] buffer = new byte[1024];
        int len = 0;
        try (OutputStream outputStream = new FileOutputStream(filePath + newName)) {
            //Reads some number of bytes from the input stream and stores them into the buffer array
            while ((len = inputStream.read(buffer)) != -1) {
                //Writes <code>len</code> bytes from the specified byte array starting at offset <code>off</code> to this output stream
                outputStream.write(buffer, 0, len);
                //Flushes this output stream and forces any buffered output bytes to be written out, such bytes should immediately be written to their intended destination
                outputStream.flush();
            }
        }catch (FileNotFoundException e) {
            log.error("文件[" + (filePath+newName) + "]未找到!", e);
            result = false;
        }catch (IOException e) {
            log.error("文件["+(filePath + newName)+"]IOException", e);
            result = false;
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                log.error("OperatingFileUtil類#saveStreamToFile方法SequenceInputStream關閉異常", e);
                result = false;
            }
        }
        return result;
    }

    /**
     * 刪除文件夾以及文件夾裏面所有文件
     *
     * @param filePath 文件夾路徑
     * @return true:刪除成功,false:刪除失敗
     */
    public static boolean deleteFolder(String filePath) {
        File dir = new File(filePath);
        File[] files = dir.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isDirectory()){
                    deleteFolder(file.getPath());
                }else {
                    if(!file.delete()) {
                        log.info("文件{}刪除失敗", file.getName());
                        return false;
                    }
                }
            }
        }
        return dir.delete();
    }

}

其餘的代碼是業務邏輯相關,不關乎實現

前端實現:
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ include file="/WEB-INF/views/include/taglib.jsp"%>
<html>
<head>
   <title>視頻上傳</title>
   <meta name="decorator" content="default"/>
   <link rel="stylesheet" type="text/css" href="${ctxStatic}/webuploader/webuploader.css">
   <script type="text/javascript" src="${ctxStatic}/webuploader/webuploader.min.js"></script>
   <link rel="stylesheet" href="${ctxStatic}/jiaoben5515/css/zcity.css">
   <script type="text/javascript" src="${ctxStatic}/jiaoben5515/js/zcity.js"></script>
   <style>
      .handleArea{position: absolute;top:0;left:0;width: 100px;height: 100px;background-color: rgba(0,0,0,.7);display: none;}
      .info:hover .handleArea{display: block;}
      .zcityGroup{float: left;width: 50%;}
      .zcityGroup .zcityItem{width: 48%;}
   </style>
</head>
<body>
   <br/>
   <form:form id="inputForm" modelAttribute="mediaUploadInfo" action="${ctx}/uploadfiles/mediaUploadInfo/save" method="post" class="form-horizontal m-b-55">
      <sys:message content="${message}"/>       
      <div class="control-group">
         <label class="control-label">文件屬於&nbsp;${company.name}:</label>
         <div class="controls" style="padding-top: 3px;">
            <input type="radio" id="share" name="shareStatus" value="0" checked/><label for="share" >共享</label> &nbsp;&nbsp; &nbsp; &nbsp;
            <input type="radio" id="nonShare" name="shareStatus" value="1" /><label for="nonShare">不共享</label> &nbsp;
         </div>
      </div>
      <div class="control-group">
         <label class="control-label">上傳標題:</label>
         <div class="controls">
            <form:input path="uploadTitle" htmlEscape="false" maxlength="255" class="input-xlarge required"/>
            <span class="help-inline"><font color="red">*</font> </span>
         </div>
      </div>
      <%--<div class="control-group">--%>
         <%--<label class="control-label">標題封面物理路徑:</label>--%>
         <%--<div class="controls">--%>
            <%--<form:input path="uploadCover" htmlEscape="false" maxlength="255" class="input-xlarge "/>--%>
         <%--</div>--%>
      <%--</div>--%>
      <div class="control-group">
         <label class="control-label">上傳分類:</label>
         <div class="controls">
            <form:select path="uploadType" class="input-xlarge required" style="width: 284px">
            
             <form:options items="${typeList}" itemLabel="name" itemValue="id" htmlEscape="false"/>
            </form:select>
            <span class="help-inline"><font color="red">*</font> </span>
         </div>
      </div>
      <div class="control-group">
         <label class="control-label">攝影師:</label>
         <div class="controls">
            <form:input path="shootAuthor" htmlEscape="false" maxlength="255" class="input-xlarge "/>
         </div>
      </div>
      <div class="control-group">
         <label class="control-label">拍攝時間:</label>
         <div class="controls">
            <input id="shootTime" name="shootTime" type="text" readonly="readonly" maxlength="20" class="input-medium Wdate "
               value="<fmt:formatDate value="${mediaUploadInfo.shootTime}" pattern="yyyy-MM-dd HH:mm:ss"/>"
               onclick="WdatePicker({dateFmt:'yyyy-MM-dd HH:mm:ss',isShowClear:false});"/>
         </div>
      </div>
      <div class="control-group">
         <label class="control-label">拍攝地點:</label>
         <div class="controls">
            <div class="zcityGroup" city-range="{'level_start':1,'level_end':2}" city-ini="四川,成都市">
            </div>
            <input type="text" id="shootplace" placeholder="輸入拍攝地點" style="height: 21px"/>
         </div>
      </div>
      <div class="control-group">
         <label class="control-label">描述、說明、備註:</label>
         <div class="controls">
            <form:textarea path="remarks" htmlEscape="false" rows="4" class="input-xxlarge required"/>
                <span class="help-inline"><font color="red">*</font> </span>
         </div>
        </div>
        
        <div class="control-group">
            <label class="control-label">封面圖片:</label>
            <div class="controls">
                <div id="uploaderimg" class="wu-example">
                    <!--用來存放文件信息-->
                    <div id="imglist" class="uploader-list o-h"></div>
                    <div class="btns">
                        <div id="pickerimg" class="pull-left">
                            選擇圖片
                            <!-- <input type="file" class="webuploader-container"> -->
                        </div>
                        <!-- <button id="ctlBtn" type="button" class="btn btn-default">開始上傳</button> -->
                    </div>
                </div>
            </div>
        </div>

      <div class="control-group">
         <label class="control-label">視頻上傳:</label>
         <div class="controls">
            <div id="uploader" class="wu-example">
               <!--用來存放文件信息-->
               <div id="thelist" class="uploader-list o-h"></div>
               <div class="btns">
                  <div id="picker" class="pull-left">
                     選擇視頻
                     <!-- <input type="file" class="webuploader-container"> -->
                  </div>
                  <!-- <button id="ctlBtn" type="button" class="btn btn-default">開始上傳</button> -->
               </div>
            </div>
         </div>
      </div>

      <div class="btnBox">
         <button class="btn btn-primary" id="ctlBtn">上傳並提交</button>
         <button type="button" class="btn btn-default" id="close">關閉</button>
      </div>

    </form:form>
    <script type="text/javascript">
        $(function () {
         var iframeIndex = parent.layer.getFrameIndex(window.name); //先得到當前iframe層的索引
         zcityrun('.zcityGroup');//省市聯動
         $("#close").click(function(){
            parent.layer.close(iframeIndex);
         });
            $list = $('#thelist');
            var code = "";//封面圖片地址
         var examineP;//審覈人員
         var count = 0;
            var flie_count = 0;
            var classesId = "${tcId}";
         var uploadType = 'xlsx';
            var isupload = false;
            
            //上傳封面圖片
            var uploaderimg = WebUploader.create({
                //設置選完文件後是否自動上傳
                auto: false,
                //swf文件路徑
                swf:  '${ctxStatic}/webuploader/Uploader.swf',
                // 文件接收服務端。
                server: "${ctx}/uploadfiles/mediaUploadInfo/sliceUploadFiles",
                // 選擇文件的按鈕。可選。
                // 內部根據當前運行是創建,可能是input元素,也可能是flash.
                pick: '#pickerimg',
                chunked: true, //開啓分塊上傳
                chunkSize: 10 * 1024 * 1024,
                chunkRetry: 3,//網絡問題上傳失敗後重試次數
                threads: 3, //上傳併發數
                //fileNumLimit :1,
                fileSizeLimit: 1024*1024*1024*5,//最大2GB
                //fileSingleSizeLimit: ltsot*1024*1024*1024*1024,
                resize: false,//不壓縮
                compress:false, //不壓縮源文件
                //選擇文件類型
                accept : {
               title: 'Images', 
               extensions : 'bmp,jpg,png,gif,',         
                mimeTypes: '/*'  
            },
            });

            uploaderimg.on("error", function (type) {
                if (type == "Q_TYPE_DENIED") {
               layer.alert('請上傳正確文件格式!', {icon: 2,closeBtn: false});
                }else if (type == "Q_EXCEED_SIZE_LIMIT") {
               layer.alert('上傳文件不能超過2G!', {icon: 2,closeBtn: false});
                }else {
               layer.alert('上傳出錯!請檢查後重新上傳!錯誤代碼'+type, {icon: 2,closeBtn: false});
                }
            });

            var index = 0;
            uploaderimg.onFileQueued = function( file ) { //文件添加到對列的監聽
                console.log(file);
                var fileContent = uploaderimg.getFiles();
                index++;
                if(index > 1){
                    uploaderimg.removeFile(fileContent[fileContent.length-2]);
                }

                var $li = $('<div id="' + file.id + '" class="item m-b-10" style="border-bottom: 1px solid #eee;">' + '<h4 class="info"><img></h4>' + '<p class="state m-t-5">文件自動上傳準備中...</p><input type="hidden" id="s_WU_FILE_'+flie_count+'" />' + '</div>');
            flie_count++;

                var $img = $li.find('img');
                $("#imglist").html($li);

                uploaderimg.makeThumb( file, function( error, src ) {
               if ( error ) {
                  $img.replaceWith('<span>不能預覽</span>');
                  return;
               }

               $img.attr( 'src', src );
                }, 100, 100 );
                
                //md5計算
                uploaderimg.md5File(file)
                    .progress(function(percentage) {
                       // console.log('Percentage:', percentage);
                    })
                    // 完成
                    .then(function (fileMd5) { // 完成
                        var end = +new Date();
                        //console.log("before-send-file  preupload: file.size="+file.size+" file.md5="+fileMd5);
                        file.wholeMd5 = fileMd5;//獲取到了md5
                        //uploader.options.formData.md5value = file.wholeMd5;//每個文件都附帶一個md5,便於實現秒傳
                        
                        console.log(file);
                        $('#' + file.id).find('p.state').text('MD5計算完畢,將自動上傳請耐心等待');
                        uploaderimg.upload(file);//上傳
                        //console.info("MD5="+fileMd5);
                    });
            };

            // 文件上傳過程中創建進度條實時顯示。
            // uploaderimg.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('已上傳'+Math.floor(percentage*100)+"%").css("color","#555");
            //     $percent.css('width', percentage * 100 + '%');
            // });

            uploaderimg.on( 'uploadBeforeSend', function( block, data ) {
                var file = block.file;
                var fileMd5 = file.wholeMd5;
                console.log(data);
                data.uploadType = $("#uploadType").val();;
                data.UUID = "${UUID}";
                data.md5Value = fileMd5;
                data.uploadCounts = flie_count;
                data.chunks = block.chunks; //總分片數
                data.chunk = block.chunk; //當前第幾片
            data.isCover = true;
            data.isVideo = true;
            });

            uploaderimg.on( 'uploadSuccess', function( file,res ) {
                console.log(res);
                code = res.code;
                $('#' + file.id).find('p.state').text('已上傳').css("color","green");
                $('#' + file.id).find(".progress").find(".progress-bar").attr("class", "progress-bar progress-bar-success");
            });

            uploaderimg.on( 'uploadError', function( file ) {
                $('#' + file.id).find('p.state').text('上傳出錯').css("color","red");
                //上傳出錯後進度條變紅
                $('#' + file.id).find(".progress").find(".progress-bar").attr("class", "progress-bar progress-bar-danger");
                //添加重試按鈕
                //爲了防止重複添加重試按鈕,做一個判斷
                //var retrybutton = $('#' + file.id).find(".btn-retry");
                //$('#' + file.id)
                if ($('#' + file.id).find(".btn-retry").length < 1) {
                    var btn = $('<button type="button" fileid="' + file.id + '" class="btn btn-success btn-retry m-l-5">重試</button>');
                    $('#' + file.id).find(".info").append(btn);//.find(".btn-danger")
                }
                $(".btn-retry").click(function () {
                    //console.log($(this).attr("fileId"));//拿到文件id
                    uploaderimg.retry(uploaderimg.getFile($(this).attr("fileId")));
                });
            });

            uploaderimg.on( 'uploadComplete', function( file ) {
                $('#' + file.id).find('.progress').fadeOut();//上傳完刪除進度條
            });


            // 上傳視頻
            var uploader = WebUploader.create({
                //設置選完文件後是否自動上傳
                auto: false,
                //swf文件路徑
                swf:  '${ctxStatic}/webuploader/Uploader.swf',
                // 文件接收服務端。
                server: "${ctx}/uploadfiles/mediaUploadInfo/sliceUploadFiles",
                // 選擇文件的按鈕。可選。
                // 內部根據當前運行是創建,可能是input元素,也可能是flash.
                pick: '#picker',
                chunked: true, //開啓分塊上傳
                chunkSize: 10 * 1024 * 1024,
                chunkRetry: 3,//網絡問題上傳失敗後重試次數
                threads: 3, //上傳併發數
                //fileNumLimit :1,
                fileSizeLimit: 1024*1024*1024*10,//最大10GB
                //fileSingleSizeLimit: ltsot*1024*1024*1024*1024,
                resize: false,//不壓縮
                //選擇文件類型
                accept : {
               title: 'vedio', 
               extensions : 'mp4,rm,rmvb,mpeg1-4,mov,mtv,wmv,avi,3gp,amv,dmv,flv,ogg',          
                mimeTypes: 'vedio/*'  
            },
                //accept: {
                //    title: 'Video',
                //    extensions: 'mp4,avi',
                //    mimeTypes: 'video/*'
                //}
            });
         
         // $("#ctlBtn").click(function(){
         //     layer.msg('捕獲就是從頁面已經存在的元素上,包裹layer的結構', {icon:2,time:0,btn:'確定'});
         // });
            /**
             * 驗證文件格式以及文件大小
             */
            uploader.on("error", function (type) {
                if (type == "Q_TYPE_DENIED") {
               layer.alert('請上傳正確文件格式!', {icon: 2,closeBtn: false});
                }else if (type == "Q_EXCEED_SIZE_LIMIT") {
               layer.alert('上傳文件總大小不能超過10G!', {icon: 2,closeBtn: false});
                }else {
               layer.alert('上傳出錯!請檢查後重新上傳!錯誤代碼'+type, {icon: 2,closeBtn: false});
                }

            });
            // 當有文件被添加進隊列的時候
            uploader.on('fileQueued', function (file) {
            console.log(file);
            var $li = $('<div id="' + file.id + '" class="item m-b-10" style="border-bottom: 1px solid #eee;">' +'<h4 class="info">' + file.name + '<button type="button" fileId="' + file.id + '" class="btn btn-danger btn-delete m-l-5">刪除</button></h4>' + '<p class="state ready">文件準備中...</p><input type="hidden" id="s_WU_FILE_'+flie_count+'" />' +
                '</div>');
                //console.info("id=file_"+flie_count);
            flie_count++;
            $list.append($li);

                //刪除要上傳的文件
                //每次添加文件都給btn-delete綁定刪除方法
                $(".btn-delete").click(function () {
                    //console.log($(this).attr("fileId"));//拿到文件id
                    uploader.removeFile(uploader.getFile($(this).attr("fileId"), true));
                    $(this).parent().parent().fadeOut();//視覺上消失了
               $(this).parent().parent().remove();//DOM上刪除了
                });
                //uploader.options.formData.guid = WebUploader.guid();//每個文件都附帶一個guid,以在服務端確定哪些文件塊本來是一個
            //console.info("guid= "+WebUploader.guid());

                //md5計算
                uploader.md5File(file)
                    .progress(function(percentage) {
                        $li.find('p.state').text('文件準備中...'+Math.floor(percentage*100)+"%").css("color","#555");
                       // console.log('Percentage:', percentage);
                    })
                    // 完成
                    .then(function (fileMd5) { // 完成
                        var end = +new Date();
                        //console.log("before-send-file  preupload: file.size="+file.size+" file.md5="+fileMd5);
                        file.wholeMd5 = fileMd5;//獲取到了md5
                        //uploader.options.formData.md5value = file.wholeMd5;//每個文件都附帶一個md5,便於實現秒傳
                  
                        $('#' + file.id).find('p.state').text('等待上傳').removeClass("ready").css("color","#555");
                        // uploader.upload(file);//上傳
                        isupload = true;
                        //console.info("MD5="+fileMd5);
                    });


            });
            //文件上傳過程中創建進度條實時顯示。
            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').removeClass("ready").text('已上傳'+Math.floor(percentage*100)+"%").css("color","#555");
                $percent.css('width', percentage * 100 + '%');
            });

            //發送前填充數據
            uploader.on( 'uploadBeforeSend', function( block, data ) {
            console.log(block,data);
            // block爲分塊數據。
                // file爲分塊對應的file對象。
                var file = block.file;
                var fileMd5 = file.wholeMd5;
            // 修改data可以控制發送哪些攜帶數據。

            data.uploadCounts = count;//文件個數

                //console.info("fileName= "+file.name+" fileMd5= "+fileMd5+" fileId= "+file.id);
                //console.info("input file= "+ flie_count);
                // 將存在file對象中的md5數據攜帶發送過去。
                data.md5Value = fileMd5;//md5
                // data.fileName_ = $("#s_"+file.id).val();
                //console.log("fileName_: "+data.fileName_);
                // 刪除其他數據
                // delete data.key;
                // if(block.chunks>1){ //文件大於chunksize 分片上傳
                //     data.isChunked = true;
                //    // console.info("data.isChunked= "+data.isChunked);
                // }else{
                //     data.isChunked = false;
                //     //console.info("data.isChunked="+data.isChunked);
            // }
            // data.uploadAuditor = examineP[0].userid; //審覈人員
            data.uploadType = $("#uploadType").val(); //文件類型
            data.uploadTitle = $("#uploadTitle").val(); //上傳標題
            data.shootAuthor = $("#shootAuthor").val(); //攝影師
            data.shootTime = $("#shootTime").val(); //拍攝時間
            data.shootAddress = $(".zcityItem[item-level='1']").find(".currentValue").val() + 
                           $(".zcityItem[item-level='2']").find(".currentValue").val() + 
                           $("#shootplace").val(); //拍攝地址
            data.remarks = $("#remarks").val(); //備註
            data.shareStatus = $('input:radio[name="shareStatus"]:checked').val(); //共享狀態
            data.filePixel = ""; //像素
            data.fileRp = ""; //分辨率
            // data.fileSize = file.size; //分片文件大小
            data.fileNum = ""; //文件編號
            data.UUID = "${UUID}"; //隨機生成的UUID
            data.isCover = false; //是否爲封面
            data.chunks = block.chunks; //總分片數
            data.chunk = block.chunk; //當前第幾片
            // data.currentFileId= file.id; //當前文件ID
            data.name = file.name; //文件名
                data.isVideo = true;
            // data.file = ""; //文件
            });


            uploader.on('uploadSuccess', function (file,data) {
            console.log(data);
                $('#' + file.id).find('p.state').removeClass("ready").text('已上傳').css("color","green");
                $('#' + file.id).find(".progress").find(".progress-bar").attr("class", "progress-bar progress-bar-success");
                $('#' + file.id).find(".info").find('.btn').fadeOut('slow');//上傳完後刪除"刪除"按鈕
                $('#StopBtn').fadeOut('slow');
            });
            uploader.on('uploadError', function (file) {
                $('#' + file.id).find('p.state').removeClass("ready").text('上傳出錯').css("color","red");
                //上傳出錯後進度條變紅
                $('#' + file.id).find(".progress").find(".progress-bar").attr("class", "progress-bar progress-bar-danger");
                //添加重試按鈕
                //爲了防止重複添加重試按鈕,做一個判斷
                //var retrybutton = $('#' + file.id).find(".btn-retry");
                //$('#' + file.id)
                if ($('#' + file.id).find(".btn-retry").length < 1) {
                    var btn = $('<button type="button" fileid="' + file.id + '" class="btn btn-success btn-retry m-l-5">重試</button>');
                    $('#' + file.id).find(".info").append(btn);//.find(".btn-danger")
                }
                $(".btn-retry").click(function () {
                    //console.log($(this).attr("fileId"));//拿到文件id
                    uploader.retry(uploader.getFile($(this).attr("fileId")));
                });
            });
            uploader.on('uploadComplete', function (file) {//上傳完成後回調
                $('#' + file.id).find('.progress').fadeOut();//上傳完刪除進度條
                $('#' + file.id + 'btn').fadeOut('slow')//上傳完後刪除"刪除"按鈕
            });
            uploader.on('uploadFinished', function () {
            var list = $("#thelist").find(".state");
            var flag = true;
            for(var i=0;i<list.length;i++){
               if($(list[i]).text() == "等待上傳" || $(list[i]).text() == "上傳出錯"){
                  flag = false;
               }
            }
            if(flag){
                    layer.alert('上傳成功,請等待審覈!', {icon: 1,closeBtn: false}, function () {
                        parent.layer.close(iframeIndex);
                    });
            }else{
               layer.alert('有視頻上傳出錯', {icon: 2,closeBtn: false});
            }
                //上傳完後的回調方法
                // layer.msg('所有文件上傳完畢', {icon:1,time:0,btn:'確定'});
                //提交表單
         });
            // $("#ctlBtn").click(function () {
            //     if(!isupload){
         //        layer.msg('請耐心等待', {icon:2,time:0,btn:'確定'});
            //     }else{
         //        uploader.upload();
         //     }
            // });
            $("#StopBtn").click(function () {
                //console.log($('#StopBtn').attr("status"));
                var status = $('#StopBtn').attr("status");
                if (status == "suspend") {
                    //console.log("當前按鈕是暫停,即將變爲繼續");
                    $("#StopBtn").html("繼續上傳");
                    $("#StopBtn").attr("status", "continuous");
                   // console.log("當前所有文件==="+uploader.getFiles());
                    //console.log("=============暫停上傳==============");
                    uploader.stop(true);
                   // console.log("=============所有當前暫停的文件=============");
                   // console.log(uploader.getFiles("interrupt"));
                } else {
                    //console.log("當前按鈕是繼續,即將變爲暫停");
                    $("#StopBtn").html("暫停上傳");
                    $("#StopBtn").attr("status", "suspend");
                    //console.log("===============所有當前暫停的文件==============");
                   // console.log(uploader.getFiles("interrupt"));
                    uploader.upload(uploader.getFiles("interrupt"));
                }
            });
            uploader.on('uploadAccept', function (file, response) {
                if (response._raw === '{"error":true}') {
                    return false;
                }
            });

         $("#inputForm").validate({
            submitHandler: function(form){
                    // loading('正在提交,請稍等...');
                    if(code == "100"){
                        if(uploader.getFiles('inited').length == 0){
                      layer.alert('請選擇視頻', {icon: 2,closeBtn: false});
                        }else{
                            if($("#thelist").find("p.ready").length == 0){ //文件準備完畢才能上傳
                                count = uploader.getFiles('inited').length;
                                uploader.upload();
                            }else{
                                layer.alert('文件正在準備中,待文件準備完畢,再點擊上傳', {icon: 2,closeBtn: false});
                            }
                        }
                    }else{
                        layer.alert('請上傳封面圖片', {icon: 2,closeBtn: false});
                    }
               // form.submit();
            },
            errorContainer: "#messageBox",
            errorPlacement: function(error, element) {
               $("#messageBox").text("輸入有誤,請先更正。");
               if (element.is(":checkbox")||element.is(":radio")||element.parent().is(".input-append")){
                  error.appendTo(element.parent().parent());
               } else {
                  error.insertAfter(element);
               }
            }
         });

         // 選人按鈕
         // $("#btn-select").click(function(){
         //        layer.open({
         //        type: 2
         //        ,area: ["600px", '100%']
         //        ,title: '選人'
         //        ,content: '${ctx}/sys/user/select'
         //        ,closeBtn: false
         //     });
         // });

         // window.selecFuc = function(data){
         //     examineP = data;
         //     $("#examineP").val(examineP[0].userName);
         // }
        });
    </script>
</body>
</html>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章