vue3.0 + ts 实现上传工厂(oss与cos)

发布时间 2023-11-06 16:28:12作者: Sanctuary

概述

将上传基类命名为MOS(Mine Object Storage)

mos.ts代码

import {MosType} from './mosConfig'
import {Loading} from '../loading'
import {type BinaryFile, type MosFile} from './fileUtil'
import type { PathTemplate } from './pathTemplate'
import axios, { type AxiosResponse } from 'axios'
import {loadUser} from '../user'
export interface MosSettings{
    cdn:string | null
    bucket:string
    endpoint:string
}

export interface UploadOptions{
    cover:boolean 
    progress?:Function
    failed?:Function
}

export abstract class Mos{
    settings : MosSettings
    finished : boolean = false
    loading : Loading
    pathTemplate:string 

    constructor(settings:MosSettings,template:PathTemplate){
        this.settings = settings
        this.loading = new Loading()
        this.pathTemplate = `${template.appId}/@ownerId/@fileName`
    }

    abstract init() : void

    abstract uploadFile(file:BinaryFile,options:UploadOptions,token:string) : Promise<MosFile|null>

    async upload(file:BinaryFile,options:UploadOptions) : Promise<MosFile|null> {
        const user=loadUser()
        this.pathTemplate=this.pathTemplate.replace('@ownerId',user?.userId.toString() ?? '0')
        return await this.uploadFile(file,options,user?.token ?? '')
    }

    async getCredential(token:string,bucket:string,type: MosType) : Promise<AxiosResponse<any, any>>{
        const params = { bucket: bucket, ossSource: type }
        const url = `https://api.mos.com/api/v1/Credential` // 接口会根据不同的ossSource参数值返回不同的数据结构,因此可以放在基类

        return axios.get(url, { params ,headers:{
            'Authorization':token,
            'Content-Type':'application/json'
        }})
    }
}

oss.ts代码

import axios from "axios";
import type { BinaryFile, MosFile } from "./fileUtil";
import { Mos, type UploadOptions } from "./mos";
import { MosType, type OssConfig } from "./mosConfig";
import type { OssCredential } from "./mosCredential";
import type { PathTemplate } from "./pathTemplate";

export class Oss extends Mos{
    policy : string = ''
    signature : string = ''
    accessKeyId:string
    host:string

    constructor(config:OssConfig,template:PathTemplate) {
        super({bucket:config.bucket,endpoint:config.endpoint,cdn:config.cdn},template)
        this.accessKeyId = config.accessKeyId
        this.host=`https://${config.bucket}.${config.endpoint}`
    }

    init() {
        return
    }

    async getSTSToken(token:string){
        if (this.policy) return Promise.resolve()
        
        return await this.getCredential(token,this.settings.bucket,MosType.Oss).then(result => {
            const data = result.data.data as OssCredential;
            this.policy = data.policy;
            this.signature = data.signature;
        })
    }

    async uploadFile(file: BinaryFile, options: UploadOptions,token:string): Promise<MosFile|null> {
        this.finished = false
        this.loading.startLoading()
        await this.getSTSToken(token)
        const fileName = file.name
        const ossPrefix = 'TempFiles/'
        const saveKey = ossPrefix + this.pathTemplate.replace('@fileName',fileName)

        const formData = new FormData();
        formData.append("policy", this.policy);
        formData.append("Signature", this.signature);
        formData.append("bucket", this.settings.bucket);
        formData.append("key", saveKey);
        formData.append("OSSAccessKeyId", this.accessKeyId);
        formData.append("success_action_status", "201");

        formData.append("Content-Disposition", "attachment;filename=" + fileName + ";filename*=UTF-8''" + fileName);
        formData.append("file", file.binary);

        return axios.post(this.host, formData, {
            responseType: 'document',
            onUploadProgress: evt => options.progress && options.progress(evt),
        }).then(result => {
            this.finished = true;
            this.loading.endLoading();

            console.log("上传成功");

            const data = result.data;
            const url = data.getElementsByTagName("Location")[0].childNodes[0].nodeValue;

            return url ? Promise.resolve({ url,  fileName }) : Promise.reject(result);
        }).catch(error => {
            this.finished = true;
            this.loading.endLoading()

            console.log("上传失败");

            options.failed &&options.failed(error);

            return null
        })
    }    

}

cos.ts 代码

import type { BinaryFile, MosFile } from './fileUtil'
import { Mos, type UploadOptions } from './mos'
import { MosType, type CosConfig } from './mosConfig'
import type { PathTemplate } from './pathTemplate'
import COS from 'cos-js-sdk-v5'
import type { CosCredential } from './mosCredential'

export class TencentCOS extends Mos {
  cos: COS | null = null
  currentToken:string = ''

  constructor(config: CosConfig, template: PathTemplate) {
    super({ bucket: config.bucket, endpoint: config.endpoint, cdn: config.cdn }, template)
  }

  init() {
    this.cos = new COS({
      getAuthorization:  (options, callback) => this.getAuthorizationCredential(callback),
      UploadCheckContentMd5: true 
    })
  }
  async uploadFile(file: BinaryFile, options: UploadOptions, token: string): Promise<MosFile | null> {
    this.currentToken = token
    this.finished = false;
    this.loading.startLoading()
    const fileName = file.name
    const savekey = this.pathTemplate.replace('@fileName',fileName)
    return new Promise((resolve, reject) => {
        this.cos!.uploadFile({
            Bucket: this.settings.bucket,
            Region: this.settings.endpoint,
            Key: savekey,
            Body: file.binary,
            SliceSize: 1024 * 1024 * 5, 
            Headers: {
                'content-disposition': "attachment;filename=" + fileName + ";filename*=UTF-8''" + fileName
            },
            onProgress: evt => options.progress && options.progress(evt),
        }, (err, data) => {
            this.finished = true;
            this.loading.endLoading()

            if (err) {
                console.log("上传失败");
                options.failed && options.failed(err);
                reject(err);
            }
            if (data) {
                console.log("上传成功");
                resolve({ url, fileName });
            }
        });
    })
  }

  getAuthorizationCredential(callback:Function){
      this.getCredential(this.currentToken, this.settings.bucket, MosType.Cos).then((result) => {
      const data = result.data.data as CosCredential
      callback({
        TmpSecretId: data.tmpSecretId,
          TmpSecretKey: data.tmpSecretKey,
          SecurityToken: data.token,
          StartTime: data.startTime,
          ExpiredTime: data.expiredTime
      })
    })
  }

}

mosFactory.ts 代码

import { TencentCOS as Cos } from "./cos";
import type { Mos } from "./mos";
import {MosConfig, MosType } from "./mosConfig";
import { Oss } from "./oss";
import type { PathTemplate } from "./pathTemplate";

export function CreateMos (template:PathTemplate,type:MosType = MosType.Cos) : Mos {
    const config:MosConfig = new MosConfig()
    let mos:Mos 
    switch (type) {
        case MosType.Cos:
            mos= new Cos(config.cos,template)
            break;
        case MosType.Oss:
            mos= new Oss(config.oss,template)
            break
        default:
            mos= new Cos(config.cos,template)
            break;
    }
    mos.init()

    return mos
}

export function CreateDefaultMos(type:MosType = MosType.Cos) : Mos {
    return CreateMos({appId:'demo'},type)
}

其他代码

PathTemplate.ts

export interface PathTemplate{
    appId:string //应用Id 
}

mossCredential.ts

export interface OssCredential{
    policy:string
    signature:string
    accessKeyId:string|null
}

export interface CosCredential{
    tmpSecretId:string
    tmpSecretKey:string
    token:string
    startTime:number
    expiredTime:number
}

fileUtil.ts

export interface MosFile {
  url: string | null
  fileName: string
}

export interface BinaryFile{
  binary: Blob
  name:string
}

mosConfig.ts

export interface OssConfig {
  isTemp: boolean
  bucket: string
  endpoint: string
  cdn: string
  accessKeyId: string
}
export interface CosConfig{
    bucket: string
    endpoint: string
    cdn: string
}

export class MosConfig {
  oss: OssConfig = {
    bucket: 'demo-bucket',
    endpoint: 'oss-cn.aliyuncs.com',
    cdn: '',
    accessKeyId: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
  }
  cos:CosConfig = {
    bucket: 'demo-bucket',
    endpoint: 'beijing',
    cdn: ""
  }
}

export enum MosType{
    Oss = 1,
    Cos
}

在vue3.0是使用

在App.vue中

import {provide} from 'vue'
import {CreateDefaultMos} from './assets/ts/mos/mosFactory'

provide('mos',CreateDefaultMos())

在组件中注入使用

import {inject } from 'vue'

const mos:Mos = inject('mos')!