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 |
- I hope you can support this up and speak really well
1, Construction of helloworld environment
1. Front end environment construction
- Download the Vue simple upload source code https://github.com/simple-uploader/vue-uploader

- 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)

- Solve cross domain problems

'/ffmpeg-video':{
target:'http://localhost:3000',
changeOrigin:true,
pathRewrite:{
'^/ffmpeg-video':'/ffmpeg-video'
}
}
- npm install installs all dependencies

- npm run dev startup project


2. Back end environment
- Create a basic spring boot project with spring boot starter web dependency
- Configuration file, configuration upload path and port number (the port number needs to be consistent with the one specified by your front end)

- 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;
}
- 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");
}
}
- 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
- debug start backend
- Remove the built-in debugger and start the front end

- Upload file

2, Source code analysis
Before looking at the source code, let's learn a few knowledge |
---|
- directive allows you to create an instruction and encapsulate the corresponding logic
- 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
- The function of extends is not to copy, but to inherit and extend
- 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

- Parent components can use props to transfer data to child components.
- Child components can use $emit to let parent components listen to custom events.
- VM. $emit (event name, passed parameter) / / trigger the event on the current instance
- VM. $on (event name, function function)// Run fn after listening for event events
- 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
- Because Vue simple upload is an encapsulated simple uploader.js, you need to know the common properties and events
- Common properties

- 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
- First, the uploader.vue file provides an uploader

mixins.js this file is specially provided for component mixing through the minxins instruction |
---|

- This file exposes the support variable and injects the uploader through inject
2. uploader-btn


- The above assignBrowse method is simple-uploader.js. The specific reason is explained in uploader.vue
- This method not only transfers three props variables, but also transfers itself in, and obtains the dom node through $refs (this. $refs. Name)
- 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) |
---|

- 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
- 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
- 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
1. Back end
- Entity class

import lombok.Data;
@Data
public class FileInfo {
private String uniqueIdentifier;//File unique id
private String name;//file name
}
- 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);
}
}
- 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");
}
}
- 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
- Introducing axios


- Judgment before uploading: before uploading a file, first judge whether a file already exists (the code is placed at the end)

- 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