探索支付寶賬單的技術實現

裝逼未遂的程式設計師發表於2018-01-09

2017年度的支付寶賬單果然不負眾望,再一次刷屏了。

回顧一下這個年關,現象級的刷屏活動就有三起:

秀“18歲”;秀網易音樂歌單;秀支付寶賬單。

一位網友調侃道:2018年大型“相親”節目就此拉開帷幕……

秀“18歲”,看顏值

秀網易音樂歌單,曬品味

秀支付寶賬單,炫財富

“相親”的三個重要指標不就都齊了嗎?!

玩笑歸玩笑,身為一名程式設計師的我,“職業病”又犯了。就像微信出品的“跳一跳”小程式,各種外掛,刷分攻略絡繹不絕,佔領各大技術媒體頭條。

於是乎,我決定探究一下支付寶賬單背後的技術實現。但這並不意味著我會再造一個支付寶賬單出來,畢竟這份賬單核心部分是背後的海量消費資料,這並不是我等刁民能獲取得到的。

由於個人經驗水平有限,本文所作猜想如有不足之處,敬請指正。如果有螞蟻金服的同學願意分享賬單背後的技術架構,想必也是非常受歡迎的。

本文是對支付寶賬單技術實現的個人猜想,我將此簡單粗暴的分成了兩部分:前端後端。由表及裡,從看得見的前端展示來推測看不見的後端邏輯。

一、尋找突破口

整個探索過程的第一步就是找入口,我認為這是一個非常關鍵的突破口。我將賬單通過釘釘分享出來,然後進入釘釘的PC版,右鍵那條分享記錄,即可複製整個URL。

dingding

為了方便講解,這裡把整個URL放出來給大家看看:

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
複製程式碼

整個scheme引數的作用就是會去嘗試開啟手機上的支付寶應用,這對於做移動開發的同學來說是很容易看懂的。當然這種嘗試不一定都是成功的,這要看瀏覽器的安全策略了,以下是在Safari(iPhone)和Chrome(PC)中開啟的結果:

scheme

scheme自帶了6個引數,這都將會傳入到APP中,執行相應的操作。本以為每個使用者分享出來的scheme引數會有所不同,但是經過我一番對比後,發現都是同樣的引數。經過後面對原始碼的一番研究後,發現「bizScenario」引數會不同,因為我是從釘釘分享找到突破口的,所以bizScenario的值都是Share,但如果是從其他渠道開啟的,取值就不一定是一樣了。

至於使用者資訊,應該是在頁面中通過js與native進行互動獲取的。如果我們想看最終的賬單頁面的話,直接把scheme當中的url引數copy出來,在瀏覽器中開啟即可。

至此,我們就開啟了一扇探索支付寶賬單的大門了。

二、前端頁面實現

首先需要明白的是支付寶賬單是一個Single Page Application,換言之,就是一個由html+js實現的單頁應用,我瞭解到的是靜態背景圖片+過場動畫+資料圖層。這樣做不僅有效解決了跨平臺問題,而且更有利於傳播。

值得一提的是整個應用的css和js檔案,包括資源請求url等,都做了一定的加密和混淆,要想讀懂,還是有一定困難的,尤其是js程式碼。

靜態背景圖片總共有9張,我這裡放三張,大家可以感受一下。

這裡寫圖片描述
過場動畫其實是一系列的mp4檔案組成的,放在video標籤裡播放,篇幅有限,這裡只放一段視訊,讓大家感受一下。

至於資料來源及其圖層都是通過js來完成的,下面的截圖來自chrome控制檯,展現了頁面的主體DOM元素

DOM

大致分為了三個部分:載入,賬單主體內容,錯誤提示

載入過程中主要有兩個元素,一個是載入動畫,其實就是一個翻日曆的gif動畫;另一個就是進度百分比,這個不用細想,肯定做的是假的進度指示。通過**「AlipayJSBridge」**,委託Native APP傳送賬單資料請求,在這個過程中,進度指示按照一定的速率增長,大概是到了97%的時候會停下來,直到資料獲取完畢了再正式進入賬單頁面。

賬單主體內容由三部分組成:第一個是swiper,滑動螢幕切換場景,其下有9個子元素,9張靜態圖片,分別對應了9個場景;第二個和第三個部分則是兩個video標籤,分別播放下一個場景和上一個場景的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"
    }
];
複製程式碼

這段程式碼出自賬單頁面,從Chrome控制檯裡複製出來的。

scene 為靜態圖片,用作背景;forward 為前進動畫,back 為後退動畫;poster 似乎意義不大(從實現上考慮),可以認為是對scene的“備胎”;至於__key欄位最值得斟酌,在以下給出的程式碼中,會有我對此欄位的理解。

特別注意一下最後一組場景物件,forward 裡面的視訊內容在整個賬單似乎都沒有出現過,一個不小心就讓我看到了動畫製作的外包商名字了,這是程式猿哥哥加的“雞腿”嗎?

還需要注意的是 forward 和 back 的取值含義。forward 欄位的取值含義還是比較好理解,就是當前場景滑向下一個場景所播放的過場動畫,而 back 欄位的取值含義稍微有點繞,我直接舉個例子:__key=3的場景物件中,back欄位記錄的是回退到__key=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取模,對場景進行前後切換,這樣做可以達到迴圈播放的效果。對於__key欄位的猜想也在程式碼中體現了,值得一提的是,在所有的js原始碼中搜尋了一番,並沒有發現有任何地方用到了這個欄位,不知道是不是被加密混淆了。

至此,關於前端的頁面展示部分的介紹就結束了。

三、後端資料整合

這部分內容將重點介紹支付寶賬單資料的形成,純屬個人對支付寶技術架構的瞭解進行猜想的,並不代表是真實的運作情況。

架構圖

以上是我認為比較合理的架構圖,架構的視角放在了Data層面。

1、Database,這一個層次表示的是雲資料庫叢集,整個叢集中的資料庫極有可能是異構的,如MySQL,Oracle,PostgreSQL,MongoDB等等,此外,這裡所說的叢集也涵蓋了淘寶,天貓,支付寶等阿里體系中的產品所使用的資料庫,所以這一部分承載了較多的資料輸入輸出的工作,至關重要。

2、DW,Data Warehouse,即資料倉儲。其中重要的資料來源是雲資料庫叢集,也會有一些直接來自檔案。在資料倉儲裡面能實現的功能就非常多了,其中當屬ETL工作,這也是BI的必經之路,配合Reporting System,可以實現資料視覺化,日誌分析,運維監控等功能。

3、MaxCompute,這個其實是屬於阿里雲的一個大規模分散式計算平臺,其中以Hadoop、Spark為代表的分散式計算框架,Hadoop擅長離線計算,Spark則可以完成快速實時計算。

4、DRDS和REST APIs。DRDS同樣也是阿里雲出品的資料庫中介軟體產品,上述提到過雲資料庫叢集是異構的,必須有一箇中介軟體參與資料的讀寫工作。至於REST APIs,主要是提供一些列的API,以便客戶端進行資料操作。

解釋完了整張架構圖後,我再進一步將整個資料請求流程梳理一遍。

1、資料的產生。主要是使用者2017年度的消費記錄,來自天貓,淘寶,支付寶,螞蟻信用等平臺,這些資料大部分被結構化的儲存在了資料庫叢集中;

2、年度賬單資料的生成。將使用者2017年度的消費資料匯入到資料倉儲中,經由分散式計算平臺離線計算出每位使用者的賬單資料,將這份結構化的賬單資料再放入資料庫叢集中。這裡使用離線計算是比較明智的,畢竟資料都是PB級別的,實時計算也只能針對個別使用者,不然的話,會對使用者體驗造成一定的影響。這部分的工作,簡單地說,就是寫若干個MapReduce任務,在分散式計算平臺上跑2~3天應該就差不多了;

3、資料獲取。到這個步驟,說明賬單資料已經準備就緒了,客戶端只需要呼叫API即可獲取,也就能做出我們現在所看到的賬單頁面了。

四、一點小改進

基於對前端展示的研究,我才做出了上述架構的猜想,但這並不是我第一直覺的產物。我一直認為像支付寶賬單,網易歌單這類年度盤點的營銷活動,可以使用「頁面靜態化」技術。當然,這樣的架構也是有利有弊,先看一張改進後的架構圖。

頁面靜態化技術

可以看到,前端和後端可以說已經處於一個高度解耦的狀態了,後端只負責賬單資料的生成,並填充好使用者的賬單頁面,而前端訪問指定的靜態HTML頁面即可。針對這個架構,我們來討論如下三個問題:

1、html檔案命名方式。這個想象空間還是很大的,規則也各式各樣,比如簡單粗暴地將使用者id,生成時間等元素進行Hash。當然,對於檔案目錄也是有要求的,這裡就不再深究了。

2、頁面靜態化技術選型。理論上,最佳的選擇就是CDN技術,這方面的技術在市面上已經比較成熟了,可以放心使用。如果不用CDN的話,那可以考慮利用squid,做一個快取代理快取服務,可以認為是精簡版的CDN,如果只需要內容分發而不考慮其他更高階的功能,squid不失為一個好的解決方案。

3、適用場景分析。頁面靜態化最吸引我的地方就是減輕了大量後端資料訪問的壓力,將壓力轉移給了CDN,但是大可不必擔心,因為這是CDN的長項,實現成本低,不易觸及瓶頸,此外,沒有額外的網路資料訪問,不僅不會暴露API,有一定的安全保障,前端頁面也可以做到秒開,給使用者帶來了絕佳的體驗。因此,既然是頁面靜態化,那麼肯定就不適合那些頁面頻繁改動,或者有強互動的場景。

本來這次支付寶賬單頁面完全可以靜態化,後來發生的「授權協議門」事件讓我打消了這個念頭,這個小插曲的出現就意味著需要將之前生成好的頁面全部失效並整改,又會引起一大波流量,也會引起儲存空間的浪費,除非是替換之前的檔案。不過細想一下,支付寶賬單頁面嵌入了動態授權,也就不好做頁面靜態化了。

五、總結

本文從前端到後端兩個層面,對支付寶賬單的技術實現做了一次非常淺顯的剖析,對於一些無法得知的技術細節,也給了一部分自己的實現思路。如果讀者看了這篇文章之後,對此也非常感興趣,也可以針對這個話題發表自己的想法。

技術匯

每日干貨分享,傳遞網際網路世界有價值的訊息,微信公眾號:技術匯

相關文章