• Java实现分片上传(前端分,后端合并)


    注:一些实体类数据自行修改 

    1,api文件 

    1. import request from '@/router/axiosInfo';
    2. export const uploadChunk = (data) => {
    3. return request({
    4. url: '/api/blade-sample/sample/covid19/uploadChunk',
    5. method: 'post',
    6. data
    7. })
    8. }
    9. export const uploadMerge = (data) => {
    10. return request({
    11. url: '/api/blade-sample/sample/covid19/uploadMerge',
    12. method: 'post',
    13. data
    14. })
    15. }

    2, axios文件

    1. /**
    2. * 全站http配置
    3. *
    4. * axios参数说明
    5. * isSerialize是否开启form表单提交
    6. * isToken是否需要token
    7. */
    8. import axios from 'axios';
    9. import store from '@/store/';
    10. import router from '@/router/router';
    11. import { serialize } from '@/util/util';
    12. import { getToken } from '@/util/auth';
    13. import { Message } from 'element-ui';
    14. import { isURL } from "@/util/validate";
    15. import website from '@/config/website';
    16. import { Base64 } from 'js-base64';
    17. import { baseUrl } from '@/config/env';
    18. // import NProgress from 'nprogress';
    19. // import 'nprogress/nprogress.css';
    20. import crypto from '@/util/crypto';
    21. //默认超时时间
    22. axios.defaults.timeout = 100000;
    23. //返回其他状态码
    24. axios.defaults.validateStatus = function (status) {
    25. return status >= 200 && status <= 500;
    26. };
    27. //跨域请求,允许保存cookie
    28. axios.defaults.withCredentials = true;
    29. // NProgress 配置
    30. // NProgress.configure({
    31. // showSpinner: false
    32. // });
    33. //http request拦截
    34. axios.interceptors.request.use(config => {
    35. //开启 progress bar
    36. // NProgress.start();
    37. //地址为已经配置状态则不添加前缀
    38. if (!isURL(config.url) && !config.url.startsWith(baseUrl)) {
    39. config.url = baseUrl + config.url
    40. }
    41. //headers判断是否需要
    42. const authorization = config.authorization === false;
    43. if (!authorization) {
    44. config.headers['Authorization'] = `Basic ${Base64.encode(`${website.clientId}:${website.clientSecret}`)}`;
    45. }
    46. //headers判断请求是否携带token
    47. const meta = (config.meta || {});
    48. const isToken = meta.isToken === false;
    49. //headers传递token是否加密
    50. const cryptoToken = config.cryptoToken === true;
    51. //判断传递数据是否加密
    52. const cryptoData = config.cryptoData === true;
    53. const token = getToken();
    54. if (token && !isToken) {
    55. config.headers[website.tokenHeader] = cryptoToken
    56. ? 'crypto ' + crypto.encryptAES(token, crypto.cryptoKey)
    57. : 'bearer ' + token;
    58. }
    59. // 开启报文加密
    60. if (cryptoData) {
    61. if (config.params) {
    62. const data = crypto.encryptAES(JSON.stringify(config.params), crypto.aesKey);
    63. config.params = { data };
    64. }
    65. if (config.data) {
    66. config.text = true;
    67. config.data = crypto.encryptAES(JSON.stringify(config.data), crypto.aesKey);
    68. }
    69. }
    70. //headers中配置text请求
    71. if (config.text === true) {
    72. config.headers["Content-Type"] = "text/plain";
    73. }
    74. //headers中配置serialize为true开启序列化
    75. if (config.method === 'post' && meta.isSerialize === true) {
    76. config.data = serialize(config.data);
    77. }
    78. return config
    79. }, error => {
    80. return Promise.reject(error)
    81. });
    82. //http response 拦截
    83. axios.interceptors.response.use(res => {
    84. //关闭 progress bar
    85. // NProgress.done();
    86. //获取状态码
    87. const status = res.data.code || res.status;
    88. const statusWhiteList = website.statusWhiteList || [];
    89. const message = res.data.msg || res.data.error_description || '未知错误';
    90. const config = res.config;
    91. const cryptoData = config.cryptoData === true;
    92. //如果在白名单里则自行catch逻辑处理
    93. if (statusWhiteList.includes(status)) return Promise.reject(res);
    94. //如果是401则跳转到登录页面
    95. if (status === 401) store.dispatch('FedLogOut').then(() => router.push({ path: '/login' }));
    96. // 如果请求为非200否者默认统一处理
    97. if (status !== 200) {
    98. Message({
    99. message: message,
    100. type: 'error'
    101. });
    102. return Promise.reject(new Error(message))
    103. }
    104. // 解析加密报文
    105. if (cryptoData) {
    106. res.data = JSON.parse(crypto.decryptAES(res.data, crypto.aesKey));
    107. }
    108. return res;
    109. }, error => {
    110. // NProgress.done();
    111. return Promise.reject(new Error(error));
    112. });
    113. export default axios;

    3,vue源码 

    1. <template>
    2. <el-upload class="upload-demo" :on-change="handleChange" :show-file-list="false" :limit="1">
    3. <el-button slot="trigger" size="small" type="primary">选取文件</el-button>
    4. <el-button style="margin-left: 10px;" size="small" type="success" @click="uploadMerge" v-loading.fullscreen.lock='loading'>上传到服务器</el-button>
    5. <el-progress :percentage="percentage" :color="customColor"></el-progress>
    6. </el-upload>
    7. </template>
    8. <script>
    9. import { uploadMerge, uploadChunk } from "@/api/sample/sampleCovid19Info";
    10. export default {
    11. data() {
    12. return {
    13. percentage: 0,
    14. customColor: '#409eff',
    15. fileList: [], // 文件列表
    16. chunks: [], // 切片数组
    17. currentChunkIndex: 0, // 当前切片索引
    18. totalChunks: 0, // 总切片数
    19. currentFile: null, // 当前处理文件
    20. totalSize: 0,
    21. loading: false
    22. };
    23. },
    24. methods: {
    25. async uploadMerge() {
    26. this.loading = true
    27. const formData = new FormData();
    28. formData.append('fileName', this.currentFile.name);
    29. formData.append('totalSize', this.totalSize);
    30. console.log(22222);
    31. try {
    32. let res = await uploadMerge({ fileName: this.currentFile.name, totalSize: this.totalSize })
    33. console.log(res, '2');
    34. if (res.data.code === 200) {
    35. this.fileList = []
    36. this.chunks = []
    37. this.currentChunkIndex = 0
    38. this.totalChunks = 0
    39. this.currentFile = null
    40. this.totalSize = 0
    41. this.$message({
    42. type: "success",
    43. message: "添加成功!"
    44. });
    45. this.loading = false
    46. } else {
    47. this.$message({
    48. type: "success",
    49. message: "添加失败!"
    50. });
    51. this.loading = false
    52. }
    53. } catch (error) {
    54. this.$message.error(error)
    55. } finally {
    56. this.loading = false
    57. }
    58. },
    59. handleChange(file, fileList) {
    60. this.totalSize = file.size
    61. // 当文件变化时,执行切片操作
    62. if (file.status === 'ready') {
    63. this.currentFile = file.raw; // 获取原始文件对象
    64. this.sliceFile(this.currentFile);
    65. }
    66. },
    67. async sliceFile(file) {
    68. const chunkSize = 1024 * 1024 * 10; // 切片大小设置为1MB
    69. const totalSize = file.size;
    70. this.totalChunks = Math.ceil(totalSize / chunkSize);
    71. for (let i = 0; i < this.totalChunks; i++) {
    72. const start = i * chunkSize;
    73. const end = Math.min(start + chunkSize, totalSize);
    74. const chunk = file.slice(start, end);
    75. this.chunks.push(chunk);
    76. const formData = new FormData();
    77. formData.append('chunk', chunk);
    78. formData.append('chunkIndex', i);
    79. formData.append('totalChunks', this.totalChunks);
    80. formData.append('fileName', this.currentFile.name);
    81. formData.append('totalSize', this.totalSize);
    82. let res = await uploadChunk(formData)
    83. if (res.data.code === 200) {
    84. this.currentChunkIndex = i + 1;
    85. if (this.percentage != 99) {
    86. this.percentage = i + 1;
    87. }
    88. //判断文件是否上传完成
    89. if (this.currentChunkIndex === this.totalChunks) {
    90. this.percentage = 100
    91. this.$message({
    92. type: "success",
    93. message: "上传成功!"
    94. });
    95. }
    96. }
    97. }
    98. }
    99. }
    100. }
    101. </script>

    4,切片请求接口

    /**
     * 大上传文件
     */
    @PostMapping("/uploadChunk")
    @ApiOperationSupport(order = 15)
    @ApiOperation(value = "大上传文件", notes = "大上传文件")
    public R uploadChunk(@RequestParam("chunk") MultipartFile chunk,
                    @RequestParam("chunkIndex") Integer chunkIndex,
                    @RequestParam("totalChunks") Integer totalChunks,
                    @RequestParam("fileName") String fileName,
                    @RequestParam("totalSize") Long totalSize) throws IOException {
       Boolean isOk = sampleCovid19Service.LargeFiles(chunk, chunkIndex, totalChunks, fileName, totalSize);
       return R.status(isOk);
    }
    /**
     * 大上传文件
     * @param chunk 文件流
     * @param chunkIndex 切片索引
     * @param totalChunks 切片总数
     * @param fileName 文件名称
     * @param totalSize 文件大小
     * @return
     */
    Boolean LargeFiles(MultipartFile chunk, Integer chunkIndex, Integer totalChunks, String fileName, Long totalSize);

     

    /**
     * 大上传文件
     * @param chunk 文件流
     * @param chunkIndex 切片索引
     * @param totalChunks 切片总数
     * @param fileName 文件名称
     * @param totalSize 文件大小
     * @return
     */
    @Override
    public Boolean LargeFiles(MultipartFile chunk, Integer chunkIndex, Integer totalChunks, String fileName, Long totalSize) {
       if (chunk == null) {
          throw new ServiceException("添加失败,文件名称获取失败");
       }
       if (chunkIndex == null) {
          throw new ServiceException("添加失败,分片序号不能为空");
       }
       if (fileName == null) {
          throw new ServiceException("添加失败,文件名称获取失败");
       }
       if (totalSize == null || totalSize == 0) {
          throw new ServiceException("添加失败,文件名大小不能为空或0");
       }
    
       //获取文件名称
       fileName = fileName.substring(0, fileName.lastIndexOf("."));
       File chunkFolder = new File(chunkPath + totalSize + "/" + fileName);
       //判断文件路径是否创建
       if (!chunkFolder.exists()) {
          chunkFolder.mkdirs();
       }
       //直接将文件写入
       File file = new File(chunkPath + totalSize + "/" + fileName + "/" + chunkIndex);
       try {
          chunk.transferTo(file);
       } catch (IOException e) {
          log.info("时间:{}" + new Date() + "文件名称:{}" + fileName + "上传失败");
          throw new ServiceException("时间:{}" + new Date() + "文件名称:{}" + fileName + "上传失败");
       }
       return true;
    }

     5,合并接口

    package org.springblade.sample.vo;
    
    import lombok.Data;
    
    @Data
    public class UploadMergeVO {
    
       //文件上传名称
       private String fileName;
    
       //文件大小
       private Long totalSize;
    }
    
    /**
     * 大文件合并
     */
    @PostMapping("/uploadMerge")
    @ApiOperationSupport(order = 16)
    @ApiOperation(value = "文件合并", notes = "文件合并")
    public R uploadMerge(@RequestBody UploadMergeVO vo) throws IOException {
       Boolean isOk = sampleCovid19Service.LargeMerge(vo);
       return R.status(isOk);
    }
    /**
     *  大文件合并
     * @param vo 
     * @return
     * @throws IOException
     */
    Boolean LargeMerge(UploadMergeVO vo) throws IOException;

     

    /**
     *  大文件合并
     * @param vo
     * @return
     * @throws IOException
     */
    @Override
    public Boolean LargeMerge(UploadMergeVO vo) throws IOException {
    
       if (StringUtil.isBlank(vo.getFileName())) {
          throw new ServiceException("上传失败,文件名称获取失败");
       }
       if (vo.getTotalSize() == 0) {
          throw new ServiceException("上传失败,文件大小不能为空");
       }
       //块文件目录
       String fileNameInfo = vo.getFileName().substring(0, vo.getFileName().lastIndexOf("."));
       //合并文件夹
       File chunkFolder = new File(chunkPath + vo.getTotalSize() + "/" + fileNameInfo + "/");
       File depositFile = new File(sourceFile + vo.getTotalSize() + "/" + fileNameInfo + "/");
       //创建文件夹
       if (!depositFile.exists()) {
          depositFile.mkdirs();
       }
       //创建新的合并文件
       File largeFile = new File(depositFile + "/" + vo.getFileName());
       largeFile.createNewFile();
       //用于写文件
       RandomAccessFile raf_write = new RandomAccessFile(largeFile, "rw");
       //指针指向文件顶端
       raf_write.seek(0);
       //缓冲区
       byte[] b = new byte[1024];
       //分块列表
       File[] fileArray = chunkFolder.listFiles();
       // 转成集合,便于排序
       List fileList = Arrays.asList(fileArray);
       // 从小到大排序
       Collections.sort(fileList, new Comparator() {
          @Override
          public int compare(File o1, File o2) {
             return Integer.parseInt(o1.getName()) - Integer.parseInt(o2.getName());
          }
       });
       //合并文件
       for (File chunkFile : fileList) {
          RandomAccessFile raf_read = new RandomAccessFile(chunkFile, "rw");
          int len = -1;
          while ((len = raf_read.read(b)) != -1) {
             raf_write.write(b, 0, len);
    
          }
          raf_read.close();
       }
       raf_write.close();
       //取出合并文件大小和hash
       long fileSize = getFileSize(largeFile);
       int hashCode = largeFile.hashCode();
       //判断是否合并成功
       if (fileSize == vo.getTotalSize()) {
          //将内容写入数据库
          FileBack fileBack = new FileBack();
          fileBack.setFileName(vo.getFileName());
          fileBack.setFileSize(Integer.parseInt(String.valueOf(vo.getTotalSize())));
          fileBack.setUpdateTime(new Date());
          fileBack.setFileUrl(chunkFolder.toString());
          fileBack.setFileHash(String.valueOf(hashCode));
          fileBack.setUploadData(new Date());
          fileBackService.save(fileBack);
          //删除临时文件目录
          File chunkFolderInfo = new File(chunkPath + vo.getTotalSize() + "/");
          FileUtils.deleteDirectory(chunkFolderInfo);
          log.info("时间:{}" + new Date() + "文件名称:{}" + vo.getFileName() + "上传成功");
          return true;
       } else {
          //删除临时文件目录
          File chunkFolderInfo = new File(chunkPath + vo.getTotalSize() + "/");
          FileUtils.deleteDirectory(chunkFolderInfo);
          log.info("时间:{}" + new Date() + "文件名称:{}" + vo.getFileName() + "上传失败");
          return false;
       }
    }
    
  • 相关阅读:
    C //例 4.4 输入一个字符,判别它是否为大写字母,如果是,将它转换成小写字母;如果不是,不转换。然后输出最后得到的字符。
    程序员的护城河
    MES系统是如何解决工厂上的难题的?
    windwos安装vcpkg(包含grpc)
    C++如何进行字符串分割,C++如何按照空格对字符串进行解析,C++如何按照逗号对字符串解析
    win10中ros与ubuntu中ros通信
    单机Linux下搭建MongoDB副本集-三节点
    kafka入门小结( windows本地环境)
    vue 前端实现login页登陆 验证码
    ssm基于SSM技术的医院在线预约诊疗系统设计与实现毕业设计源码011130
  • 原文地址:https://blog.csdn.net/weixin_62108279/article/details/138091719