Vue实现文件上传(单文件、多文件、分片上传)

前端使用UI封装好的upload组件

1.单文件上传

  <a-upload
            :disabled="!editpersonflag"
            name="avatar"
            listType="picture-card"
            class="avatar-uploader"
            :showUploadList="false"
            :beforeUpload="beforeUpload"
            @change="handleAvatar"
          >
            <img v-if="imageUrl" :src="imageUrl" alt="avatar" style="width:204px;height:164px">
          </a-upload>

 // 处理文件上传
    handleUpload(file) {
      axios({
        url: "http://127.0.0.1:9898/filemodule/file/upload",
        method: "post",
        headers: {
          "Content-Type": "multipart/form-data",
          token:
            localStorage.getItem("token") === null
              ? ""
              : localStorage.getItem("token")
        },
        data: {
          file: file
        },
        transformRequest: [
          function(oldData) {
            var form = new FormData();
            for (let item in oldData) {
              form.append(item, oldData[item]);
            }
            return form;
          }
        ]
      }).then(response => {
        if (response.data.code === 200) {
          this.$message.success("上传成功");
              this.imageUrl = response.data.data.url;
        } else {
          this.$message.error("上传失败");
        }
      });
    },
    beforeUpload() {
      return false;
    },

2.多文件上传

    <!-- 预览图 -->
    <b-row class="evaluate-row">
      <span class="evaluate-span">预览图</span>
      <a-upload multiple :fileList="fileList" :remove="handleRemove" :beforeUpload="beforeUpload">
        <a-button>
          <a-icon type="upload"/>选择文件
        </a-button>
      </a-upload>
      <a-button
        type="primary"
        @click="handlePreviewAvatar"
        :disabled="fileList.length === 0"
        :loading="uploading"
        style="margin-left: 16px"
      >{{uploading ? '上传中' : '开始上传' }}</a-button>
    </b-row>

  handleRemove(file) {
      const index = this.fileList.indexOf(file);
      const newFileList = this.fileList.slice();
      newFileList.splice(index, 1);
      this.fileList = newFileList;
    },
    beforeUpload1(file) {
      this.fileList = [...this.fileList, file];
      return false;
    },
    // 预览图
    handlePreviewAvatar() {
      const previewurls = [];
      for (var i = 0; i < this.fileList.length; i++) {
        axios({
          url: "http://127.0.0.1:9898/filemodule/file/upload",
          method: "post",
          headers: {
            "Content-Type": "multipart/form-data",
            token:
              localStorage.getItem("token") === null
                ? ""
                : localStorage.getItem("token")
          },
          data: {
            file: this.fileList[i]
          },
          transformRequest: [
            function(oldData) {
              var form = new FormData();
              for (let item in oldData) {
                form.append(item, oldData[item]);
              }
              return form;
            }
          ]
        }).then(response => {
          if (response.data.code === 200) {
            this.$message.success("上传成功");
            previewurls.push(response.data.data.url);
            this.previewurls = previewurls;
            console.log(previewurls);
          } else {
            this.$message.error("上传失败");
          }
        });
      }
    },

3.分片上传(使用blob切片对文件进行切割)

 //文件预上传
    handlePrepareUpload() {
      this.uploading = "上传中";
      var file = this.fileList[0];
      const fileSize = file.size; // 文件大小
      this.filesize = fileSize;
      const chunkSize = 1024 * 1024 * 10; // 切片的大小
      const chunks = Math.ceil(fileSize / chunkSize); // 获取切片个数
      const fileReader = new FileReader();
      const spark = new SparkMD5.ArrayBuffer();
      const bolbSlice =
        File.prototype.slice ||
        File.prototype.mozSlice ||
        File.prototype.webkitSlice;
      let currentChunk = 0;

      fileReader.onload = e => {
        const res = e.target.result;
        spark.append(res);
        currentChunk++;
        if (currentChunk < chunks) {
          loadNext();
        } else {
          const md5 = spark.end();
          this.getMd5Checked(md5);
        }
      };

      const loadNext = () => {
        const start = currentChunk * chunkSize;
        const end =
          start + chunkSize > file.size ? file.size : start + chunkSize;
        fileReader.readAsArrayBuffer(bolbSlice.call(file, start, end));
      };
      loadNext();
    },
    getMd5Checked(value) {
      this.fileMD5 = value;
      HttpService.getFileCheckByMd5({ md5: value }).then(response => {
        if (response.data.data.md5 !== value) {
          const { fileList } = this;
          HttpService.getFilePrepare({
            extension: fileList[0].name.slice(
              fileList[0].name.lastIndexOf(".") + 1
            ),
            chunks: Math.ceil(this.fileList[0].size / 1024 / 1024 / 10)
          })
            .then(response => {
              const downloadaddress = response.data.data.url;
              this.downloadaddress = downloadaddress;
              const contextId = response.data.data.contextId;
              this.filecontextId = contextId;
              this.handleUpload(response);
            })
            .catch(error => {
          
            });
        } else {
          // 如果文件之前上传过 则返回数据
          this.uploading = "上传完成";
          this.fileMD5 = response.data.data.md5;
          this.filecontextId = response.data.data.contextId;
          this.downloadaddress = response.data.data.url;
          this.filesize = response.data.data.length;
        }
      });
    },
    // 文件上传
    handleUpload(res) {
      var type = this.fileList[0].type; // 文件类型
      var chunk = 1024 * 1024 * 10; // 每个文件切片大小定为10MB .
      var blobs = [];
      var start = 0;
      //文件切割
      for (var i = 0; i < Math.ceil(this.fileList[0].size / chunk); i++) {
        var end = start + chunk;
        blobs[i] = this.fileList[0].slice(start, end, type);
        start = end;
      }
      var uploads = res.data.data.uploads;
      var count = 0;
      for (var i = 0; i < uploads.length; i++) {
        var params = uploads[i].params;
        var url = uploads[i].host;
        axios({
          url: url,
          method: "post",
          headers: {
            "Content-Type": "multipart/form-data"
          },
          data: {
            appKey: params.appKey,
            contextId: params.contextId,
            expires: params.expires,
            token: params.token,
            file: blobs[i]
          },
          transformRequest: [
            function(oldData) {
              var form = new FormData();
              for (let item in oldData) {
                form.append(item, oldData[item]);
              }
              return form;
            }
          ]
        })
          .then(response => {
            count++;
            if (count === i) {
              HttpService.getFileComplete({
                contextId: this.filecontextId,
                md5: this.fileMD5
              }).then(response => {
                this.uploading = "上传完成";
              });
            }
          })
          .catch(error => {
   
          });
      }
    },

服务端代码
web端上传文件后,后端读取文件并存储到静态资源存储位置,并将地址存进数据库,这样通过地址就能访问到资源了,在这之前我们需要配置服务端存储文件的本地文件夹。

#    配置静态文件夹,这是我的图片服务器(可以直接端口地址加文件夹内的名称可以直接访问该文件)
  resources:
        static-locations: classpath:/META-INF/resources/,classpath:/resources/,\
                            classpath:/static/,classpath:/public/,file:D:\workspace\imgs

处理上传

@Service(value = "fileSerice")
public class FileService implements IFileService {

    @Autowired
    private FileMapper fileMapper;

    //    当前时间戳
    SimpleDateFormat dformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
    @Override
    public Map<String,Object> insertfile(MultipartFile file,long uploader) {
//        文件本地存储位置
        String filepath = "D:\\workspace\\imgs";

//        文件后缀名
        String extension = file.getOriginalFilename().substring(file.getOriginalFilename().indexOf('.') + 1);
//        文件前缀名
        String suffix = MD5Utils.md5(file.getOriginalFilename());
//        拼接成新的文件名
        String fileName = suffix + "." + extension;

//         文件存储到数据库的地址
        String url = "http://127.0.0.1:9898/" + fileName;
        File dest = new File(filepath + "/" + fileName);
        try {
            file.transferTo(dest);
        } catch (IOException e) {
            e.printStackTrace();
        }
        String md5 = FileUtils.getFileMD5(dest);

        Map<String, Object> filemaps = new HashMap<>();

        filemaps.put("filename",file.getOriginalFilename());
        filemaps.put("length",dest.length());
        filemaps.put("md5",md5);
        filemaps.put("url",url);
        filemaps.put("uploader",uploader);
        filemaps.put("uploadDate", dformat.format(new Date()));

        Integer num = fileMapper.insertFile(url, md5, file.getOriginalFilename(), dest.length(), uploader,0,uploader, dformat.format(new Date()));
        if (num != 1) {
            throw new OperationFailException();
        }
        return filemaps;
    }
}

接口部分代码

/**
 *文件上传
 * zpwan
 * 2019/3/25
 */
@RestController
@CrossOrigin
public class FileController {
    @Autowired
    private FileService fileService;


    /**
     * 文件上传
     * @param file
     * @return
     */
    @PostMapping(value="/filemodule/file/upload")
    public ResultResponse handleUpload(
            @RequestParam(value = "file", required = true) MultipartFile file,
            HttpServletRequest request
    ) {
        String token = request.getHeader("token");

        Long userId = JsonWebTokenUtils.getAppUID(token);
        if (file.isEmpty()) {
            return new ResultResponse(501, "文件为空,请重新上传");
        } else {
            Map<String, Object> fileMaps = fileService.insertfile(file,userId);
            return new ResultResponse(200, "上传成功", fileMaps);
        }
    }

}

最近又对之前代码进行了封装,封装成一个函数,实现复用性:

import axios from 'axios'
import { AxiosResponse } from 'axios'
export const handleFileUpload = async (file: File, callback: Function) => {

    const res: AxiosResponse<ApiResponse<FileInfo>> = await axios({
        url: "http://127.0.0.1:9898/blogManage/filemodule/upload",
        method: "post",
        headers: {
            "Content-Type": "multipart/form-data",
            'token': localStorage.getItem('token') === null ? '' : localStorage.getItem('token')
        },
        data: {
            file: file
        },
        transformRequest: [
            function (oldData) {
                var form = new FormData();
                for (let item in oldData) {
                    form.append(item, oldData[item]);
                }
                return form;
            }
        ]
    })
    callback(res.data);
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,914评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,935评论 2 383
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,531评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,309评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,381评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,730评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,882评论 3 404
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,643评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,095评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,448评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,566评论 1 339
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,253评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,829评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,715评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,945评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,248评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,440评论 2 348

推荐阅读更多精彩内容