vue file upload component vue simple upload basic use and source code analysis, front and rear end separation, upload, slice upload and other processes

On the right side of the page, there is a directory index, which can jump to the content you want to see according to the title
If not on the right, look for the left
This article is suitable for students who have done full stack development. At least they need to be able to build the front and back-end environment of vue+spring boot and the basic front and back-end interaction logic. Otherwise, you can't understand it and at least you can't test it
If you are just a front-end engineer, you can mock the response yourself
This blog is a source note for learning the whole stack of station b up. Video link: https://www.bilibili.com/video/BV1df4y1E77G?p=8&spm_id_from=pageDriver
  1. I hope you can support this up and speak really well

1, Construction of helloworld environment

1. Front end environment construction

  1. Download the Vue simple upload source code https://github.com/simple-uploader/vue-uploader
  2. Import the project into the development tool, then enter the App.vue file in the example folder and specify the back-end upload file interface (this interface path is specified by ourselves. If you don't understand it, it will be written directly as I do)
  3. Solve cross domain problems
'/ffmpeg-video':{
 target:'http://localhost:3000',
  changeOrigin:true,
  pathRewrite:{
    '^/ffmpeg-video':'/ffmpeg-video'
  }
}
  1. npm install installs all dependencies
  2. npm run dev startup project

2. Back end environment

  1. Create a basic spring boot project with spring boot starter web dependency
  2. Configuration file, configuration upload path and port number (the port number needs to be consistent with the one specified by your front end)
  3. Entity class
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;

@Data
public class MultipartFileParams {
    //Slice number
    private int chunkNumber;
    //Slice size
    private long chunkSize;
    //Current slice size
    private long currentChunkSize;
    //Total file size
    private long totalSize;
    //Slice id
    private String identifier;
    //file name
    private String filename;
    //Relative path
    private String relativePath;
    //Total number of slices
    private int totalChunks;
    //spring receives the file object transmitted from the front end
    private MultipartFile file;
}
  1. service layer

import com.yzpnb.entity.MultipartFileParams;
import com.yzpnb.service.FileUploadService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;

@Service
public class FileUploadServiceImpl implements FileUploadService {
    @Value("${upload.file.path}")
    private String uploadFilePath;

    @Override
    public ResponseEntity<String> upload(MultipartFileParams fileParams) {
        String filePath = uploadFilePath + fileParams.getFilename();

        File fileTemp = new File(filePath);

        File parentFile = fileTemp.getParentFile();

        if(!parentFile.exists()){
            parentFile.mkdirs();
        }
        try {
            MultipartFile file = fileParams.getFile();
            //Use the transferTo(dest) method to write the uploaded file to the specified file on the server;
            //It can only be used once because the file stream can only be received and read once. When the transmission is completed, the stream will be closed;
            file.transferTo(fileTemp);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return ResponseEntity.ok("SUCCESS");
    }
}
  1. controller
import com.yzpnb.entity.MultipartFileParams;
import com.yzpnb.service.FileUploadService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(value = "/ffmpeg-video",produces = "application/json; charset=UTF-8")
@Slf4j
public class UploadController {

    @Autowired
    private FileUploadService fileUploadService;


    @PostMapping("/upload")
    public ResponseEntity<String> upload(MultipartFileParams file){
   		log.info("file: ",file);//Print log
        return fileUploadService.upload(file);
    }
}

3. Upload file test

  1. debug start backend
  2. Remove the built-in debugger and start the front end
  3. Upload file

2, Source code analysis

Before looking at the source code, let's learn a few knowledge
  1. directive allows you to create an instruction and encapsulate the corresponding logic
  2. mixins allows you to mix the encapsulated data, mounted, etc. into what you need, which is equivalent to copying one copy. For example, several components have the same data and mounted, so we don't need to write each file once, just mix it directly
  3. The function of extends is not to copy, but to inherit and extend
  4. Provide can be pseudo responsive, and a wide range of data and menthod can be shared. When we expose some things with provide, we can inject them into other components with inject, but there must be blood relationship
  5. Parent components can use props to transfer data to child components.
  6. Child components can use $emit to let parent components listen to custom events.
  1. VM. $emit (event name, passed parameter) / / trigger the event on the current instance
  2. VM. $on (event name, function function)// Run fn after listening for event events
  3. For example, if the sub component defines vm.$emit("show", data), the parent component can reference events through @ show = "" when using the sub component. Every time the sub component executes vm.$emit("show", data), the parent component triggers @ show once
  1. Because Vue simple upload is an encapsulated simple uploader.js, you need to know the common properties and events
  1. Common properties
  2. Common events>8. Simple-uploader.js for more events, please refer to the official document https://github.com/simple-uploader/Uploader/blob/develop/README_zh-CN.md
I didn't see the above things. It's hard to look at the source code, or I don't know when looking at the source code. You can go back and check it

1. mixins.js file

  1. First, the uploader.vue file provides an uploader
mixins.js this file is specially provided for component mixing through the minxins instruction

  1. This file exposes the support variable and injects the uploader through inject

2. uploader-btn


  1. The above assignBrowse method is simple-uploader.js. The specific reason is explained in uploader.vue
  2. This method not only transfers three props variables, but also transfers itself in, and obtains the dom node through $refs (this. $refs. Name)
  3. This method is mainly to click Select File to pop up the select File window,

3. uploader-unsupport

This file is used to handle the prompt that the user does not support HTML5

When your browser does not support Uploader.js, you will be prompted that this library needs to support HTML5 File API and file slicing.

4. uploader-drop

This file is to drag the file to the specified location, that is, to select a file. You can drag the file here, but there is no upload logic, just select the file


5. uploader-list

This file is mainly responsible for the list display after uploading files (an array composed of Uploader.File file and folder objects, where files and folders coexist)

  1. In the figure above, we can see that the fileList variable is defined in the calculated field instead of the data field. Its purpose is to display the results immediately when the value in uploader.fileList changes. For example, when we upload a file, the fileList will add a file object, and then it will be rendered by two-way binding immediately
  1. Computed is used to monitor the variables defined by itself. The variables are not declared in data, but directly defined in computed. Then, two-way data binding can be carried out on the page to display the results or used for other processing
  2. computed is more suitable for returning a result value after processing multiple variables or objects, that is, if a value in several variables changes, the value we monitor will also change. For example, the relationship between the commodity list in the shopping cart and the total amount, as long as the quantity of commodities in the commodity list changes, Or reduce or increase or delete goods, the total amount should change. The total amount here is calculated using the calculated attribute, which is the best choice

6. uploader-files

It is basically the same as the uploader list above. The only difference is that the referenced objects are different (an array of file objects, a pure file list, and no folders)

7. uploader-file

A single component of file and folder is a single file displayed in the list. This component has more code. I move the contents of the document directly and introduce them one by one. It's a waste of time













8. uploader

Source location: Vue upload master folder - > SRC folder - > components folder - > uploader.vue file





3, Fragment upload

Only the basic functions are realized. Other exception handling, unified return results, save file information to the database, but did not do it because of space
You can refer to this practical article https://blog.csdn.net/grd_java/article/details/121679400

1. Back end

  1. Entity class
import lombok.Data;

@Data
public class FileInfo {
    private String uniqueIdentifier;//File unique id
    private String name;//file name
}

  1. controller
import com.yzpnb.entity.FileInfo;
import com.yzpnb.entity.MultipartFileParams;
import com.yzpnb.service.FileUploadService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.Map;


/**
 * @author luoliang
 * @date 2018/6/19
 */
@RestController
@RequestMapping(value = "/ffmpeg-video",produces = "application/json; charset=UTF-8")
@Slf4j
public class UploadController {

    @Autowired
    private FileUploadService fileUploadService;

    /**
     * Call before uploading (only one time) to determine whether the file has been uploaded, if so, skip.
     * If not, judge whether half of the fragments are transmitted. If yes, return the missing fragment number and let the front end transmit the missing fragment
     * @param file File parameters
     * @return
     */
    @GetMapping("/upload")
    public ResponseEntity<Map<String,Object>> uploadCheck(MultipartFileParams file){
        log.info("file: "+file);//Print log
        return fileUploadService.uploadCheck(file);
    }
    /**
     * Upload call
     * @param file
     * @return
     */
    @PostMapping("/upload")
    public ResponseEntity<String> upload(MultipartFileParams file){
        log.info("file: "+file);//Print log
        return fileUploadService.upload(file);
    }

    /**
     * After the upload is completed, call to merge the fragment files
     */
    @PostMapping("/upload-success")
    public ResponseEntity<String> uploadSuccess(@RequestBody FileInfo file){
        return fileUploadService.uploadSuccess(file);
    }
}
  1. service

import com.yzpnb.entity.FileInfo;
import com.yzpnb.entity.MultipartFileParams;
import com.yzpnb.service.FileUploadService;
import com.yzpnb.utils.MergeFileUtil;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
@Log4j2
public class FileUploadServiceImpl implements FileUploadService {
    @Value("${upload.file.path}")
    private String uploadFilePath;

    /**
     * Judge whether the file has been uploaded. If yes, skip,
     * If not, judge whether half of the fragments are transmitted. If yes, return the missing fragment number and let the front end transmit the missing fragment
     * @param fileParams
     * @return
     */
    @Override
    public ResponseEntity<Map<String,Object>> uploadCheck(MultipartFileParams fileParams) {
        //Get file unique id
        String fileDir = fileParams.getIdentifier();
        String filename = fileParams.getFilename();
        //Partition directory
        String chunkPath = uploadFilePath + fileDir+"/chunk/";
        //Fragment directory object
        File file = new File(chunkPath);
        //Get fragment collection
        List<File> chunkFileList = MergeFileUtil.chunkFileList(file);

        //Merged file path
        //Merge file path, D:/develop/video / file unique id/merge/filename
        String filePath = uploadFilePath + fileDir+"/merge/"+filename;
        File fileMergeExist = new File(filePath);

        String [] temp;//Save list of existing files
        boolean isExists = fileMergeExist.exists();//Are files that have been merged
        if(chunkFileList == null){
            temp= new String[0];
        }else{
            temp = new String[chunkFileList.size()];
            //If there is no merged file, the upload is not completed
            //If the upload is not completed, save the existing slice list if there are slices, otherwise do not save
            if(!isExists && chunkFileList.size()>0){
                for(int i = 0;i<chunkFileList.size();i++){
                    temp[i] = chunkFileList.get(i).getName();//Save list of existing files
                }
            }
        }

        //Return result set
        HashMap<String, Object> hashMap = new HashMap<>();
        hashMap.put("code",1);
        hashMap.put("message","Success");
        hashMap.put("needSkiped",isExists);
        hashMap.put("uploadList",temp);

        return ResponseEntity.ok(hashMap);
    }

    /**D:/develop/video/File unique id/chunk / fragment number
     * Fragment upload file
     * @param fileParams File parameters
     * @return
     */
    @Override
    public ResponseEntity<String> upload(MultipartFileParams fileParams) {

        //Get file unique id
        String fileDir = fileParams.getIdentifier();
        //Slice number
        int chunkNumber = fileParams.getChunkNumber();
        //File path, file specific path, D: / development / video / file unique id/chunk/1
        String filePath = uploadFilePath + fileDir+"/chunk/"+chunkNumber;

        File fileTemp = new File(filePath);

        File parentFile = fileTemp.getParentFile();

        if(!parentFile.exists()){
            parentFile.mkdirs();
        }
        try {
            MultipartFile file = fileParams.getFile();
            //Use the file.transferTo(dest) method to write the uploaded file to the specified dest file on the server;
            //It can only be used once because the file stream can only be received and read once. When the transmission is completed, the stream will be closed;
            file.transferTo(fileTemp);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return ResponseEntity.ok("SUCCESS");
    }

    @Override
    public ResponseEntity<String> uploadSuccess(FileInfo fileInfo) {
        log.info("filename: "+fileInfo.getName());
        log.info("UniqueIdentifier: "+fileInfo.getUniqueIdentifier());
        //Partition directory path
        String chunkPath = uploadFilePath + fileInfo.getUniqueIdentifier()+"/chunk/";
        //Merge directory path
        String mergePath = uploadFilePath + fileInfo.getUniqueIdentifier()+"/merge/";
        //Merge file, D:/develop/video / file unique id/merge/filename
        File file = MergeFileUtil.mergeFile(uploadFilePath,chunkPath, mergePath,fileInfo.getName());
        if(file == null){
            return ResponseEntity.ok("ERROR:File merge failed");
        }
        return ResponseEntity.ok("SUCCESS");
    }
}
  1. Tool class
package com.yzpnb.utils;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.*;

public class MergeFileUtil {
    /**
     * Judge whether the directory path to upload the partition exists, and create it if it does not exist,
     * @param filePath Fragment path
     * @return File object
     */
    public static File isUploadChunkParentPath(String filePath){

        File fileTemp = new File(filePath);

        File parentFile = fileTemp.getParentFile();

        if(!parentFile.exists()){
            parentFile.mkdirs();
        }
        return fileTemp;
    }

    /**
     * Merge file, D:/develop/video / file unique id/merge/filename
     * @param uploadPath Upload path D:/develop/video/
     * @param chunkPath Partition file directory path
     * @param mergePath Merge file directory D:/develop/video / file unique id/merge/
     * @param fileName file name
     * @return
     */
    public static File mergeFile(String uploadPath,String chunkPath,String mergePath,String fileName){
        //Get the directory where the block file is located
        File file = new File(chunkPath);

        List<File> chunkFileList = chunkFileList(file);

        //Before merging files, first judge whether there is a merged directory, which is not created
        File fileTemp = new File(mergePath);

        if(!fileTemp.exists()){
            fileTemp.mkdirs();
        }

        //Merge file path
        File mergeFile = new File(mergePath + fileName);
        // The merged file exists. Delete it before creating it
        if(mergeFile.exists()){
            mergeFile.delete();
        }
        boolean newFile = false;
        try {
            newFile = mergeFile.createNewFile();//When a file is created, false is returned if it already exists. If there is no created file, no direct exception is thrown in the directory
        } catch (IOException e) {
            e.printStackTrace();
        }
        if(!newFile){//If newFile=false, the file exists
            return null;
        }
        try {
            //Create write file object
            RandomAccessFile raf_write = new RandomAccessFile(mergeFile,"rw");
            //Traverse the block file and start merging
            // Read file buffer
            byte[] b = new byte[1024];
            for(File chunkFile:chunkFileList){
                RandomAccessFile raf_read = new RandomAccessFile(chunkFile,"r");
                int len =-1;
                //Read block file
                while((len = raf_read.read(b))!=-1){
                    //Write data to merge file
                    raf_write.write(b,0,len);
                }
                raf_read.close();
            }
            raf_write.close();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return mergeFile;
    }

    /**
     * Gets all files in the specified block file directory
     * @param file Block directory
     * @return File list
     */
    public static List<File> chunkFileList(File file){
        //Get all files in the directory
        File[] files = file.listFiles();
        if(files == null){
            return null;
        }
        //Convert to list to facilitate sorting
        List<File> chunkFileList = new ArrayList<>();
        chunkFileList.addAll(Arrays.asList(files));
        //sort
        Collections.sort(chunkFileList, new Comparator<File>() {
            @Override public int compare(File o1, File o2) {
                if(Integer.parseInt(o1.getName())>Integer.parseInt(o2.getName())){
                    return 1;
                }
                return -1;
            }
        });
        return chunkFileList;
    }
}

2. Front end

  1. Introducing axios

  2. Judgment before uploading: before uploading a file, first judge whether a file already exists (the code is placed at the end)
  3. The callback after successful upload requests the backend to upload the successful interface, and then merge the fragments
<template>
  <uploader
  :options="options"
  :file-status-text="statusText"
  class="uploader-example"
  ref="uploader"
  @file-complete="fileComplete"
  @complete="complete"
  @file-success="fileSuccess"
  ></uploader>
</template>

<script>
  export default {
    data () {
      return {
        options: {
          target: '/ffmpeg-video/upload', // '//jsonplaceholder.typicode.com/posts/',
          testChunks: true,//Enable slice test. If it exists, it will not be uploaded. If it does not exist, it will be uploaded
          //Before the whole file upload starts, a get request with the same name as' / ffmpeg video / upload 'will be sent, and the response body is message, which will be passed into the following function
          //The backend is only requested once, but the following function is called automatically every time the fragment is sent
          checkChunkUploadedByResponse:function(chunk,message){//Call the function every time you upload a fragment
            let messageObj = JSON.parse(message)
            if(messageObj.needSkiped){
              return true//If the upload is completed, skip directly
            }else{//Otherwise, according to
              return (messageObj.uploadList || []).indexOf(chunk.offset+1+"")>=0
            }
            return true

          }
        },
        attrs: {
          accept: 'image/*'
        },
        statusText: {
          success: 'succeed',
          error: 'Error ',
          uploading: 'Uploading',
          paused: 'Suspended',
          waiting: 'Waiting'
        }
      }
    },
    methods: {
      complete () {
        // debugger
        console.log('complete', arguments)
      },
      fileComplete () {
        console.log('file complete', arguments)
      },
      fileSuccess(){
        this.$axios({
          method:'post',
          url:'/ffmpeg-video/upload-success',
          data: arguments[1]//This is the callback value of fileSuccess, which can be used directly
        }).then(response =>{
          console.log("fileSuccess")
        },error =>{

        })
      }
    },
    mounted () {
      console.log( localStorage.getItem("Access-Token"))
      this.$nextTick(() => {
        window.uploader = this.$refs.uploader.uploader
      })
    }
  }
</script>

<style>
  .uploader-example {
    width: 880px;
    padding: 15px;
    margin: 40px auto 0;
    font-size: 12px;
    box-shadow: 0 0 10px rgba(0, 0, 0, .4);
  }
  .uploader-example .uploader-btn {
    margin-right: 4px;
  }
  .uploader-example .uploader-list {
    max-height: 440px;
    overflow: auto;
    overflow-x: hidden;
    overflow-y: auto;
  }
</style>

3. Operation results







4, Actual development

You can refer to this practical article https://blog.csdn.net/grd_java/article/details/121679400

Keywords: Java Javascript Front-end Vue.js

Added by amylisa on Wed, 08 Dec 2021 04:47:49 +0200