Java实现分片上传(前端分,后端合并)

devtools/2024/10/8 22:48:05/

注:一些实体类数据自行修改 

1,api文件 

import request from '@/router/axiosInfo';export const uploadChunk = (data) => {return request({url: '/api/blade-sample/sample/covid19/uploadChunk',method: 'post',data})
}export const uploadMerge = (data) => {return request({url: '/api/blade-sample/sample/covid19/uploadMerge',method: 'post',data})
}

2, axios文件

/*** 全站http配置** axios参数说明* isSerialize是否开启form表单提交* isToken是否需要token*/
import axios from 'axios';
import store from '@/store/';
import router from '@/router/router';
import { serialize } from '@/util/util';
import { getToken } from '@/util/auth';
import { Message } from 'element-ui';
import { isURL } from "@/util/validate";
import website from '@/config/website';
import { Base64 } from 'js-base64';
import { baseUrl } from '@/config/env';
// import NProgress from 'nprogress';
// import 'nprogress/nprogress.css';
import crypto from '@/util/crypto';//默认超时时间
axios.defaults.timeout = 100000;
//返回其他状态码
axios.defaults.validateStatus = function (status) {return status >= 200 && status <= 500;
};
//跨域请求,允许保存cookie
axios.defaults.withCredentials = true;
// NProgress 配置
// NProgress.configure({
//   showSpinner: false
// });
//http request拦截
axios.interceptors.request.use(config => {//开启 progress bar// NProgress.start();//地址为已经配置状态则不添加前缀if (!isURL(config.url) && !config.url.startsWith(baseUrl)) {config.url = baseUrl + config.url}//headers判断是否需要const authorization = config.authorization === false;if (!authorization) {config.headers['Authorization'] = `Basic ${Base64.encode(`${website.clientId}:${website.clientSecret}`)}`;}//headers判断请求是否携带tokenconst meta = (config.meta || {});const isToken = meta.isToken === false;//headers传递token是否加密const cryptoToken = config.cryptoToken === true;//判断传递数据是否加密const cryptoData = config.cryptoData === true;const token = getToken();if (token && !isToken) {config.headers[website.tokenHeader] = cryptoToken? 'crypto ' + crypto.encryptAES(token, crypto.cryptoKey): 'bearer ' + token;}// 开启报文加密if (cryptoData) {if (config.params) {const data = crypto.encryptAES(JSON.stringify(config.params), crypto.aesKey);config.params = { data };}if (config.data) {config.text = true;config.data = crypto.encryptAES(JSON.stringify(config.data), crypto.aesKey);}}//headers中配置text请求if (config.text === true) {config.headers["Content-Type"] = "text/plain";}//headers中配置serialize为true开启序列化if (config.method === 'post' && meta.isSerialize === true) {config.data = serialize(config.data);}return config
}, error => {return Promise.reject(error)
});
//http response 拦截
axios.interceptors.response.use(res => {//关闭 progress bar// NProgress.done();//获取状态码const status = res.data.code || res.status;const statusWhiteList = website.statusWhiteList || [];const message = res.data.msg || res.data.error_description || '未知错误';const config = res.config;const cryptoData = config.cryptoData === true;//如果在白名单里则自行catch逻辑处理if (statusWhiteList.includes(status)) return Promise.reject(res);//如果是401则跳转到登录页面if (status === 401) store.dispatch('FedLogOut').then(() => router.push({ path: '/login' }));// 如果请求为非200否者默认统一处理if (status !== 200) {Message({message: message,type: 'error'});return Promise.reject(new Error(message))}// 解析加密报文if (cryptoData) {res.data = JSON.parse(crypto.decryptAES(res.data, crypto.aesKey));}return res;
}, error => {// NProgress.done();return Promise.reject(new Error(error));
});export default axios;

3,vue源码 

<template><el-upload class="upload-demo" :on-change="handleChange" :show-file-list="false" :limit="1"><el-button slot="trigger" size="small" type="primary">选取文件</el-button><el-button style="margin-left: 10px;" size="small" type="success" @click="uploadMerge" v-loading.fullscreen.lock='loading'>上传到服务器</el-button><el-progress :percentage="percentage" :color="customColor"></el-progress></el-upload>
</template>
<script>import { uploadMerge, uploadChunk } from "@/api/sample/sampleCovid19Info";export default {data() {return {percentage: 0,customColor: '#409eff',fileList: [], // 文件列表  chunks: [], // 切片数组  currentChunkIndex: 0, // 当前切片索引  totalChunks: 0, // 总切片数  currentFile: null, // 当前处理文件  totalSize: 0,loading: false};},methods: {async uploadMerge() {this.loading = trueconst formData = new FormData();formData.append('fileName', this.currentFile.name);formData.append('totalSize', this.totalSize);console.log(22222);try {let res = await uploadMerge({ fileName: this.currentFile.name, totalSize: this.totalSize })console.log(res, '2');if (res.data.code === 200) {this.fileList = []this.chunks = []this.currentChunkIndex = 0this.totalChunks = 0this.currentFile = nullthis.totalSize = 0this.$message({type: "success",message: "添加成功!"});this.loading = false} else {this.$message({type: "success",message: "添加失败!"});this.loading = false}} catch (error) {this.$message.error(error)} finally {this.loading = false}},handleChange(file, fileList) {this.totalSize = file.size// 当文件变化时,执行切片操作  if (file.status === 'ready') {this.currentFile = file.raw; // 获取原始文件对象  this.sliceFile(this.currentFile);}},async sliceFile(file) {const chunkSize = 1024 * 1024 * 10; // 切片大小设置为1MB  const totalSize = file.size;this.totalChunks = Math.ceil(totalSize / chunkSize);for (let i = 0; i < this.totalChunks; i++) {const start = i * chunkSize;const end = Math.min(start + chunkSize, totalSize);const chunk = file.slice(start, end);this.chunks.push(chunk);const formData = new FormData();formData.append('chunk', chunk);formData.append('chunkIndex', i);formData.append('totalChunks', this.totalChunks);formData.append('fileName', this.currentFile.name);formData.append('totalSize', this.totalSize);let res = await uploadChunk(formData)if (res.data.code === 200) {this.currentChunkIndex = i + 1;if (this.percentage != 99) {this.percentage = i + 1;}//判断文件是否上传完成if (this.currentChunkIndex === this.totalChunks) {this.percentage = 100this.$message({type: "success",message: "上传成功!"});}}}}}
}
</script>

4,切片请求接口

/*** 大上传文件*/
@PostMapping("/uploadChunk")
@ApiOperationSupport(order = 15)
@ApiOperation(value = "大上传文件", notes = "大上传文件")
public R uploadChunk(@RequestParam("chunk") MultipartFile chunk,@RequestParam("chunkIndex") Integer chunkIndex,@RequestParam("totalChunks") Integer totalChunks,@RequestParam("fileName") String fileName,@RequestParam("totalSize") Long totalSize) throws IOException {Boolean isOk = sampleCovid19Service.LargeFiles(chunk, chunkIndex, totalChunks, fileName, totalSize);return R.status(isOk);
}
/*** 大上传文件* @param chunk 文件流* @param chunkIndex 切片索引* @param totalChunks 切片总数* @param fileName 文件名称* @param totalSize 文件大小* @return*/
Boolean LargeFiles(MultipartFile chunk, Integer chunkIndex, Integer totalChunks, String fileName, Long totalSize);

 

/*** 大上传文件* @param chunk 文件流* @param chunkIndex 切片索引* @param totalChunks 切片总数* @param fileName 文件名称* @param totalSize 文件大小* @return*/
@Override
public Boolean LargeFiles(MultipartFile chunk, Integer chunkIndex, Integer totalChunks, String fileName, Long totalSize) {if (chunk == null) {throw new ServiceException("添加失败,文件名称获取失败");}if (chunkIndex == null) {throw new ServiceException("添加失败,分片序号不能为空");}if (fileName == null) {throw new ServiceException("添加失败,文件名称获取失败");}if (totalSize == null || totalSize == 0) {throw new ServiceException("添加失败,文件名大小不能为空或0");}//获取文件名称fileName = fileName.substring(0, fileName.lastIndexOf("."));File chunkFolder = new File(chunkPath + totalSize + "/" + fileName);//判断文件路径是否创建if (!chunkFolder.exists()) {chunkFolder.mkdirs();}//直接将文件写入File file = new File(chunkPath + totalSize + "/" + fileName + "/" + chunkIndex);try {chunk.transferTo(file);} catch (IOException e) {log.info("时间:{}" + new Date() + "文件名称:{}" + fileName + "上传失败");throw new ServiceException("时间:{}" + new Date() + "文件名称:{}" + fileName + "上传失败");}return true;
}

 5,合并接口

package org.springblade.sample.vo;import lombok.Data;@Data
public class UploadMergeVO {//文件上传名称private String fileName;//文件大小private Long totalSize;
}
/*** 大文件合并*/
@PostMapping("/uploadMerge")
@ApiOperationSupport(order = 16)
@ApiOperation(value = "文件合并", notes = "文件合并")
public R uploadMerge(@RequestBody UploadMergeVO vo) throws IOException {Boolean isOk = sampleCovid19Service.LargeMerge(vo);return R.status(isOk);
}
/***  大文件合并* @param vo * @return* @throws IOException*/
Boolean LargeMerge(UploadMergeVO vo) throws IOException;

 

/***  大文件合并* @param vo* @return* @throws IOException*/
@Override
public Boolean LargeMerge(UploadMergeVO vo) throws IOException {if (StringUtil.isBlank(vo.getFileName())) {throw new ServiceException("上传失败,文件名称获取失败");}if (vo.getTotalSize() == 0) {throw new ServiceException("上传失败,文件大小不能为空");}//块文件目录String fileNameInfo = vo.getFileName().substring(0, vo.getFileName().lastIndexOf("."));//合并文件夹File chunkFolder = new File(chunkPath + vo.getTotalSize() + "/" + fileNameInfo + "/");File depositFile = new File(sourceFile + vo.getTotalSize() + "/" + fileNameInfo + "/");//创建文件夹if (!depositFile.exists()) {depositFile.mkdirs();}//创建新的合并文件File largeFile = new File(depositFile + "/" + vo.getFileName());largeFile.createNewFile();//用于写文件RandomAccessFile raf_write = new RandomAccessFile(largeFile, "rw");//指针指向文件顶端raf_write.seek(0);//缓冲区byte[] b = new byte[1024];//分块列表File[] fileArray = chunkFolder.listFiles();// 转成集合,便于排序List<File> fileList = Arrays.asList(fileArray);// 从小到大排序Collections.sort(fileList, new Comparator<File>() {@Overridepublic int compare(File o1, File o2) {return Integer.parseInt(o1.getName()) - Integer.parseInt(o2.getName());}});//合并文件for (File chunkFile : fileList) {RandomAccessFile raf_read = new RandomAccessFile(chunkFile, "rw");int len = -1;while ((len = raf_read.read(b)) != -1) {raf_write.write(b, 0, len);}raf_read.close();}raf_write.close();//取出合并文件大小和hashlong fileSize = getFileSize(largeFile);int hashCode = largeFile.hashCode();//判断是否合并成功if (fileSize == vo.getTotalSize()) {//将内容写入数据库FileBack fileBack = new FileBack();fileBack.setFileName(vo.getFileName());fileBack.setFileSize(Integer.parseInt(String.valueOf(vo.getTotalSize())));fileBack.setUpdateTime(new Date());fileBack.setFileUrl(chunkFolder.toString());fileBack.setFileHash(String.valueOf(hashCode));fileBack.setUploadData(new Date());fileBackService.save(fileBack);//删除临时文件目录File chunkFolderInfo = new File(chunkPath + vo.getTotalSize() + "/");FileUtils.deleteDirectory(chunkFolderInfo);log.info("时间:{}" + new Date() + "文件名称:{}" + vo.getFileName() + "上传成功");return true;} else {//删除临时文件目录File chunkFolderInfo = new File(chunkPath + vo.getTotalSize() + "/");FileUtils.deleteDirectory(chunkFolderInfo);log.info("时间:{}" + new Date() + "文件名称:{}" + vo.getFileName() + "上传失败");return false;}
}

http://www.ppmy.cn/devtools/10237.html

相关文章

数据挖掘实验(Apriori,fpgrowth)

Apriori&#xff1a;这里做了个小优化&#xff0c;比如abcde和adcef自连接出的新项集abcdef&#xff0c;可以用abcde的位置和f的位置取交集&#xff0c;这样第n项集的计算可以用n-1项集的信息和数字本身的位置信息计算出来&#xff0c;只需要保存第n-1项集的位置信息就可以提速…

算法-二分查找

二分查找&#xff1a;在数组有序的条件下&#xff0c;通过不断选取中间结点&#xff0c;根据中间结点的数值与目标值比较结果&#xff0c;将数组分成两段&#xff0c;由此来找出两段的中间位置或者边界位置&#xff0c;将暴力枚举的时间复杂读O(n)提高到O(log2N): 具体&#xf…

C语言(扫雷游戏)

Hi~&#xff01;这里是奋斗的小羊&#xff0c;很荣幸各位能阅读我的文章&#xff0c;诚请评论指点&#xff0c;关注收藏&#xff0c;欢迎欢迎~~ &#x1f4a5;个人主页&#xff1a;小羊在奋斗 &#x1f4a5;所属专栏&#xff1a;C语言 本系列文章为个人学习笔记&#x…

《神经网络与深度学习:案例与实践》动手练习1.3

飞桨AI Studio星河社区-人工智能学习与实训社区 动手练习1.3 执行上述算子的反向过程&#xff0c;并验证梯度是否正确。 import mathclass Op(object):def __init__(self):passdef __call__(self, inputs):return self.forward(inputs)# 前向函数# 输入&#xff1a;张量inpu…

7、线上系统部署时如何设置JVM内存大小?

7.1、前文回顾 让我们先来回顾一下我们已经学到的知识。现在,大家应该都明白了,在我们日常编写代码时,所创建的对象通常是首先在新生代区域进行分配的。然后,当一些方法执行完毕后,大部分位于新生代区域中的对象将不再被引用,从而变成垃圾对象。如下图所示: 随着程序…

QT C++(信号与槽函数,自定义信号和槽函数)

文章目录 1. QT信号与槽函数2. QT自定义信号和槽函数 1. QT信号与槽函数 QT信号关键要素&#xff1a; 信号源&#xff1a;那个控件发送的信号信号类型&#xff1a;用户进行不同的操作&#xff0c;就可能触发不同的信号。 eg&#xff1a;点击按钮&#xff0c;移动鼠标等信号处…

分布式与微服务区别?

1、概念角度&#xff1a; 分布式&#xff1a;把多个应用部署到多台服务器&#xff08;云&#xff09;上&#xff0c;多个应用之间相互协作&#xff0c;提高系统的扩展性和稳定性。 微服务&#xff1a;是分布式的一种实现方式。 2、粒度划分&#xff1a; 分布式&#x…

单片机 VS 嵌入式LInux (学习方法)

linux 嵌入式开发岗位需要掌握Linux的主要原因之一是&#xff0c;许多嵌入式系统正在向更复杂、更功能丰富的方向发展&#xff0c;需要更强大的操作系统支持。而Linux作为开源、稳定且灵活的操作系统&#xff0c;已经成为许多嵌入式系统的首选。以下是为什么嵌入式开发岗位通常…