创建 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>