2017年支付宝账单不负众望,再次霸屏。
回顾这一年,有三件现象级的刷屏事件:
炫耀“18岁”;炫耀网易云音乐歌单;炫耀支付宝账单。
有网友调侃:2018年大型“相亲”秀拉开帷幕了……
显示“18岁”,看外观;
展示你的网易云音乐播放列表和品味;
展示你的支付宝账单,炫耀你的财富。
“相亲”的三大重要指标难道还不都满足吗?!
玩笑归玩笑,身为程序员,我的“职业病”又回来了。就像微信出品的“跳一跳”小程序一样,各种外挂、刷分攻略层出不穷,占据着各大科技媒体的头条。
于是,我决定探究支付宝账单背后的技术实现。但这并不意味着我会再创建一个支付宝账单。毕竟这个账单的核心部分是其背后的海量消费数据,而这些数据不是我这样的普通人能够接触到的。
由于个人经验有限,如果本文的假设有不足之处,还请指正。如果有蚂蚁金服的同仁愿意分享账单背后的技术架构,一定会非常欢迎。
本文是我个人对于支付宝账单技术实现的猜测,我简单分为前端和后端两部分,从外到内,从可见的前端展示推断出不可见的后端逻辑。
1. 找到突破口
整个探索过程第一步就是找到入口,我觉得这是一个非常关键的突破口。我通过钉钉分享了账单,然后进入PC版钉钉,右键点击分享的记录,把整个网址复制过来。
为了方便起见,这里贴出完整网址,供大家查看:
https://render.alipay.com/p/s/i/?scheme=alipays://platformapi/startapp?appId=68687017&showOptionMenu=NO&allowsBounceVertical=NO&transparentTitle=auto&bizScenario=Share&url=https://render.alipay.com/p/f/fd-jbg7if4k/index.html
整个参数的作用就是尝试在手机上打开支付宝应用,这个对于做移动开发的同学来说很容易理解。当然这个尝试不一定总能成功,这取决于浏览器的安全策略。以下是在()和(PC)中打开的结果:
它自带了6个参数,会传递给APP进行相应的操作。我以为每个用户分享的参数会不一样,但是经过一番对比发现都是相同的参数。经过一番研究源码发现,"" 参数会不一样,因为我是从钉钉分享中找到突破口的,所以值都一样,但如果是从其他渠道打开的,值可能不一样。
至于用户信息,应该是通过和页面中的js交互来获取的,如果我们想看到最终的账单页面,只需要复制url参数,然后在浏览器里打开就可以了。
至此,我们打开了探索支付宝账单的大门。
2.前端页面实现
首先我们要明白支付宝账单是一个Page,换句话说就是html+js实现的单页应用。我的理解就是静态背景图+转场动画+数据层。这样不仅有效解决了跨平台问题,也方便传播。
值得一提的是,整个应用的css、js文件,包括资源请求URL等都经过了一定程度的加密和混淆,想要看懂它们,尤其是js代码,还是有一定的难度。
总共有 9 张静态背景图片。我在这里放了三张,以便大家可以感受一下。
过场动画其实是由一系列mp4文件组成的,在tag里播放,篇幅有限,这里只贴一个视频,让大家感受一下。
至于数据源以及其所属的层,都是通过js完成的,下面的截图是控制台的,展示了页面的主要DOM元素。
大致分为三个部分:加载,账单正文内容,错误提示。
加载过程中主要有两个元素,一个是动画,其实就是一个日历翻页的GIF动图,另一个就是进度百分比,别想了,肯定是假的进度条,通过“”,APP受托发送账单数据请求,在这个过程中,进度条会以一定的速率增加,直到获取到数据后,才会在97%左右停止,然后进入账单页面。
该片主要内容由三部分组成:第一部分为滑动屏幕切换场景,该部分有9个子元素,9张静态图片,分别对应9个场景;第二部分和第三部分是两个标签,分别播放下一个场景和上一个场景的mp4文件。
错误信息没什么可解释的,就一行提示文字和一个重试按钮,一目了然。
有读者可能会问,这些图片、视频等资源是怎么获取的呢?这就是我接下来要讲解的。
我们先来看一个数据结构的定义:
// 静态资源。 window.resource = [ { "scene": "https://gw.alipayobjects.com/zos/rmsportal/epRpbpBcCZIKasROmxcL.jpg", "video": { "forward": "", "back": "" }, "__key": 9 }, { "scene": "https://gw.alipayobjects.com/zos/rmsportal/epRpbpBcCZIKasROmxcL.jpg", "video": { "forward": "https://gw.alipayobjects.com/os/rmsportal/KwIPQNAHVfCMQSToOqxX.mp4", "back": "https://gw.alipayobjects.com/os/rmsportal/zRAHVrBufLRAlmOMwXgA.mp4" }, "__key": 1 }, { "scene": "https://gw.alipayobjects.com/zos/rmsportal/ALzmXZZYrnFDqYFFrGjY.jpg", "video": { "forward": "https://gw.alipayobjects.com/os/rmsportal/QuZvhHxfIyRqgNxGQRqq.mp4", "back": "https://gw.alipayobjects.com/os/rmsportal/zRAHVrBufLRAlmOMwXgA.mp4" }, "__key": 2 }, { "scene": "https://gw.alipayobjects.com/zos/rmsportal/ZcmJnyzRQNuFspDzfoxX.jpg", "video": { "forward": "https://gw.alipayobjects.com/os/rmsportal/CLryDglMNEQLfDxmYnUW.mp4", "back": "https://gw.alipayobjects.com/os/rmsportal/nmqWCcwURUxRdUqgdJje.mp4" }, "__key": 3 }, { "scene": "https://gw.alipayobjects.com/zos/rmsportal/qQLBJCGEDtXCoCiOtPzc.jpg", "video": { "forward": "https://gw.alipayobjects.com/os/rmsportal/TUKbyXyonamHXwQifpDQ.mp4", "back": "https://gw.alipayobjects.com/os/rmsportal/rwcPCPShQgxvVYfobSeU.mp4" }, "__key": 4 }, { "scene": "https://gw.alipayobjects.com/zos/rmsportal/zQzcNkGFJJqHinrABCDa.jpg", "video": { "forward": "https://gw.alipayobjects.com/os/rmsportal/OBocUmcqGaHuZJJkNQvV.mp4", "back": "https://gw.alipayobjects.com/os/rmsportal/HbyqRtKZMlKFQZcxwCJy.mp4" }, "__key": 5 }, { "scene": "https://gw.alipayobjects.com/zos/rmsportal/JzzNsINqFRVOCAMNJonO.jpg", "video": { "forward": "https://gw.alipayobjects.com/os/rmsportal/kBYTpmZElHykGKYytIIG.mp4", "back": "https://gw.alipayobjects.com/os/rmsportal/zvFLfvTTgXdUUCFHuIlv.mp4" }, "__key": 6 }, { "scene": "https://gw.alipayobjects.com/zos/rmsportal/vOpPBFXzjbvlSAxZBcSB.jpg", "video": { "forward": "https://gw.alipayobjects.com/os/rmsportal/dIPIDFjkwlJkxwjbtmRO.mp4", "back": "https://gw.alipayobjects.com/os/rmsportal/VIbUIjvACyUlBSVULXbB.mp4" }, "__key": 7 }, { "scene": "https://gw.alipayobjects.com/zos/rmsportal/HJUaMfRgdvNwrxJgibsJ.jpg", "video": { "forward": "https://gw.alipayobjects.com/os/rmsportal/egQUtbPUbknxskiWkGgU.mp4", "back": "https://gw.alipayobjects.com/os/rmsportal/JlBuZvsTbtOGIiUDNTuW.mp4" }, "__key": 8, "poster": "https://gw.alipayobjects.com/zos/rmsportal/guBrTMglMdSRsWcRnaXB.jpg" } ];
此代码来自结算页面,并从控制台复制。
是作为背景的静态图片; 是前进动画,back 是后退动画; 看上去意义不大(从实现角度来说),也算是恰到好处的“备胎”吧; 至于最值得考虑的字段,我对这个字段的理解会在下面给出的代码中展现。
特别注意最后一组场景物体,里面的视频内容好像在整个账单里都没有出现过,无意中看到了动画制作外包公司的名字,这是程序员小哥加的“鸡腿”吗?
另外需要注意的是back和的值的含义。值的含义比较容易理解,就是当前场景滑到下一个场景时播放的过场动画,而back字段值的含义就比较混乱了。我举个例子:在=3的场景对象中,back字段记录的是回滚到=2场景的过场动画。
由于js源码比较混淆,理解起来比较困难,我们只能根据交互效果来猜测代码实现,以下是场景切换的代码:
//此处也可以直接赋值为0,猜想__key的作用,为了用上此字段 var index = window.resource[0].__key; var len = window.resource.length; function next() { if(index == window.resource[len - 1].__key) return; //at the last page index = (index + 1) % len; var resource = window.resource[index]; //1. play video in resource.forward. //2. video player listener binding. //3. to display data with animations after forward video completed. } function previous() { if(index == window.resource[0].__key) return; //at the first page var resource = window.resource[index]; //1. play video in resource.back. //2. video player listener binding. //3. to display data with animations after back video completed. //4. set value for index variable. index = (index == window.resource[1].__key) ? window.resource[0].__key : index--; }
主要方法是通过对len取模,来回切换场景,从而达到循环播放的效果。对于该字段的猜测也体现在代码中,值得一提的是,找遍了所有的js源码,也没发现用到这个字段的地方,不知道是加密了还是混淆了。
到此为止前端页面展示的介绍就结束了。
3. 后端数据集成
本节将重点讲述支付宝账单数据的形成,纯属本人根据对支付宝技术架构的理解而做出的猜测,不代表实际操作。
以上是我认为比较合理的架构图,将架构视角放在数据层面。
1、这一层代表的是云数据库集群,整个集群里的数据库很有可能是异构的,比如,,,等等。另外这里说的集群还涵盖了淘宝、天猫、支付宝等阿里巴巴体系内产品使用的数据库,所以这一部分承载了很多数据的输入输出工作,至关重要。
2、DW,Data,即数据仓库。重要的数据来源是云数据库集群,部分数据直接来自文件。数据仓库可以实现的功能很多,其中ETL工作是BI的必经之路。配合上可以实现数据可视化、日志分析、运维监控等功能。
3、这个其实是属于阿里云的一个大型分布式计算平台,以 为代表的分布式计算框架,擅长离线计算,又能完成快速的实时计算。
4、DRDS 与 REST API。DRDS 也是阿里云出品的一款数据库中间件产品。如上文所说,云数据库集群是异构的,数据的读写必须要有中间件的参与。至于 REST API,主要提供了一系列的 API 供客户端进行数据操作。
在讲解完整个架构图之后,我会进一步梳理整个数据请求的流程。
1. 数据产生。主要是用户2017年的消费记录,来自天猫、淘宝、支付宝、蚂蚁金服等平台。这些数据大部分都是结构化的,存储在数据库集群中;
2.生成年度账单数据。将用户2017年的消费数据导入数据仓库,通过分布式计算平台离线计算出每个用户的账单数据,然后将这个结构化的账单数据放入数据库集群中。这里使用离线计算是明智的,毕竟数据是PB级别的,只能针对单个用户进行实时计算,否则对用户体验会有一定的影响。这部分工作,简单来说就是写一些任务,在分布式计算平台上跑起来大概需要2到3天的时间;
3.数据获取。到了这一步,计费数据已经准备好了,客户端只需要调用API获取,就可以做出我们现在看到的计费页面了。
4. 稍有进步
基于对前端展示的研究,我做出了上述架构上的猜测,但这并不是我第一直觉的产物。我一直认为,支付宝账单、网易歌单等年度营销活动都可以使用“页面静态化”技术。当然,这样的架构也有优点也有缺点,我们来看一个改进后的架构图。
可以看到,前后端已经处于高度解耦的状态,后端只负责生成计费数据并填充到用户的计费页面,而前端只需要访问指定的静态 HTML 页面即可。针对这个架构,我们来讨论以下三个问题:
1. HTML 文件的命名方式。想象空间还是很大的,各种规则都有,比如简单粗暴的对用户 ID、生成时间等元素进行哈希处理。当然对文件目录也有要求,这里就不展开说了。
2、页面静态化技术选择。理论上最好的选择是CDN技术,这个技术在市场上比较成熟,可以放心使用。如果不用CDN,可以考虑用它做一个缓存代理缓存服务,也算是CDN的简化版。如果只需要内容分发,不考虑其他更高级的功能,是个不错的方案。
3、适用场景分析。页面静态化最吸引我的一点就是,它减轻了大量后端数据访问的压力,把压力转移到了CDN上。不过,其实不用担心,因为这是CDN的强项,实施成本低,而且不容易达到瓶颈。另外,没有额外的网络数据访问,不仅不会暴露API,有一定的安全性,前端页面也能秒开,给用户带来极佳的体验。所以,既然页面是静态的,肯定不适合那些页面频繁变动或者交互性强的场景。
本来支付宝账单页面是可以做成静态的,但后来的“授权协议门”事件让我放弃了这个想法。这个小插曲的出现意味着之前生成的页面全部需要作废整改,会造成一大波流量和存储空间的浪费,除非替换之前的文件。不过仔细想想,支付宝账单页面内嵌了动态授权,想把页面静态化也不容易。
五、结论
本文从前端和后端两个层面对支付宝账单的技术实现做了一个非常简单的分析,同时也针对一些不为人知的技术细节提供了自己的一些实现思路。如果读者看完本文之后对此非常感兴趣,也可以针对这个话题发表自己的想法。