qq推广引流网站,做网站编辑,百度下载2022新版安装,电子商务公司属于什么行业类别1、大文件上传面临的问题#xff1a;
在传统的文件上传中#xff0c;由于文件过大#xff0c;导致网络传输时间长#xff0c;这过程中会遇到网络不稳定或者不小心关闭的浏览器#xff08;电脑#xff09;的情况#xff0c;从而导致文件上传中断。中断之后#xff0c;又…1、大文件上传面临的问题
在传统的文件上传中由于文件过大导致网络传输时间长这过程中会遇到网络不稳定或者不小心关闭的浏览器电脑的情况从而导致文件上传中断。中断之后又要重新点击上传文件从而开始新一轮的文件上传。或者该大文件已经上传过了又一次点击上传该文件。对于这些情况简直就浪费时间和浪费服务器。 那有没有什么解决办法呢 针对文件过大的解决办法可以将文件进行切割成规定大小的几个小文件再上传。由于切割了上传文件就一块一块地进行成功一块是一块不影响其他的文件快。这样要是在文件上传失败情况下重新上传时检查成功的是那些文件块失败的是那些文件块从而只需要再次上传失败的文件块检测到全部上传之后就合并。这就实现了断点续传的目的。还有就是大文件已经上传过再次上传就是浪费时间和浪费服务器资源。所有针对的这情景可以先对大文件进行MD5取值通过MD5值和文件名去服务器中匹配是否已经存在了要是这两个值都相同说明该大文件已经上传就直接返回告诉客户端文件已上传从而达到秒传的效果。
2、大文件上传实现
下面演示的项目中前端使用百度的web-uploader文件上传组件后端使用novelweb的工具包springboot项目实现大文件的分片上传、断点续传、秒传。
dependencygroupIdcn.novelweb/groupIdartifactIdtool-core/artifactIdversion1.3.6/version
/dependency整个流程如下 校验文件上传状态 前端生成该文件的MD5密文并进行分片上传之前请求check-md5接口传入文件名和密文接口校验文件是未上传 或 上传了一部分 或 已上传完成三个状态其中未上传返回自定义状态码404上传一部分则返回状态206未上传的分片ID上传完成则返回状态200。 前端逐片上传 校验完成后根据校验结果对未上传的分片进行逐个上传上传分片时参数主要是总片数、当前片ID、片文件
上传接口 上传接口会先去获取并解析该文件的conf文件conf文件是RandomAccessFile该类是通过提供指针的方式操作文件文件存储的是一个二进制数组所以可以用来数组下标标记片ID使用setLength方法设置conf文件长度使用seek方法跳到当前上传的片ID的位置把该位置的值替换成127然后将该分片使用指针偏移的方式插入到_tmp临时文件临时文件也是RandomAccessFile文件中然后校验是否所有的片都上传完成是则修改临时文件为正式文件名至此上传完成否则直接返回该分片上传完成
上传进度 前端收到当前片的响应结果后会根据已上传片数量获取到上传进度
MD5的用法 以上就是分片上传的全部过程其中本项目中文件MD5并没有派上用场原因是我这里是使用文件名做区分的这样的弊端就是如果上传同名文件但是内容不同时仍然会走秒传功能解决办法是可以使用checkFileMd5(String fileMd5, String fileName)方法和fragmentFileUploader(UploadFileParam param, long chunkSize, HttpServletRequest request)方法配合使用这样可以识别非同一文件但同名的文件上传或者上传时前端传入一个唯一ID作为上传后的文件名。
2.1 前段代码
组件版本web-uploader.min-0.1.6.js 、jquery-3.3.1.min.js web-uploader官方地址 项目结构 坑点 WebUploader.Uploader.register要放到 WebUploader.create之前否则注册的插件是不起作用的。
!DOCTYPE html
html
headtitle文件上传页面/title!--引入JS--script typetext/javascript srclib/jquery-3.3.1.min.js/scriptscript typetext/javascript srclib/web-uploader.min-0.1.6.js/script
/head
style
/style
bodyh1文件上传/h1div iduploader classwu-example!--用来存放文件信息--div classbtnsdiv idpicker选择文件/div!-- button idctlBtn classbtn btn-default开始上传/button --/divdiv idthelist classuploader-list/div/divscript$list $(.uploader-list);WebUploader.Uploader.register({add-file: addFiles,before-send-file: beforeSendFile,before-send: beforeSend}, {addFiles: function (files) {console.log(添加文件add-file);console.log(files);},beforeSendFile: function (file) {//在文件发送之前request此时还没有分片如果配置了分片的话可以用来做文件整体md5验证。console.log(beforeSendFile);let deferred WebUploader.Deferred();(new WebUploader.Uploader()).md5File(file, 0, 5242880).progress(function (percentage) {// 显示计算进度$(# file.id).find(p.state).text(校验MD5中...);}).then(function (val) {file.md5 val;file.uid WebUploader.Base.guid();// 进行md5判断$.ajax({url: http://localhost:9555/api/file/check-file,type: GET,showError: false,global: false,data: {fileName: file.name,md5: file.md5},success: (data) {let status data.errorCode;deferred.resolve();switch (status) {case 0:// 忽略上传过程直接标识上传成功uploader.skipFile(file);file.pass true;break;case 16:// 部分已经上传到服务器了但是差几个模块。file.missChunks data.data;break;default:break;}}})})return deferred.promise();},beforeSend: function (block) {//block: 分片对象console.log(beforeSend);console.log(block);let deferred WebUploader.Deferred();// 当前未上传分块let missChunks block.file.missChunks;// 当前分块let blockChunk block.chunk;if (missChunks ! null missChunks ! undefined missChunks ! ) {let flag true;for (let i 0; i missChunks.length; i) {if (blockChunk parseInt(missChunks[i])) {// 存在还未上传的分块flag false;break;}}if (flag) {deferred.reject();} else {deferred.resolve();}} else {deferred.resolve();}return deferred.promise();}});// 初始化 WebUploadervar uploader WebUploader.create({// 选定容器这里使用 id 选择器pick: {id: #picker,multiple: true},server: http://localhost:9555/api/file/breakpoint-upload,chunked: true,chunkSize: 5242880,threads:3,prepareNextFile: true,auto: true});// 文件上传过程中创建进度条实时显示。uploader.on(uploadProgress, function (file, percentage) {console.log(文件上传进度 percentage);var $li $(# file.id),$percent $li.find(.progress .progress-bar);// 避免重复创建if (!$percent.length) {$percent $(div classprogress progress-striped active div classprogress-bar roleprogressbar stylewidth: 0% /div /div).appendTo($li).find(.progress-bar);}$li.find(p.state).text(上传中);$percent.css(width, percentage * 100 %);});//当某个文件的分块在发送前触发主要用来询问是否要添加附带参数大文件在开起分片上传的前提下此事件可能会触发多次uploader.on(uploadBeforeSend, function (object, data, headers) {console.log(uploadBeforeSend);console.log(object);console.log(data);console.log(headers);let file object.file;data.md5 file.md5 || ;data.uid file.uid;});// 当有文件被添加进队列的时候uploader.on(fileQueued, function (file) {console.log(文件被添加进了队列 file.name);$list.append(div id file.id classitem h4 classinfo file.name /h4 p classstate等待上传.../p /div);});uploader.on(fileQueued, function (file) {console.log(file);var $li $(div id file.id classfile-item thumbnail img /div),$img $li.find(img);// $list为容器jQuery实例$list.append($li);// 创建缩略图// 如果为非图片文件可以不用调用此方法。// thumbnailWidth x thumbnailHeight 为 100 x 100uploader.makeThumb(file, function (error, src) {if (error) {$img.replaceWith(span不能预览/span);return;}console.log(src);$img.attr(src, src);}, 100, 100);});//上传成功uploader.on(uploadSuccess, function (file) {console.log(uploadSuccess);$(# file.id).find(p.state).text(已上传);});//上传错误uploader.on(uploadError, function (file) {console.log(uploadError);$(# file.id).find(p.state).text(上传出错);});//完成上传uploader.on(uploadComplete, function (file) {console.log(uploadComplete);$(# file.id).find(.progress).fadeOut();});/script
/body/html前端渲染效果
2.1 后端实现
2.1.1、校验文件上传状态 confFilePathconf文件保存的路径 savePath文件保存的路径 public ResultObject checkFileMd5(String md5, String fileName) {try {cn.novelweb.tool.http.Result result LocalUpload.checkFileMd5(md5, fileName, confFilePath, savePath);return NovelWebUtils.forReturn(result);} catch (Exception e) {log.error(e.getMessage(), e);}return Results.newFailResult(ErrorCode.FILE_UPLOAD, 上传失败);}下面是文件和配置文件保存的目录 还没有上传完成的临时文件 检验代码 /*** 秒传、断点的文件MD5验证* 根据文件路径获取要上传的文件夹下的 文件名.conf 文件* 通过判断 *.conf 文件状态来验证(有条件的可以使用redis来记录上传状态和文件地址)** param fileMd5 文件的MD5* param fileName 文件名(包含文件格式)* param confFilePath 分片配置文件全路径(不包含文件名)* param tmpFilePath 上传的缓存文件全路径(不包含文件名)* return 返回文件MD5验证状态* 200:文件已存在、文件已上传成功、可以执行秒传* 206:文件已经上传了一部分、上传了部分分片文件、未上传完整的文件* 404:文件不存在、文件没有被上传过、第一次上传* throws Exception 抛出自定义Exception异常*/public static Result? checkFileMd5(String fileMd5,String fileName,String confFilePath,String tmpFilePath) throws Exception {boolean isParamEmpty StringUtils.isBlank(fileMd5)|| StringUtils.isBlank(fileName)|| StringUtils.isBlank(confFilePath)|| StringUtils.isBlank(tmpFilePath);if (isParamEmpty) {throw new Exception(参数值为空);}// 构建分片配置文件对象File confFile new File(confFilePath File.separatorChar fileName .conf);// 布尔值:上传的文件缓存对象是否存在boolean isTmpFileEmpty new File(tmpFilePath File.separatorChar fileName _tmp).exists();// 分片记录文件 和 文件缓存文件 同时存在 则 状态码定义为 206if (confFile.exists() isTmpFileEmpty) {byte[] completeList FileUtils.readFileToByteArray(confFile);ListString missChunkList new LinkedListString();for (int i 0; i completeList.length; i) {if (completeList[i] ! Byte.MAX_VALUE) {missChunkList.add(Integer.toString(i));}}JSONArray jsonArray JSON.parseArray(JSONObject.toJSONString(missChunkList));return Result.ok(HttpStatus.PARTIAL_CONTENT.value(), 文件已经上传了一部分,jsonArray);}// 布尔值:上传的文件对象是否存在boolean isFileEmpty new File(tmpFilePath File.separatorChar fileName).exists();// 上传的文件 和 配置文件 同时存在 则 当前状态码为 200if (isFileEmpty confFile.exists()) {return Result.ok(HttpStatus.OK.value(), 文件已上传成功);}return Result.ok(HttpStatus.NOT_FOUND.value(), 文件不存在);}
2.1.2、分片上传 分片上传代码
public Result breakpointResumeUpload(UploadFileParam param, HttpServletRequest request) {try {// 这里的 chunkSize(分片大小) 要与前端传过来的大小一致cn.novelweb.tool.http.Result result LocalUpload.fragmentFileUploader(param, confFilePath, savePath, 5242880L, request);return NovelWebUtils.forReturn(result);} catch (Exception e) {log.error(e.getMessage(), e);}return Results.newFailResult(ErrorCode.FILE_UPLOAD, 上传失败);}/*** 文件分片、断点续传上传程序* 创建 文件名.conf 文件记录已上传分片信息* 使用 RandomAccessFile(随机访问文件) 类随机指定位置写入文件,类似于合成分片* 检验分片文件是否全部上传完成重命名缓存文件** param param 上传文件时 需要接收的基本参数信息* param confFilePath 分片配置文件的路径,考虑到配置文件与缓存文件分开的情况(不包含文件名)* param filePath 上传文件的路径,同时也是生成缓存文件的路径(不包含文件名)* param chunkSize 每块分片的大小,单位:字节(这个值需要与前端JS的值保持一致) 5M5242880* param request HTTP Servlet请求* return 返回文件上传状态* 200:文件上传成功、单个分片文件上传成功、未全部上传成功* 201:文件全部上传完成、所有分片全部上传完成、文件完成合并后重命名操作(同时返回com.dai.pojo.file.Files信息)* 500:文件上传失败、文件上传异常* throws Exception 抛出自定义Exception异常*/public static synchronized Result? fragmentFileUploader(UploadFileParam param, String confFilePath,String filePath, long chunkSize,HttpServletRequest request) throws Exception {boolean isParamEmpty StringUtils.isBlank(filePath)|| StringUtils.isBlank(confFilePath) param.getFile() null;if (isParamEmpty) {throw new Exception(参数值为空);}// 判断enctype属性是否为multipart/form-databoolean isMultipart ServletFileUpload.isMultipartContent(request);if (!isMultipart) {throw new IllegalArgumentException(上传内容不是有效的multipart/form-data类型.);}try {// 分片配置文件File confFile FileUtil.file(FileUtil.mkdir(confFilePath), String.format(%s.conf, param.getName()));RandomAccessFile accessConfFile new RandomAccessFile(confFile, rw);// 把该分段标记为 true 表示完成accessConfFile.setLength(param.getChunks());accessConfFile.seek(param.getChunk());accessConfFile.write(Byte.MAX_VALUE);accessConfFile.close();// _tmp的缓存文件对象File tmpFile FileUtil.file(FileUtil.mkdir(filePath), String.format(%s_tmp, param.getName()));// 随机位置写入文件RandomAccessFile accessTmpFile new RandomAccessFile(tmpFile, rw);long offset chunkSize * param.getChunk();// 定位到该分片的偏移量、写入该分片数据、释放accessTmpFile.seek(offset);accessTmpFile.write(param.getFile().getBytes());accessTmpFile.close();// 检查是否全部分片都成功上传byte[] completeList FileUtils.readFileToByteArray(confFile);byte isComplete Byte.MAX_VALUE;for (int i 0; i completeList.length isComplete Byte.MAX_VALUE; i) {// 与运算, 如果有部分没有完成则 isComplete 不是 Byte.MAX_VALUEisComplete (byte) (isComplete completeList[i]);}if (isComplete ! Byte.MAX_VALUE) {return Result.ok(HttpStatus.OK.value(), 文件上传成功);}boolean isSuccess renameFile(tmpFile, param.getName());if (!isSuccess) {throw new Exception(文件重命名时失败);}// 全部上传成功后构建文件对象FileInfo fileInfo FileInfo.builder().hash(param.getMd5()).name(param.getName()).type(param.getFile().getContentType()).path(tmpFile.getParent() File.separatorChar param.getName()).createTime(System.currentTimeMillis()).build();return Result.ok(HttpStatus.CREATED.value(), 文件上传完成, fileInfo);} catch (IOException e) {e.printStackTrace();return Result.error(文件上传失败);}}