人人貸活動(dòng)運(yùn)營(yíng)平臺(tái),是一個(gè)由人人貸大前端團(tuán)隊(duì)進(jìn)行開發(fā)和維護(hù),并用于自動(dòng)化、可視化構(gòu)建人人貸常規(guī)活動(dòng)的系統(tǒng)。本文將會(huì)分享"人人貸活動(dòng)運(yùn)營(yíng)平臺(tái)"的設(shè)計(jì)思想和部分技術(shù)實(shí)現(xiàn),希望對(duì)大家有所幫助。
一、背景
人人貸前端團(tuán)隊(duì)在過去的幾年里,接到了很多來自市場(chǎng)部門的活動(dòng)開發(fā)需求,這些活動(dòng)主要分為4大類:
LP活動(dòng)(Landing Page,引導(dǎo)頁(yè)),用于新人注冊(cè)的活動(dòng)頁(yè)面,基本都是一個(gè)注冊(cè)框 + 若干產(chǎn)品卡
MGM活動(dòng)(Members get member),用于好友間邀請(qǐng)拉新的活動(dòng)頁(yè)面,一般包括邀請(qǐng)記錄,邀請(qǐng)排行,分享等功能
常規(guī)活動(dòng),一般包括抽獎(jiǎng)、產(chǎn)品卡、排行榜、分享等功能,用于促進(jìn)銷量
特殊活動(dòng),一般是游戲頁(yè)面,相同的形式基本只會(huì)使用一次
1.1 活動(dòng)開發(fā)人力瓶頸和上線周期長(zhǎng)
一個(gè)普通的活動(dòng)(包括上述的LP活動(dòng)、MGM活動(dòng)、常規(guī)活動(dòng))開發(fā)時(shí)間得多久?
正常情況下,一個(gè)涉及到H5和PC兩端的簡(jiǎn)單活動(dòng),從產(chǎn)品提出需求開始,設(shè)計(jì)師需要2人*2天來完成設(shè)計(jì)工作,開發(fā)需要3人*2天(包括H5、PC端頁(yè)面、后端接口開發(fā)、接口聯(lián)調(diào)),測(cè)試需要2人*2天投入。
就這樣,從需求提出到頁(yè)面上線,需要一共投入14人*天的人力資源,得 7個(gè)工作日 才能完成。
而遇到比較緊急的活動(dòng)開發(fā)需求,上線周期需要壓縮,大家都得加班來完成。
另外,由于公司財(cái)務(wù)結(jié)算的特殊性,很多活動(dòng)的開始時(shí)間一般都是凌晨,開發(fā)、測(cè)試人員需要確認(rèn)活動(dòng)在線上運(yùn)行正常,才能下班。
1.2 活動(dòng)功能重復(fù)和反復(fù)修改
隨著公司業(yè)務(wù)的發(fā)展,活動(dòng)的開發(fā)需求越來越多。前端團(tuán)隊(duì)有三分之一的人力,長(zhǎng)期投入在活動(dòng)頁(yè)面的開發(fā)中。
事實(shí)上,咱們大部分的普通活動(dòng),功能并不復(fù)雜,而且大部分的功能比較重復(fù)。
這種功能重復(fù)性的頁(yè)面開發(fā),對(duì)于個(gè)人和團(tuán)隊(duì)的成長(zhǎng)來說,并沒有太多的價(jià)值。而且活動(dòng)上線后,經(jīng)常因?yàn)闃邮胶臀陌傅年P(guān)系,需要修改代碼,重新上線,導(dǎo)致團(tuán)隊(duì)成員普遍比較反感這種普通活動(dòng)的開發(fā)。
期間,團(tuán)隊(duì)也提出過組件化開發(fā)的方式,試圖將不同的功能模塊抽取出來,在不同的活動(dòng)頁(yè)面上進(jìn)行引用,以便節(jié)省開發(fā)時(shí)間。但由于設(shè)計(jì)方案的不確定性,以及不同開發(fā)人員參與,這種抽取的功能模塊復(fù)用性不太高,效果不是特別理想。并且不能解決樣式和文案修改,需要重新上線的問題。
總之,普通活動(dòng)的特點(diǎn)是:頁(yè)面功能大同小異、開發(fā)時(shí)間緊、下線快、技術(shù)成長(zhǎng)低。
隨著團(tuán)隊(duì)的技術(shù)體系日益成熟,我們終于騰出精力,試圖解決普通活動(dòng)開發(fā)中各項(xiàng)痛點(diǎn)。
二、人人貸活動(dòng)運(yùn)營(yíng)平臺(tái)
早在十幾年前,使用Dreamweaver就能可視化地搭建出前端靜態(tài)頁(yè)面。雖然Dreamweaver已經(jīng)成為過去式,但是可視化搭建的思想,卻被廣泛使用。
我們?cè)谡{(diào)研業(yè)界常用解決方案中發(fā)現(xiàn),很多公司都有自己的活動(dòng)運(yùn)營(yíng)系統(tǒng),可用來高效、可視化地配置活動(dòng),以及監(jiān)控活動(dòng)運(yùn)營(yíng)數(shù)據(jù)。我們希望采用這種活動(dòng)頁(yè)面可視化搭建的思想,由運(yùn)營(yíng)人員根據(jù)實(shí)際的運(yùn)營(yíng)需求,自行添加活動(dòng),并配置對(duì)應(yīng)的活動(dòng)頁(yè)面。

2.1 整體框架
首先,簡(jiǎn)單介紹下人人貸前端的開發(fā)模式。
隨著 Node.js 的興起,我們從2016年開始,將原有基于 JSP 的前端開發(fā)模式,改造成使用 Node.js 做中間層,進(jìn)行前后端分離的模式。

人人貸前端使用的就是圖中所示的前后端分離的開發(fā)模式(圖片來自Web 研發(fā)模式演變),這種開發(fā)模式下,前后端的職責(zé)清晰。對(duì)于前端來說,兩個(gè)UI層各司其職:Front-end UI layer 處理瀏覽器層的展現(xiàn)邏輯,Back-end UI layer 可以用來處理路由、模板、數(shù)據(jù)獲取、cookie、服務(wù)器端渲染等。
在這種前后端開發(fā)模式下,整個(gè)人人貸活動(dòng)運(yùn)營(yíng)平臺(tái)的架構(gòu)圖如下:

整個(gè)運(yùn)營(yíng)平臺(tái)系統(tǒng)分為四大塊。
1、組件庫(kù)。運(yùn)營(yíng)平臺(tái)采用了業(yè)界通用的 組件化 方案,并且選用 React.js 作為組件的開發(fā)庫(kù)。下面會(huì)詳細(xì)介紹組件庫(kù)的拆分和開發(fā)模式。
2、前端系統(tǒng)。整個(gè)運(yùn)營(yíng)平臺(tái)包括 積木系統(tǒng) 、 rrd-h5 、rrd-pc 三個(gè)前端系統(tǒng)。其中 積木系統(tǒng) 是運(yùn)營(yíng)創(chuàng)建、編輯、發(fā)布活動(dòng)頁(yè)面的系統(tǒng),屬于內(nèi)部系統(tǒng)。而rrd-h5 和 rrd-pc 屬于面向用戶的Back-end UI layer,它們是基于生成的活動(dòng)配置數(shù)據(jù),對(duì)活動(dòng)頁(yè)面進(jìn)行渲染及提供異步接口,以供用戶訪問。
3、后端接口。在后臺(tái)服務(wù)上,由于活動(dòng)并不是特別復(fù)雜,我們有較大一部分接口,比如抽獎(jiǎng)、記錄收獲地址,只是做一些簡(jiǎn)單的存儲(chǔ)或者計(jì)算,就直接使用了Node.js實(shí)現(xiàn),也就是上圖中的node-market-service服務(wù)。而部分與公司主營(yíng)業(yè)務(wù)相關(guān)的接口,比如投資返現(xiàn)這類,還是直接使用后端提供的Java接口。
4、數(shù)據(jù)層。用于存儲(chǔ)活動(dòng)配置相關(guān)的數(shù)據(jù)以及部分運(yùn)營(yíng)數(shù)據(jù)。
2.2 組件庫(kù)
我們按照功能模塊,將往期的活動(dòng)頁(yè)面拆分成了不同的組件。
以 移動(dòng)端的邀請(qǐng)好友頁(yè)面 為例,這個(gè)頁(yè)面就包括:圖片組件(banner圖)、活動(dòng)規(guī)則組件、邀請(qǐng)記錄組件、戰(zhàn)隊(duì)排行榜組件、平臺(tái)增信組件和邀請(qǐng)好友按鈕組件等。
組件拆分完成后,我們就得到了一個(gè)組件庫(kù)。
為了便于對(duì)組件庫(kù)進(jìn)行管理,我們按照所屬的平臺(tái),將組件庫(kù)拆分為 jm-common 、jm-mobile 、jm-pc 三類,分別對(duì)應(yīng)兩端公用組件、H5組件、PC端組件。
如上文所述,我們的積木系統(tǒng)定位為 可視化編輯平臺(tái),在對(duì)組件進(jìn)行配置后,需要在編輯頁(yè)面實(shí)時(shí)展示。rrd-h5和 rrd-pc服務(wù)也需要根據(jù)頁(yè)面的配置數(shù)據(jù),對(duì)組件進(jìn)行渲染。
這三個(gè)系統(tǒng)中都需要使用組件庫(kù),為了方便組件的開發(fā)以及預(yù)覽,我們將組件庫(kù)的源碼集成到了積木系統(tǒng)的代碼倉(cāng)庫(kù)中。
積木系統(tǒng)通過項(xiàng)目代碼中的組件庫(kù)源碼來加載組件庫(kù),當(dāng)組件的代碼有修改,積木系統(tǒng)能通過重新編譯,刷新頁(yè)面并預(yù)覽到新的組件樣式。
組件庫(kù)還會(huì)通過開發(fā)環(huán)境判斷,會(huì)自動(dòng)在編輯系統(tǒng)中使用模擬數(shù)據(jù),方便了組件開發(fā)時(shí)的測(cè)試。
為了方便進(jìn)行版本管理和組件庫(kù)接入,當(dāng)組件開發(fā)完成后,我們會(huì)將組件庫(kù)發(fā)布在私有的npm倉(cāng)庫(kù)中,同時(shí)在rrd-h5和rrd-pc中,更新對(duì)應(yīng)的組件庫(kù)版本號(hào),就能加載到新的組件。
2.3 活動(dòng)配置與頁(yè)面配置
在積木系統(tǒng)中,需要先創(chuàng)建活動(dòng),然后才能創(chuàng)建該活動(dòng)對(duì)應(yīng)的移動(dòng)端、PC端頁(yè)面,而不是直接創(chuàng)建活動(dòng)頁(yè)面。
這是因?yàn)楦鶕?jù)以往的運(yùn)營(yíng)經(jīng)驗(yàn),一個(gè)活動(dòng),是可以對(duì)應(yīng)多個(gè)推廣頁(yè)面(至少是H5和PC端兩個(gè)頁(yè)面),而這些推廣頁(yè)面需要共享一些活動(dòng)配置。
數(shù)據(jù)存儲(chǔ)上看,我們新建了 activity 、 page 、 page_record 三張表用于存儲(chǔ)活動(dòng)配置、頁(yè)面配置和組件配置相關(guān)的數(shù)據(jù)。
-
activity是活動(dòng)配置表,負(fù)責(zé)記錄活動(dòng)名,活動(dòng)的上下線時(shí)間,業(yè)務(wù)相關(guān)的活動(dòng)配置以及公用活動(dòng)配置等。其中公用組件配置,是指該活動(dòng)下的頁(yè)面,需要公用的組件配置項(xiàng)。比如領(lǐng)取優(yōu)惠券組件,會(huì)將優(yōu)惠券的金額、類型、批次等,放到公用組件配置中,這樣能有效避免在多個(gè)頁(yè)面的組件中分開進(jìn)行配置時(shí),配置出錯(cuò)或者不統(tǒng)一的情況。

page是頁(yè)面表。新建頁(yè)面時(shí),就會(huì)往該表中插入數(shù)據(jù)。信息會(huì)記錄頁(yè)面名,頁(yè)面所屬的活動(dòng)id,頁(yè)面所屬的平臺(tái)(移動(dòng)端 or PC端),發(fā)布時(shí)間等。需要注意的是,該表不會(huì)記錄具體的組件配置數(shù)據(jù)。這是為了將頁(yè)面數(shù)據(jù)與組件配置數(shù)據(jù)分離。但是頁(yè)面表會(huì)記錄線上頁(yè)面使用的 online_record_id ,用來關(guān)聯(lián)查詢線上頁(yè)面使用的組件數(shù)據(jù)。每次發(fā)布頁(yè)面后,我們會(huì)將最新的 online_record_id 更新到對(duì)應(yīng)頁(yè)面數(shù)據(jù)中。page_record是組件配置記錄表,主要負(fù)責(zé)記錄所屬的頁(yè)面id,具體的組件數(shù)據(jù),編輯人,發(fā)布時(shí)間等。在積木系統(tǒng)的活動(dòng)頁(yè)面編輯中,每一次保存頁(yè)面,都會(huì)在這個(gè)表中插入一條數(shù)據(jù),這樣方便查找編輯記錄,同時(shí)也方便回滾。

2.4 組件的配置與配置數(shù)據(jù)解析
按照我們規(guī)劃的操作流程,運(yùn)營(yíng)同學(xué)在 積木系統(tǒng) 中新增活動(dòng),創(chuàng)建頁(yè)面后,需要給頁(yè)面添加組件,修改組件配置,配置完成后,保存頁(yè)面中的組件配置,最后發(fā)布頁(yè)面。
不同的組件,需要用到不同的配置項(xiàng)。那么我們?cè)撛趺丛?code>積木系統(tǒng)中,給不同的組件提供不同的配置項(xiàng)呢?
首先得介紹一下組件的開發(fā)模式。
在開發(fā)組件前,我們會(huì)提前和產(chǎn)品同事確認(rèn)該組件所需要的配置,包括組件樣式配置、組件文案配置以及組件業(yè)務(wù)屬性配置等。
開發(fā)組件時(shí),我們一般會(huì)添加三個(gè)文件。以圖片組件為例,我們添加image.jsx、image.scss、spec.js,分別是組件的具體實(shí)現(xiàn)代碼、組件樣式文件、組件的配置文件數(shù)據(jù)。
在配置某個(gè)組件時(shí),積木系統(tǒng)通過讀取該組件下的spec.js文件,提供不同的配置彈窗。
以下是圖片組件的spec.js文件內(nèi)容:
import Image from './image.js';
export default {
Component: Image,
type: Image.type,
_name: '圖片', //組件列表使用的名稱
_platform: 'common', //該組件屬于公共組件
_acceptChild: false, //是否允許嵌套其他組件
_dataSchema: { //組件配置項(xiàng)需要的JSON Schema
type: "object",
required: ["src"],
properties: {
src: {
type: "string",
title: "請(qǐng)?zhí)顚憟D片地址",
},
alt: {
type: "string",
title: "圖片無法加載時(shí)的文案",
},
title: {
type: "string",
title: "鼠標(biāo)移到圖片上的提示文案",
}
}
},
data: { //默認(rèn)數(shù)據(jù)
src: 'https://www.we.com/cms/5864b0d6a24d131067ef7956/jimu/default-img.jpg',
alt: '',
title: ''
},
style: { //樣式配置項(xiàng)
width: '',
height: '',
position: 'static',
display: 'block',
margin: '',
padding: ''
},
_defaultStyle: { //默認(rèn)樣式
mobile: {
width: '100%'
},
pc: {
maxWidth: '1080px',
height: 'auto'
}
}
}
以上的配置文件中,有部分屬性是以下劃線_開頭,這部分屬性屬于積木系統(tǒng)專用的屬性,會(huì)在給前端頁(yè)面?zhèn)鬟f組件配置數(shù)據(jù)時(shí),過濾掉這些專屬屬性,避免配置數(shù)據(jù)過多,也避免部分內(nèi)部數(shù)據(jù)泄露。
一個(gè)活動(dòng)頁(yè)面,一般會(huì)添加多個(gè)組件,而且組件間還可能存在嵌套關(guān)系,頁(yè)面上的組件配置數(shù)據(jù)如何組織、解析,也是必須要解決的問題。
我們這樣定義頁(yè)面上的組件數(shù)據(jù):
{
"dataMap":{
"id1":{
"cid":"id1", // 組件id
"type":"pc_component_1", //組件類型
"name":"組件一", //組件名
"platform":"pc", //組件所屬的平臺(tái)
"acceptChild":true, //是否能添加子組件
"data":{},//組件數(shù)據(jù)
"style":{},//組件樣式
"childs":['id3'] //子組件id
},
"id2":{
"cid":"id2", // 組件id
"type":"pc_component_2", //組件類型
"name":"組件二", //組件名
"platform":"pc", //組件所屬的平臺(tái)
"acceptChild":false, //是否能添加子組件
"data":{},//組件數(shù)據(jù)
"style":{},//組件樣式
"childs":[] //子組件id
},
"id3":{
"cid":"id3", // 組件id
"type":"pc_component_3", //組件類型
"name":"組件三", //組件名
"platform":"pc", //組件所屬的平臺(tái)
"acceptChild":false, //是否能添加子組件
"data":{},//組件數(shù)據(jù)
"style":{},//組件樣式
"childs":[] //子組件id
},
...
},
"main": ["id1","id2"] //第一層級(jí)的組件id
}
頁(yè)面的組件配置數(shù)據(jù)中有dataMap和childs兩個(gè)字段。
在需要通過這些配置的組件數(shù)據(jù)來渲染頁(yè)面時(shí),首先使用配置項(xiàng)中的 main 字段獲取所有第一層級(jí)子組件的id,然后在dataMap中,根據(jù)組件id來查找對(duì)應(yīng)的具體配置,進(jìn)行渲染。
如果某個(gè)組件配置數(shù)據(jù)中的 childs 字段不為空數(shù)組,就意味該組件中嵌套了其他組件,就繼續(xù)通過 childs 中的id值,在dataMap中查找對(duì)應(yīng)組件的配置數(shù)據(jù),并渲染子組件。
還有,我們之前提到過的公共組件配置數(shù)據(jù),在渲染組件前,也會(huì)和dataMap中的對(duì)應(yīng)組件配置進(jìn)行數(shù)據(jù)合并。
2.5 rrd-h5 & rrd-pc 中如何渲染?
那么積木系統(tǒng)中生成的頁(yè)面組件配置數(shù)據(jù),是如何在 rrd-h5 和 rrd-pc 中,進(jìn)行渲染的呢?
其實(shí),目前主流的組件渲染方式有三種:
加載所有的組件定義,然后通過活動(dòng)id和頁(yè)面id獲取頁(yè)面的配置數(shù)據(jù),進(jìn)而動(dòng)態(tài)渲染出頁(yè)面
先通過活動(dòng)id和頁(yè)面id獲取頁(yè)面的配置數(shù)據(jù),然后按需加載組件,渲染出頁(yè)面
服務(wù)器通過頁(yè)面配置和組件定義,直接在發(fā)布時(shí)生成靜態(tài)頁(yè)面
不同的方案各有優(yōu)劣。rrd-h5 和 rrd-pc 系統(tǒng)中,我們使用了第一種方案來進(jìn)行渲染:我們的線上頁(yè)面模板,會(huì)默認(rèn)加載所有的組件。
以rrd-h5為例,我們會(huì)在活動(dòng)頁(yè)面模板中引用所有的 jm-common(公共組件) 和jm-mobile組件庫(kù)代碼。然后使用活動(dòng)頁(yè)面URL中攜帶的活動(dòng)id和頁(yè)面id,通過 node-market-service 服務(wù)獲取活動(dòng)數(shù)據(jù)和頁(yè)面組件配置數(shù)據(jù)。之后就按上述 2.4 組件的配置與配置數(shù)據(jù)解析 中介紹的組件配置數(shù)據(jù)解析方式,渲染出整個(gè)活動(dòng)頁(yè)面。
就這樣,用戶就能看到配置的活動(dòng)頁(yè)面。
三、TODO
目前的運(yùn)營(yíng)平臺(tái),其實(shí)主要以編輯系統(tǒng)為主,并提供了少量的查詢功能。
我們未來會(huì)繼續(xù)迭代,將繼續(xù)集成運(yùn)營(yíng)監(jiān)控、報(bào)警,自動(dòng)生成活動(dòng)數(shù)據(jù)報(bào)表等功能。
同時(shí),為了提高頁(yè)面加載速度,考慮到活動(dòng)頁(yè)面上圖片較多、且切圖普遍較大的問題,我們即將在rrd-h5、rrd-pc中引入 webp 、http2、service-worker等。
四、總結(jié)
人人貸活動(dòng)運(yùn)營(yíng)平臺(tái)在2018年9月上線后,效果極其明顯:
活動(dòng)運(yùn)營(yíng)平臺(tái),能夠自動(dòng)化、可視化地創(chuàng)建活動(dòng)及活動(dòng)頁(yè)面。
活動(dòng)運(yùn)營(yíng)平臺(tái),讓活動(dòng)的上線周期,從以往的6天,降低到了2天。設(shè)計(jì)師切完圖,運(yùn)營(yíng)人員就能配置上線。
活動(dòng)可配置上線和下線時(shí)間,開發(fā)人員基本不會(huì)因?yàn)榛顒?dòng)的開發(fā)而加班。
運(yùn)營(yíng)平臺(tái)規(guī)范了活動(dòng)功能的形式,同時(shí),設(shè)計(jì)師也會(huì)在組件的可編輯范圍內(nèi)進(jìn)行設(shè)計(jì),組件可配置項(xiàng)豐富。
活動(dòng)頁(yè)面的樣式和文案的修改,不再需要重新上線。
釋放出的前、后端開發(fā)人員,能將更多的精力投入到對(duì)新技術(shù)的研究。
由于篇幅有限,活動(dòng)運(yùn)營(yíng)平臺(tái)的很多具體實(shí)現(xiàn)細(xì)節(jié)并沒有過多描述。如果大家有感興趣的問題,可以留言進(jìn)行交流。
最后,歡迎大家star我們的人人貸大前端團(tuán)隊(duì)博客,所有的文章還會(huì)同步更新到知乎專欄 和 掘金賬號(hào),我們每周都會(huì)分享幾篇高質(zhì)量的大前端技術(shù)文章。