溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

vue怎么實(shí)現(xiàn)文件上傳

發(fā)布時(shí)間:2021-07-09 16:44:02 來源:億速云 閱讀:670 作者:chen 欄目:大數(shù)據(jù)

本篇內(nèi)容主要講解“vue怎么實(shí)現(xiàn)文件上傳”,感興趣的朋友不妨來看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“vue怎么實(shí)現(xiàn)文件上傳”吧!

為什么使用Vue-Simple-Uploader

最近用到了Vue + Spring Boot來完成文件上傳的操作,踩了一些坑,對(duì)比了一些Vue的組件,發(fā)現(xiàn)了一個(gè)很好用的組件——Vue-Simple-Uploader

再說說為什么選用這個(gè)組件,對(duì)比vue-ant-design和element-ui的上傳組件,它能做到更多的事情,比如:

  • 可暫停、繼續(xù)上傳

  • 上傳隊(duì)列管理,支持最大并發(fā)上傳

  • 分塊上傳

  • 支持進(jìn)度、預(yù)估剩余時(shí)間、出錯(cuò)自動(dòng)重試、重傳等操作

  • 支持“快傳”,通過文件判斷服務(wù)端是否已存在從而實(shí)現(xiàn)“快傳”

由于需求中需要用到斷點(diǎn)續(xù)傳,所以選用了這個(gè)組件,下面我會(huì)從最基礎(chǔ)的上傳開始說起:

單文件上傳、多文件上傳、文件夾上傳

Vue代碼:

 <uploader
        :options="uploadOptions1"
        :autoStart="true"
        class="uploader-app"
      >
        <uploader-unsupport></uploader-unsupport>
        <uploader-drop>
          <uploader-btn  :attrs="attrs">選擇文件</uploader-btn>
          <uploader-btn :attrs="attrs" directory>選擇文件夾</uploader-btn>
        </uploader-drop>
        <uploader-list></uploader-list>
</uploader>

該組件默認(rèn)支持多文件上傳,這里我們從官方demo中粘貼過來這段代碼,然后在uploadOption1中配置上傳的路徑即可,其中uploader-btn 中設(shè)置directory屬性即可選擇文件夾進(jìn)行上傳。

uploadOption1:

 uploadOptions1: {
        target: "//localhost:18080/api/upload/single",//上傳的接口
        testChunks: false, //是否開啟服務(wù)器分片校驗(yàn)
        fileParameterName: "file",//默認(rèn)的文件參數(shù)名
        headers: {},
        query() {},
        categaryMap: { //用于限制上傳的類型
          image: ["gif", "jpg", "jpeg", "png", "bmp"]
        }
}

在后臺(tái)的接口的編寫,我們?yōu)榱朔奖?,定義了一個(gè)chunk類用于接收組件默認(rèn)傳輸?shù)囊恍┖竺娣奖惴謮K斷點(diǎn)續(xù)傳的參數(shù):

Chunk類

@Data
public class Chunk implements Serializable {

    private static final long serialVersionUID = 7073871700302406420L;

    private Long id;
    /**
     * 當(dāng)前文件塊,從1開始
     */
    private Integer chunkNumber;
    /**
     * 分塊大小
     */
    private Long chunkSize;
    /**
     * 當(dāng)前分塊大小
     */
    private Long currentChunkSize;
    /**
     * 總大小
     */
    private Long totalSize;
    /**
     * 文件標(biāo)識(shí)
     */
    private String identifier;
    /**
     * 文件名
     */
    private String filename;
    /**
     * 相對(duì)路徑
     */
    private String relativePath;
    /**
     * 總塊數(shù)
     */
    private Integer totalChunks;
    /**
     * 文件類型
     */
    private String type;

    /**
     * 要上傳的文件
     */
    private MultipartFile file;
}

在編寫接口的時(shí)候,我們直接使用這個(gè)類作為參數(shù)去接收vue-simple-uploader傳來的參數(shù)即可,注意這里要使用POST來接收喲~

接口方法:

    @PostMapping("single")
    public void singleUpload(Chunk chunk) {
                   // 獲取傳來的文件
        MultipartFile file = chunk.getFile();
        // 獲取文件名
        String filename = chunk.getFilename();
        try {
            // 獲取文件的內(nèi)容
            byte[] bytes = file.getBytes();
            // SINGLE_UPLOADER是我定義的一個(gè)路徑常量,這里的意思是,如果不存在該目錄,則去創(chuàng)建
            if (!Files.isWritable(Paths.get(SINGLE_FOLDER))) {
                Files.createDirectories(Paths.get(SINGLE_FOLDER));
            }
            // 獲取上傳文件的路徑
            Path path = Paths.get(SINGLE_FOLDER,filename);
            // 將字節(jié)寫入該文件
            Files.write(path, bytes);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

這里需要注意一點(diǎn),如果文件過大的話,Spring Boot后臺(tái)會(huì)報(bào)錯(cuò)

org.apache.tomcat.util.http.fileupload.FileUploadBase$FileSizeLimitExceededException: The field file exceeds its maximum permitted size of 1048576 bytes.

這時(shí)需要在application.yml中配置servlet的最大接收文件大小(默認(rèn)大小是1MB和10MB)

spring:
  servlet:
    multipart:
      max-file-size: 10MB 
      max-request-size: 100MB

下面我們啟動(dòng)項(xiàng)目,選擇需要上傳的文件就可以看到效果了~ 是不是很方便~ 但是同樣的事情其余的組件基本上也可以做到,之所以選擇這個(gè),更多的是因?yàn)樗梢灾С謹(jǐn)帱c(diǎn)分塊上傳,實(shí)現(xiàn)上傳過程中斷網(wǎng),再次聯(lián)網(wǎng)的話可以從斷點(diǎn)位置開始繼續(xù)秒傳~下面我們來看看斷點(diǎn)續(xù)傳是怎么玩的。

斷點(diǎn)分塊續(xù)傳

先說一下分塊斷點(diǎn)續(xù)傳的大概原理,我們?cè)诮M件可以配置分塊的大小,大于該值的文件會(huì)被分割成若干塊兒去上傳,同時(shí)將該分塊的chunkNumber保存到數(shù)據(jù)庫(Mysql or Redis,這里我選擇的是Redis

組件上傳的時(shí)候會(huì)攜帶一個(gè)identifier的參數(shù)(這里我采用的是默認(rèn)的值,你也可以通過生成md5的方式來重新賦值參數(shù)),將identifier作為Redis的key,設(shè)置hashKey為”chunkNumber“,value是由每次上傳的chunkNumber組成的一個(gè)Set集合。

在將uploadOption中的testChunk的值設(shè)置為true之后,該組件會(huì)先發(fā)一個(gè)get請(qǐng)求,獲取到已經(jīng)上傳的chunkNumber集合,然后在checkChunkUploadedByResponse方法中判斷是否存在該片段來進(jìn)行跳過,發(fā)送post請(qǐng)求上傳分塊的文件。

每次上傳片段的時(shí)候,service層返回當(dāng)前的集合大小,并與參數(shù)中的totalChunks進(jìn)行對(duì)比,如果發(fā)現(xiàn)相等,就返回一個(gè)狀態(tài)值,來控制前端發(fā)出merge請(qǐng)求,將剛剛上傳的分塊合為一個(gè)文件,至此文件的斷點(diǎn)分塊上傳就完成了。

vue怎么實(shí)現(xiàn)文件上傳

下面是對(duì)應(yīng)的代碼~

Vue代碼:

<uploader
        :options="uploadOptions2"
        :autoStart="true"
        :files="files"
        @file-added="onFileAdded2"
        @file-success="onFileSuccess2"
        @file-progress="onFileProgress2"
        @file-error="onFileError2"
      >
        <uploader-unsupport></uploader-unsupport>
        <uploader-drop>
          <uploader-btn :attrs="attrs">分塊上傳</uploader-btn>
        </uploader-drop>
        <uploader-list></uploader-list>
</uploader>

校驗(yàn)是否上傳過的代碼

 uploadOptions2: {
        target: "//localhost:18080/api/upload/chunk",
        chunkSize: 1 * 1024 * 1024,
        testChunks: true,
        checkChunkUploadedByResponse: function(chunk, message) {
          let objMessage = JSON.parse(message);
              // 獲取當(dāng)前的上傳塊的集合
          let chunkNumbers = objMessage.chunkNumbers;
          // 判斷當(dāng)前的塊是否被該集合包含,從而判定是否需要跳過
          return (chunkNumbers || []).indexOf(chunk.offset + 1) >= 0;
        },
        headers: {},
        query() {},
        categaryMap: {
          image: ["gif", "jpg", "jpeg", "png", "bmp"],
          zip: ["zip"],
          document: ["csv"]
        }
}

上傳后成功的處理,判斷狀態(tài)來進(jìn)行merge操作

onFileSuccess2(rootFile, file, response, chunk) {
      let res = JSON.parse(response);
          // 后臺(tái)報(bào)錯(cuò)
      if (res.code == 1) {
        return;
      }
      // 需要合并
      if (res.code == 205) {
        // 發(fā)送merge請(qǐng)求,參數(shù)為identifier和filename,這個(gè)要注意需要和后臺(tái)的Chunk類中的參數(shù)名對(duì)應(yīng),否則會(huì)接收不到~
        const formData = new FormData();
        formData.append("identifier", file.uniqueIdentifier);
        formData.append("filename", file.name);
        merge(formData).then(response => {});
      } 
    },

判定是否存在的代碼,注意這里的是GET請(qǐng)求!?。?/p>

                    @GetMapping("chunk")
    public Map<String, Object> checkChunks(Chunk chunk) {
        return uploadService.checkChunkExits(chunk);
    }

    @Override
    public Map<String, Object> checkChunkExits(Chunk chunk) {
        Map<String, Object> res = new HashMap<>();
        String identifier = chunk.getIdentifier();
        if (redisDao.existsKey(identifier)) {
            Set<Integer> chunkNumbers = (Set<Integer>) redisDao.hmGet(identifier, "chunkNumberList");
            res.put("chunkNumbers",chunkNumbers);
        }
        return res;
    }

保存分塊,并保存數(shù)據(jù)到Redis的代碼。這里的是POST請(qǐng)求?。。?/p>

    @PostMapping("chunk")    
                public Map<String, Object> saveChunk(Chunk chunk) {
        // 這里的操作和保存單段落的基本是一致的~
        MultipartFile file = chunk.getFile();
        Integer chunkNumber = chunk.getChunkNumber();
        String identifier = chunk.getIdentifier();
        byte[] bytes;
        try {
            bytes = file.getBytes();
            // 這里的不同之處在于這里進(jìn)行了一個(gè)保存分塊時(shí)將文件名的按照-chunkNumber的進(jìn)行保存
            Path path = Paths.get(generatePath(CHUNK_FOLDER, chunk));
            Files.write(path, bytes);
        } catch (IOException e) {
            e.printStackTrace();
        }
                    // 這里進(jìn)行的是保存到redis,并返回集合的大小的操作
        Integer chunks = uploadService.saveChunk(chunkNumber, identifier);
        Map<String, Object> result = new HashMap<>();
        // 如果集合的大小和totalChunks相等,判定分塊已經(jīng)上傳完畢,進(jìn)行merge操作
        if (chunks.equals(chunk.getTotalChunks())) {
            result.put("message","上傳成功!");
            result.put("code", 205);
        }
        return result;
    }


        /**
         * 生成分塊的文件路徑
         */
        private static String generatePath(String uploadFolder, Chunk chunk) {
        StringBuilder sb = new StringBuilder();
        // 拼接上傳的路徑
        sb.append(uploadFolder).append(File.separator).append(chunk.getIdentifier());
        //判斷uploadFolder/identifier 路徑是否存在,不存在則創(chuàng)建
        if (!Files.isWritable(Paths.get(sb.toString()))) {
            try {
                Files.createDirectories(Paths.get(sb.toString()));
            } catch (IOException e) {
                log.error(e.getMessage(), e);
            }
        }
        // 返回以 - 隔離的分塊文件,后面跟的chunkNumber方便后面進(jìn)行排序進(jìn)行merge
        return sb.append(File.separator)
                .append(chunk.getFilename())
                .append("-")
                .append(chunk.getChunkNumber()).toString();

    }

    /**
     * 保存信息到Redis
     */
        public Integer saveChunk(Integer chunkNumber, String identifier) {
        // 獲取目前的chunkList
        Set<Integer> oldChunkNumber = (Set<Integer>) redisDao.hmGet(identifier, "chunkNumberList");
        // 如果獲取為空,則新建Set集合,并將當(dāng)前分塊的chunkNumber加入后存到Redis
        if (Objects.isNull(oldChunkNumber)) {
            Set<Integer> newChunkNumber = new HashSet<>();
            newChunkNumber.add(chunkNumber);
            redisDao.hmSet(identifier, "chunkNumberList", newChunkNumber);
            // 返回集合的大小
            return newChunkNumber.size();
        } else {
                // 如果不為空,將當(dāng)前分塊的chunkNumber加到當(dāng)前的chunkList中,并存入Redis
            oldChunkNumber.add(chunkNumber);
            redisDao.hmSet(identifier, "chunkNumberList", oldChunkNumber);
            // 返回集合的大小
            return oldChunkNumber.size();
        }

    }

合并的后臺(tái)代碼:

    @PostMapping("merge")
    public void mergeChunks(Chunk chunk) {
        String fileName = chunk.getFilename();
        uploadService.mergeFile(fileName,CHUNK_FOLDER + File.separator + chunk.getIdentifier());
    }

        @Override
    public void mergeFile(String fileName, String chunkFolder) {
        try {
            // 如果合并后的路徑不存在,則新建
            if (!Files.isWritable(Paths.get(mergeFolder))) {
                Files.createDirectories(Paths.get(mergeFolder));
            }
            // 合并的文件名
            String target = mergeFolder + File.separator + fileName;
            // 創(chuàng)建文件
            Files.createFile(Paths.get(target));
            // 遍歷分塊的文件夾,并進(jìn)行過濾和排序后以追加的方式寫入到合并后的文件
            Files.list(Paths.get(chunkFolder))
                     //過濾帶有"-"的文件
                    .filter(path -> path.getFileName().toString().contains("-"))
                     //按照從小到大進(jìn)行排序
                    .sorted((o1, o2) -> {
                        String p1 = o1.getFileName().toString();
                        String p2 = o2.getFileName().toString();
                        int i1 = p1.lastIndexOf("-");
                        int i2 = p2.lastIndexOf("-");
                        return Integer.valueOf(p2.substring(i2)).compareTo(Integer.valueOf(p1.substring(i1)));
                    })
                    .forEach(path -> {
                        try {
                            //以追加的形式寫入文件
                            Files.write(Paths.get(target), Files.readAllBytes(path), StandardOpenOption.APPEND);
                            //合并后刪除該塊
                            Files.delete(path);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    });
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

至此,我們的斷點(diǎn)續(xù)傳就完美結(jié)束了,完整的代碼我已經(jīng)上傳到gayhub~,歡迎star fork pr~(后面還會(huì)把博文也上傳到gayhub喲~)

前端:https://github.com/viyog/viboot-front

后臺(tái):https://github.com/viyog/viboot

到此,相信大家對(duì)“vue怎么實(shí)現(xiàn)文件上傳”有了更深的了解,不妨來實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

vue
AI