• Springboot分片下载实现


    DownloadClient  是模拟前端,若前端实现需要worker线程JS插件方法
    DownLoadController 是后端服务

    1. package org.xxx.wx.web;
    2. import org.apache.commons.io.FileUtils;
    3. import org.apache.http.HttpEntity;
    4. import org.apache.http.HttpResponse;
    5. import org.apache.http.client.HttpClient;
    6. import org.apache.http.client.methods.HttpGet;
    7. import org.apache.http.impl.client.HttpClients;
    8. import org.slf4j.Logger;
    9. import org.slf4j.LoggerFactory;
    10. import org.springframework.web.bind.annotation.RequestMapping;
    11. import org.springframework.web.bind.annotation.RestController;
    12. import javax.servlet.http.HttpServletRequest;
    13. import javax.servlet.http.HttpServletResponse;
    14. import java.io.*;
    15. import java.net.HttpURLConnection;
    16. import java.net.URL;
    17. import java.net.URLDecoder;
    18. import java.util.concurrent.*;
    19. // 模拟前端(客户端)请求
    20. @RestController
    21. @RequestMapping("/wx/downloadclient")
    22. public class DownloadClient {
    23. private static final Logger LOGGER = LoggerFactory.getLogger(DownloadClient.class);
    24. private final static long PER_PAGE = 1024L * 1024L * 50L;
    25. private final static String DOWN_PATH = "D:\\fileItem";
    26. ExecutorService taskExecutor = Executors.newFixedThreadPool(10);
    27. @RequestMapping("/downloadFile")
    28. public String downloadFile() {
    29. // 探测下载
    30. FileInfo fileInfo = download(0, 10, -1, null);
    31. if (fileInfo != null) {
    32. long pages = fileInfo.fSize / PER_PAGE;
    33. for (long i = 0; i <= pages; i++) {
    34. Future<FileInfo> future = taskExecutor.submit(new DownloadThread(i * PER_PAGE, (i + 1) * PER_PAGE - 1, i, fileInfo.fName));
    35. if (!future.isCancelled()) {
    36. try {
    37. fileInfo = future.get();
    38. } catch (InterruptedException | ExecutionException e) {
    39. e.printStackTrace();
    40. }
    41. }
    42. }
    43. return System.getProperty("user.home") + "\\Downloads\\" + fileInfo.fName;
    44. }
    45. return null;
    46. }
    47. class FileInfo {
    48. long fSize;
    49. String fName;
    50. public FileInfo(long fSize, String fName) {
    51. this.fSize = fSize;
    52. this.fName = fName;
    53. }
    54. }
    55. /**
    56. * 根据开始位置/结束位置
    57. * 分片下载文件,临时存储文件分片
    58. * 文件大小=结束位置-开始位置
    59. *
    60. * @return
    61. */
    62. private FileInfo download(long start, long end, long page, String fName) {
    63. File dir = new File(DOWN_PATH);
    64. if (!dir.exists()) {
    65. dir.mkdirs();
    66. }
    67. // 断点下载
    68. File file = new File(DOWN_PATH, page + "-" + fName);
    69. if (file.exists() && page != -1 && file.length() == PER_PAGE) {
    70. return null;
    71. }
    72. try {
    73. HttpClient client = HttpClients.createDefault();
    74. HttpGet httpGet = new HttpGet("http://127.0.0.1:8080/wx/downloadmain/download");
    75. httpGet.setHeader("Range", "bytes=" + start + "-" + end);
    76. HttpResponse response = client.execute(httpGet);
    77. String fSize = response.getFirstHeader("fSize").getValue();
    78. fName = URLDecoder.decode(response.getFirstHeader("fName").getValue(), "UTF-8");
    79. HttpEntity entity = response.getEntity();
    80. InputStream is = entity.getContent();
    81. FileOutputStream fos = new FileOutputStream(file);
    82. byte[] buffer = new byte[1024];
    83. int ch;
    84. while ((ch = is.read(buffer)) != -1) {
    85. fos.write(buffer, 0, ch);
    86. }
    87. is.close();
    88. fos.flush();
    89. fos.close();
    90. // 最后一个分片
    91. if (end - Long.parseLong(fSize) > 0) {
    92. // 开始合并文件
    93. mergeFile(fName, page);
    94. }
    95. return new FileInfo(Long.parseLong(fSize), fName);
    96. } catch (IOException e) {
    97. e.printStackTrace();
    98. }
    99. return null;
    100. }
    101. private void mergeFile(String fName, long page) {
    102. File file = new File(DOWN_PATH, fName);
    103. try {
    104. BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(file));
    105. for (long i = 0; i <= page; i++) {
    106. File tempFile = new File(DOWN_PATH, i + "-" + fName);
    107. while (!file.exists() || (i != page && tempFile.length() < PER_PAGE)) {
    108. try {
    109. Thread.sleep(100);
    110. } catch (InterruptedException e) {
    111. e.printStackTrace();
    112. }
    113. }
    114. byte[] bytes = FileUtils.readFileToByteArray(tempFile);
    115. os.write(bytes);
    116. os.flush();
    117. tempFile.delete();
    118. }
    119. File testFile = new File(DOWN_PATH, -1 + "-null");
    120. testFile.delete();
    121. os.flush();
    122. os.close();
    123. } catch (IOException e) {
    124. e.printStackTrace();
    125. }
    126. }
    127. /**
    128. * 获取远程文件尺寸
    129. */
    130. private long getRemoteFileSize(String remoteFileUrl) throws IOException {
    131. long fileSize = 0;
    132. HttpURLConnection httpConnection = (HttpURLConnection) new URL(remoteFileUrl).openConnection();
    133. //使用HEAD方法
    134. httpConnection.setRequestMethod("HEAD");
    135. int responseCode = httpConnection.getResponseCode();
    136. if (responseCode >= 400) {
    137. LOGGER.debug("Web服务器响应错误!");
    138. return 0;
    139. }
    140. String sHeader;
    141. for (int i = 1;; i++) {
    142. sHeader = httpConnection.getHeaderFieldKey(i);
    143. if (sHeader != null && sHeader.equals("Content-Length")) {
    144. LOGGER.debug("文件大小ContentLength:" + httpConnection.getContentLength());
    145. fileSize = Long.parseLong(httpConnection.getHeaderField(sHeader));
    146. break;
    147. }
    148. }
    149. return fileSize;
    150. }
    151. class DownloadThread implements Callable<FileInfo> {
    152. long start;
    153. long end;
    154. long page;
    155. String fName;
    156. public DownloadThread(long start, long end, long page, String fName) {
    157. this.start = start;
    158. this.end = end;
    159. this.page = page;
    160. this.fName = fName;
    161. }
    162. @Override
    163. public FileInfo call() {
    164. return download(start, end, page, fName);
    165. }
    166. }
    167. }
    1. package org.xxx.wx.web;
    2. import org.springframework.stereotype.Controller;
    3. import org.springframework.web.bind.annotation.RequestMapping;
    4. import javax.servlet.http.HttpServletRequest;
    5. import javax.servlet.http.HttpServletResponse;
    6. import java.io.*;
    7. import java.net.URLEncoder;
    8. //后端下载服务
    9. @Controller
    10. @RequestMapping("/wx/downloadmain")
    11. public class DownLoadController {
    12. private static final String UTF8 = "UTF-8";
    13. @RequestMapping("/download")
    14. public void downLoadFile(HttpServletRequest request, HttpServletResponse response) throws IOException {
    15. File file = new File("D:\\小黄人大眼萌:神偷奶爸前传.2022.HD.1080P.中英双字.mkv");
    16. response.setCharacterEncoding(UTF8);
    17. InputStream is = null;
    18. OutputStream os = null;
    19. try {
    20. // 分片下载 Range表示方式 bytes=100-1000 100-
    21. long fSize = file.length();
    22. response.setContentType("application/x-download");
    23. String fileName = URLEncoder.encode(file.getName(), UTF8);
    24. response.addHeader("Content-Disposition", "attachment;filename=" + fileName);
    25. // 支持分片下载
    26. response.setHeader("Accept-Range", "bytes");
    27. response.setHeader("fSize", String.valueOf(fSize));
    28. response.setHeader("fName", fileName);
    29. long pos = 0, last = fSize - 1, sum = 0;
    30. if (null != request.getHeader("Range")) {
    31. response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
    32. String numberRange = request.getHeader("Range").replaceAll("bytes=", "");
    33. String[] strRange = numberRange.split("-");
    34. if (strRange.length == 2) {
    35. pos = Long.parseLong(strRange[0].trim());
    36. last = Long.parseLong(strRange[1].trim());
    37. if (last > fSize-1) {
    38. last = fSize - 1;
    39. }
    40. } else {
    41. pos = Long.parseLong(numberRange.replaceAll("-", "").trim());
    42. }
    43. }
    44. long rangeLength = last - pos + 1;
    45. String contentRange = new StringBuffer("bytes").append(pos).append("-").append(last).append("/").append(fSize).toString();
    46. response.setHeader("Content-Range", contentRange);
    47. response.setHeader("Content-Length", String.valueOf(rangeLength));
    48. os = new BufferedOutputStream(response.getOutputStream());
    49. is = new BufferedInputStream(new FileInputStream(file));
    50. is.skip(pos);
    51. byte[] buffer = new byte[1024];
    52. int length = 0;
    53. while (sum < rangeLength) {
    54. int readLength = (int) (rangeLength - sum);
    55. length = is.read(buffer, 0, (rangeLength - sum) <= buffer.length ? readLength : buffer.length);
    56. sum += length;
    57. os.write(buffer,0, length);
    58. }
    59. System.out.println("下载完成");
    60. }finally {
    61. if (is != null){
    62. is.close();
    63. }
    64. if (os != null){
    65. os.close();
    66. }
    67. }
    68. }
    69. }

  • 相关阅读:
    LVM逻辑卷
    电容笔可以用什么代替?好用不贵电容笔测评
    JavaScript中的闭包
    lua循环
    企业如何走出固定资产管理的困境?
    【Python】二、变量与数据类型练习
    SI3262—高度集成的低功耗SOC读卡器芯片
    助力OTT大屏营销,酷开科技引领产业变革与创新
    ESP8266-Arduino网络编程实例-接入WiFi网络
    基于NAMUR开放式架构(NOA)的工业设备数据采集方案
  • 原文地址:https://blog.csdn.net/jeff06143132/article/details/127649222