文件上传功能

111 阅读2分钟

普通上传功能

1. 前端页面

  <div class="home">
    <a-upload
      name="file"
      :multiple="true"
      action="/open-api/file/upload"
      :headers="headers"
      @change="handleChange"
    >
      <a-button> <a-icon type="upload" /> Click to Upload </a-button>
    </a-upload>
  </div>
</template>

<script>
export default {
  data() {
    return {
      headers: {
        authorization: "authorization-text",
      },
    };
  },
  methods: {
    handleChange(info) {
      if (info.file.status !== "uploading") {
        console.log(info.file, info.fileList);
      }
      if (info.file.status === "done") {
        this.$message.success(`${info.file.name} file uploaded successfully`);
      } else if (info.file.status === "error") {
        this.$message.error(`${info.file.name} file upload failed.`);
      }
    },
  },
};
</script>

2. 跨域请求

  devServer: {
    port: 8080,
    proxy: {
      "/": { target: "http://localhost:8091", changeOrigin: true },
    },
  },
};

3. 后端接口完成

package com.longfor.board.controller.file;

import io.swagger.annotations.Api;
import org.springframework.stereotype.Controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.util.List;

@RestController
@Api(tags = "决策字典")
@RequestMapping("/open-api/file")
public class FileController {
    //读取application.properties文件中的filePath属性
    @Value("/Users/admin/Desktop/company/project/ps-project/ps-board-server/")
    private String filePath;

    /**
     * 前往上传页面
     * @return 页面名称
     */
    @GetMapping({"/upload", ""})
    public String goIndex() {
        return "upload";
    }

    /**
     *  将文件保存到指定文件夹
     * @param file  单个文件
     * @param files 多个文件
     * @return      重定向到controller层中前往下载页面的url
     * @throws IOException
     */
    @PostMapping("/upload")
    public String uploadAndGoDownLoad(@RequestPart("file") MultipartFile file,
                                      @RequestPart("files") List<MultipartFile> files) throws IOException {


        //判断文件夹是否存在,不存在时,创建文件夹
        File directoryFile = new File(filePath);
        if (!directoryFile.exists()) {
            //创建多个文件夹
            directoryFile.mkdirs();
        }

        //判断文件是否为空,不为空时,保存文件
        if (!file.isEmpty()) {
            saveFile(file);
        }

        //判断上传文件个数是否为0
        if (files.size() > 0) {
            for (MultipartFile multipartFile : files) {
                if (!multipartFile.isEmpty()) {
                    saveFile(multipartFile);
                }
            }
        }
        return "redirect:/goDownload";
    }

    /**
     * 保存所有的所有上传的文件名称,前往下载页面
     * @param model
     * @return 页面名称
     */
    @GetMapping("/goDownload")
    public String goDownload(Model model) {
        File file = new File(filePath);
        //判断文件夹是否存在
        if (file.exists()) {
            //获取文件夹下面的所有名称
            String[] list = file.list();
            model.addAttribute("fileNames", list);
        }

        return "download";
    }

    /**
     * 保存文件到指定位置
     * @param file 需要上传的文件
     * @throws IOException
     */
    public void saveFile(MultipartFile file) throws IOException {
        //获取文件名
        String name = file.getOriginalFilename();
        file.transferTo(new File(filePath + name));
    }

}
```
```

实现断点续传

<template>
  <div class="home">
    <a-upload
      name="file"
      :multiple="true"
      action="/open-api/file/upload"
      :headers="headers"
      @change="handleChange"
    >
      <a-button> <a-icon type="upload" /> Click to Upload </a-button>
    </a-upload>
    <a-upload-dragger
      name="file"
      class="upload-drag-box"
      :multiple="false"
      action="/open-api/file/cutupload"
      :beforeUpload="beforeUpload"
    >
      <p class="ant-upload-drag-icon">
        <a-icon type="upload" /> Click to Upload
      </p>
      <p class="ant-upload-text">点击选择或拖拽文件到此处上传</p>
    </a-upload-dragger>
    {{ cutRate }}
  </div>
</template>

<script>
import BreakPointLoad from "@/utils/cutrate/index";
import Axios from "axios";
const { CancelToken } = Axios;
let axiosList = [];

export default {
  data() {
    return {
      headers: {
        authorization: "authorization-text",
      },
      handStop: false,
      loading: false,
      fileData: {
        uid: "",
        name: "",
        status: "",
        response: "",
        url: "",
        propName: "",
      },
      state: {
        curtData: [],
        finished: false,
        fileName: "",
        waitLoadList: [],
        handLoadList: [],
      },
      cutRate: 0,
    };
  },
  methods: {
    // 上传前
    beforeUpload(e) {
      this.fileData = e;
      this.state.fileName = e.name || "file";
      // 不上传,等待切片后上传
      this.cutFile();
      return false;
    },
    // 切片
    async cutFile() {
      let cutLoad = new BreakPointLoad();
      let chunkList = await cutLoad.inputChange(this.fileData, (list, len) => {
        // 已切片长度
        let listLen = list.length;
        // 切片进度
        this.cutRate = (len / listLen) * 100;
      });
      // 是否完成切片
      this.state.finished = true;
      this.state.curtData = chunkList;
      debugger;
      // this.uploadCutfile();
      this.handClassify()

    },
    // 上传切片
    uploadCutfile() {
      this.uploadChunks(
        this.state.waitLoadList,
        this.state.fileName,
        (percent) => {
          this.uploadRate = percent;
        }
      );
    },
    // 切片数组转为FormData表单数据格式
    uploadChunks(chunkList, fileName, cb) {
      if (!Array.isArray(chunkList)) {
        throw `chunkList is not a Array`;
        // return;
      }
      const requestList = chunkList.map((chunk) => {
        let formData = new FormData();
        for (let i in chunk) {
          formData.append(i, chunk[i]);
        }
        return formData;
      });
      this.sendRequest(requestList, (percent) => {
        cb(percent);
      });
    },

    // 上传
    sendRequest(chunkList, cb) {
      let len = chunkList.length;
      // 总数
      const uploadedTotal = len;
      // 已循环次数
      let sendCount = 0;
      // 已上传数量
      let uploadedCount = 0;
      while (len > 0 && sendCount <= uploadedTotal) {
        const source = CancelToken.source();
        const formData = chunkList[sendCount];
        sendCount++;
        len--;
        Axios({
          method: "post",
          url: "baseUrl.uploadChunkUrl",
          data: formData,
          headers: {
            "Content-Type": "multipart/form-data",
          },
          cancelToken: source.token,
        }).then(() => {
          uploadedCount++;
          this.rmUploadedRequest(source);
          cb(Math.round((uploadedCount / uploadedTotal) * 100));
        });

        axiosList.push(source);
      }
    },
    rmUploadedRequest(source) {
      axiosList.forEach((item, index) => {
        if (item.token === source.token) {
          axiosList.splice(index, 1);
        }
      });
    },
    // 切片对比
    async handClassify(auto) {
      this.loading = true;
      let fileHash = "";
      const { curtData } = this.state;
      Array.isArray(curtData) && (fileHash = curtData[0].fileHash);
      let res = await this.findReadyChunk(fileHash);
      if (res.code) {
        const { waitList, handLoadList } = this.useFilechunkFilter(
          this.state.curtData,
          res.data
        );
        this.state.waitLoadList = waitList;
        this.state.handLoadList = handLoadList;
        console.log("auto", auto);
        auto && this.uploadCutfile();
      }
      this.loading = false;
    },
    findReadyChunk(fileChunk) {
      Axios({
        url: `chunk/readychunk/${fileChunk}`,
      });
    },
    useFilechunkFilter(waitFiles, upedFiles) {
      let handLoadList = [];
      let waitList = [];

      waitList = waitFiles.filter((file) => {
        const index = upedFiles.findIndex((fileHash) => {
          return fileHash === file.fileMd5No;
        });
        index > -1 && handLoadList.push(file);
        return index === -1;
      });

      return {
        handLoadList,
        waitList,
      };
    },
    // 暂停、继续上传
    stopUpload() {
      this.handStop = !this.handStop;
      // 暂停
      if (this.handStop) {
        this.handPauseUpload();
      } else {
        // 继续
        this.handClassify(true);
      }
    },
    handleChange(info) {
      if (info.file.status !== "uploading") {
        console.log(info.file, info.fileList);
      }
      if (info.file.status === "done") {
        this.$message.success(`${info.file.name} file uploaded successfully`);
      } else if (info.file.status === "error") {
        this.$message.error(`${info.file.name} file upload failed.`);
      }
    },
  },
};
</script>


依赖文件

 * @Author: wfl
 * @LastEditors: wfl
 * @description:
 * @updateInfo:
 * @Date: 2020-12-08 13:56:54
 * @LastEditTime: 2021-03-19 15:28:03
 */
import getFileChunkMd5 from "./getFileMd5";
// 切片大小 2M
export let CHUNK_SIZE = 2 * 1024 * 1024;
// export interface FileBlob {
//   file: Blob;
//   type?: String;
// }

// export interface IChunkData {
//   fileHash: string;
//   fileMd5No: string;
//   chunk: Blob;
//   percentage?: number;
//   type?: string;
// }

export default class BreakPointLoad {
  // 切片数据
   fileChunkList=[];
  // 文件md5
   fileMd5Data= "";
  // 最终数据
   chunkData= [];
  // 文件类型
   type= "";
  // 获取上传文件  size字节,例:切片大小为1M则size= 1 * 1024 * 1024
  async inputChange(file, cb, size) {
    // 若传入切片大小,则按照传入切片大小切片
    if (size) {
      CHUNK_SIZE = size;
    }
    this.type = file.type;
    const name = file.name;
    if (file) {
      this.fileChunkList = await this.createFileChunk(file, (data, size) => {
        cb(data, size);
      });
      this.fileMd5Data = await getFileChunkMd5(this.fileChunkList);
      this.chunkData = this.fileChunkList.map(({ file }, index) => ({
        // 文件名
        name: name,
        // 文件类型
        type: this.type,
        // 文件md5
        fileHash: this.fileMd5Data,
        // 切片标记
        fileMd5No: `${this.fileMd5Data}-${index}`,
        // 切片文件
        chunk: file,
        // 上传进度
        percentage: 0,
      }));
      return this.chunkData;
    }
  }
  // 文件切片
  async createFileChunk(file, cb) {
    const list = [];
    const type = this.type;
    // 切片位置
    let ssize = 0;
    let len = await Math.ceil(file.size / CHUNK_SIZE);
    while (ssize < file.size) {
      const data = {
        file: file.slice(ssize, ssize + CHUNK_SIZE),
        type: type,
      };
      list.push(data);
      ssize += CHUNK_SIZE;
      cb(list, len);
    }
    return await list;
  }
}

加密文件

 * @Author: wfl
 * @LastEditors: wfl
 * @description:
 * @updateInfo:
 * @Date: 2020-12-08 14:50:40
 * @LastEditTime: 2020-12-08 16:21:29
 */
import SparkMD5 from "spark-md5";
// import { FileBlob, CHUNK_SIZE } from './index'
// 获取文件Md5值
const getFileChunkMd5 = (fileChunkList) => {
  return new Promise((resolve) => {
    // 总切片数
    const chunkSize = fileChunkList.length;
    // 当前处理位置
    let currentChunk = 0;
    // SparkMD5实例的ArrayBuffer
    let spark = new SparkMD5.ArrayBuffer();
    let fileReader = new FileReader();
    fileReader.onload = (e) => {
      try {
        spark.append(e.target.result);
      } catch (error) {
        console.log("获取Md5错误,错误位置:" + currentChunk);
      }
      currentChunk++;

      if (currentChunk < chunkSize) {
        loadNext();
      } else {
        console.info("获取Md5完成");
        resolve(spark.end());
      }
    };
    fileReader.onerror = function () {
      console.warn("Md5:文件读取错误");
    };

    function loadNext() {
      fileReader.readAsArrayBuffer(fileChunkList[currentChunk].file);
    }
    loadNext();
  });
};

export default getFileChunkMd5;

参考文件:gitee.com/xiosheng/br…