前端直传文件到 OSS,有可行性吗?

大家好,我是刘布斯。

文件上传是咱们工作中经常遇到的场景,而且一般有两种方案进行上传操作:

  • 通过后端提供的接口进行上传
  • 前端直接上传到 OSS

今天看到这么一个问题:


目前公司涉及文件上传的,服务端都要求前端自己主动上传至阿里云服务,然后把获取到的文件id再调用一次服务端借口绑定关联关系;

他们的理由是减少后端服务的带宽,这种模式好吗?

今天就分享下关于这个问题下,一个很有趣的回答,别开生面地以问答的形式,生动地介绍了前端直传 OSS 的可行性和过程,并对常见问题进行了解答。


先说结论,非常可以,而且大多数场景是非常推荐这么做的。

传统上,我们是前端把文件传给后端,然后后端存到自己服务器或者 OSS 上,现在你可以直接从前端传到OSS上,然后就没有然后了。

其实OSS是个可玩性很高的东西,但是大多数开发者也就当成个“网盘”使用了,加上磨磨唧唧的逆天文档,很多人看都不想看一眼捏鼻子再用,所以对里面的东西了解不多,也经常有很多误会,下面就假装以QA的方式回答一些常见误解


Q:前端上传完,后端怎么知道上没上传完?

A:前端上传完主动通知后端,或者OSS也有回调功能,OSS去通知后端(Callback_对象存储-阿里云帮助中心


Q:别骗我,OSS上传是需要token和秘钥的,我直接放前端那岂不是白给啊?

A:提供token和秘钥只是上传文件的其中一种方式而已(而且很多开发者只知道这一种方式,所以一直以为前端上传不安全hhhh),除此之外还有其他好几种方式,比如 STS ,可以由后端生成临时的token和秘钥来让前端使用,超时自己销毁,但是STS仍然很麻烦直接略过。

我们可以使用 “表单上传” 的方式,简单来说后端可以根据秘钥去生成一个sign字符串,sign不会暴露秘钥,就是前端发起POST请求给OSS,请求体里附带上sign(后端给你的)、key(要上传文件的位置)、file(你要上传的文件)这几样参数就完事了,没有秘钥泄露,大功告成。

如何使用表单上传文件到指定存储空间_对象存储-阿里云帮助中心


Q:这不就漏洞就来了,key(上传位置)是前端决定的,那我只需要从你后端拿到sign,然后我想上传到哪都是我自己说的算,想上传多大的文件也是我自己决定的,反正你后端验证不了,阁下该如何应对?

A:这就得隆重请出来表单上传的重要一员:policy

它可以限制一系列上传的条件,sign里是包含policy的,你只要拿着带policy的sign,就必须遵守里面的policy条件,否则这个sign就用不了。

policy是一串json,有一些语法,可以限制各种你能想到的常见情况。比如必须上传到某个位置(conditions里的eq $key 你的OSS位置)、比如上传的文件最大/最小的限制(content-length-range)、比如上传的位置已经有文件了是否允许覆盖(x-oss-forbid-overwrite)、比如sign值超时时间(expiration)等等都可以配置。

policy都是后端生成的,前端只能在这些限制条件里上传文件,如果上传的文件不在限定条件里,OSS那边直接帮你拦截,上传直接失败,根本不需要后端参与验证。

所以你想象中的“不安全”,比如秘钥泄露、或者我想上传到哪个位置都行剽窃你的OSS当图床用、或者直接一次性上传1TB文件塞爆你的OSS之类的事情其实加了限制是根本不存在的

PostObject_对象存储-阿里云帮助中心


Q:但是sign值不是一次性的啊,后端生成的sign在有效期内前端是可以一直使用的,我拿你的sign就非得做坏事,你该怎么办?

A:所以说了policy的重要性,假如我们希望前端把文件上传到abc/1.jpg这个位置,那么后端我们的policy就定制下面的规则,前端拿着附有这个规则的sign就只能做下面的事情,做别的事情OSS自动帮你拒绝了:

  • 必须上传到abc/1.jpg(conditions,eq,$key,abc/1.jpg)
  • 上传的文件必须小于2MB(content-length-range,0,2097152(字节,2x1024x1024))
  • 上传的位置如果有文件,则上传失败,防止重复上传(conditions,eq,$x-oss-forbid-overwrite,false)

这样我们的sign就另类的相当于“一次性”的sign了,即便你拿到了sign,也只能对着abc/1.jpg上传2MB以内的文件,并且上传一次之后就不允许在重复上传了,这个sign也就完成了任务,即便它可能还没有“过期”,但是它已经没有任何意义了,就算你把这个sign暴露给别人用,它也做不了任何事情了。

最主要的是,其实上传位置是否覆盖根本无所谓,激进一点甚至是否限制大小也无所谓,因为OSS的上传是免费的,这也是一种商业套路,其实OSS赚钱的大头是你存储、下载的钱,它巴不得你多上传点东西,所以上传这里是免费的。只要你指定好必须上传的位置和大小,就算有个人拿到sign对着一个地方重复上传一万次,对我来说都是无所谓不痛不痒的,反正我也不花钱,你攻击的流量是OSS又不是我,你抓捕周树人关我鲁迅屁事。


Q:整挺好,但我上传的是某些固定格式,而且我需要读取并验证的,比如我这个地方必须上传图片,而且图片的宽高都有限制的,如果按你那么做,后端不参与的话那我怎么知道前端上传的图片宽高对不对,甚至前端不上传图片上传个音频我也不知道啊?

A:这个肯定OSS不能帮你做了,再怎么逆天也得后端参与了,如果你是后端,前端上传完毕通知后端,后端去OSS上把这个文件下载到服务器里并且读取来验证,这个是最简单可行的。


Q:越整越麻烦了,那我不如直接上传到后端就完事了,哪来这么多脱裤子放屁的事情

A:这就是看你自己衡量了,尽管代码量可能多了一点,但前端直接上传到OSS是有很多好处的,最简单就是节省了带宽,正常我们买个云服务器,为了节约成本带宽是限制大小的,比如一般就3M、5M这种小水管,如果大量用户同时上传水管就被挤爆了,如果用户在自己的前端就上传到OSS,而不是上传到服务器,自然不会挤爆服务器了。

上面说过OSS上传是免费的,而且OSS在内网下载也是免费的,意思就是你的OSS在杭州,服务器也在杭州,他俩就属于一个内网,服务器从OSS上下载东西,服务器不花带宽钱也不占用你那个3M 5M的小水管,走的是内网通道,同理OSS也不用交下载钱,这样的话即便你有上面说的“必须要校验图片”这种需求,也可以先让用户免费上传到OSS,然后免费的走内网下载到服务器上,最后进行校验,虽然可能会多耗时一点点,但是换来的是省钱省带宽,所以这个其实就是自己定夺,如果你是个人开发者我是建议走OSS直接上传的,一点点成本扩大到无数个用户也是很费钱的,那如果你是打工仔,哪有什么说法,写起来咋省事咋来就完事了,早点写完早点下班润了


Q:那我拿到某个用户的sign,往里面塞入非法文件,岂不是可以胡作非为?

A:sign是后端生成的,而且是临时的过一会就过期了(过期时间你可以自己配置),而且根据上面的policy可以做到“一次性”用完就弃,如果合法用户拿到sign已经上传完毕了,那现在这个sign打印出来贴你家门口你也用不了第二次了。

你非得搁那研究不存在的场景,我想问你“那我拿到某个用户的sign……”和“那我拿到某个用户的账号密码……”和“那我拿到某个用户cookies/ak来模拟请求……”有什么本质上的区别吗?你都能让恶意用户随便拿其他正常用户的sign了我还能说什么捏,还是说你经典前后端上传那种模式下就不怕被人拿到cookies或者ak模拟上传请求了?

我发现这个2010年甚至比移动互联网兴起都早个四五年就出来的OSS,前端直传这个早就应用的遍地开花的事情,可能对于某些清朝人来说还是个什么新奇大胆的实验性玩意,还在质疑是否靠谱、“出了事你能负责啊”之类的逆天言论,首先明确一点,你愿意用啥就用啥,跟我一点关系也没有,我既不是卖云服务的,也完全不在意你是什么业务场景hhh,上面说的方法都是我们自己业务最佳实践出来的,属于是既能节省服务器资源又能白嫖阿里云,你喜欢就可以参考,不喜欢就点个踩划走,别一天天跟脑瘫杠精一样


Q:可是我们业务要记录上传者IP、上传的日志啊?

A:有没有一种可能,sign是后端生成的,后端都能生成sign了,能不能顺手把日志记录一下,咋跟个做题家似的,遇到不一样的题就不会做了?

咱们正常的经典上传流程:

  1. 前端发送上传表单给后端
  2. 后端拿到文件、校验、写日志或者你愿意干啥都行

现在改成了:

  1. 前端希望上传文件,通知后端
  2. 后端生成policy、sign返回给前端(同时也可以根据前端的申请请求记录IP、日志)
  3. 前端拿着sign去上传文件到OSS
  4. 后端收到上传完毕的callback,执行上传完成的相应逻辑(这里你也可以记录日志)

你可以说是繁琐,当然也可以用经典上传模式,好处坏处上面都写着了,但是你要是连这种最基本的业务逻辑流程都看不懂,那这边建议亲还是赶紧找个厂子打螺丝呢,写代码不适合你呢


Q:我是懂哥,你这玩意肯定不安全,调用后端接口返回sign,然后前端拿sign去上传,那我调用一亿次接口申请一亿个sign,然后上传一亿个文件,你不还是被我攻击么?

A:不是哥,这个和sign甚至OSS有啥关系啊,咱们把这篇文章忘干净了,用你高贵的经典前后端上传方案,现在攻守转换了,我来调用你后端上传接口一亿次,我也上传个一亿个文件到你后端,你怎么办?

你是不是会做一些检测,根据IP、频率或者当前用户session之类的限制下调用?

那现在把这篇文章捡回来,你申请sign的接口,就必须只能申请sign呗?别人来一个你发一个sign给别人呗?你咋这么慈善呢?申请sign这里难道就不能也根据IP、session之类的限制一下了调用了?

另外,还有说OSS被大量DDOS攻击会暂停服务的,这属于宇宙终极逆天之纯为了杠而杠了,我想说你传统前后端上传,我DDOS攻击你服务器你被阿里云黑洞关起来速度更快hhhhh。OSS本身就比你那小水管服务器对DDOS宽松的多,你那点DDOS的量可能打到服务器上就让对方服务器关了,打到OSS上不痛不痒的,就算OSS真被攻击到暂停服务了,那假设你传统前后端也被关起来了,那你怎么办?你是不是用防火墙或者CDN之类的限制一下?那有没有可能OSS也提供了挂载到CDN的服务呢?也可以外围包一层防火墙的呢?

你怎么设置你服务器的防DDOS的,也可以同样的设置OSS的呢亲。别总绞尽脑汁寻思杠别人了,实在闲的没事干你就拎个桶去楼下把所有车都洗一遍


Q:上传的“路径”是前端决定的还是后端决定的?

A:在99.9999999%业务场景下,都应该是后端决定,由后端生成一个上传路径给前端,前端只负责“申请上传”,sign、policy、key(路径)都是后端发过来的。我之前说的只是一种大方向的思路,并不是“教程”手把手一步一步教你,你可以在这个“思路”下自己决定任何的事情。

具体的,这个和经典前后端上传是没区别的,你经典情况下后端把文件存到硬盘,会起什么名字?是有意义的名字?还是UUID之类的不会重复的名字?后缀名是什么?这些文件放在哪里?是不是文件路径得存到数据库方便后期别人调用、展示、下载出来?

上面想通了,把“硬盘”替换成“OSS”就是你的实现了。


Q:那我发现一个很重要的事情,根据你的流程,前端是首先“申请上传”,从后端拿到sign后,假如我“不上传”,或者上传完毕后,如果是前端通知的而不是走OSS的callback的话,我“不通知”,那你的这套流程不就崩溃了?

A:这个问题问非常好!说明是仔细想过这套流程里的可能出现的情况了。

拿我们的业务举例,正常的经典上传情况下,很少都直接传一个文件,而是带着表单其他的东西(文本、单选多选下拉选,或者任何的你需要的内容)。

现在我们改成前端直传的方式,首先前端调用后端的“申请上传”接口,同时附带上表单的其他数据。但是!后端拿到这些其他数据时候,并不是要你马上保存到数据库,而是缓存起来, 比如借助 redis来完成,我们将这些表单数据放入redis,超时的时间可以和sign超时时间一样即可,并且这条缓存数据的redis-key要返回给前端,前端通知上传完毕时要把redis-key发回来让我们后端能找到缓存的数据。

也就是我们目前的后端并没有实际的往数据库写任何东西,如果前端在这个步骤“不传”了,那这个任务的状态就过期自动作废了不需要人去关心,对于后端来说并没有实际的数据改变。

接下来我们前端正常的上传到OSS,然后我们前端发一个“上传完毕”通知给后端。

那问题2出现了,假如我们不发通知呢?目前OSS上已经有这个文件了,该文件处于“游离”状态,还没有绑定到实际的业务里。

这里是有两种解决方法的:

  • 第一种“主动型”,就是借助OSS的callback功能,OSS本身支持callback。

即文件上传后,OSS会发送你指定的url一个通知,那我们可以使用OSS的callback功能,而不是用“前端通知上传完毕”这样,这样任何的文件上传到OSS,我们后端都能知道,不会错过。

但这里可能有暗坑——前端在上传完毕后需要进一步从后端拿到某些处理结果(比如文件上传校验是否合法,不合法前端要展示alert),这个在callback情况下做起来比较麻烦,前端还需要在自行上传完毕后调用一下后端查看结果——这里有个坑中坑就是前端查询结果的时候可能OSS的callback还在校验中还没有出结果,会导致经典的“不同步”问题,当然也能解决,但是非常麻烦,所以我们使用方法2。

  • 方法二是被动型,这个我们要请出来OSS的另外两个“可玩性”——生命周期和标签。

生命周期是OSS可以配置的一个东西,可以允许某些文件超时自动删除,而如何让OSS知道哪些文件该删除捏,我们就得使用“标签”。

OSS里每个文件都可以挂上许多个标签,标签是key-value形状的,那么我可以配置以下的生命周期:

所有OSS上的文件(或某些文件夹下的文件),身上没有“donot-delete-me: true”(随便起名)标签的,将在一天后删除。

这个生命周期不需要后端配置,我们打开OSS的网页界面就可以配置,注意要在测试环境先调试好以免误操作删除东西。

我们仍然使用“前端通知”的方式,OSS callback是有一定暗坑的,比如上面说的不同步问题,而前端通知可以让后端处理逻辑阻塞并同步。

当前端通知“上传完毕”后,后端进行正常的校验、从redis里拿到刚才的缓存写数据库,写日志或者任何你想做的事情,最主要的是,要给刚才上传的OSS文件身上挂载“donot-delete-me: true”标签,这样OSS就不会删除了。

而假如前端没有通知,这个文件仍在“游离状态”,没问题,因为这个文件身上没有标签,过期后这个文件就会自行删除。


Q:那我以后不用OSS了呢?

A:别问了别问了,你愿意用啥都行,你喜欢可以自己包装一个通用云存储的抽象类,做N个实现,什么这个云那个云的你都包装一遍,前端也一样,也都抽象并包装一遍,你喜欢怎么样都行,你不喜欢的话用经典前后端上传也没毛病,这个问题多少招人烦了捏


番外篇:

顺便其实上面说的“图片校验”其实有更“云”的方法,一般云服务器会有一个叫“函数计算”的东西,比如阿里云的 FC、腾讯云的SFC、华为云的FG、亚马逊的lambda,其实都是一个玩意,这玩意简单来说就是一个能帮你执行一段代码的轻量虚拟机,支持很多种编程语言,后端可以通过http请求去调用这些方法,速度很快,零点几秒就把你的代码执行完并且返回结果了,最主要可以无限并发,你不用考虑如何实现无限并发以及背后那些有的没的什么扩容弹性之类的词儿,跟咱们臭打工的没啥关系,咱们就很简单的调用一次收一次钱就完事了(当然也有长时间运行的常驻服务型的FC,这里不说那种情况)

但是!FC之类的每个月会有免费算力额度,而且这个额度除非你是超大型应用不然根本用不完,所以其实就是白嫖hhh

前面说到我们在后端把OSS文件拉到服务器上进行校验,其实这一步也是或多或少占用IO和CPU,我们甚至可以把这一块的功能也切割出去,交给FC去处理,FC负责下载和验证——比如我们写一个FC(任何语言实现都行),从OSS下载指定的文件到FC的虚拟机里,然后判断它是否是图片,并且返回宽度和高度的值,然后我们在后端用SDK调用这个FC,就像调用一个函数一样,后端只需要验证FC传回来的结果就行,而不用后端自己去下载图片、读取、判断,进一步的节省了IO和CPU占用。

这样我们的服务器就更轻量了,即便同时有100个用户上传东西,要换成以前的前后端方案,估计水管早炸了,而现在的方案服务器的压力小的跟挠痒痒一样,给前端生成个上传sign,然后调用fc去验证结果,调用几下就完事了,什么带宽什么IO什么CPU占用,关我屁事,主打的就是一个轻松休闲。


最后
 

还没有使用过我们的刷题网站(https://fe.ecool.fun/)或者小程序 前端面试题宝典 的同学,如果近期准备或者正在找工作,千万不要错过,题库主打题全和更新快哦~。

有会员购买、辅导咨询的小伙伴,可以通过下面的二维码,联系我们的小助手。


原文地址:https://www.zhihu.com/question/461803154/answer/3178042223