?
<uploadFolder
:targetUrl="targetUrl"
ref="uploader"
@complete="onFileSuccess"
/>
<template>
<div>
<uploader
ref="uploader"
:key="uploader_key"
@file-added="fileAdded"
@filesAdded="filesAdded"
:options="options"
@file-progress="onFileProgress"
@file-success="onFileSuccess">
<uploader-unsupport></uploader-unsupport>
<uploader-btn
v-show="false"
ref="selectFileBtn"
id="selectFileBtn"
:directory="true"
:single="true">文件夹</uploader-btn>
</uploader>
</div>
</template>
<script>
import { getTno, getToken } from '@/utils/auth';
import FileMd5 from './uploader/common/file-md5';
export default {
props:{
targetUrl:{
type:String,
default:()=>{
return '/';
}
}
},
data(){
return{
headers: {
Authorization: 'Bearer ' + getToken()
},
uploader_key: new Date().getTime(),
options: {
target: this.targetUrl,//SpringBoot后台接收文件夹数据的接口
testChunks: false,//是否分片-不分片
headers:this.headers,
query:{
tno:getTno()
}
},
filesCount:0,
}
},
created() {
if (getToken()) {
this.headers = {
Authorization: 'Bearer ' + getToken()
};
} else {
this.headers = {};
}
},
methods:{
onFileSuccess: function (rootFile, file, response, chunk) {
this.filesCount--;
if(this.filesCount == 0){
this.$emit("complete",rootFile, file, JSON.parse(response), chunk);
}
this.$refs.uploader.fileRemoved(file);
//这里可以根据response(接口)返回的数据处理自己的实际问题(如:从response拿到后台返回的想要的数据进行组装并显示)
//注,这里从文件夹每上传成功一个文件会调用一次这个方法
this.$emit("on-file-success",rootFile, file, JSON.parse(response), chunk);
},
fileAdded(file){
this.filesCount++;
FileMd5(file.file, (e, md5) => {
file.identifier = md5;
file.status = 1;
});
this.$emit("file-added",file);
},
filesAdded(files){
},
onFileError(rootFile, file, response, chunk) {
console.log(error)
},
onFileProgress(rootFile, file, chunk) {
console.log(`上传中 ${file.name},chunk:${chunk.startByte / 1024 / 1024} ~ ${chunk.endByte / 1024 / 1024}`)
},
fileSelect(){
if (this.$refs.selectFileBtn) {
this.$refs.selectFileBtn.$el.click();
}
}
}
}
</script>
file-md5.js
'use strict';
// import SparkMD5 from './js-spark-md5.js'
import SparkMD5 from 'spark-md5'
export default function (file, callback) {
// console.log(new SparkMD5.ArrayBuffer())
var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,
file = file,
chunkSize = 3145728, // Read in chunks of 3MB
chunks = Math.ceil(file.size / chunkSize),
currentChunk = 0,
spark = new SparkMD5.ArrayBuffer(),
fileReader = new FileReader();
// console.log(spark)
fileReader.onload = function (e) {
// console.log('read chunk nr', currentChunk + 1, 'of', chunks);
spark.append(e.target.result); // Append array buffer
currentChunk++;
if (currentChunk < chunks) {
loadNext();
} else {
callback(null, spark.end());
// console.log('finished loading');
}
};
fileReader.onerror = function () {
callback('oops, something went wrong.');
};
function loadNext() {
var start = currentChunk * chunkSize,
end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;
fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
}
loadNext();
};
?
controller层
public Result parseBatchDBFile(String filename, String identifier, Long currentChunkSize, Integer totalChunks,
Integer chunkNumber, MultipartFile file,String tno,String relativePath) {
UploadRspVO uploadRspVO = null;
try {
uploadRspVO = UploadUtils.webUpload(filename,identifier,currentChunkSize,totalChunks,chunkNumber-1,file,relativePath,tno);
} catch (IOException e) {
e.printStackTrace();
return Result.fail("文件上传失败.");
}
return Result.ok(uploadRspVO);
}
UploadUtils
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
/**
* 分块上传工具类
* 记录文件签名
*/
@Component
public class UploadUtils {
@org.springframework.beans.factory.annotation.Value("${fzhx.file.download}")
private String download;
/**
* 内部类记录分块上传文件信息
*/
private static class Value {
String name;
boolean[] status;
Value(int n) {
this.name = generateFileName();
this.status = new boolean[n];
}
}
private static Map<String, Value> chunkMap = new HashMap<>();
/**
* 判断文件所有分块是否已上传
*
* @param key 关键字
* @return 返回了是否上传成功,true表示上传成功
*/
public static boolean isUploaded(String key) {
if (isExist(key)) {
for (boolean b : chunkMap.get(key).status) {
if (!b) {
return false;
}
}
return true;
}
return false;
}
/**
* 判断文件是否有分块已上传
*
* @param key
* @return
*/
private static boolean isExist(String key) {
return chunkMap.containsKey(key);
}
/**
* 为文件添加上传分块记录
*
* @param key
* @param chunk
*/
public static void addChunk(String key, int chunk) {
chunkMap.get(key).status[chunk] = true;
}
/**
* 从map中删除键为key的键值对
*
* @param key
*/
public static void removeKey(String key) {
if (isExist(key)) {
chunkMap.remove(key);
}
}
/**
* 获取随机生成的文件名
*
* @param key
* @param chunks
* @return
*/
public static String getFileName(String key, int chunks) {
if (!isExist(key)) {
synchronized (UploadUtils.class) {
if (!isExist(key)) {
chunkMap.put(key, new Value(chunks));
}
}
}
return chunkMap.get(key).name;
}
/**
* 分片上传文件或文件夹(还原目录结构)或不分片
*
* @param name 带文件类型的原文件名称
* @param md5
* @param size
* @param chunks
* @param chunk
* @param file
* @param tno 时间戳
* @return
* @throws IOException
*/
public static UploadRspVO webUpload(String name, String md5, Long size, Integer chunks,
Integer chunk, MultipartFile file, String filePath, String tno) throws IOException {
UploadRspVO uploadRspVO = new UploadRspVO().setSuccess(false).setTno(tno);
String path = "";
if (chunks != null && chunks != 0) {
path = FileUtils.uploadWithBlock(md5, size, chunks, chunk, file);
} else {
path = FileUtils.upload(file);
}
if (UploadUtils.isUploaded(md5) && chunk == chunks - 1) {
try {
//建立临时文件夹
String tmpSavePath = Paths.get(System.getProperty("user.dir")+"/downloads/").toAbsolutePath().toString();
FileUtil.mkdir(tmpSavePath);
//保存到本地的文件路径
String localFilePath = "";
//上传类型是文件夹中的文件
if (StrUtil.isNotBlank(filePath)) {
//去掉文件名获取相对路径
String uploadFolderPath = StrUtil.removeSuffix(filePath, StrUtil.SLASH + name);
//文件保存在本地的路径=临时文件夹加上前端传来的相对路径
String localFolderPath = Paths.get(tmpSavePath, uploadFolderPath).toString();
FileUtil.mkdir(localFolderPath);
localFilePath = Paths.get(localFolderPath, name).toString();
} else {
localFilePath = Paths.get(tmpSavePath, name).toString();
}
new File(path).renameTo(new File(localFilePath));
uploadRspVO.setSavePath(localFilePath).setSuccess(true).setMd5(md5);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (StrUtil.isNotBlank(path)) {
FileUtil.del(path);
}
}
}
return uploadRspVO;
}
/**
* 分片上传文件或文件夹(还原目录结构)或不分片
*
* @param name 带文件类型的原文件名称
* @param md5
* @param size
* @param chunks
* @param chunk
* @param file
* @param tno 时间戳
* @return
* @throws IOException
*/
public UploadRspVO upload(String name, String md5, Long size, Integer chunks,
Integer chunk, MultipartFile file, String tno) throws IOException {
UploadRspVO uploadRspVO = new UploadRspVO().setSuccess(false).setTno(tno);
String path = "";
if (chunks != null && chunks != 0) {
path = FileUtils.uploadWithBlock(md5, size, chunks, chunk, file);
} else {
path = FileUtils.upload(file);
}
if (UploadUtils.isUploaded(md5)) {
try {
//建立临时文件夹
String tmpSavePath = Paths.get(download, IdUtil.fastSimpleUUID()).toAbsolutePath().toString();
FileUtil.mkdir(tmpSavePath);
String filePath = Paths.get(tmpSavePath, name).toString();
new File(path).renameTo(new File(filePath));
uploadRspVO.setSavePath(filePath).setSuccess(true).setMd5(md5);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (StrUtil.isNotBlank(path)) {
FileUtil.del(path);
}
}
}
return uploadRspVO;
}
}
UploadRspVO??
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
public class UploadRspVO {
@ApiModelProperty(value = "是否上传完成")
private Boolean success;
@ApiModelProperty(value = "时间戳")
private String tno;
@ApiModelProperty(value = "服务器保存文件路径")
private String savePath;
@ApiModelProperty(value = "文件夹路径")
private String folderPath;
@ApiModelProperty(value = "md5")
private String md5;
}
?
?FileUtils
import cn.hutool.core.io.FileUtil;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.nio.file.Paths;
import java.util.UUID;
/**
* 文件操作工具类
*/
public class FileUtils {
// public static final String PATH = "d:/download/";
/**
* 写入文件
*
* @param target
* @param src
* @throws IOException
*/
public static void write(String target, InputStream src) throws IOException {
OutputStream os = new FileOutputStream(target);
byte[] buf = new byte[1024];
int len;
while (-1 != (len = src.read(buf))) {
os.write(buf, 0, len);
}
os.flush();
os.close();
}
/**
* 分块写入文件
*
* @param target
* @param targetSize
* @param src
* @param srcSize
* @param chunks
* @param chunk
* @throws IOException
*/
public static void writeWithBlok(String target, Long targetSize, InputStream src, Long srcSize, Integer chunks, Integer chunk) throws IOException {
RandomAccessFile randomAccessFile = new RandomAccessFile(target, "rw");
randomAccessFile.setLength(targetSize);
if (chunk == chunks - 1) {
randomAccessFile.seek(targetSize - srcSize);
} else {
randomAccessFile.seek(chunk * srcSize);
}
byte[] buf = new byte[1024];
int len;
while (-1 != (len = src.read(buf))) {
randomAccessFile.write(buf, 0, len);
}
randomAccessFile.close();
}
/**
* 生成随机文件名
*
* @return
*/
public static String generateFileName() {
return UUID.randomUUID().toString();
}
/**
* 创建文件
*
* @param path
* @return
*/
public static File createFile(String path) {
if (path == null) {
return null;
} else {
File file = new File(path);
File fileParent = file.getParentFile();
if (!fileParent.exists()) {
fileParent.mkdirs();
}
return new File(path);
}
}
/**
* 上传文件
*
* @param
* @param file
*/
public static String upload(MultipartFile file) throws IOException {
String generateName = generateFileName();
String path = Paths.get(System.getProperty("user.dir")+"/downloads/").toAbsolutePath().toString() + generateName;
File parent = FileUtil.newFile(path).getParentFile();
if (!parent.exists()) {
parent.mkdirs();
}
FileUtils.write(path, file.getInputStream());
return path;
}
/**
* 分块上传文件
*
* @param md5
* @param size
* @param chunks
* @param chunk
* @param file
* @throws IOException
*/
public static String uploadWithBlock(String md5,
Long size,
Integer chunks,
Integer chunk,
MultipartFile file) throws IOException {
String fileName = getFileName(md5, chunks);
String path = Paths.get(System.getProperty("user.dir")+"/downloads/").toAbsolutePath().toString() + fileName;
FileUtils.writeWithBlok(path, size, file.getInputStream(), file.getSize(), chunks, chunk);
addChunk(md5, chunk);
/*if (isUploaded(md5)) {
}*/
return path;
}
/**
* 为文件添加上传分块记录
*
* @param key
* @param chunk
*/
public static void addChunk(String key, int chunk) {
chunkMap.get(key).status[chunk] = true;
}
/**
* 获取随机生成的文件名
*
* @param key
* @param chunks
* @return
*/
public static String getFileName(String key, int chunks) {
if (!isExist(key)) {
synchronized (UploadUtils.class) {
if (!isExist(key)) {
chunkMap.put(key, new Value(chunks));
}
}
}
return chunkMap.get(key).name;
}
}