网站备案和域名备案的区别,网站移动窗口代码,长治推广型网站建设,wordpress slider 插件前言 大文件分片上传和断点续传是为了解决在网络传输过程中可能遇到的问题#xff0c;以提高文件传输的效率和稳定性。 首先#xff0c;大文件分片上传是将大文件分割成较小的片段进行上传。这样做的好处是可以减少单个文件的传输时间#xff0c;因为较小的文件片段更容易快…前言 大文件分片上传和断点续传是为了解决在网络传输过程中可能遇到的问题以提高文件传输的效率和稳定性。 首先大文件分片上传是将大文件分割成较小的片段进行上传。这样做的好处是可以减少单个文件的传输时间因为较小的文件片段更容易快速上传到目标服务器。同时如果在传输过程中出现错误或中断只需要重新上传出现问题的文件片段而不需要重新上传整个文件从而减少了传输的时间和带宽消耗。其次断点续传是指在文件传输过程中如果传输被中断或者发生错误可以从上一次中断的地方继续传输而不是从头开始。这对于大文件的传输尤为重要因为传输一个大文件可能需要较长的时间而中断可能是由网络问题、电源故障、软件崩溃或其他因素引起的。断点续传功能允许用户在中断后恢复传输而无需重新开始节省了时间和资源。 大文件分片上传和断点续传在以下情况下尤为重要 低带宽网络环境在网络速度较慢或不稳定的情况下将大文件分割为较小的片段进行上传可以降低传输的时间和失败的风险。大文件传输对于大文件一次性完整上传可能需要很长时间而且中途出现问题时需要重新传输整个文件因此将文件分割并实现断点续传功能可以提高效率和可靠性。网络中断或传输错误网络中断、电源故障或软件崩溃等因素可能导致文件传输中断断点续传功能可以从中断处恢复避免重新传输整个文件。多用户并发上传在有多个用户同时上传文件的情况下分片上传和断点续传可以减少对服务器资源的占用提高并发传输的效率。
前端
采用百度的webuploader在file.html中引用webuploader.js、jquery.js
代码如下
!DOCTYPE html
html langen
headmeta charsetUTF-8title大文件上传下载/titlelink relstylesheet typetext/css hrefwebuploader.cssscript srcjquery.js/scriptscript srcwebuploader.js/scriptstyle#upload-container {width: 100px;height: 50px;background: #94d3e7;padding-bottom: 10px;}/style
/head
bodydiv idupload-containerspan文件拖拽上传/span/divbutton idpicker stylemargin-top: 20px分片上传/buttondiv idupload-list/divhr/
a href/file/download 普通下载/a
hr/
a href/file/downloads target_blank分片下载/a/body
script$(#upload-container).click(function (event) {$(#picker).find(input).click();});// 初始化上传组件const uploader WebUploader.create({auto: true,swf: Uploader.swf, // swf文件路径server: /file/upload, // 上传接口dnd: #upload-container,pick: #picker, // 内部根据当前运行创建multiple: true, // 选择多个chunked: true, // 开启分片threads: 8, // 并发数默认 3chunkRetry: 8, // 如果遇到网络错误,重新上传次数method: POST,fileSizeLimit: 1024 * 1024 * 1024 * 10, // 文件总大小为10GfileSingleSizeLimit: 1024 * 1024 * 1024 * 1, // 单个文件大小最大为1GfileVal: upload});// 入队之前触发事件uploader.on(beforeFileQueued, function (file) {// 获取文件后缀console.log(file.name);});// 当有文件被添加进队列的时候uploader.on(fileQueued, function (file) {$(#upload-list).append( div id file.id classitem h4 classinfo file.name /h4 p classstate等待上传.../p /div );});// 文件上传过程中创建进度条实时显示。uploader.on(uploadProgress, function (file, 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( uploadSuccess, function( file ) {$( #file.id ).find(p.state).text(已上传);});uploader.on( uploadError, function( file ) {$( #file.id ).find(p.state).text(上传出错);});uploader.on( uploadComplete, function( file ) {$( #file.id ).find(.progress).fadeOut();});/script
/html后端
1.Pom文件添加依赖
?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersionparentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion2.7.4/versionrelativePath//parentgroupIdcom.liyh/groupIdartifactIdspringboot-file/artifactIdversion0.0.1/versionnamespringboot-file/namedescriptionDemo project for Spring Boot/descriptionpropertiesjava.version1.8/java.version/propertiesdependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependencydependencygroupIdcommons-fileupload/groupIdartifactIdcommons-fileupload/artifactIdversion1.3.1/version/dependencydependencygroupIdcommons-io/groupIdartifactIdcommons-io/artifactIdversion2.4/version/dependency!-- 做断点下载使用 --dependencygroupIdorg.apache.httpcomponents/groupIdartifactIdhttpcore/artifactId/dependencydependencygroupIdorg.apache.httpcomponents/groupIdartifactIdhttpclient/artifactId/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdversion1.18.22/version/dependencydependencygroupIdorg.apache.commons/groupIdartifactIdcommons-lang3/artifactIdversion3.12.0/version/dependency/dependenciesbuildpluginsplugingroupIdorg.springframework.boot/groupIdartifactIdspring-boot-maven-plugin/artifactId/plugin/plugins/build/project2.yml配置文件
# 配置服务端口
server:port: 8018spring:servlet:multipart:# Spring Boot中有默认的文件上传组件在使用ServletFileUpload时需要关闭Spring Boot的默认配置enabled: false# 设置单个文件大小max-file-size: 1GB# 设置单次请求文件的总大小max-request-size: 10GB3..编写测试接口controller
package com.liyh.controller;import com.liyh.service.FileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/*** 文件上传测试接口** author liyh*/
RestController
RequestMapping(/file)
public class FileController {Autowiredprivate FileService fileService;/*** 单个文件上传支持断点续传*/PostMapping(/upload)public void upload(HttpServletRequest request, HttpServletResponse response) {try {fileService.upload(request, response);} catch (Exception e) {e.printStackTrace();}}/*** 普通文件下载*/GetMapping(/download)public void download(HttpServletRequest request, HttpServletResponse response) throws IOException {fileService.download(request, response);}/*** 分片文件下载*/GetMapping(/downloads)public String downloads() throws IOException {fileService.downloads();return 下载成功;}}4.编写service
package com.liyh.service;import com.liyh.entity.DownloadFileInfo;
import com.liyh.entity.FileInfo;
import com.liyh.entity.UploadFileInfo;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClients;
import org.springframework.stereotype.Service;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;Service
public class FileService {/*** 编码*/private static final String UTF_8 UTF-8;/*** 文件上传路径(当前项目路径下也可配置固定路径)*/private String uploadPath System.getProperty(user.dir) /springboot-file/upload/;/*** 下载指定文件*/private String downloadFile D:\\Download\\git.exe;/*** 文件下载地址(当前项目路径下也可配置固定路径)*/private String downloadPath System.getProperty(user.dir) /springboot-file/download/;/*** 分片下载每一片大小为50M*/private static final Long PER_SLICE 1024 * 1024 * 50L;/*** 定义分片下载线程池*/private ExecutorService executorService Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());/*** final string*/private static final String RANGE Range;/*** 上传文件*/public void upload(HttpServletRequest request, HttpServletResponse response) throws Exception {// 获取ServletFileUploadServletFileUpload servletFileUpload getServletFileUpload();ListFileItem items servletFileUpload.parseRequest(request);// 获取文件信息UploadFileInfo uploadFileInfo getFileInfo(items);// 写入临时文件writeTempFile(items, uploadFileInfo);// 判断是否合并mergeFile(uploadFileInfo);// 返回结果response.setCharacterEncoding(UTF_8);response.getWriter().write(上传成功);}/*** 获取ServletFileUpload*/private ServletFileUpload getServletFileUpload() {// 设置缓冲区大小先读到内存里在从内存写DiskFileItemFactory factory new DiskFileItemFactory();factory.setSizeThreshold(1024);File file new File(uploadPath);// 如果文件夹不存在则创建if (!file.exists() !file.isDirectory()) {file.mkdirs();}factory.setRepository(file);// 解析ServletFileUpload upload new ServletFileUpload(factory);// 设置单个大小与最大大小upload.setFileSizeMax(1 * 1024 * 1024 * 1024L);upload.setSizeMax(10 * 1024 * 1024 * 1024L);return upload;}/*** 获取文件信息** param items* return* throws UnsupportedEncodingException*/private UploadFileInfo getFileInfo(ListFileItem items) throws UnsupportedEncodingException {UploadFileInfo uploadFileInfo new UploadFileInfo();for (FileItem item : items) {if (item.isFormField()) {// 获取分片数据if (chunk.equals(item.getFieldName())) {uploadFileInfo.setCurrentChunk(Integer.parseInt(item.getString(UTF_8)));}if (chunks.equals(item.getFieldName())) {uploadFileInfo.setChunks(Integer.parseInt(item.getString(UTF_8)));}if (name.equals(item.getFieldName())) {uploadFileInfo.setFileName(item.getString(UTF_8));}}}return uploadFileInfo;}/*** 写入临时文件** param items* param uploadFileInfo* throws Exception*/private void writeTempFile(ListFileItem items, UploadFileInfo uploadFileInfo) throws Exception {// 获取文件基本信息后for (FileItem item : items) {if (!item.isFormField()) {// 有分片需要临时目录String tempFileName uploadFileInfo.getFileName();if (StringUtils.isNotBlank(tempFileName)) {if (uploadFileInfo.getCurrentChunk() ! null) {tempFileName uploadFileInfo.getCurrentChunk() _ uploadFileInfo.getFileName();}// 判断文件是否存在File tempFile new File(uploadPath, tempFileName);// 断点续传判断文件是否存在若存在则不传if (!tempFile.exists()) {item.write(tempFile);}}}}}/*** 判断是否合并** param uploadFileInfo* throws IOException* throws InterruptedException*/private void mergeFile(UploadFileInfo uploadFileInfo) throws IOException, InterruptedException {Integer currentChunk uploadFileInfo.getCurrentChunk();Integer chunks uploadFileInfo.getChunks();String fileName uploadFileInfo.getFileName();// 如果当前分片等于总分片那么合并文件if (currentChunk ! null chunks ! null currentChunk.equals(chunks - 1)) {File tempFile new File(uploadPath, fileName);try (BufferedOutputStream os new BufferedOutputStream(new FileOutputStream(tempFile))) {// 根据之前命名规则找到所有分片for (int i 0; i chunks; i) {File file new File(uploadPath, i _ fileName);// 并发情况需要判断所有因为可能最后一个分片传完之前有的还没传完while (!file.exists()) {// 不存在休眠100毫秒后在重新判断Thread.sleep(100);}// 分片存在读入数组中byte[] bytes FileUtils.readFileToByteArray(file);os.write(bytes);os.flush();file.delete();}os.flush();}}}/*** 文件下载** param request* param response* throws IOException*/public void download(HttpServletRequest request, HttpServletResponse response) throws IOException {// 获取文件File file new File(downloadFile);// 获取下载文件信息DownloadFileInfo downloadFileInfo getDownloadFileInfo(file.length(), request, response);// 设置响应头setResponse(response, file.getName(), downloadFileInfo);// 下载文件try (InputStream is new BufferedInputStream(new FileInputStream(file));OutputStream os new BufferedOutputStream(response.getOutputStream())) {// 跳过已经读取文件is.skip(downloadFileInfo.getPos());byte[] buffer new byte[1024];long sum 0;// 读取while (sum downloadFileInfo.getRangeLength()) {int length is.read(buffer, 0, (downloadFileInfo.getRangeLength() - sum) buffer.length ? (int) (downloadFileInfo.getRangeLength() - sum) : buffer.length);sum sum length;os.write(buffer, 0, length);}}}/*** 有两个map我要去判断里面相同键的值一致不一致除了双重for循环有没有别的好办法*/private DownloadFileInfo getDownloadFileInfo(long fSize, HttpServletRequest request, HttpServletResponse response) {long pos 0;long last fSize - 1;// 判断前端是否需要分片下载if (request.getHeader(RANGE) ! null) {response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);String numRange request.getHeader(RANGE).replace(bytes, );String[] strRange numRange.split(-);if (strRange.length 2) {pos Long.parseLong(strRange[0].trim());last Long.parseLong(strRange[1].trim());// 若结束字节超出文件大小取文件大小if (last fSize - 1) {last fSize - 1;}} else {// 若只给一个长度开始位置一直到结束pos Long.parseLong(numRange.replace(-, ).trim());}}long rangeLength last - pos 1;String contentRange bytes pos - last / fSize;return new DownloadFileInfo(fSize, pos, last, rangeLength, contentRange);}/*** 分片下载** throws IOException*/public void downloads() throws IOException {File file new File(downloadPath);// 如果文件夹不存在则创建if (!file.exists() !file.isDirectory()) {file.mkdirs();}// 探测下载获取文件相关信息FileInfo fileInfoDto sliceDownload(1, 10, -1, null);// 如果不为空执行分片下载if (fileInfoDto ! null) {// 计算有多少分片long pages fileInfoDto.getFileSize() / PER_SLICE;// 适配最后一个分片for (long i 0; i pages; i) {long start i * PER_SLICE;long end (i 1) * PER_SLICE - 1;executorService.execute(new SliceDownloadRunnable(start, end, i, fileInfoDto.getFileName()));}}}/*** 分片下载** param start 分片起始位置* param end 分片结束位置* param page 第几个分片, page-1时是探测下载*/private FileInfo sliceDownload(long start, long end, long page, String fName) throws IOException {// 断点下载File file new File(downloadPath, page - fName);// 如果当前文件已经存在并且不是探测任务并且文件的长度等于分片的大小那么不用下载当前文件if (file.exists() page ! -1 file.length() PER_SLICE) {return null;}// 创建HttpClientHttpClient client HttpClients.createDefault();HttpGet httpGet new HttpGet(http://localhost:8018/file/download);httpGet.setHeader(RANGE, bytes start - end);HttpResponse httpResponse client.execute(httpGet);String fSize httpResponse.getFirstHeader(fSize).getValue();fName URLDecoder.decode(httpResponse.getFirstHeader(fName).getValue(), UTF_8);HttpEntity entity httpResponse.getEntity();// 下载try (InputStream is entity.getContent();FileOutputStream fos new FileOutputStream(file)) {byte[] buffer new byte[1024];int ch;while ((ch is.read(buffer)) ! -1) {fos.write(buffer, 0, ch);}fos.flush();}// 判断是否是最后一个分片如果是那么合并if (end - Long.parseLong(fSize) 0) {mergeFile(fName, page);}return new FileInfo(Long.parseLong(fSize), fName);}private void mergeFile(String fName, long page) throws IOException {File file new File(downloadPath, fName);try (BufferedOutputStream os new BufferedOutputStream(new FileOutputStream(file))) {for (int i 0; i page; i) {File tempFile new File(downloadPath, i - fName);// 文件不存在或文件没写完while (!tempFile.exists() || (i ! page tempFile.length() PER_SLICE)) {Thread.sleep(100);}byte[] bytes FileUtils.readFileToByteArray(tempFile);os.write(bytes);os.flush();tempFile.delete();}// 删除文件File f new File(downloadPath, -1 -null);if (f.exists()) {f.delete();}} catch (InterruptedException e) {e.printStackTrace();}}private class SliceDownloadRunnable implements Runnable {private final long start;private final long end;private final long page;private final String fName;private SliceDownloadRunnable(long start, long end, long page, String fName) {this.start start;this.end end;this.page page;this.fName fName;}Overridepublic void run() {try {sliceDownload(start, end, page, fName);} catch (IOException e) {e.printStackTrace();}}}/*** 设置响应头*/private void setResponse(HttpServletResponse response, String fileName, DownloadFileInfo downloadFileInfo) throws UnsupportedEncodingException {response.setCharacterEncoding(UTF_8);response.setContentType(application/x-download);response.addHeader(Content-Disposition, attachment;filename URLEncoder.encode(fileName, UTF_8));// 支持分片下载response.setHeader(Accept-Range, bytes);response.setHeader(fSize, String.valueOf(downloadFileInfo.getFSize()));response.setHeader(fName, URLEncoder.encode(fileName, UTF_8));// range响应头response.setHeader(Content-Range, downloadFileInfo.getContentRange());response.setHeader(Content-Length, String.valueOf(downloadFileInfo.getRangeLength()));}}5..编写上传文件实体类、文件信息实体类和下载文件实体类
package com.liyh.entity;import lombok.Data;Data
public class UploadFileInfo {/*** 文件名称*/private String fileName;/*** 上传文件会有多个分片记录当前为那个分片*/private Integer currentChunk;/*** 总分片数*/private Integer chunks;}package com.liyh.entity;import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.Data;Data
AllArgsConstructor
NoArgsConstructor
public class FileInfo {private long fileSize;private String fileName;}package com.liyh.entity;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;AllArgsConstructor
NoArgsConstructor
Data
public class DownloadFileInfo {/*** 文件总大小*/private long fSize;/*** 断点起始位置*/private long pos;/*** 断点结束位置*/private long last;/*** rang响应*/private long rangeLength;/*** range响应*/private String contentRange;}运行效果