阿里云 oss 文件上传

创建
阅读 563

创建 bucket

跨域配置

sts 服务

为了不在网页中暴露 AccessKeyId 和 AccessKeySecret,我们采用 STS 进行临时授权

STS 服务返回结果示例

各个字段的值已做变换处理,请以实际值为准

{
  "success": true,
  "data": {
    "AccessKeyId": "STS.NTZD6Zk5vG34Xxxxxxxabcdef",
    "AccessKeySecret": "3amyZLywPUMeFsAboX2MMVxxxxxxxxxxxxxxxabcdefg",
    "SecurityToken": "CAIS5wJ1q6Ft5B2yfSjIr5fvD4zuhupX8PHfWmrS3EYWXfd1mbae1zz2IHBEfXVgBusftf8xlWpY6voelqp6U4cdj9hwzHc2vPpt6gqET9frbKXXhOV2WfTHdEGXDxnkpiW7AYHQR8/cffGAck3NkjQJr5LxaTSlWS6nU/iOkoU1QdkLeQO6YDFaZrJRPRAwkNIGEnHTOP2xUGbtmXGCIEdstxZxrml95K+joKib8QGMqFzh1tccvZjqQOijdNI+FZ14ScuQwehqd7LIyjJt8xxN/asU66tf4mXjv8qBJFNT7h6aKJCXkLtVIRR+e7IxFoNdsfH4jocYgOHIkJntwBs/WoMwWi/EFoe725mGSqH7dPRZL+ehYi+cj4i1W8Or419+UxUyLxhXftctEHh0BCE3RyvSQq3dowCaO1vzFPPci/pogMAlkwm056mDI1meXrOeyj0EPZwxaUwlMRMM1HDmaLUBdwFc5bL4YhqAAROPauRKm1OZZKJJDbGN2kOV6HhPAO0c1ymfCiG9kN4hMSnJiZgvlHOhR5m63nFrvkgMA4FwOxVLk2mPheh/BE9Dfd39FRJr+nPNHsx21QUxNQdYCQqvDzmLcE6LUsEzR4xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxabcdefg",
    "Region": "oss-cn-shenzhen",
    "BucketName": "OSS_BUCKET_NAME",
    "FileNamePrefix": "NWYzNGZmYjQxxxxxxxxxxxxxxxxxxxxxxxxxxabcdefg",
    "Expiration": "2020-10-21T03:32:38Z"
  }
}

同名文件覆盖的问题

因上传到 oss 的文件名是由前端定义的,如果文件名重名,则覆盖原文件。为了防止文件重名覆盖,亦或恶意用户上传覆盖文件,这里通过动态设置 policy 来限制文件名。

通过使用系统中不会重复的 userid 加上年月日来指定文件前缀,在 policy 指定上传文件的前缀,如果文件名不符合规范,前端将文件上传到 oss 时,会失败,从而确保目录中文件不会被其他恶意用户上传同名文件覆盖

(这里同一用户,在一天内上传同名文件是可以覆盖其他文件,姑且认为各用户是友好的吧)

const prefix = `${userid}${year}${month}${day}`;
const prefixToBase64 = Buffer.from(prefix).toString('base64');

const OSS_BUCKET_NAME = 'Your oss bucket name';
const policy = {
  Statement: [
    {
      Action: ['oss:PutObject'],
      Effect: 'Allow',
      Resource: [`acs:oss:*:*:${OSS_BUCKET_NAME}/${prefixToBase64}*`],
    },
  ],
  Version: '1',
};
完整 sts 服务代码
const express = require('express');
const { STS } = require('ali-oss');

const router = express.Router();
require('dotenv').config();
// web上传oss,前端文档
// https://help.aliyun.com/document_detail/64056.html?spm=a2c4g.11186623.6.1462.bdd6677a5MG3yR
router.get('/sts', (req, res) => {
  const userid = req.user.id;
  const d = new Date();
  const year = d.getFullYear();
  let month = d.getMonth() + 1;
  let day = d.getDate();

  if (month < 10) {
    month = `0${month}`;
  }
  if (day < 10) {
    day = `0${day}`;
  }
  // TODO: 考虑 如果时间跨天,sts与 policy 规则是否存在冲突的问题?
  const prefix = `${userid}${year}${month}${day}`;
  const prefixToBase64 = Buffer.from(prefix).toString('base64');
  const policy = {
    Statement: [
      {
        Action: [
          'oss:GetObject',
          'oss:PutObject',
          'oss:ListParts',
          'oss:AbortMultipartUpload',
          'oss:ListObjects',
        ],
        Effect: 'Allow',
        Resource: [`acs:oss:*:*:yidiankuaile/${prefixToBase64}*`],
      },
    ],
    Version: '1',
  };

  const client = new STS({
    accessKeyId: process.env.OSS_ACCESS_KEY_ID,
    accessKeySecret: process.env.OSS_ACCESS_KEY_SECRET,
  });

  client
    .assumeRole(
      process.env.OSS_ROLE_ARN,
      policy,
      process.env.OSS_TOKEN_EXPIRE_TIME
    )
    .then((result) => {
      // console.log(result);
      // res.set('Access-Control-Allow-Origin', '*');
      // res.set('Access-Control-Allow-METHOD', 'GET');
      res.json({
        success: true,
        data: {
          AccessKeyId: result.credentials.AccessKeyId,
          AccessKeySecret: result.credentials.AccessKeySecret,
          SecurityToken: result.credentials.SecurityToken,
          Region: process.env.OSS_REGION,
          BucketName: process.env.OSS_BUCKET_NAME,
          FileNamePrefix: prefixToBase64,
          Expiration: result.credentials.Expiration,
        },
      });
    })
    .catch((err) => {
      res.status(400).json({
        success: false,
        message: err.message,
      });
    });
});

module.exports = router;

web 直接传

前端完整代码
<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/bulma@0.9.0/css/bulma.min.css"
/>

<template>
  <div>
    <div class="px-5 py-2">
      <div class="field">
        <label class="label">前缀</label>
        <div class="control">
          <input class="input" type="text" v-model="fileNamePrefix" disabled />
        </div>
      </div>
      <div class="field">
        <div class="control">
          <input class="input" type="file" ref="file" />
        </div>
      </div>
      <div class="field">
        <label class="label">文件名</label>
        <div class="control">
          <input class="input" type="text" v-model="filename" />
        </div>
      </div>
      <div class="field is-grouped">
        <div class="control">
          <button @click="upload" class="button is-primary">上传</button>
        </div>
      </div>
      <div class="field" v-show="resultUrl">
        <label class="label">上传结果</label>
        <div class="control">
          <textarea class="textarea" type="text" v-model="resultUrl" readonly />
        </div>
      </div>
    </div>
  </div>
</template>

<script>
  import OSS from 'ali-oss';
  import { customAlphabet } from 'nanoid';
  import { ossSts } from '../../api/oss';

  export default {
    data() {
      return {
        stsInfo: {},
        fileNamePrefix: '',
        filename: '',
        resultUrl: '',
        suffix: '',
      };
    },
    methods: {
      getSts() {
        ossSts()
          .then((res) => {
            this.stsInfo = res.data;
            this.fileNamePrefix = res.data.FileNamePrefix;
          })
          .catch((err) => {
            console.log(err);
          });
      },
      upload() {
        let file = this.$refs.file;
        let client = new OSS({
          region: this.stsInfo.Region,
          accessKeyId: this.stsInfo.AccessKeyId,
          accessKeySecret: this.stsInfo.AccessKeySecret,
          stsToken: this.stsInfo.SecurityToken,
          bucket: this.stsInfo.BucketName,
        });

        // 支持File对象、Blob数据以及OSS Buffer。
        // const data = file;
        const data = file.files[0];
        // or const data = new Blob('content');
        // or const data = new OSS.Buffer('content'));

        let arr = data.name.split('.');
        this.suffix = arr[arr.length - 1];

        let putObject = async () => {
          try {
            let result = await client.put(
              this.fileNamePrefix + '/' + this.filename + '.' + this.suffix,
              data
            );
            this.resultUrl = result.url;
          } catch (e) {
            console.log(e);
          }
        };
        putObject();
      },
    },
    created() {
      const nanoid = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', 6);
      this.filename = nanoid();
      this.getSts();
    },
  };
</script>

本文链接 https://www.yidiankuaile.com/post/aliyun-oss-upload

最后更新