阅读 1456

el-upload 直连阿里云oss

今天后台说上传文件占带宽,想办法上传弄前台吧,别走后台了,吃不住。所以有了如下探索。

步骤一:阿里OSS配置

登录阿里云

homenew.console.aliyun.com/home/scene/…

image.png

阿里云配置STS AssumeRole临时授权访问OSS资源

  1. 用户管理——>创建RAM子账号,并自动生成AccessKeyId和AccessKeySecret;
  2. 策略管理——>新建自定义策略,授权访问OSS的bucket;
  3. 角色管理——>创建角色,将步骤2的策略授权给该角色;
  4. 策略管理——>新建自定义策略,授权访问STS的AssumeRole,该策略使用步骤3的角色;
  5. 用户管理——>将步骤4的策略授权给步骤1 的子账号。
  6. 使用子账号的AccessKeyId、AccessKeySecret和角色的roleArn,roleSessionName可以自己随意起,申请STS临时授权访问OSS。

步骤二:后端nest配置

ossUpload.service.ts

import { Injectable } from '@nestjs/common';
import config from '../../../config';
import * as STS from '@alicloud/sts-sdk';

@Injectable()
export class OssUploadService {
  /**
   * 获取 STS 认证
   * @param username 登录用户名
   */
  async getIdentityFromSTS(username: string) {
    const sts = new STS({
      endpoint: 'sts.aliyuncs.com',
      ...config,
    });
    
    const identity = await sts.getCallerIdentity();
    
    // 打*号是因为涉及安全问题,具体角色需要询问公司管理阿里云的同事
    const stsToken = await sts.assumeRole(`acs:ram::${identity.AccountId}:role/ram***oss`, `${username}`);

    return {
      code: 200,
      data: {
        stsToken,
      },
      msg: 'Success',
    };
  }
}

复制代码

ossUpload.module.ts

import { Module } from '@nestjs/common';
import { OssUploadService } from './ossUpload.service';

@Module({
  providers: [OssUploadService],
  exports: [OssUploadService],
})
export class OssUploadModule {}
复制代码

ossUpload.controller.ts

import { Controller, Body, Post, Request, UseGuards } from '@nestjs/common';
import { OssUploadService } from './ossUpload.service';

@Controller()
export class OssUploadController {
  constructor(private readonly ossUploadService: OssUploadService) {}

  @Post('oss-upload')
  async getIdentityFromSTS(@Body() Body: any) {
    return await this.ossUploadService.getIdentityFromSTS(Body.username);
  }
}
复制代码

报错修复指南链接

help.aliyun.com/knowledge_d…

help.aliyun.com/document_de…

help.aliyun.com/knowledge_d…

步骤三:前端代码

OSSFileUpload.vue

<script lang="ts">
import { Component, Vue, Prop, Emit } from 'vue-property-decorator';
import { getTokenFormLocal } from '@/utils/Storage';
import { API_FILE_UPLOAD } from '@/api';

import oss from '@/utils/ossForUpload.ts';

/**
 * 文件上传(aliOSS)
 *
 * @param {string} action - 上传的地址
 * @param {object} data - 上传时附带的额外参数
 * @param {string} name - 上传的文件字段名
 * @param {object} headers - 设置上传的请求头部
 * @param {number} maxSize - 文件上传的大小上限 单位kb
 * @param {number} limit - 最大允许上传个数
 * @param {string} fileTypes - 上传文件的类型
 * @param {array} fileList - 上传的文件列表, 例如: [{name: 'food.jpg', url: 'https://xxx.cdn.com/xxx.jpg'}]
 *
 * @event on-success - 文件上传成功时的钩子 function(response, file, fileList)
 * @event on-remove - 文件列表移除文件时的钩子 function(file, fileList)
 * @event on-error - 文件上传错误 function(errorType, file) errorType: typeError 类型不匹配;overSize: 超出大小上线; exceed: 超出最大个数
 */

@Component
export default class OSSFileUpload extends Vue {
  @Prop({default() { return []; }}) public fileList!: any[]; // 上传的文件列表, 例如: [{name: 'food.jpg', url: 'https://xxx.cdn.com/xxx.jpg'}]

  @Prop({default() {
    return `${process.env.VUE_APP_BASE_API_URL}${API_FILE_UPLOAD}`; // 上传接口
  }}) public action!: string; // 必选参数,上传的地址
  @Prop({default() {
    return {};
  }}) public data!: object; // 上传时附带的额外参数
  @Prop() public name!: string; // 上传的文件字段名
  @Prop({
    default() {
      return {
        identity: getTokenFormLocal() || '',
      };
    },
  }) public headers!: object; // 设置上传的请求头部
  @Prop(Number) public limit!: number; // 最大允许上传个数
  @Prop({default: 5 * 1024}) public maxSize!: number; // 文件上传的大小上线 单位kb
  @Prop() public fileTypes!: string;

  public httpRequest = oss;
  public alertMessage = ''; // 错误信息

  @Emit()
  public onSuccess(response: any, file: any, fileList: any) {
  }

  public beforeUpload(file: any) {
    file.percentage = 20;
    const enabledSize = file.size / 1024;

    if (enabledSize > this.maxSize) {
      this.$nextTick(function() {
        this.alertMessage = `文件大小不得超过${this.maxSize}kb`;
      });
      this.$emit('on-error-text', 'overSize', file);
      return false;
    }

    this.alertMessage = '';
  }

  /**
   * 文件超出个数限制时的钩子
   */
  public onExceed(files: any, fileList: any) {
    this.alertMessage = `超出最大上传数量${this.limit}`;
    this.$emit('on-error', this.alertMessage, files, fileList);
  }

  /**
   * 文件列表移除文件时的钩子
   */
  @Emit()
  public onRemove(file: any, fileList: any) {
    this.alertMessage = '';
  }
  public onError(err: any, file: any, fileList: any) {
    console.log('报错了');
    this.alertMessage = process.env.NODE_ENV === 'production' ? '网络异常,请稍后再试' : err;
    this.$emit('on-error', this.alertMessage, err, file, fileList);
  }
  public uploadProcess(event: any, file: any, fileList: any) {
    // console.log('文件上传中');
  }
}
</script>

<template>
  <section>
    <el-alert
      :style="{
        marginBottom: '8px'
      }"
      v-show="!!alertMessage"
      :title="alertMessage"
      type="error"
      show-icon>
    </el-alert>
    <el-upload
      :limit="limit"
      :action="action"
      :data="data"
      :name="name"
      :accept="fileTypes"
      drag
      multiple
      :file-list="fileList"
      :headers="headers"
      :on-success="onSuccess"
      :before-upload="beforeUpload"
      :on-progress="uploadProcess"
      :on-exceed="onExceed"
      :on-remove="onRemove"
      :http-request="httpRequest"
      :on-error="onError">
      <i class="el-icon-upload"></i>
      <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
      <div class="el-upload__tip" slot="tip"><slot name="tip"></slot></div>
    </el-upload>
  </section>
</template>

<style lang="less">
</style>
复制代码

ossForUpload.ts

// 记得npm i ali-oss一下
import * as OSS from 'ali-oss';
import { getPcBasicOss } from '@/model';
import ImgZipper from 'imgzipper';

function getError(action: any, option: any, xhr: any) {
  let msg;
  if (xhr.response) {
    msg = `${xhr.response.error || xhr.response}`;
  } else if (xhr.responseText) {
    msg = `${xhr.responseText}`;
  } else {
    msg = `fail to post ${action} ${xhr.status}`;
  }

  const err: any = new Error(msg);
  err.status = xhr.status;
  err.method = 'post';
  err.url = action;
  return err;
}

function getBody(xhr: any) {
  const text = xhr.responseText || xhr.response;
  if (!text) {
    return text;
  }

  try {
    return JSON.parse(text);
  } catch (e) {
    return text;
  }
}

function zipImg(file: any) {
  try {
    const imageType = file.type.includes('image');

    if ( imageType ) {
       return new Promise((res, rej) => {
        ImgZipper( file, (a: any, b: any) => {
          const zipfile = new File([a], file.name, {type: file.type, lastModified: Date.now()});
          res(zipfile);
        }, {
          scale: 0.78, // 生成图像缩放大小
          quality: 0.62, // 生成图像的质量
          disableBlob: null, // canvas.toBlob方法失败后调用函数
          type: 'image/jpeg', // 生成图像格式
          exif: true, // 是否使用调整相机旋转
        });
      });
    }
  } catch (err) {
    throw err;
  }
}

export default async function upload(option: any) {
  const {file = {}} = option;
  const newFile: any = await zipImg(file);

  try {
    // 这里改成我们node的请求
    const { credentials = {} } = await getPcBasicOss({ bucket: 'asal' });

    const client = new OSS({
      region: credentials.region,
      accessKeyId: credentials.AccessKeyId,
      accessKeySecret: credentials.AccessKeySecret ,
      stsToken: credentials.SecurityToken,
      bucket: 你的bucket,
    });

    const result = await client.put(`/l-web-pc/${newFile.name}`, newFile);

    return result;

  } catch (err) {
    option.onError(err);
    throw err;
  }
}

复制代码

PS.如果是后端出接口,那就忽略node配置


如果这篇文章有用,欢迎评论, 点赞, 加关注

我是leo:祝大家早日升职加薪。

文章分类
前端
文章标签