• 【SpringBoot】文件分片上传、合并


    背景

    在上传大型文件时,一般采用的都是分片、断点续传等技术,这样不会导致因文件过大而造成系统超时或者过压等情况。

     接下来我们进入教学

    如果有帮助到您,麻烦请点击个收藏、赞,谢谢~

    一、实际效果图

    整个前端网页的效果图:

    二、后端代码

    1.开发环境

    开发工具:idea  ,开发语言:java  ,框架:springboot

    2.创建文件上传工具类

    创建工具类:FileUtils

    1. import com.caozhen.common.constant.HttpStatusConstant;
    2. import org.springframework.stereotype.Component;
    3. import org.springframework.web.multipart.MultipartFile;
    4. import java.io.*;
    5. /**
    6. * @author caozhen
    7. * @ClassName FileUtils
    8. * @description: 文件上传
    9. * @date 2023年10月07日
    10. * @version: 1.0
    11. */
    12. @Component
    13. public class FileUtils {
    14. //文件路径,可以统一配置
    15. private static final String UPLOAD_DIR = "d:\\uploads";
    16. /***
    17. * 断点续传(上传文件)
    18. * @param file
    19. * @return
    20. */
    21. public ResultAjax resumeFile(MultipartFile file,
    22. int chunkNumber,
    23. int totalChunks,
    24. String identifier) {
    25. try {
    26. File uploadDir = new File(UPLOAD_DIR);
    27. if (!uploadDir.exists()) {
    28. uploadDir.mkdirs();
    29. }
    30. String fileName = identifier + "-chunk-" + chunkNumber;
    31. File chunkFile = new File(uploadDir, fileName);
    32. try (InputStream inputStream = file.getInputStream();
    33. FileOutputStream outputStream = new FileOutputStream(chunkFile)) {
    34. byte[] buffer = new byte[1024];
    35. int bytesRead;
    36. while ((bytesRead = inputStream.read(buffer)) != -1) {
    37. outputStream.write(buffer, 0, bytesRead);
    38. }
    39. }
    40. if (chunkNumber == totalChunks - 1) {
    41. File finalFile = new File(UPLOAD_DIR + File.separator + identifier);
    42. for (int i = 0; i < totalChunks; i++) {
    43. File part = new File(uploadDir, identifier + "-chunk-" + i);
    44. try (FileInputStream partStream = new FileInputStream(part);
    45. FileOutputStream finalStream = new FileOutputStream(finalFile, true)) {
    46. byte[] buffer = new byte[1024];
    47. int bytesRead;
    48. while ((bytesRead = partStream.read(buffer)) != -1) {
    49. finalStream.write(buffer, 0, bytesRead);
    50. }
    51. }
    52. part.delete();
    53. }
    54. uploadDir.delete();
    55. }
    56. return ResultAjax.ok("Chunk " + chunkNumber + " uploaded");
    57. } catch (IOException e) {
    58. e.printStackTrace();
    59. return ResultAjax.error(HttpStatusConstant.HTTP_CODE_500, "上传失败:" + e.getMessage());
    60. }
    61. }
    62. }

    3.创建controller类

    1. @RestController
    2. @Slf4j
    3. @Api(tags = "系统服务")
    4. @RequestMapping("/sys/test/")
    5. public class SystemController {
    6. @Autowired
    7. private FileUtils fileUtils;
    8. @ApiOperation("测试文件断点续传-上传")
    9. @RequestMapping(value = "/upload", method = RequestMethod.POST)
    10. public ResultAjax fileUploads(@RequestParam("file") MultipartFile file,
    11. @RequestParam("chunkNumber") int chunkNumber,
    12. @RequestParam("totalChunks") int totalChunks,
    13. @RequestParam("identifier") String identifier) {
    14. return fileUtils.resumeFile(file,chunkNumber,totalChunks,identifier);
    15. }
    16. }

    4.自定义封装类ResultAjax

    1. import java.util.HashMap;
    2. import java.util.LinkedHashMap;
    3. import java.util.Map;
    4. /**
    5. * @author caozhen
    6. * @ClassName ResultAjax
    7. * @description: 全局封装统一返回实体类
    8. * @date 2023年07月26日
    9. * @version: 1.0
    10. */
    11. public class ResultAjax extends LinkedHashMap {
    12. private static final long serialVersionUID = 1L;
    13. public ResultAjax() {
    14. put("success", true);
    15. put("code", HttpStatusConstant.HTTP_CODE_200);
    16. put("msg", "操作成功!");
    17. put("data", null);
    18. }
    19. public ResultAjax(Integer code) {
    20. put("success", true);
    21. put("code", code);
    22. put("msg", "操作成功!");
    23. put("data", null);
    24. }
    25. public ResultAjax(Integer code, String msg) {
    26. put("success", true);
    27. put("code", code);
    28. put("msg", msg);
    29. put("data", null);
    30. }
    31. public static ResultAjax error() {
    32. return error(HttpStatusConstant.HTTP_CODE_500, "未知异常,请联系管理员");
    33. }
    34. public static ResultAjax errorDebug(String message) {
    35. return error(HttpStatusConstant.HTTP_CODE_500, "未知异常 " + message + ",请联系管理员");
    36. }
    37. public static ResultAjax error(String msg) {
    38. return error(HttpStatusConstant.HTTP_CODE_500, msg);
    39. }
    40. public static ResultAjax error(int code, String msg) {
    41. ResultAjax r = new ResultAjax();
    42. r.put("success", false);
    43. r.put("code", code);
    44. r.put("msg", msg);
    45. return r;
    46. }
    47. public ResultAjax errorInfo(String msg) {
    48. this.put("errorMsg", msg);
    49. return this;
    50. }
    51. public static ResultAjax ok(Object data) {
    52. ResultAjax r = new ResultAjax();
    53. r.put("success", true);
    54. r.put("code", HttpStatusConstant.HTTP_CODE_200);
    55. r.put("msg", "操作成功!");
    56. r.put("data", data);
    57. return r;
    58. }
    59. public static ResultAjax ok(Map map) {
    60. ResultAjax r = new ResultAjax();
    61. r.putAll(map);
    62. r.put("data", null);
    63. return r;
    64. }
    65. public static ResultAjax ok(String msg, Integer code) {
    66. ResultAjax r = new ResultAjax();
    67. r.put("success", true);
    68. r.put("code", code);
    69. r.put("msg", msg);
    70. r.put("data", null);
    71. return r;
    72. }
    73. public static ResultAjax ok() {
    74. return new ResultAjax().put("msg", "操作成功!").put("data", null);
    75. }
    76. public static ResultAjax ok(Integer size) {
    77. return new ResultAjax().put("data", null);
    78. }
    79. @Override
    80. public ResultAjax put(String key, Object value) {
    81. super.put(key, value);
    82. return this;
    83. }
    84. /**
    85. * 添加返回结果数据
    86. *
    87. * @param key
    88. * @param value
    89. * @return
    90. */
    91. public ResultAjax putData(String key, Object value) {
    92. Map map = (HashMap) this.get("data");
    93. map.put(key, value);
    94. return this;
    95. }
    96. }
    1. public class HttpStatusConstant {
    2. //目前常用的,200,500
    3. public final static Integer HTTP_CODE_200 = 200;
    4. public final static Integer HTTP_CODE_500 = 500;
    5. //拦截
    6. public final static Integer HTTP_CODE_403 = 403;
    7. public final static Integer HTTP_CODE_401 = 401;
    8. }

    三、前端代码

    1. html>
    2. <html>
    3. <head>
    4. <title>文件分片上传title>
    5. head>
    6. <body>
    7. <input type="file" id="fileInput">
    8. <button id="startUpload">开始上传button>
    9. <button id="pauseResumeUpload">暂停上传button>
    10. <progress id="progress" max="100" value="0">progress>
    11. <span id="progressText">0%span>
    12. <script>
    13. const fileInput = document.getElementById('fileInput');
    14. const startUpload = document.getElementById('startUpload');
    15. const pauseResumeUpload = document.getElementById('pauseResumeUpload');
    16. const progress = document.getElementById('progress');
    17. const progressText = document.getElementById('progressText');
    18. let file;
    19. let uploading = false;
    20. let currentChunk = 0;
    21. let uploadPaused = false;
    22. let xhr;
    23. fileInput.addEventListener('change', (event) => {
    24. file = event.target.files[0];
    25. });
    26. async function uploadFile() {
    27. const chunkSize = 1 * 1024 * 1024; // 1MB
    28. const totalChunks = Math.ceil(file.size / chunkSize);
    29. for (currentChunk; currentChunk < totalChunks; currentChunk++) {
    30. if (uploadPaused) {
    31. break;
    32. }
    33. const startByte = currentChunk * chunkSize;
    34. const endByte = Math.min(startByte + chunkSize, file.size);
    35. const chunk = file.slice(startByte, endByte);
    36. const formData = new FormData();
    37. formData.append('file', chunk);
    38. formData.append('chunkNumber', currentChunk);
    39. formData.append('totalChunks', totalChunks);
    40. formData.append('identifier', file.name);
    41. const response = await fetch('http://localhost:8000/system/sys/test/upload', {
    42. method: 'POST',
    43. body: formData,
    44. });
    45. if (response.status !== 200) {
    46. console.error('Error uploading chunk: ', response.statusText);
    47. break;
    48. }
    49. const percent = ((currentChunk + 1) / totalChunks) * 100;
    50. progress.value = percent;
    51. progressText.textContent = `${Math.round(percent)}%`;
    52. }
    53. if (currentChunk >= totalChunks) {
    54. alert('文件上传成功!');
    55. uploading = false;
    56. currentChunk = 0;
    57. progress.value = 100;
    58. progressText.textContent = '100%';
    59. }
    60. }
    61. startUpload.addEventListener('click', () => {
    62. if (file && !uploading) {
    63. uploading = true;
    64. uploadFile();
    65. }
    66. });
    67. pauseResumeUpload.addEventListener('click', () => {
    68. if (uploading) {
    69. if (!uploadPaused) {
    70. uploadPaused = true;
    71. pauseResumeUpload.textContent = '继续上传';
    72. } else {
    73. uploadPaused = false;
    74. pauseResumeUpload.textContent = '暂停上传';
    75. uploadFile();
    76. }
    77. }
    78. });
    79. script>
    80. body>
    81. html>

    四、接下来我们演示下

    五、留言区

    有什么问题可以在底下评论区留言,看到会在本文后追加回复!

     

  • 相关阅读:
    2023年最新ADB工具箱R34下载-自带驱动常见ADB命令刷机ROOT神器
    Fiddler的安装及配置2-2
    内质网应激+预后模型新方向,干湿结合搞定5+sci
    TiDB ——TiKV
    gdb调试常见命令详细总结
    面对外部攻击威胁,怎样确保API安全
    李宁「向上」:不止缺一个FILA
    【Linux】VMware下载和安装
    Centos7宝塔部署python
    Linux下线程池(ThreadPool)
  • 原文地址:https://blog.csdn.net/qq_23126581/article/details/133642129