一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第2天,点击查看活动详情。
OSS 简介
TL;DR,所以我简化为两个QA。
Q: OSS 是什么?
A: 翻译过来叫做对象存储,依托于其兄弟 CDN 可以提供可靠,快速的存储,分发服务。
Q: OSS 有哪些使用场景?
A:
- 存储,分发
- 备份、归档
- 富媒体处理
- 静态资源托管
架构图
就从七牛抠一张图下来,在所有的 OSS 服务中,七牛的文档是写的最好的。这点阿里云,腾讯云的官方文档明显在划水。
后端
后端需要进行的操作大概分成以下几个步骤
配置
这里是一个最基础的配置,这里会提供下面几个操作需要的信息。
qiniu:
ak: xxxxxx
sk: xxxxxx
dev:
host: 这里是 oss 的 host
bucket: abc-image-stg
callback_url: 服务器处理回调的地址
callback_body: 'key=$(key)&fsize=$(fsize)&info=$(imageInfo)&uid=$(x:uid)'
prod:
host: 这里是 oss 的 host
bucket: abc-image-prod
callback_url: 服务器处理回调的地址
callback_body: 'key=$(key)&fsize=$(fsize)&info=$(imageInfo)&uid=$(x:uid)'
生成 upload token
这里简单的写一写逻辑,关键的地方就是构建一个包含 callback_url 和 callback_body 的 PutPolicy
class Video
def self.uptoken
env = ENV['RACK_ENV'] == 'production' ? 'prod' : 'dev'
put_policy = Qiniu::Auth::PutPolicy.new(NORAML_CONFIG['qiniu']["#{env}"]['bucket'], nil, 3600 * 12, global: 'global')
#构建回调策略,这里上传文件到七牛后, 七牛将文件名和文件大小回调给业务服务器.
put_policy.callback_url = NORAML_CONFIG['qiniu']["#{env}"]['callback_url']
put_policy.callback_body = NORAML_CONFIG['qiniu']["#{env}"]['callback_body']
#生成上传 Token
uptoken = Qiniu::Auth.generate_uptoken(put_policy)
end
end
// GET
// 这个接口是给前端提供的,会生成一个合法的 token 供 SDK 使用。
on 'uptoken' do
as_json do
{ data: { uptoken: Video.uptoken } }
end
end
处理回调
在拿到以上的 upload token 之后前端上传文件,OSS 端会发起一个回调,频率以及次数会在文档中说明,所以我们后端需要一个处理回调内容的接口,在这里我们在自己的数据库中存储文件的元信息,比如 key,ext,size这些。
举个例子,上一块提到的 callback_url 就是你服务器处理回调的地址,callback_body就是对方 OSS 服务器给你服务器 POST 的内容。(⚠️ 这里是可以带上鉴权的。所以不需要担心安全的问题。重复一遍。你不需要担心安全的问题。)
Video 数据结构
==================================================
id int(11)
key varchar(255)
ext varchar(255)
avinfo text
created_at datetime
updated_at datetime
zip_key varchar(255)
cleaned tinyint(1)
cut_key varchar(255)
--------------------------------------------------
// POST
on param('key'), param('ext'),param('info') do |key, ext, avinfo|
video = Video.find_or_create(key: key) do |v|
v.ext = ext
v.avinfo = avinfo
end
as_json {{key: video.key}}
end
生成 download url
这里会生成一个带上 token https://image.abc.com?e=blahblah这种形式的地址。
def full_download_url
env = ENV['RACK_ENV'] == 'production' ? 'prod' : 'dev'
Qiniu::Auth.authorize_download_url "#{NORAML_CONFIG['qiniu']["#{env}"]['host']}/#{self.key}", {expires_in: 3600 * 2 }
end
前端
使用服务器提供的 upload token 去上传
按照文档上,这里抠一点七牛的文档来举例子说明
const observable = qiniu.upload(file, key, token, putExtra, config)
const subscription = observable.subscribe(observer) // 上传开始
// or
const subscription = observable.subscribe(next, error, complete) // 这样传参形式也可以
subscription.unsubscribe() // 上传取消
可见这里是没有使用到 ak 和 sk 这样敏感的信息的,这两个信息无论如何都是不应该出现在前端服务中的(包括客户端)。
剩下的就是处理业务逻辑
在上传完了之后,会有一个 key 之类的返回,这是在 callback_url 接口中预先定义的。一般来说,你也只需要 key 就可以了,因为这个在你的后端服务中已经有了一条 Video 的记录。
总结
在群里中我发现有几个主要的观点:
- 直传的话相关的密码什么的信息都会存在前端,禁止一切前端直传,为了安全,这点(后端)开销是可以被允许的。
- 前端直传确实有风险。另外如果oss和后端信息不同步,可能会存在脏文件。如果要解决脏文件,就增加了oss治理的成本。
- 正常流程确实是这样,但是用户量多,一定会有黑天鹅事件发生:上传完了,但是用户断网断电了,或者用户关浏览器了,数据库没收到。这就导致了数据不同步。更大风险主要是,token泄露,用户恶意上传多次大文件,但是你不知道,导致你oss费用损失。
在这里评论一下:
- 在上述的例子中,不存在 ak,sk 在前端中泄漏的问题;这里上传也不占用服务器的资源,通过上传服务器,再由服务器上传 OSS 是多此一举;如果你说无法保证 token 安全的问题,同样你也无法保证你的服务器端鉴权策略一定是可靠的。
- 通过回调的方式,服务器端会有一条记录,记录着你这一条信息,因为 OSS 会通知你 8 次,你完全没有必要担心数据丢失的问题,如果 8 次你都没存下来,那应该反思一下你自己的代码是否有问题。你自己的服务器有了这条 key 的记录,oss 治理成本无疑是显著的降低的。
- 这一点来说,是完全忽略了 OSS 的最佳实践,而是用自己摸索出的一条能用,但是不好的实践。这一点和上面的解释是一样的。