vue项目Minio上传附件记录

1.初始化Minio

创建minio.js文件

import { getOssSign } from '@/api/upload'

// 初始化minioClient
var Minio = require('minio')
export function initMinio () {
    return new Promise((resolve, reject) => {
        let localMinio = JSON.parse(localStorage.getItem('os'))
        if (localMinio) {
            let minioObj = {
                endPoint: 'oss.shlgbj.gov.cn',
                // port: 9200,
                useSSL: true,
                accessKey: localMinio.accessid,
                secretKey: localMinio.accesskey
            }
            var minioClient = newMinio(minioObj)
            resolve(minioClient)
        } else {
            let param = { ossType: 'minio' }
            getOssSign(param).then(response => {
                let minioDeploy = response.data
                let minioObj = {
                    endPoint: 'oss.shlgbj.gov.cn',
                    // endPoint: '10.0.51.96',
                    // port: 9200,
                    useSSL: true,
                    accessKey: minioDeploy.accessid,
                    secretKey: minioDeploy.accesskey
                    // accessKey: 'minioadmin',
                    // secretKey: 'minioadmin'
                }
                localStorage.setItem('os', JSON.stringify(minioDeploy))
                var minioClient = newMinio(minioObj)
                resolve(minioClient)
            }).catch(error => {
                console.log(error)
                reject()
            })
        }
    })
}

export const newMinio = (minioObj) => {
    return new Minio.Client(minioObj)
}

2.fileUpload组件使用Minio

<template>
  <div class="minioBox">
    <section class="uploadBar">
      <el-button
        class="btn_upload"
        icon="el-icon-plus"
        @click="getFileName"
        type="primary"
      >{{uploadText}}</el-button>
      <!-- <el-button v-if="fileList.length>0" style="marginRight:10px;" @click="upload" size="mini" type="success">上传</el-button> -->
      <input
        type="file"
        id="minIoFile"
        ref="minIoFile"
        :accept="allowType"
        v-show="false"
        @change="getFileObj"
      />
      <div class="attachmentsTips el-upload__tip" v-html="note"></div>
    </section>
    <section class="uploadFileList">
      <div v-for="(item, index) in fileList" :key="index" class="fileItem">
        <span class="name">{{item.name}}</span>
        <!-- <span>({{(item.size/1024/1024).toFixed(2)}}M)</span> -->
        <span class="progress_bar">
          <el-progress
            :percentage="item.uploadProgress"
            :format="setItemText(item)"
            v-if="item.showBar"
          />
        </span>
        <div class="btn_right">
          <i class="el-icon-circle-check el-icon-upload-success" v-if="!item.showBar"></i>
          <i class="el-icon-close" style="cursor: pointer;" @click="remove(item)"></i>
        </div>
      </div>
    </section>
  </div>
</template>
<script>
import { confirmOss, getFile } from '@/api/upload'
let stream = require('stream')
import { initMinio } from './minio'

export default {
  props: {
    uploadText: {
      type: String,
      default: '添加附件',
    },
    note: {
      type: String,
      default: () => ''
    },
    allowType: {
      type: String,
      default: () => ''
    },
    fileList: {
      type: Array,
      default: () => {
        return []
      }
    },
    fileSize: {
      type: Number,
      default: 20 // 0:表示没有限制
    },
    onSuccess: {
      type: Function,
      default: () => { }
    },
    onRemove: {
      type: Function,
      default: () => { }
    },
  },
  data () {
    return {
      //   fileList: [],
    }
  },
  methods: {
    setItemText (item) {
      return () => {
        return item.uploadProgress == 0 ? '解析中...' : item.uploadProgress + '%'
      }
    },
    upload () {
      this.fileList.map((item, index) => {
        this.uploadMinIo(item, index)
      })
    },
    remove (item) {
      this.onRemove(item)
    },
    getFileName () {
      let inputDOM = this.$refs.minIoFile
      inputDOM.click()
    },
    getFileObj (event) {
      let files = document.getElementById('minIoFile').files
      let fileSwitch = true
      if (files.length > 0) {
        for (let i = 0; i < files.length; i++) {
          const sizeLimit = this.fileSize !== 0 ? ((files[i].size / 1024 / 1024).toFixed(2) < this.fileSize) : true
          const nameLength = files[i].name.length <= 100
          if (!sizeLimit) {
            this.$message.error(`上传文件大小不能超过 ${this.fileSize}MB`)
            fileSwitch = false
            return
          }
          if (!nameLength) {
            this.$message.error(`附件名称过长,请重新上传`)
            fileSwitch = false
            return
          }
        }
        if (fileSwitch) {
          for (let i = 0; i < files.length; i++) {
            console.log(files[i][File])
            files[i].showBar = true
            files[i].uploadProgress = 0
            // this.$set(files[i], 'uploadProgress', 0)
            this.fileList.push(files[i])
          }
        }
        // this.upload()  //多选上传
        this.uploadMinIo(files[0])
      }
    },
    //上传文件
    async uploadMinIo (fileObj, index) {
      let minioClient = await initMinio()
      var localMinio = JSON.parse(localStorage.getItem('os'))
      let vm = this
      // 重置进度条
      // vm.uploadProgress = 0
      if (fileObj) {
        let file = fileObj
        //判断是否选择了文件
        if (file == undefined) {
          this.$message.error(`请上传文件`)
        } else {
          console.log(file)
          //获取文件类型及大小
          const filePath = file.name.split('.')
          const fileType = filePath[filePath.length - 1].toLowerCase()
          const fileNewName = `${filePath[0]}_${new Date().getTime()}.${fileType}`
          // const fileName = `${localMinio.bucketName}/${localMinio.dir}/${fileNewName}`
          const fileName = `${localMinio.dir}/${fileNewName}`
          const mineType = file.type
          const fileSize = file.size

          //参数
          let metadata = {
            "content-type": mineType,
            "content-length": fileSize
          }
          //判断储存桶是否存在
          minioClient.bucketExists(localMinio.bucketName, function (err) {
            if (err) {
              console.log(err)
              // if (err.code == 'NoSuchBucket') return console.log("bucket does not exist.")
              vm.$message.error(err)
              return
            }
            //存在
            console.log('Bucket exists.')
            //准备上传
            let reader = new FileReader()
            reader.readAsDataURL(file)
            reader.onloadend = function (e) {
              const dataurl = e.target.result
              //base64转blob
              const blob = vm.toBlob(dataurl)
              //blob转arrayBuffer
              let reader2 = new FileReader()
              reader2.readAsArrayBuffer(blob)
              reader2.onload = function (ex) {
                //定义流
                let bufferStream = new stream.PassThrough()
                //将buffer写入
                bufferStream.end(new Buffer(ex.target.result))
                // 单个上传对象进度条控制
                let newItem = vm.fileList[vm.fileList.length - 1]
                var timer = setInterval(() => {
                  if (newItem.uploadProgress <= 98) {
                    newItem.uploadProgress++
                    vm.$set(vm.fileList, vm.fileList.length - 1, newItem)
                  }
                }, 20)
                //上传
                minioClient.putObject(localMinio.bucketName, fileName, bufferStream, fileSize, metadata, function (err, etag) {
                  clearInterval(timer)
                  if (err == null) {
                    minioClient.presignedGetObject(localMinio.bucketName, fileName, 24 * 60 * 60, function (err, presignedUrl) {
                      if (err) {
                        this.$message.error(err)
                        return
                      }
                      //输出url
                      let curItem = vm.fileList[vm.fileList.length - 1]
                      curItem.showBar = false
                      curItem.uploadProgress = 100
                      vm.$set(vm.fileList, vm.fileList.length - 1, curItem)
                      vm.confirmOssUpload(fileObj, fileName)
                      console.log(presignedUrl)
                    })
                  }
                })
              }
            }
          })
        }

      }
    },
    //base64转blob
    toBlob (base64Data) {
      let byteString = base64Data
      if (base64Data.split(',')[0].indexOf('base64') >= 0) {
        byteString = atob(base64Data.split(',')[1]) // base64 解码
      } else {
        byteString = unescape(base64Data.split(',')[1])
      }
      // 获取文件类型
      let mimeString = base64Data.split(';')[0].split(":")[1] // mime类型

      // ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区
      // let arrayBuffer = new ArrayBuffer(byteString.length) // 创建缓冲数组
      // let uintArr = new Uint8Array(arrayBuffer) // 创建视图

      let uintArr = new Uint8Array(byteString.length) // 创建视图

      for (let i = 0; i < byteString.length; i++) {
        uintArr[i] = byteString.charCodeAt(i)
      }
      // 生成blob
      const blob = new Blob([uintArr], {
        type: mimeString
      })
      // 使用 Blob 创建一个指向类型化数组的URL, URL.createObjectURL是new Blob文件的方法,可以生成一个普通的url,可以直接使用,比如用在img.src上
      return blob
    },
    confirmOssUpload (file, fileName) {
      const paramsObj = [{
        // moduleId: getMid(),
        moduleId: '10102',
        orgFileName: file.name,
        postName: fileName,
        size: file.size + '',
        needTransferFormat: 'Y',
        disableIdentify: 'N'
      }]
      confirmOss(paramsObj).then(res => {
        res = res.data
        getFile({ 'fileId': res[0].fileId }).then(res => {
          this.fileList.pop()   //删除上传时放入数组的file对象
          this.onSuccess(res.data, file)
          // this.onSuccess(res.data, file, fileList)
        }).catch(error => {
          console.log(error)
        })
      })
    }
  }
}
</script>
<style lang="scss" scoped>
.minioBox {
  color: #000000;
  // padding: 10px;
  .uploadBar {
    display: flex;
    .attachmentsTips {
      margin-top: 0;
      padding-left: 10px;
      font-size: 12px;
      display: inline-block;
      height: 30px;
      vertical-align: middle;
      line-height: 30px;
      color: #aaa;
      text-align: left;
    }
  }
  .uploadFileList {
    margin-top: 10px;
    .fileItem {
      display: flex;
      align-items: center;
      width: 100%;
      height: 30px;
      .name {
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
      }
      .progress_bar {
        flex: 1;
        margin: 0 20px;
        /deep/ .el-progress {
          display: flex;
          align-items: center;
          .el-progress__text {
            width: 80px;
          }
        }
      }
      .btn_right {
        .el-icon-close {
          display: none;
        }
      }
      &:hover {
        cursor: pointer;
        background-color: #f5f7fa;
        .el-icon-close {
          display: block;
        }
        .el-icon-upload-success {
          display: none;
        }
      }
      i {
        margin: 0 5px 0 0;
      }
      .el-icon-upload-success {
        color: #67c23a;
      }
      .upload-fail {
        color: red;
      }
    }
  }
}
</style>

一些坑:

  1. 使用node的stream模块将file进行了可读流的转化,至今不知为啥可以用;
  2. 给每个上传附件自定义进度条的时候,set的更新数据问题; 需要改变单个item,更新整个fileList:vm.set(vm.fileList, vm.fileList.length - 1, curItem)
  3. 上传中fileList展示区域,本质上是本地文件的进度状态,
    成功之后展示已上传的文件信息,此处需要删除上传时放入数组的file对象 this.fileList.pop()
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容