1 大文件上传(支持断点续传)

1.1 前端

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>upload</title><link rel="stylesheet" type="text/css" href="webuploader.css"><script src="jquery.js"></script><script src="webuploader.js"></script><style>#upload-container {width: 500px;height: 500px;background: lightskyblue;padding-bottom: 10px;}</style></head>
<body>
<div id="upload-container"><span>文件拖拽上传</span>
</div>
<div id="upload-list"></div><button id="picker" style="margin-top: 50px">点击上传</button>
</body>
<script>$('#upload-container').click(function (event) {$("#picker").find('input').click();});//初始化上传组件const uploader = WebUploader.create({auto: true,swf: 'Uploader.swf', //swf文件路径server: 'http://localhost:9000/upload', //上传接口dnd: '#upload-container',pick: '#picker',  //内部根据当前运行创建multiple: true,     //选择多个chunked: true,      //开启分片threads: 20,        //并发数method: 'POST',fileSizeLimit: 1024 * 1024 * 1024 * 100, // 文件总大小为100GfileSingleSizeLimit: 1024 * 1024 * 1024 * 5,  //单个文件大小最大为1GfileVal: 'upload'});//入队之前触发事件uploader.on("beforeFileQueued", function (file) {console.log(file); //获取文件后缀});//入队触发事件uploader.on('fileQueued', function (file) {//选中文件要做的事console.log(file.ext);console.log(file.size);console.log(file.name);const html = '<div class="upload-item"><span>文件名:' + file.name + '</span><span data-file_id="' + file.id + '"class="btn-delete">删除</span><span data-file_id="' + file.id + '"class="btn-retry">重试</span><div class="percentage ' + file.id + '" style="width: 0%;"></div></div>';$('#upload-list').append(html);uploader.md5File(file)  //给文件定义唯一的md5值,当再次上传相同文件时,就不用传了  大文件秒传实际上是没传,直接拷贝之前文件地址//显示进度.progress(function (percentage) {console.log('Percentage:', percentage);})//完成.then(function (val) {console.log('md5 result', val);});});
</script>
</html>

1.2 后端

    /*** 上传文件*/public void upload(HttpServletRequest request, HttpServletResponse response) throws Exception {//获取ServletFileUploadServletFileUpload servletFileUpload = getServletFileUpload();List<FileItem> items = servletFileUpload.parseRequest(request);//获取文件信息UploadFileInfoDto uploadFileInfoDto = getFileDataDto(items);//写临时文件writeTempFile(items, uploadFileInfoDto);//判断是否合并mergeFile(uploadFileInfoDto);//返回结果response.setCharacterEncoding(UTF_8);response.getWriter().write("上传成功");}private void mergeFile(UploadFileInfoDto uploadFileInfoDto) throws IOException, InterruptedException {Integer currentChunk = uploadFileInfoDto.getCurrentChunk();Integer chunks = uploadFileInfoDto.getChunks();//如果当前分片等于总分片那么合并文件if (currentChunk != null && chunks != null && currentChunk.equals(chunks - 1)) {File tempFile = new File(uploadPath, uploadFileInfoDto.getFileName());try (BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(tempFile))) {for (int i = 0; i < chunks; i++) {File file = new File(uploadPath, i + "_" + uploadFileInfoDto.getFileName());//并发情况 需要判断所有  因为可能最后一个分片传完,之前有的还没传完while (!file.exists()) {//不存在休眠100毫秒后在从新判断Thread.sleep(100);}//分片存在  读入数组中byte[] bytes = FileUtils.readFileToByteArray(file);os.write(bytes);os.flush();file.delete();}os.flush();}}}private void writeTempFile(List<FileItem> items, UploadFileInfoDto uploadFileInfoDto) throws Exception {//取出文件基本信息后for (FileItem item : items) {if (!item.isFormField()) {//有分片需要临时目录String tempFileName = uploadFileInfoDto.getFileName();if (StringUtils.isNotBlank(tempFileName)) {if (uploadFileInfoDto.getCurrentChunk() != null) {tempFileName = uploadFileInfoDto.getCurrentChunk() + "_" + uploadFileInfoDto.getFileName();}//判断文件是否存在File tempFile = new File(uploadPath, tempFileName);//断点续传  判断文件是否存在,若存在则不传if (!tempFile.exists()) {item.write(tempFile);}}}}}private UploadFileInfoDto getFileDataDto(List<FileItem> items) throws UnsupportedEncodingException {UploadFileInfoDto rs = new UploadFileInfoDto();for (FileItem item : items) {if (item.isFormField()) {//获取分片数赋值给遍量if ("chunk".equals(item.getFieldName())) {rs.setCurrentChunk(Integer.parseInt(item.getString(UTF_8)));}if ("chunks".equals(item.getFieldName())) {rs.setChunks(Integer.parseInt(item.getString(UTF_8)));}if ("name".equals(item.getFieldName())) {rs.setFileName(item.getString(UTF_8));}}}return rs;}/*** 获取ServletFileUpload: High level API for processing file uploads*/private ServletFileUpload getServletFileUpload() {//设置缓冲区大小  先读到内存里在从内存写DiskFileItemFactory factory = new DiskFileItemFactory();factory.setSizeThreshold(1024);factory.setRepository(new File(uploadPath));//解析ServletFileUpload upload = new ServletFileUpload(factory);//设置单个大小与最大大小upload.setFileSizeMax(5 * 1024 * 1024 * 1024L);upload.setSizeMax(100 * 1024 * 1024 * 1024L);return upload;}

1.3 测试


2 文件下载

2.1 HTTP Range 信息

Range 请求头: Range: bytes=start-end

Range: bytes=10- //:从第10个字节开始到最后一个字节的数据
Range:bytes=20-39 //:从第20个字节到第39个字节之间的数据

Content-Range 响应头 :

#表示服务器返回了前(0-10)个字节的数据,总共3000字节的数据。
Content-Range:bytes 0-10/3000

Content-Length 资源的长度:

#表示服务器响应了11个字节的数据
Content-Length:11

2.2 代码

 public void download(HttpServletRequest request, HttpServletResponse response) throws IOException {//获取文件File file = new File(downloadFile);//获取下载文件信息DownloadFileInfoDto downloadFileInfoDto = getDownloadFileInfoDto(file.length(), request, response);//设置响应头setResponse(response, file.getName(), downloadFileInfoDto);//下载文件try (InputStream is = new BufferedInputStream(new FileInputStream(file));OutputStream os = new BufferedOutputStream(response.getOutputStream())) {//跳过已经读取文件is.skip(downloadFileInfoDto.getPos());byte[] buffer = new byte[1024];long sum = 0;//读取while (sum < downloadFileInfoDto.getRangeLength()) {int length = is.read(buffer, 0, (downloadFileInfoDto.getRangeLength() - sum) <= buffer.length ? (int) (downloadFileInfoDto.getRangeLength() - sum) : buffer.length);sum = sum + length;os.write(buffer, 0, length);}}}/*** 有两个map,我要去判断里面相同键的值一致不一致,除了双重for循环,有没有别的好办法*/private DownloadFileInfoDto getDownloadFileInfoDto(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 DownloadFileInfoDto(fSize, pos, last, rangeLength, contentRange);}/*** 设置响应头*/private void setResponse(HttpServletResponse response, String fileName, DownloadFileInfoDto downloadFileInfoDto) 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(downloadFileInfoDto.getFSize()));response.setHeader("fName", fileName);//range响应头response.setHeader("Content-Range", downloadFileInfoDto.getContentRange());response.setHeader("Content-Length", String.valueOf(downloadFileInfoDto.getRangeLength()));}

2.3 测试

3 分片下载

3.1 CODE

  /*** 下载地址*/@Value("${file.download-path}")private String downloadPath;/*** 分片下载每一片大小为5M*/private static final Long PER_SLICE = 1024 * 1024 * 5L;/*** 定义分片下载线程池*/private ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());/*** final string*/private static final String RANGE = "Range";/*** 分片下载** @param start 分片起始位置* @param end   分片结束位置* @param page  第几个分片, page=-1时是探测下载*/private FileInfoDto 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:9000/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 FileInfoDto(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();}}public void sliceDownload() throws IOException {//探测下载,获取文件相关信息FileInfoDto 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()));}}}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();}}}

3.2 测试


PS: Gitee 地址

https://gitee.com/zhurongsheng/spring-file

Spring Boot 大文件上传(断点上传)、服务端分片下载、客户端分片下载(断点下载)相关推荐

  1. 项目_功能模块_基于Spring Boot的文件上传下载功能的设计与实现

    文章目录 基于Spring Boot的文件上传下载功能模块的设计与实现 1.前言 2.技术栈 3.关键源码 4.实现效果 4.1.登录 4.2.文件列表 4.3.上传文件测试 4.3.1.测试图片 4 ...

  2. nodejs ajax进度条,Ajax异步文件上传与NodeJS express服务端处理的示例分析

    Ajax异步文件上传与NodeJS express服务端处理的示例分析 发布时间:2021-07-24 11:17:21 来源:亿速云 阅读:79 作者:小新 这篇文章主要介绍Ajax异步文件上传与N ...

  3. Java IOS客户端上传多张图片到服务端

    Java IOS客户端上传多张图片到服务端 业务场景:用户相册需要上传多张图片到服务器,上限为12张.本文主要介绍Java服务端的文件和流的处理. 下图为iOS端和服务端最终结果一览.  iOS端 : ...

  4. spring boot 字体文件等静态资源无法获取

    spring boot 字体文件等静态资源无法获取 原因 原因maven打包时会过滤掉一些静态文件 解决办法 在pom.xml文件中配置静态资源过滤,然后再放行静态资源,这样就能让maven识别到那些 ...

  5. Spring boot yml文件的书写格式

    Spring boot yml文件的书写格式 使用ide 创建好spring boot文件格式后https://blog.csdn.net/weixin_42292697/article/detail ...

  6. 什么是Spring Boot以及为什么它是用于创建微服务的首选框架

    为什么要使用Spring Boot创建微服务? Spring Boot是Java领域众所周知的首选框架,用于创建Micro Services. 使用Spring引导框架,可以非常轻松地创建Java应用 ...

  7. Spring Boot与Docker(一):微服务架构和容器化概述

    本文讲的是Spring Boot与Docker(一):微服务架构和容器化概述,[编者的话]本篇是<使用Spring Boot和Docker构建微服务架构>系列四部曲的第一篇,本篇将会对我们 ...

  8. 阿里云服务器上搭建微信小程序服务端环境。

    无论是搭建个人博客空间也好,微信小程序也罢,搭建环境必需的两点:云服务器.域名,下面一步步给搭建演示如果在一台阿里云服务器上搭建微信小程序服务端环境. 1.云服务器准备:可在阿里云购买ECS服务器   ...

  9. 精品手游小精灵大作战源码 安卓+IOS+H5三端Cocos Creator 客户端 JAVA服务端

    小精灵大作战 数据库说明 数据库使用MySQL,推荐管理软件Navicat For MySQL. 创建数据库命名为pet_battle,字符集选用utf8 -- UTF-8 Unicode,排序规则选 ...

  10. springboot 上传文件解析入库_十五分钟用Spring Boot实现文件上传功能

    Spring Boot最好的学习方法就是实战训练,今天我们用很短的时间启动我们第一个Spring Boot应用,并且制作一个文件上传系统, 用户可以将本地文件上传到服务器上.我将假设读者为几乎零基础, ...

最新文章

  1. ko文件卸载 linux_Linux驱动06 | 为什么使用内核模块?
  2. python生成斐波那契_python学习-生成器(generator)及斐波那契;yield
  3. web服务器虚拟,虚拟web服务器
  4. 通过一个简单的例子学习Angular Injection Token工作原理
  5. android onlescan 参数,Android BLE:从iOS外设广告时,在onLeScan()回调中检索服务UUID
  6. Python 条件判断 if/else - Python零基础入门教程
  7. vue从哪看组件版本_VUE源码解析之路
  8. App山寨疯狂 爱加密Apk加密平台防破解
  9. 每日单词20110606
  10. PHP获取MP3时长类
  11. 网站打开速度慢如何解决
  12. GitHub快速上手指南
  13. 递归函数求解阶层(C语言)
  14. cnsl是什么意思_CNSL是什么意思
  15. visio2016安装包中文专业版下载及安装教程
  16. SiI9136 -3 HDMI Transmitter 寄存器配置
  17. 深入浅出XDL(二):embedding
  18. Linux命令如何进入screen,linux screen命令基础
  19. js实现unicode转换,支持中英文
  20. .zip: Skipping, found more recently modified local copy (use --force to (关于kaggle下载数据集中断后下载失败的原因)

热门文章

  1. 【第一组】第八次冲刺例会纪要
  2. 网页视频下载方法二:手机浏览器下载
  3. 2022年起重机司机(限桥式起重机)考试题库模拟考试平台操作
  4. 北京轨道交通新机场线“无人驾驶” 最高时速160公里
  5. 实现3D 场景——three.js学习篇二之理解基础概念
  6. 如何理解“修身齐家治国平天下”这句儒家经典
  7. ES6(十八)Module
  8. 解决IntelliJ IDEA Properties中Unused property提示
  9. 你是否还记得c语言的这些文件操作?
  10. 《自控力》读书笔记思维导图