普通上传功能
1. 前端页面
<div class="home">
<a-upload
name="file"
:multiple="true"
action="/open-api/file/upload"
:headers="headers"
@change="handleChange"
>
<a-button> <a-icon type="upload" /> Click to Upload </a-button>
</a-upload>
</div>
</template>
<script>
export default {
data() {
return {
headers: {
authorization: "authorization-text",
},
};
},
methods: {
handleChange(info) {
if (info.file.status !== "uploading") {
console.log(info.file, info.fileList);
}
if (info.file.status === "done") {
this.$message.success(`${info.file.name} file uploaded successfully`);
} else if (info.file.status === "error") {
this.$message.error(`${info.file.name} file upload failed.`);
}
},
},
};
</script>
2. 跨域请求
devServer: {
port: 8080,
proxy: {
"/": { target: "http://localhost:8091", changeOrigin: true },
},
},
};
3. 后端接口完成
package com.longfor.board.controller.file;
import io.swagger.annotations.Api;
import org.springframework.stereotype.Controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.util.List;
@RestController
@Api(tags = "决策字典")
@RequestMapping("/open-api/file")
public class FileController {
//读取application.properties文件中的filePath属性
@Value("/Users/admin/Desktop/company/project/ps-project/ps-board-server/")
private String filePath;
/**
* 前往上传页面
* @return 页面名称
*/
@GetMapping({"/upload", ""})
public String goIndex() {
return "upload";
}
/**
* 将文件保存到指定文件夹
* @param file 单个文件
* @param files 多个文件
* @return 重定向到controller层中前往下载页面的url
* @throws IOException
*/
@PostMapping("/upload")
public String uploadAndGoDownLoad(@RequestPart("file") MultipartFile file,
@RequestPart("files") List<MultipartFile> files) throws IOException {
//判断文件夹是否存在,不存在时,创建文件夹
File directoryFile = new File(filePath);
if (!directoryFile.exists()) {
//创建多个文件夹
directoryFile.mkdirs();
}
//判断文件是否为空,不为空时,保存文件
if (!file.isEmpty()) {
saveFile(file);
}
//判断上传文件个数是否为0
if (files.size() > 0) {
for (MultipartFile multipartFile : files) {
if (!multipartFile.isEmpty()) {
saveFile(multipartFile);
}
}
}
return "redirect:/goDownload";
}
/**
* 保存所有的所有上传的文件名称,前往下载页面
* @param model
* @return 页面名称
*/
@GetMapping("/goDownload")
public String goDownload(Model model) {
File file = new File(filePath);
//判断文件夹是否存在
if (file.exists()) {
//获取文件夹下面的所有名称
String[] list = file.list();
model.addAttribute("fileNames", list);
}
return "download";
}
/**
* 保存文件到指定位置
* @param file 需要上传的文件
* @throws IOException
*/
public void saveFile(MultipartFile file) throws IOException {
//获取文件名
String name = file.getOriginalFilename();
file.transferTo(new File(filePath + name));
}
}
```
```
实现断点续传
<template>
<div class="home">
<a-upload
name="file"
:multiple="true"
action="/open-api/file/upload"
:headers="headers"
@change="handleChange"
>
<a-button> <a-icon type="upload" /> Click to Upload </a-button>
</a-upload>
<a-upload-dragger
name="file"
class="upload-drag-box"
:multiple="false"
action="/open-api/file/cutupload"
:beforeUpload="beforeUpload"
>
<p class="ant-upload-drag-icon">
<a-icon type="upload" /> Click to Upload
</p>
<p class="ant-upload-text">点击选择或拖拽文件到此处上传</p>
</a-upload-dragger>
{{ cutRate }}
</div>
</template>
<script>
import BreakPointLoad from "@/utils/cutrate/index";
import Axios from "axios";
const { CancelToken } = Axios;
let axiosList = [];
export default {
data() {
return {
headers: {
authorization: "authorization-text",
},
handStop: false,
loading: false,
fileData: {
uid: "",
name: "",
status: "",
response: "",
url: "",
propName: "",
},
state: {
curtData: [],
finished: false,
fileName: "",
waitLoadList: [],
handLoadList: [],
},
cutRate: 0,
};
},
methods: {
// 上传前
beforeUpload(e) {
this.fileData = e;
this.state.fileName = e.name || "file";
// 不上传,等待切片后上传
this.cutFile();
return false;
},
// 切片
async cutFile() {
let cutLoad = new BreakPointLoad();
let chunkList = await cutLoad.inputChange(this.fileData, (list, len) => {
// 已切片长度
let listLen = list.length;
// 切片进度
this.cutRate = (len / listLen) * 100;
});
// 是否完成切片
this.state.finished = true;
this.state.curtData = chunkList;
debugger;
// this.uploadCutfile();
this.handClassify()
},
// 上传切片
uploadCutfile() {
this.uploadChunks(
this.state.waitLoadList,
this.state.fileName,
(percent) => {
this.uploadRate = percent;
}
);
},
// 切片数组转为FormData表单数据格式
uploadChunks(chunkList, fileName, cb) {
if (!Array.isArray(chunkList)) {
throw `chunkList is not a Array`;
// return;
}
const requestList = chunkList.map((chunk) => {
let formData = new FormData();
for (let i in chunk) {
formData.append(i, chunk[i]);
}
return formData;
});
this.sendRequest(requestList, (percent) => {
cb(percent);
});
},
// 上传
sendRequest(chunkList, cb) {
let len = chunkList.length;
// 总数
const uploadedTotal = len;
// 已循环次数
let sendCount = 0;
// 已上传数量
let uploadedCount = 0;
while (len > 0 && sendCount <= uploadedTotal) {
const source = CancelToken.source();
const formData = chunkList[sendCount];
sendCount++;
len--;
Axios({
method: "post",
url: "baseUrl.uploadChunkUrl",
data: formData,
headers: {
"Content-Type": "multipart/form-data",
},
cancelToken: source.token,
}).then(() => {
uploadedCount++;
this.rmUploadedRequest(source);
cb(Math.round((uploadedCount / uploadedTotal) * 100));
});
axiosList.push(source);
}
},
rmUploadedRequest(source) {
axiosList.forEach((item, index) => {
if (item.token === source.token) {
axiosList.splice(index, 1);
}
});
},
// 切片对比
async handClassify(auto) {
this.loading = true;
let fileHash = "";
const { curtData } = this.state;
Array.isArray(curtData) && (fileHash = curtData[0].fileHash);
let res = await this.findReadyChunk(fileHash);
if (res.code) {
const { waitList, handLoadList } = this.useFilechunkFilter(
this.state.curtData,
res.data
);
this.state.waitLoadList = waitList;
this.state.handLoadList = handLoadList;
console.log("auto", auto);
auto && this.uploadCutfile();
}
this.loading = false;
},
findReadyChunk(fileChunk) {
Axios({
url: `chunk/readychunk/${fileChunk}`,
});
},
useFilechunkFilter(waitFiles, upedFiles) {
let handLoadList = [];
let waitList = [];
waitList = waitFiles.filter((file) => {
const index = upedFiles.findIndex((fileHash) => {
return fileHash === file.fileMd5No;
});
index > -1 && handLoadList.push(file);
return index === -1;
});
return {
handLoadList,
waitList,
};
},
// 暂停、继续上传
stopUpload() {
this.handStop = !this.handStop;
// 暂停
if (this.handStop) {
this.handPauseUpload();
} else {
// 继续
this.handClassify(true);
}
},
handleChange(info) {
if (info.file.status !== "uploading") {
console.log(info.file, info.fileList);
}
if (info.file.status === "done") {
this.$message.success(`${info.file.name} file uploaded successfully`);
} else if (info.file.status === "error") {
this.$message.error(`${info.file.name} file upload failed.`);
}
},
},
};
</script>
依赖文件
* @Author: wfl
* @LastEditors: wfl
* @description:
* @updateInfo:
* @Date: 2020-12-08 13:56:54
* @LastEditTime: 2021-03-19 15:28:03
*/
import getFileChunkMd5 from "./getFileMd5";
// 切片大小 2M
export let CHUNK_SIZE = 2 * 1024 * 1024;
// export interface FileBlob {
// file: Blob;
// type?: String;
// }
// export interface IChunkData {
// fileHash: string;
// fileMd5No: string;
// chunk: Blob;
// percentage?: number;
// type?: string;
// }
export default class BreakPointLoad {
// 切片数据
fileChunkList=[];
// 文件md5
fileMd5Data= "";
// 最终数据
chunkData= [];
// 文件类型
type= "";
// 获取上传文件 size字节,例:切片大小为1M则size= 1 * 1024 * 1024
async inputChange(file, cb, size) {
// 若传入切片大小,则按照传入切片大小切片
if (size) {
CHUNK_SIZE = size;
}
this.type = file.type;
const name = file.name;
if (file) {
this.fileChunkList = await this.createFileChunk(file, (data, size) => {
cb(data, size);
});
this.fileMd5Data = await getFileChunkMd5(this.fileChunkList);
this.chunkData = this.fileChunkList.map(({ file }, index) => ({
// 文件名
name: name,
// 文件类型
type: this.type,
// 文件md5
fileHash: this.fileMd5Data,
// 切片标记
fileMd5No: `${this.fileMd5Data}-${index}`,
// 切片文件
chunk: file,
// 上传进度
percentage: 0,
}));
return this.chunkData;
}
}
// 文件切片
async createFileChunk(file, cb) {
const list = [];
const type = this.type;
// 切片位置
let ssize = 0;
let len = await Math.ceil(file.size / CHUNK_SIZE);
while (ssize < file.size) {
const data = {
file: file.slice(ssize, ssize + CHUNK_SIZE),
type: type,
};
list.push(data);
ssize += CHUNK_SIZE;
cb(list, len);
}
return await list;
}
}
加密文件
* @Author: wfl
* @LastEditors: wfl
* @description:
* @updateInfo:
* @Date: 2020-12-08 14:50:40
* @LastEditTime: 2020-12-08 16:21:29
*/
import SparkMD5 from "spark-md5";
// import { FileBlob, CHUNK_SIZE } from './index'
// 获取文件Md5值
const getFileChunkMd5 = (fileChunkList) => {
return new Promise((resolve) => {
// 总切片数
const chunkSize = fileChunkList.length;
// 当前处理位置
let currentChunk = 0;
// SparkMD5实例的ArrayBuffer
let spark = new SparkMD5.ArrayBuffer();
let fileReader = new FileReader();
fileReader.onload = (e) => {
try {
spark.append(e.target.result);
} catch (error) {
console.log("获取Md5错误,错误位置:" + currentChunk);
}
currentChunk++;
if (currentChunk < chunkSize) {
loadNext();
} else {
console.info("获取Md5完成");
resolve(spark.end());
}
};
fileReader.onerror = function () {
console.warn("Md5:文件读取错误");
};
function loadNext() {
fileReader.readAsArrayBuffer(fileChunkList[currentChunk].file);
}
loadNext();
});
};
export default getFileChunkMd5;