制作网站流程,网页传奇游戏怎么注销,服务佳的网站建设,有网页源码 怎么做网站起因#xff1a;最近在工作中接到了一个大文件上传下载的需求#xff0c;要求将文件上传到share盘中#xff0c;下载的时候根据前端传的不同条件对单个或多个文件进行打包并设置目录下载。
一开始我想着就还是用老办法直接file.transferTo(newFile)就算是大文件#xff0c…起因最近在工作中接到了一个大文件上传下载的需求要求将文件上传到share盘中下载的时候根据前端传的不同条件对单个或多个文件进行打包并设置目录下载。
一开始我想着就还是用老办法直接file.transferTo(newFile)就算是大文件我只要慢慢等总会传上去的。 原谅我的无知。。后来尝试之后发现真的是异想天开了如果直接用普通的上传方式基本上就会遇到以下4个问题
文件上传超时原因是前端请求框架限制最大请求时长后端设置了接口访问的超时时间或者是 nginx或其它代理/网关 限制了最大请求时长。文件大小超限原因在于后端对单个请求大小做了限制一般 nginx 和 server 都会做这个限制。上传时间过久想想10个g的文件上传这不得花个几个小时的时间由于各种网络原因上传失败且失败之后需要从头开始。
所以我只能寻求切片上传的帮助了。
整体思路
前端根据代码中设置好的分片大小将上传的文件切成若干个小文件分多次请求依次上传后端再将文件碎片拼接为一个完整的文件即使某个碎片上传失败也不会影响其它文件碎片只需要重新上传失败的部分就可以了。而且多个请求一起发送文件提高了传输速度的上限。 前端切片的核心是利用 Blob.prototype.slice 方法和数组的 slice 方法相似文件的 slice 方法可以返回原文件的某个切片
接下来就是上代码 前端代码 !DOCTYPE html
html langenheadmeta charsetUTF-8 /meta http-equivX-UA-Compatible contentIEedge /meta nameviewport contentwidthdevice-width, initial-scale1.0 /!-- 引入 Vue --script srchttps://cdn.jsdelivr.net/npm/vue2.6/dist/vue.min.js/script!-- 引入样式 --link relstylesheet hrefhttps://unpkg.com/element-ui/lib/theme-chalk/index.css!-- 引入组件库 --script srchttps://unpkg.com/element-ui/lib/index.js/scripttitle分片上传测试/title
/headbodydiv idapptemplatedivinput typefile changehandleFileChange /el-button clickhandleUpload上传/el-button/div/template/div
/body/html
script// 切片大小// the chunk sizeconst SIZE 50 * 1024 * 1024;var app new Vue({el: #app,data: {container: {file: null},data: [],fileListLong: ,fileSize:},methods: {handleFileChange(e) {const [file] e.target.files;if (!file) return;this.fileSize file.size;Object.assign(this.$data, this.$options.data());this.container.file file;},async handleUpload() { },// 生成文件切片createFileChunk(file, size SIZE) {const fileChunkList [];let cur 0;while (cur file.size) {fileChunkList.push({ file: file.slice(cur, cur size) });cur size;}return fileChunkList;},// 上传切片async uploadChunks() {const requestList this.data.map(({ chunk, hash }) {const formData new FormData();formData.append(file, chunk);formData.append(hash, hash);formData.append(filename, this.container.file.name);return { formData };}).map(({ formData }) this.request({url: http://localhost:8080/file/upload,data: formData}));// 并发请求await Promise.all(requestList);console.log(requestList.size);this.fileListLong requestList.length;// 合并切片await this.mergeRequest();},async mergeRequest() {await this.request({url: http://localhost:8080/file/merge,headers: {content-type: application/json},data: JSON.stringify({fileSize: this.fileSize,fileNum: this.fileListLong,filename: this.container.file.name})});},async handleUpload() {if (!this.container.file) return;const fileChunkList this.createFileChunk(this.container.file);this.data fileChunkList.map(({ file }, index) ({chunk: file,// 文件名 数组下标hash: this.container.file.name - index}));await this.uploadChunks();},request({url,method post,data,headers {},requestList}) {return new Promise(resolve {const xhr new XMLHttpRequest();xhr.open(method, url);Object.keys(headers).forEach(key xhr.setRequestHeader(key, headers[key]));xhr.send(data);xhr.onload e {resolve({data: e.target.response});};});}}});
/script考虑到方便和通用性这里没有用第三方的请求库而是用原生 XMLHttpRequest 做一层简单的封装来发请求
当点击上传按钮时会调用 createFileChunk 将文件切片切片数量通过文件大小控制这里设置 50MB也就是说一个 100 MB 的文件会被分成 2 个 50MB 的切片
createFileChunk 内使用 while 循环和 slice 方法将切片放入 fileChunkList 数组中返回
在生成文件切片时需要给每个切片一个标识作为 hash这里暂时使用文件名 下标这样后端可以知道当前切片是第几个切片用于之后的合并切片
随后调用 uploadChunks 上传所有的文件切片将文件切片切片 hash以及文件名放入 formData 中再调用上一步的 request 函数返回一个 proimise最后调用 Promise.all 并发上传所有的切片 后端代码 实体类
Data
public class FileUploadReq implements Serializable {private static final long serialVersionUID 4248002065970982984L;//切片的文件private MultipartFile file;//切片的文件名称private String hash;//原文件名称private String filename;
}Data
public class FileMergeReq implements Serializable {private static final long serialVersionUID 3667667671957596931L;//文件名private String filename;//切片数量private int fileNum;//文件大小private String fileSize;
}
Slf4j
CrossOrigin
RestController
RequestMapping(/file)
public class FileController {final String folderPath System.getProperty(user.dir) /src/main/resources/static/file;RequestMapping(value upload, method RequestMethod.POST)public Object upload(FileUploadReq fileUploadEntity) {File temporaryFolder new File(folderPath);File temporaryFile new File(folderPath / fileUploadEntity.getHash());//如果文件夹不存在则创建if (!temporaryFolder.exists()) {temporaryFolder.mkdirs();}//如果文件存在则删除if (temporaryFile.exists()) {temporaryFile.delete();}MultipartFile file fileUploadEntity.getFile();try {file.transferTo(temporaryFile);} catch (IOException e) {log.error(e.getMessage());e.printStackTrace();}return success;}RequestMapping(value /merge, method RequestMethod.POST)public Object merge(RequestBody FileMergeReq fileMergeEntity) {String finalFilename fileMergeEntity.getFilename();File folder new File(folderPath);//获取暂存切片文件的文件夹中的所有文件File[] files folder.listFiles();//合并的文件File finalFile new File(folderPath / finalFilename);String finalFileMainName finalFilename.split(\\.)[0];InputStream inputStream null;OutputStream outputStream null;try {outputStream new FileOutputStream(finalFile, true);ListFile list new ArrayList();for (File file : files) {String filename FileNameUtil.mainName(file);//判断是否是所需要的切片文件if (StringUtils.equals(filename, finalFileMainName)) {list.add(file);}}//如果服务器上的切片数量和前端给的数量不匹配if (fileMergeEntity.getFileNum() ! list.size()) {return 文件缺失请重新上传;}//根据切片文件的下标进行排序ListFile fileListCollect list.parallelStream().sorted(((file1, file2) - {String filename1 FileNameUtil.extName(file1);String filename2 FileNameUtil.extName(file2);return filename1.compareTo(filename2);})).collect(Collectors.toList());//根据排序的顺序依次将文件合并到新的文件中for (File file : fileListCollect) {inputStream new FileInputStream(file);int temp 0;byte[] byt new byte[2 * 1024 * 1024];while ((temp inputStream.read(byt)) ! -1) {outputStream.write(byt, 0, temp);}outputStream.flush();}} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}finally {try {if (inputStream ! null){inputStream.close();}} catch (IOException e) {e.printStackTrace();}try {if (outputStream ! null){outputStream.close();}} catch (IOException e) {e.printStackTrace();}}// 产生的文件大小和前端一开始上传的文件不一致if (finalFile.length() ! Long.parseLong(fileMergeEntity.getFileSize())) {return 上传文件大小不一致;}return 上传成功;}
}
为了图方便我就直接return 字符串了 嘿嘿当然我在这个demo里面写了方法统一结果的封装所以输出的时候还是restful风格的结果详细内容可以看我之前的文章《Spring使用AOP完成统一结果封装》
当前端调用upload接口的时候后端就会将前端传过来的文件放到一个临时文件夹中
当调用merge接口的时候后端就会认为分片文件已经全部上传完毕就会进行文件合并的工作
后端主要是根据前端返回的hash值来判断分片文件的顺序
结尾
其实分片上传听起来好像很麻烦其实只要把思路捋清楚了其实是不难的是一个比较简单的需求。
当然这个只是一个比较简单一个demo只是实现的一个较为简单的分片上传功能像断点上传上传暂停这些功能暂时还没来得及写到demo里面之后有时间了会新开一个文章写这些额外的内容。
下篇文章见啦喜欢博主的可以点点关注点点赞