技術驅動:量產數據報表的工具服務如何搭建

著做權歸做者全部。商業轉載請聯繫 Scott 得到受權,非商業轉載請註明出處[務必保留全文,勿作刪減]。

數據只有被看見,真相纔有機會浮出水面。前端

對於任何一家公司,數據未必是充分條件,但必定是必要條件,若是連數據都看不到,作任何決策都只能基於經驗算命,風險極高。面試

數據如此重要,但數據每每不容易被看到,更別提更及時的看到,如今也的確不少公司成立所謂大數據團隊,在業務場景中沒有找到有價值的發力點以前,不少大數據團隊也會淪落爲公司的報表取數器,這一方面反映出數據價值透出的緊迫性,也一方面看出公司爲了快速獲取數據而不惜動用大數據團隊的窘迫局面。算法

而這個問題的本質特別簡單:如何讓數據庫裏的 dbs/tables 的成千上萬個字段,快速的呈如今 HTML 的網頁上?本文寫於  2019 年初,介紹的是項目中前期的技術架構方式,但願能給你們一些啓發。sql

數據價值逐步呈現的鏈路

但呈現只是一個小小的環節,在它的先後有更多能夠作的事情,在個人視角里,數據從冰冷的庫表到最終對公司有更大決策價值,能夠分爲這幾個大的階段:數據庫

  • 收集階段: 依賴各類業務場景的端載體和基建支撐來把數據迴流彙總到線上數據庫;
  • 加工階段: 把業務數據庫/表同步到一個獨立的數據倉庫;
  • 製做階段: 從 SQL 到 HTML 展現報表的整個製做上線過程;
  • 整理階段: 基於公司的業務域和行業特徵梳理更嚴謹的數據定義;
  • 聚合階段: 針對公司/事業部/部門/產品線來把數據結果聚合成看板和大盤反映業務健康;
  • 趨勢階段: 在大盤基礎上增長時間和更細化的業務觀測維度來把週期內數據趨勢呈現出來;
  • 分析階段: 從產品上線/技術優化/運營玩法/業務打法等節點上把關鍵事件集成到趨勢中給出變化緣由;
  • 決策階段: 更自動化智能化的依託於算法和規則來作業務預警/目標管理來直接驅動業務。

每個階段都更進一步,更深一層,一二三階段是不少公司都作了的,是數據價值更大化的基礎,沒有一二三的基建準備,再日後會很艱難,咱們今天要關注的是第三個階段,也就是製做,這一道坎兒已經成爲不少公司的痛點,而解決這個痛點就要深刻到過程當中分析。後端

傳統數據報表的開發流程

在給出咱們的解決方案以前,咱們先看下傳統方式讓數據可見的兩個步驟,這也是小菜早些年的作法:微信

全部數據從各個端/源流入數據庫持久化成一張張表

這時候咱們每每能夠從數據庫的管理界面,或者命令行來用 SQL 命令篩選一些數據,也能夠導出成 Excel 作一些數據分析,這是最原始的階段,優勢是一句 SQL 就能搞定一張報表,快速導出分析,缺點是必須懂 SQL 語法,還得理解全部字段的具體業務含義,同時本地要維護幾十上百個 SQL 語句,來隨時登上去執行,而且數據庫和數據庫表的權限管理會逐漸放開,最終變成運營和產品經理都上來人肉執行 SQL 的混亂局面。前端工程師

後端的 API 與前端 HTML 結合

SQL 線上直接查太過於暴力,並且無非很好的維護,因此每每須要動用前端工程師和後端工程師,來共同實現一個報表,讓銷售/運營/產品經理等各個角色均可以打開網頁看數據,既然是先後端合做,就必然存在接口約定,字段約定,篩選條件約定,翻頁數量約定配置,各自開發,前端寫靜態頁面、接口調用和佔位符填充,後端工程裏寫 SQL,包裝專門一個接口註冊到網關上,前端後端聯調測試後,最終各自發布上線。不管報表是簡單仍是複雜,幾乎都要遵循這樣的一個流程,最終展現這樣的一個報表頁面:架構

在這個基礎之上,纔有可能基於 Excel 或者接口數據作一些簡單的可視化的過程展現,好比:工具

現實是,不少人對上面流程是無感的,取數據不就應該這樣麼,不管是一二十個頁面的一二十個報表,仍是三四百個頁面的三四百個報表,天然不會有人對參與這個過程的工程師投以關注,而這樣的一個 SQL 轉接口的後端工程師和一個接口轉 Table 的前端工程師的職業成長卻會收到很大影響,一年要花好多時間來機械式的作數據報表,更要命的是,因爲開發資源和工期的緊張,每每數據報表的優先級會低於業務需求,這意味着業務上線後還須要等一段時間的開發排期才能看到業務數據,這對於快速迭代的業務是很是致命的,沒法敏捷的根據數據作及時調整,後知後覺。

要打破這樣的一個魔咒,工程師能夠有真正的成長,看數據的童鞋能夠更快更舒服的看到數據,就必須從技術上找到突破點,拿到對全部人都友好的結果,就必須經過技術的升級來驅動業務,讓決策更高效。

數據倉庫是報表量產的必要基礎

全部的數據都有它的決策時效性和整理週期,好比當咱們看過往周維度的數據,這些數據能夠是昨日以前甚至是本週以前的數據,若是咱們想要看實時成交大盤,那麼可能就要有小時表或者分鐘表,能實時有數據獲取,不管是實時數據仍是非實時數據,咱們都但願不要鏈接線上的生產數據庫,而是一個更穩定又不那麼敏感的數據庫,這個角色每每是數據倉庫。

要建設一個存儲歷史數據的數據倉庫,惟一要作的事情,就是從生產數據庫按照分鐘,小時,天的規則往數據庫搬磚,搬的時候也能夠有一些簡單的必要計算規則或者收斂規則,好比把一些用戶表進行合併處理等等,而搬的動做,每每用 Python 腳本搬磚就行。

而對應到上圖,就是第二個步驟,小菜通過幾年的迭代,恰好也沉澱了一個可用的數據倉庫,咱們從這裏開始出發。

數據報表的 SQL 拼接工具開發

既然 SQL 到 HTML 展現是痛點,須要多人蔘與,那麼若是開發一款工具,經過它能夠把 SQL 自動執行,而後自動渲染爲 HTML 頁面,痛點不就解決了麼。

咱們最初也是有這種思路,但後來發現,除展現,還有一個更大的痛點,那就是 SQL 裏面大量的字段英文名,實際上它要對應到一個具體的業務中文名,而這個字段有多是通過 SQL 的多個字段計算而來,它實際的中文名只存在於運營的腦海中,開發並不清楚,因此只是丟一個 SQL 不能解決問題,另外就是有不少字段的中文名實際上是能夠複用的,但每次都從新去找人問效率很低,最好是有一箇中英文的字段名稱池,每張報表都沉澱一些字段下來,下次再製做進去挑就行了。

因此咱們讓設計師設計了一個版本,長這樣子:

顯然這徹底是沒法知足報表的製做和量產的,緣由是設計師是很難很難理解 SQL 這樣一個查詢語言打散再組裝在一個工具裏面應該是怎樣的流程,因此咱們就徹底摒棄了設計稿,徹底由前端工程師來驅動,邊學習 SQL 邊開發持續迭代。

具體實現的時候,咱們把思路倒換過來,在 SQL 執行以前,還應該有一個 SQL 拼接的過程,或者說製做 SQL 的過程,這個工具就是讓後端工程師和懂 SQL 語法的產品經理,直接在工具的可視化界面中跨庫跨表拼 SQL,同時把英文字段名和中文名稱對應起來,而後執行製做後的 SQL 就能夠了。

先看拼接後的 SQL 長的樣子:

SELECT
 a.city_name as `城市`,
 a.cat_name as `品類`,
 sum( a.sku_num ) as `訂單件數`,
 sum( a.sku_weight ) as `訂單重量`,
 ( sum(a.total_fee)/100 ) as `訂單收入`,
 ( sum(a.order_cost)/100 ) as `訂單成本`,
 ( sum(a.order_first_freight)/100 ) as `一段物流費用`,
 ( sum(a.first_gross_income)/100 ) as `一毛額`,
 ( ifnull(sum(a.first_gross_income) / sum(a.total_fee), 0) ) as `一毛率`,
 ( ifnull(sum(a.discount - a.coupons_discount_fee), sum(a.discount))/100 ) as `立減`,
 ( ifnull(sum(a.first_gross_income - a.discount + a.coupons_discount_fee),sum(a.first_gross_income - a.discount))/100 ) as `一毛淨額`,
 ( ifnull(sum(a.first_gross_income - a.discount + a.coupons_discount_fee) / sum(a.total_fee),sum(a.first_gross_income - a.discount) / sum(a.total_fee)) ) as `一毛淨率`,
 ( sum(a.coupons_discount_fee)/100 ) as `優惠券`,
 ( sum(a.first_gross_income - a.discount)/100 ) as `二毛毛利額`,
 ( ifnull(sum(a.first_gross_income - a.discount) / sum(a.total_fee), 0) ) as `二毛毛利率`,
 ( sum(a.subsidy_fee)/100 ) as `紅包消耗`,
 ( sum(a.second_gross_income)/100 ) as `二毛額`,
 ( ifnull(sum(a.second_gross_income) / sum(a.total_fee), 0) ) as `二毛率`,
 ( sum(a.pickhouse_taking_wastage)/100 ) as `週轉倉盤點報損額`,
 ( sum(a.allhouse_taking_wastage)/100 ) as `綜合倉損耗額`,
 ( sum(a.indemnity_fee)/100 ) as `賠款額`,
 ( sum(a.third_gross_income)/100 ) as `三毛額`,
 ( ifnull(sum(a.third_gross_income) / sum(a.total_fee), 0) ) as `三毛率`,
 ( sum(a.second_freight)/100 ) as `二段物流運費`,
 ( sum(a.forth_gross_income)/100 ) as `四毛額`,
 ( ifnull(sum(a.forth_gross_income) / sum(a.total_fee), 0) ) as `四毛率` 
FROM db1.tables1 a 
GROUP BY a.city_name, a.cat_name 
LIMIT 50

而 SQL 的拼接重點是兩個:

  • 字段中文名的映射,不管是具體的字段仍是公式計算後的值
  • SQL 計算符和條件的支持,好比 sum/ifnul/Group By/OrderBy/HAVING 等等

字段名映射和計算符的支持,這個過程的編輯是在這樣的界面上完成的:

除此之外,還有各類查詢條件的支持,好比小於等於大於等於等等:

有了 SQL 就等於有了數據獲取能力了,就能夠經過 NodeJS 去連接數據倉庫查詢到響應的數據了,接下來要解決的問題是如何展現。

數據報表在頁面的自動展現

在解釋頁面展現以前須要先了解一下這個服務中 Graphql 的 Schema 裏面和這個頁面有關的 Type 結構關係:

user 掛在根請求下面,report 掛在 User Type 下面,表頭和數據還有數據統計的都掛在 Report Type 下面,展現頁的頁面大體是長這樣的

整個加載分爲三部分

  • user 請求加載地址菜單
  • 報表頁加載報表基本信息(表名,表頭等)
  • 報表頁面加載數據(tbody 和 pagination)

第一步在剛進入任何頁面時已經加載了,這裏就不提了。下面這圖主要概述進入一個報表頁面的加載邏輯,與服務端的 Resolver 的執行順序。

可能你會發如今請求頁面 pagination 和 tbody 的時候依舊會通過 Query.user -> User.report 這兩個 resolver,爲何不直接把 report 的 resolver 掛到 Query 下面呢?這麼作的緣由很簡單,爲了有效的控制權限,而且不須要寫太多額外的代碼。也就是說和用戶權限相關的東西直接掛到 User 這個 Type 下面,那麼下面的的 resolver 的 parent 就是 user,方便獲取 user 權限的信息。

另外,咱們但願報表能夠適配到多端,這裏咱們也花費了大量的心力,封裝了不少種比較複雜的移動端報表組件,好比下面幾個圖是數移動端的嘗試

數據權限的處理

數據是一家公司最隱私的資產,那麼做爲一個數據產品,權限必然是一個須要建設的領域,咱們目前的權限是這樣的流程:

這裏權限分爲了兩類,菜單權限以及數據權限。所謂的菜單權限就是最通俗的該帳號是否有權限訪問這個頁面,而數據權限則是該帳號訪問這個頁面以後能夠查看的數據維度。舉例就是如今有一張運營效果的全數據表,那麼首先只有能看這張報表的運營帳號才能查看,其次就是假如這個運營帳號是北京的,那麼他便不應看到上海的運營數據。

要實現這個,須要一個基本的用戶權限中心,經過報表的 ID 以及帳號 ID 校驗該帳號是否能夠進入該頁面,比較複雜就是用戶數據維度的權限控制,可是實際實現只須要在最終的 SQL 中加入一條 WHERE 條件,經過結果反推,那麼只要一個動態的 WHERE 條件生成模塊,該模塊須要關聯用戶或角色,利用傳入的用戶或角色信息,生成對應的條件(請求其餘權限或者經過數據庫查詢或直接返回等)。

如上述的運營例子,那麼先經過 userId,查詢該用戶 ID 下的運營帳號管理的城市 ID,返回生成一條相似

a.cityCode in (3,2,1)

這樣的 SQL,替換或插入到最終查詢的 SQL 之中去就能夠了。

量產後的報表價值

在咱們實現這個報表系統後,短短一年多,幾百張報表迅猛上線,成爲了整個公司很是核心的一個產品

同時咱們也遇到了一些報表查詢相關的一些性能瓶頸和挑戰,關於這一塊咱們後面會推出一篇文章來詳談,先看下一個一些比較慢的查詢監控:

若是查詢這麼慢到了好幾秒甚至十幾秒的時候,咱們想要在上面作進一步的可視化展現,就會遇到更大的技術挑戰,這個挑戰咱們下一篇來談談如何對數據作計算方案的技術選型。

總結

這一篇咱們花費了大量筆墨討論了數據報表工程化和規模化量產的必要性,是爲了向你們展現一種純技術驅動的理念,就是沒有什麼繁重的開發模式是天經地義的,必定有能夠突破它量產它的辦法,只不過須要在技術實現和產品路子上打開想象力,那麼經過這樣的建設,咱們也拿到了很是漂亮的業務結果,也在技術上找到了很大的成就感。

Scott 近兩年不管是面試仍是線下線上的技術分享,遇到許許多多前端同窗,因爲團隊緣由,我的緣由,職業成長,技術方向,甚至家庭等等緣由,在理想國與現實之間,在放棄與堅守之間,搖擺不停,心酸硬扛,你們能夠找我聊聊南聊聊北,對工程師的宿命有更多的瞭解,有更多的看見與聽見,Scott 微信: codingdream,也能夠來 關注 Scott 跟進個人動態,本文未經許可不準轉載,得到許可請聯繫 Scott,不然在公衆號上直接轉載,尤爲是裁剪內容後轉載,我都會直接進行投訴處理。

2.png
1.png

相關文章
相關標籤/搜索