所需环境变量
- OSS_ACCESS_KEY_ID oss 授权 key
- OSS_ACCESS_KEY_SECRET oss 授权密钥
- OSS_BUCKET 所要操作的 oss 桶(公共/私有)
- OSS_REGION oss 所在区域,比如华北2(北京)即为 oss-cn-beijing
服务端生成上传临时授权 policy
import crypto from 'crypto'
interface OSSPolicy {
OSSAccessKeyId: string
host: string
policy: string
signature: string
expire: number
dir: string
}
const ossDomain = 'aliyuncs.com'
const expires = 600 // 授权有效期10分钟
function sign(bucket: string, region: string, dir: string): OSSPolicy {
const OSSAccessKeyId = process.env.OSS_ACCESS_KEY_ID
const OSSAccessKeySecret = process.env.OSS_ACCESS_KEY_SECRET
const host = `https://${bucket}.${region}.${ossDomain}`
// 设置有效期
const expire = new Date().getTime() + expires * 1000
const expiration = new Date(expire).toISOString()
const policyString = JSON.stringify({
expiration,
conditions: [
['content-length-range', 0, 1048576000],
['starts-with', '$key', dir],
],
})
const policy = Buffer.from(policyString).toString('base64')
const signature = crypto
.createHmac('sha1', OSSAccessKeySecret)
.update(policy)
.digest('base64')
return {
OSSAccessKeyId,
host,
policy,
signature,
expire,
dir,
}
}
服务端生成私有文件的临时授权地址
import OSS from 'ali-oss'
const isDev = process.env.NODE_ENV === 'development'
const internalRegionSuffix = '-internal'
const expires = 600 // 临时授权有效期10分钟
/**
* @param objectKey 文件在 oss bucket 中的路径,如 image/20200520/love.png
* @param process 图片资源处理规则 https://www.alibabacloud.com/help/zh/doc-detail/44687.htm
* @param filename 设置文件下载时的默认名称
*/
function getAccessUrl(objectKey: string, process?: string, filename?: string) {
const region = process.env.OSS_REGION
const client = new OSS({
// 开发环境使用外网 region 访问资源
// 如果部署服务器在阿里云 ECS 上,生产环境可以调用内网 region 访问资源
// 省去外网流量和调用接口时长损耗
region: isDev ? region : region + internalRegionSuffix,
accessKeyId: process.env.OSS_ACCESS_KEY_ID,
accessKeySecret: process.env.OSS_ACCESS_KEY_SECRET,
bucket: process.env.OSS_BUCKET,
})
const response = {
// 'content-type': 'application/octet-stream',
// 'content-type': 'application/force-download',
}
if (filename) {
const name = filename ? encodeURIComponent(filename) : undefined
response['content-disposition'] = `attachment; filename=${filename};filename*=UTF-8''${name}`
}
const url = client.signatureUrl(objectKey, {
expires,
process,
response,
})
// 最后生成的临时 url 需要转成外网可访问地址
return url.replace('-internal', '')
}
客户端上传
为了节省 ECS 的流量开支和 I/O 消耗,一般都是客户端直接向 OSS 上传文档
async function getUploadPolicy(dir: string): OSSPolicy {
const resp = await fetch('/api/oss/policy', {
method: 'post',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ dir }),
})
const policy = await resp.json()
return policy
}
async function upload(file: File) {
const dir = 'test-dir/'
const policy = await getUploadPolicy(dir)
const formData = new FormData()
formData.append('OSSAccessKeyId', policy.OSSAccessKeyId)
formData.append('policy', policy.policy)
formData.append('signature', policy.signature)
formData.append('success_action_status', '200')
// 注意:此处建议生成一个唯一路径名,防止冲突
// 可以要求后端接口返回,也可以前端处理
formData.append('key', policy.dir + file.name)
formData.append('file', file)
await fetch(policy.host, {
method: 'post',
mode: 'no-cors',
body: formData,
})
}
其他事项
- 客户端上传如果出现跨域错误,可以在阿里云 OSS 控制台对指定 bucket 添加跨域设置
- 涉及到 OSS 密钥的信息,不要上传到 public git 项目上
crypto
包为 nodejs 自带,不需要安装第三方依赖- 批量上传可以共用同一个policy,但是每次选择文件后的上传调起,还是需要获取新的 policy
- 浏览器自带的
fetch
无法获取实时上传进度,可以考虑使用axios
的onUploadProgress
回调 - Java 和 Node 生成临时授权地址的设置有不同,Node 如果设置了 response 的
content-disposition
,再设置content-type
,生成的地址访问会出错
附件直接预览
新的OSS bucket 使用 OSS 默认域名访问 html、图片资源,会有以附件形式下载的情况。 若需要浏览器直接访问,需使用自定义域名进行访问。
此时只需要将生成的临时授权 url 中 OSS 默认域名改成配置好的自定义域名即可,不需要设置content-type
。
但是如果配置了 content-disposition: attachment;
,则还是会以附件形式下载。