springboot+vue+elementui大文件分片上传
工具类方法:
/** * 大文件分片上传 * @param fileName 文件名 * @param file 文件 * @param fileKey 文件key * @param shardIndex 当前分片下标 * @param shardTotal 分片总量 */ public static void bigUpload(String fileName,MultipartFile file, String fileKey, Long shardIndex, Long shardTotal) throws Exception { String fileDir = getDefaultBaseDir() +"/"+ DateUtils.datePath() + "/" + fileKey; File dir=new File(fileDir); if (!dir.exists()) { dir.mkdirs(); } File dest = new File(fileDir+"/" + fileKey + "." + shardIndex); // 分片文件保存到文件目录 file.transferTo(dest); if (shardIndex == shardTotal) { merge(fileName, shardTotal, fileKey); } } /** * 分片大文件上传,文件合并 * * @param fileName 文件名比如123.mp4 * @param shardTotal 分片总量 * @param fileKey 文件key * @throws Exception */ private static void merge(String fileName, Long shardTotal, String fileKey) throws Exception { String mergeFilePath = getDefaultBaseDir()+"/" + DateUtils.datePath() + "/" + fileKey + "/" + fileName; File newFile = new File(mergeFilePath); if (newFile.exists()) { newFile.delete(); } FileOutputStream outputStream = new FileOutputStream(newFile, true);//文件追加写入 FileInputStream fileInputStream = null;//分片文件 byte[] byt = new byte[10 * 1024 * 1024]; int len; try { for (int i = 0; i < shardTotal; i++) { // 读取第i个分片 String shardFilePath = getDefaultBaseDir() +"/"+ DateUtils.datePath() + "/" + fileKey + "/" + fileKey + "." + (i + 1); fileInputStream = new FileInputStream(shardFilePath); while ((len = fileInputStream.read(byt)) != -1) { outputStream.write(byt, 0, len);//一直追加到合并的新文件中 } } } catch (IOException e) { e.printStackTrace(); } finally { try { if (fileInputStream != null) { fileInputStream.close(); } outputStream.close(); System.gc(); } catch (Exception e) { } } }
controller需要实现两个接口:上传文件和分片文件状态检查。
@GetMapping("/check")
public AjaxResult check(@RequestParam String key) {
PanoramicFileTb fileTb = panoramicFileTbService.selectLatestIndex(key);
log.info("检查分片:{}", key);
return AjaxResult.success(fileTb);
}
/**
* 大文件上传
*
* @param file
* @param filePojo
* @return
* @throws Exception
*/
@PreAuthorize("@ss.hasPermi('system:BusinessFile:add')")
@Log(title = "文件记录", businessType = BusinessType.INSERT)
@PostMapping("/big-upload")
public AjaxResult bigUpload(@RequestParam(value = "file") MultipartFile file,
FilePojoVo filePojo) throws Exception {
FileUploadUtils.bigUpload(filePojo.getFileName(),file, filePojo.getKey(), filePojo.getShardIndex(), filePojo.getShardTotal());
log.info("文件分片 {} 保存完成", filePojo.getShardIndex());
PanoramicFileTb fileTb = PanoramicFileTb.builder()
.fKey(filePojo.getKey())
.fIndex(filePojo.getShardIndex())
.fTotal(filePojo.getShardTotal())
.fName(filePojo.getFileName())
.build();
if (panoramicFileTbService.isNotExist(filePojo.getKey())) {
panoramicFileTbService.saveFile(fileTb);
} else {
panoramicFileTbService.UpdateFile(fileTb);
}
return AjaxResult.success();
}
public class FilePojoVo { private String key; private String fileName; private Long shardIndex; private Long shardSize; private Long shardTotal; private Long size; private String suffix; public String getKey() { return key; } public void setKey(String key) { this.key = key; } public String getFileName() { return fileName; } public void setFileName(String fileName) { this.fileName = fileName; } public Long getShardIndex() { return shardIndex; } public void setShardIndex(Long shardIndex) { this.shardIndex = shardIndex; } public Long getShardSize() { return shardSize; } public void setShardSize(Long shardSize) { this.shardSize = shardSize; } public Long getShardTotal() { return shardTotal; } public void setShardTotal(Long shardTotal) { this.shardTotal = shardTotal; } public Long getSize() { return size; } public void setSize(Long size) { this.size = size; } public String getSuffix() { return suffix; } public void setSuffix(String suffix) { this.suffix = suffix; } }
@Builder public class PanoramicFileTb extends BaseEntity { private static final long serialVersionUID = 1L; /** $column.columnComment */ private Integer id; /** 文件唯一标识 */ @Excel(name = "文件唯一标识") private String fKey; /** 第几个分片 */ @Excel(name = "第几个分片") private Long fIndex; /** 共有几个分片 */ @Excel(name = "共有几个分片") private Long fTotal; /** 文件名称,后面可以返回出去 */ @Excel(name = "文件名称,后面可以返回出去") private String fName; public void setId(Integer id) { this.id = id; } public Integer getId() { return id; } public void setfKey(String fKey) { this.fKey = fKey; } public String getfKey() { return fKey; } public void setfIndex(Long fIndex) { this.fIndex = fIndex; } public Long getfIndex() { return fIndex; } public void setfTotal(Long fTotal) { this.fTotal = fTotal; } public Long getfTotal() { return fTotal; } public void setfName(String fName) { this.fName = fName; } public String getfName() { return fName; } @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) .append("id", getId()) .append("fKey", getfKey()) .append("fIndex", getfIndex()) .append("fTotal", getfTotal()) .append("fName", getfName()) .toString(); } }
上面两个实体类,FilePojoVo是必须的,需要和页面做数据交互,PanoramicFileTb是非必须的,可以选择把FilePojoVo存储到数据库、内存、redis等都可以,只要能验证到对应文件的md5值是否已存在。我这里存到数据库是因为可以做急速上传,已上传的文件md5值可能会一样,加上其他验证方式,这样已上传过的文件再上传其实就不需要再传了。
下面附上对应的service方法,其中mapper方法无非就是用key去查数据或更新数据。就不提供出来了:
@Override public void saveFile(PanoramicFileTb fileTb) { panoramicFileTbMapper.insertPanoramicFileTb(fileTb); } @Override public void UpdateFile(PanoramicFileTb fileTb) { panoramicFileTbMapper.UpdateFile(fileTb); } @Override public boolean isNotExist(String key){ Integer id = panoramicFileTbMapper.isExist(key); if (ObjectUtils.isEmpty(id)) { return true; } return false; } @Override public PanoramicFileTb selectLatestIndex(String key) { PanoramicFileTb fileTb = panoramicFileTbMapper.selectLatestIndex(key); if (ObjectUtils.isEmpty(fileTb)) { fileTb = PanoramicFileTb.builder().fKey(key).fIndex(-1L).fName("").build(); } return fileTb; }
以上就是后台相关代码,可以根据自己的需求扩展功能。
下面是前端代码,需要npm install --save js-md5安装,引用import md5 from 'js-md5';
<template>
<div class="file-upload">
<h1>大文件分片上传、极速秒传</h1>
<div class="file-upload-el">
<el-upload
class="upload-demo"
drag
ref="upload"
:limit=1
:action="actionUrl"
:on-exceed="handleExceed"
:http-request="handUpLoad"
:auto-upload="false"
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
</el-upload>
<el-button style="margin-left: 10px;" size="small" type="success" @click="submitUpload">上传到服务器</el-button>
</div>
<div>
<!-- autoplay-->
<el-card class="v-box-card">
<video :src="videoUrl"
controls
autoplay
class="video"
width="100%">
</video>
</el-card>
</div>
</div>
</template>
<script>
export default {
name: "FileUpload",
data() {
return {
actionUrl: 'http://localhost:8098/upload',//上传的后台地址
shardSize: 10 * 1024 * 1024,
videoUrl: ''
};
},
methods: {
handleExceed(files, fileList) {
this.$message.warning(`当前限制选择 1个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.length} 个文件`);
},
submitUpload() {
this.$refs.upload.submit();
},
async check(key) {
var res = await this.$http.get('/check', {
params: {'key': key}
})
let resData = res.data;
return resData.data;
},
async recursionUpload(param, file) {
//FormData私有类对象,访问不到,可以通过get判断值是否传进去
let _this = this;
let key = param.key;
let shardIndex = param.shardIndex;
let shardTotal = param.shardTotal;
let shardSize = param.shardSize;
let size = param.size;
let fileName = param.fileName;
let suffix = param.suffix;
let fileShard = _this.getFileShard(shardIndex, shardSize, file);
//param.append("file", fileShard);//文件切分后的分片
//param.file = fileShard;
let totalParam = new FormData();
totalParam.append('file', fileShard);
totalParam.append("key", key);
totalParam.append("shardIndex", shardIndex);
totalParam.append("shardSize", shardSize);
totalParam.append("shardTotal", shardTotal);
totalParam.append("size", size);
totalParam.append("fileName", fileName);
totalParam.append("suffix", suffix);
let config = {
//添加请求头
headers: {"Content-Type": "multipart/form-data"}
};
console.log(param);
var res = await this.$http.post('/upload', totalParam, config)
var resData = res.data;
if (resData.status) {
if (shardIndex < shardTotal) {
this.$notify({
title: '成功',
message: '分片' + shardIndex + '上传完成。。。。。。',
type: 'success'
});
} else {
this.videoUrl = resData.data;//把地址赋值给视频标签
this.$notify({
title: '全部成功',
message: '文件上传完成。。。。。。',
type: 'success'
});
}
if (shardIndex < shardTotal) {
console.log('下一份片开始。。。。。。');
// 上传下一个分片
param.shardIndex = param.shardIndex + 1;
_this.recursionUpload(param, file);
}
}
},
async handUpLoad(req) {
let _this = this;
var file = req.file;
/* console.log('handUpLoad', req)
console.log(file);*/
//let param = new FormData();
//通过append向form对象添加数据
//文件名称和格式,方便后台合并的时候知道要合成什么格式
let fileName = file.name;
let suffix = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length).toLowerCase();
//这里判断文件格式,有其他格式的自行判断
if (suffix != 'mp4') {
this.$message.error('文件格式错了哦。。');
return;
}
// 文件分片
// let shardSize = 10 * 1024 * 1024; //以10MB为一个分片
// let shardSize = 50 * 1024; //以50KB为一个分片
let shardSize = _this.shardSize;
let shardIndex = 1; //分片索引,1表示第1个分片
let size = file.size;
let shardTotal = Math.ceil(size / shardSize); //总片数
// 生成文件标识,标识多次上传的是不是同一个文件
let key = this.$md5(file.name + file.size + file.type);
let param = {
key: key,
shardIndex: shardIndex,
shardSize: shardSize,
shardTotal: shardTotal,
size: size,
fileName: fileName,
suffix: suffix
}
/*param.append("uid", key);
param.append("shardIndex", shardIndex);
param.append("shardSize", shardSize);
param.append("shardTotal", shardTotal);
param.append("size", size);
param.append("fileName", fileName);
param.append("suffix", suffix);
*/
let checkIndexData = await _this.check(key);//得到文件分片索引
let checkIndex = checkIndexData.findex;
//console.log(checkIndexData)
if (checkIndex == -1) {
this.recursionUpload(param, file);
} else if (checkIndex < shardTotal) {
param.shardIndex = param.shardIndex + 1;
this.recursionUpload(param, file);
} else {
this.videoUrl = checkIndexData.fname;//把地址赋值给视频标签
this.$message({
message: '极速秒传成功。。。。。',
type: 'success'
});
}
//console.log('结果:', res)
},
getFileShard(shardIndex, shardSize, file) {
let _this = this;
let start = (shardIndex - 1) * shardSize; //当前分片起始位置
let end = Math.min(file.size, start + shardSize); //当前分片结束位置
let fileShard = file.slice(start, end); //从文件中截取当前的分片数据
return fileShard;
},
}
}
</script>
<style scoped lang="less">
.file-upload {
.file-upload-el {
}
}
.v-box-card{
width: 50%;
}
</style>
本文来自博客园,作者:Rolay,转载请注明原文链接:https://www.cnblogs.com/rolayblog/p/18439702

浙公网安备 33010602011771号