大型前端應(yīng)用如何做系統(tǒng)融合?
1. 背景介紹
1.1業(yè)務(wù)介紹
A平臺(tái)與B平臺(tái)同屬于同一系統(tǒng)鏈路上,前者主要致力于為用戶提供注冊(cè)入駐服務(wù),后者則專注于提供具體業(yè)務(wù)操作服務(wù)。兩者皆為運(yùn)營(yíng)人員所依賴的在線管理工具。
1.2現(xiàn)狀分析
目前這兩個(gè)平臺(tái)服務(wù)于同一業(yè)務(wù)方,且B應(yīng)用的頁(yè)面已經(jīng)100%嵌入到了A應(yīng)用的平臺(tái)上,除此以外目前存在系統(tǒng)上及體驗(yàn)上的痛點(diǎn)如下:

所以當(dāng)時(shí)我們考慮既然服務(wù)于同一業(yè)務(wù)方是否能在代碼層面上將兩個(gè)平臺(tái)進(jìn)行融合,通過(guò)系統(tǒng)的融合來(lái)達(dá)到優(yōu)化用戶體驗(yàn)以及降本增效的效果呢?
2.成果展示
平臺(tái)融合后,主要的優(yōu)化點(diǎn)體現(xiàn)在以下四方面:

優(yōu)化前(跳轉(zhuǎn)單個(gè)頁(yè)面白屏?xí)r間達(dá)2998ms左右):

優(yōu)化后(跳轉(zhuǎn)單個(gè)頁(yè)面白屏?xí)r間800ms左右):

3. 具體措施
3.1方案調(diào)研
3.1.1部署方式
?部署優(yōu)化:A應(yīng)用前后端合部署,現(xiàn)計(jì)劃分離前端獨(dú)立部署;
?資源節(jié)約:經(jīng)行云部署平臺(tái)調(diào)研,擬采用混合部署策略,將A應(yīng)用與B應(yīng)用前端靜態(tài)資源集中部署于一組容器,以優(yōu)化資源利用;

3.1.2代碼倉(cāng)庫(kù)整合
?A應(yīng)用的三個(gè)項(xiàng)目與后端共享一個(gè)代碼倉(cāng)庫(kù),采用統(tǒng)一的編碼標(biāo)準(zhǔn);而B(niǎo)應(yīng)用則使用獨(dú)立的代碼倉(cāng)庫(kù),需從中分離出前端代碼,并確保分離過(guò)程不影響現(xiàn)有配置;
3.1.3項(xiàng)目框架
?4個(gè)項(xiàng)目的技術(shù)選型框架都為vue2,依賴項(xiàng)略有不同;
3.1.3系統(tǒng)權(quán)限
?A應(yīng)用和B應(yīng)用為erp登錄;
3.2架構(gòu)設(shè)計(jì)
為了讓用戶融合無(wú)體驗(yàn)差別,兩個(gè)平臺(tái)的用戶繼續(xù)使用各自的域名進(jìn)行訪問(wèn),融合后的項(xiàng)目可以自動(dòng)識(shí)別當(dāng)前環(huán)境,加載對(duì)應(yīng)的內(nèi)容;保證融合前后用戶查看的內(nèi)容是一致的;

3.3具體方案
3.3.1 目錄結(jié)構(gòu)設(shè)計(jì)
針對(duì)融合,我們首先考慮的是融合后如何防止文件沖突,減少融合的復(fù)雜度,降低出問(wèn)題的概率。保證兩個(gè)系統(tǒng)能正常運(yùn)行;拆分邏輯分以下三個(gè)方面:
1)文件拆分與分類
兩個(gè)系統(tǒng)涉及到幾十個(gè)文件,經(jīng)過(guò)分析,我們將其拆分成以下幾部分內(nèi)容:【頁(yè)面文件、公共組件文件、mock文件、AxPI接口文件、基礎(chǔ)請(qǐng)求封裝文件、路由組件文件、Store文件、公共樣式文件、公共方法組件、mainjs文件、index.html文件】
2)結(jié)構(gòu)整合與差異化處理
由于兩個(gè)項(xiàng)目的結(jié)構(gòu)相似,我們可以針對(duì)各個(gè)部分進(jìn)行整合。整體的思路是,對(duì)于差異比較大的文件,建立兩個(gè)獨(dú)立的文件夾,分別包含系統(tǒng)A和系統(tǒng)B的內(nèi)容;然后通過(guò)一個(gè)index文件,識(shí)別到當(dāng)前的運(yùn)行環(huán)境是系統(tǒng)A還是系統(tǒng)B,再分別加載對(duì)應(yīng)的內(nèi)容;
3)內(nèi)容融合與沖突解決
針對(duì)差異比較小或者無(wú)差異的文件,我們將文件內(nèi)容進(jìn)行融合。對(duì)于沖突的內(nèi)容,我們進(jìn)行了手動(dòng)修改,并對(duì)全局引用部分進(jìn)行同步修改;
├── root
│ ├── mocks
│ ├── public
│ ├── src
│ │ ├── api
│ │ │ ├── apiA // 存儲(chǔ) A 業(yè)務(wù)請(qǐng)求接口
│ │ │ ├── apiB // 存儲(chǔ) B 業(yè)務(wù)請(qǐng)求接口
│ │ │ ├── apiC // 存儲(chǔ) C 業(yè)務(wù)請(qǐng)求接口
│ │ │ ├── baseHttp.js // 封裝基礎(chǔ)請(qǐng)求
│ │ │ ├── ARequest.js // A業(yè)務(wù)的公共處理,請(qǐng)求header和響應(yīng)code碼等處理
│ │ │ ├── BRequest.js // B業(yè)務(wù)的公共處理,請(qǐng)求header和響應(yīng)code碼等處理
│ │ │ ├── CRequest.js // C業(yè)務(wù)的公共處理,請(qǐng)求header和響應(yīng)code碼等處理
│ │ │ ├── DRequest.js // D業(yè)務(wù)的公共處理,請(qǐng)求header和響應(yīng)code碼等處理
│ │ ├── assets
│ │ │ ├── icons // icon內(nèi)容
│ │ ├── common
│ │ │ ├── config
│ │ │ ├── styles // 一些公共的樣式
│ │ ├── components // 公共組件
│ │ ├── directive // 自定義指令
│ │ ├── layout //公共布局
│ │ ├── router
│ │ │ ├── a.js // 對(duì)應(yīng)a應(yīng)用
│ │ │ ├── b.js // 對(duì)應(yīng)b應(yīng)用
│ │ │ ├── c.js // 對(duì)應(yīng)c應(yīng)用
│ │ │ ├── index.js
│ │ ├── store
│ │ │ ├── modules
│ │ │ ├── getters.js
│ │ │ ├── index.js
│ │ ├── utils
│ │ ├── views
│ │ │ ├── a // a 業(yè)務(wù)文件
│ │ │ ├── b // b 業(yè)務(wù)文件
│ │ │ ├── c // c 業(yè)務(wù)文件
│ │ ├── main.js
│ │ └── App.vue
│ ├── env
│ ├── package.json3.3.2. 應(yīng)用類型判斷
應(yīng)用類型判斷是我們重要的一環(huán),是我們識(shí)別環(huán)境的基礎(chǔ),當(dāng)用戶通過(guò)不同的域名訪問(wèn)到應(yīng)用的時(shí)候,前端維護(hù)一個(gè)映射表,不同的域名代表不同的應(yīng)用;在main.js文件中會(huì)在第一時(shí)間執(zhí)行判斷識(shí)別;
let APPLICATION_TYPE = 'a'
let host = window.location.host;
// 維護(hù)域名列表,包含測(cè)試和線上環(huán)境
const A_HOST = ['a.com','a_pre.com']
const B_HOST = []
const C_HOST = []
const D_HOST = []
if(A_HOST.includes(host)){
APPLICATION_TYPE = 'a'
}else if(B_HOST.includes(host)){
APPLICATION_TYPE = 'b'
}else if(C_HOST.includes(host)){
APPLICATION_TYPE = 'c'
}else if(D_HOST.includes(host)){
APPLICATION_TYPE = 'd'
}
// 掛載全局
window._APPLICATION_TYPE = APPLICATION_TYPE3.3.3. 路由設(shè)計(jì)
根據(jù)不同的域名獲取路由配置,根據(jù)路由配置生成路由;系統(tǒng)A和系統(tǒng)B各自維護(hù)一個(gè)路由列表;當(dāng)從后端請(qǐng)求回來(lái)路由結(jié)構(gòu)之后,根據(jù)不同的應(yīng)用映射不同的文件內(nèi)容;其中路由path保持不變,compoent增加前綴(應(yīng)用類別)找到對(duì)應(yīng)的應(yīng)用下的組件;
?第一步:前端獲取當(dāng)前域名,確認(rèn)當(dāng)前應(yīng)用
根據(jù)全局的 APPLICATION_TYPE 字段識(shí)別?第二步:前端維護(hù)一個(gè)路由列表
let router=[
{
path: '/home',
component: Layout,
meta: { title: '首頁(yè)', icon: 'el-icon-s-grid', alwaysShow: true },
redirect: '/home',
children: [
{
path: '/home',
component: () => import('@/views/home/index'),
name: 'home',
meta: { title: '首頁(yè)', icon: ''}
}
]
}
]?第三步:根據(jù)當(dāng)前應(yīng)用請(qǐng)求后端接口,獲取路由配置信息(component的路徑前拼接各個(gè)應(yīng)用的文件名)
let RouterApi={'a':'/api1','b':'/api2','c':'api3'}
api.get(RouterApi[APPLICATION_TYPE])
component='各個(gè)應(yīng)用文件名'+接口返回路徑?第四步:針對(duì)在路由信息放置在前端的應(yīng)用,前端維護(hù)一個(gè)路由的配置信息表
import dRouter from './d.json'
if(APPLICATION_TYPE==='d'){
router=dRouter
}?第五步:根據(jù)路由配置信息,生成路由結(jié)構(gòu)
?第六步:實(shí)例化Vue對(duì)象
3.3.4. 環(huán)境變量設(shè)計(jì)
環(huán)境主要分為以下幾種:mock環(huán)境、本地開(kāi)發(fā)環(huán)境、測(cè)試環(huán)境、線上環(huán)境
不同的環(huán)境對(duì)應(yīng)不同的變量文件,在變量文件中設(shè)置每個(gè)端需要用到的參數(shù),結(jié)合 APPLICATION_TYPE 和變量文件的配置,獲取到對(duì)應(yīng)的參數(shù)
示例:
# .env.pruduction
# a 業(yè)務(wù)
VUE_APP_A_BASEURL = ''
# b 業(yè)務(wù)
VUE_APP_B_BASEURL = ''
# c 業(yè)務(wù)
VUE_APP_C_BASEURL = ''
# d業(yè)務(wù)
VUE_APP_D_BASEURL = ''3.3.5. 請(qǐng)求設(shè)計(jì)
1)公共請(qǐng)求的封裝
封裝基礎(chǔ)的公共請(qǐng)求createHttp.js,各業(yè)務(wù)基于公共的請(qǐng)求和各自的code碼,請(qǐng)求參數(shù)等信息進(jìn)行再次封裝,然后可以按照業(yè)務(wù)需求調(diào)用;
?基礎(chǔ)請(qǐng)求:createHttp.js
?業(yè)務(wù)公共封裝:
a. ARequest.js(A業(yè)務(wù)公共參數(shù)和code碼處理)
b. BRequest.js (B業(yè)務(wù)公共參數(shù)和code碼處理)
c. CRequest.js(C業(yè)務(wù)公共參數(shù)和code碼處理)
d. DRequest.js(D業(yè)務(wù)公共參數(shù)和code碼處理)
?業(yè)務(wù)層調(diào)用:
a. api/apiA A業(yè)務(wù)接口
b. api/apiB B業(yè)務(wù)接口
c. api/apiC C業(yè)務(wù)接口
// 公共請(qǐng)求封裝 baseHttp.js
export const createHttp = (baseUrl, successFun = () => {}, errorFun = () => {}, requestInterceptor = () => {}) => {
const http = axios.create({
baseURL: baseUrl,
timeout: 5 * 60 * 1000,
withCredentials: true
})
// http request 攔截器
http.interceptors.request.use(
async config => {
await requestInterceptor(config)
return config
},
err => {
return Promise.reject(err)
}
)
// http response 攔截器
http.interceptors.response.use(successFun, errorFun)
return http
}//A業(yè)務(wù)基礎(chǔ)請(qǐng)求
function requestInterceptor(){
// A系統(tǒng)公共請(qǐng)求參數(shù)處理...
}
function successFn(){
// A系統(tǒng)公共響應(yīng)結(jié)果處理 統(tǒng)一新增
}
function errorFn(){
// A共異常處理 包括code碼等情況
}
export default createHttp(baseUrl,successFn,errorFn,requestinterceptor)3)業(yè)務(wù)接口使用,根據(jù)不同的業(yè)務(wù)劃分不同的目錄分支
// A業(yè)務(wù)請(qǐng)求調(diào)用
ARequest.get(url,{params:data})//B業(yè)務(wù)請(qǐng)求調(diào)用
BRequest.post(url,{params:data})3.3.6. 權(quán)限和登錄
根據(jù)應(yīng)用類型字段APPLICATION_TYPE,識(shí)別不同的環(huán)境,請(qǐng)求不同的服務(wù)端接口;不同的服務(wù)端代表了不同的應(yīng)用;
針對(duì)不同的應(yīng)用的未登錄情況,前端維護(hù)多套登錄處理邏輯,根據(jù)應(yīng)用類型進(jìn)行不同的處理邏輯;
3.3.7. 公共函數(shù)設(shè)計(jì)
創(chuàng)建一個(gè)公共的utils文件夾,針對(duì)兩個(gè)項(xiàng)目中的公共函數(shù)進(jìn)行合并,針對(duì)有沖突的函數(shù),進(jìn)行命名修改,全局引入的部分進(jìn)行路徑和函數(shù)的同步修改;
3.3.8. 腳手架配置設(shè)計(jì)
梳理了兩個(gè)項(xiàng)目的腳手架配置差異項(xiàng)及各個(gè)配置的作用,對(duì)配置作了部分的修改和優(yōu)化,在滿足原有的功能情況下不影響正常的項(xiàng)目運(yùn)行;
3.3.9. Vuex store設(shè)計(jì)
store的整體結(jié)構(gòu)保持不變,在項(xiàng)目引用的地址也保持不變,由于項(xiàng)目中使用store的地方較多,保持結(jié)構(gòu)不變能保證改動(dòng)成本最小,針對(duì)兩個(gè)項(xiàng)目中模塊名重復(fù)的情況,手動(dòng)把模塊里的內(nèi)容進(jìn)行合并;

針對(duì)現(xiàn)有的名稱重復(fù)內(nèi)容不一樣的函數(shù),根據(jù)應(yīng)用類型字段 APPLICATION_TYPE 把兩個(gè)函數(shù)進(jìn)行融合進(jìn)行分別處理;
3.3.10. 頁(yè)面引用設(shè)計(jì)
?引用方式變更
由于業(yè)務(wù)需求,應(yīng)用A中嵌套了應(yīng)用B的頁(yè)面,之前通過(guò)iframe引用。融合后,頁(yè)面文件和組件不再隔離,可以直接引入應(yīng)用B的文件和組件;
?后端數(shù)據(jù)打通
應(yīng)用A的后端服務(wù)器上有一些功能,如下載列表,應(yīng)用B項(xiàng)目需要使用時(shí)因數(shù)據(jù)不通難以直接引用。前端融合后,可以在應(yīng)用B中直接引用應(yīng)用A的頁(yè)面組件,實(shí)現(xiàn)業(yè)務(wù)的順暢使用。效果如下:

4. 總結(jié)
在經(jīng)歷了為期兩個(gè)月的緊張工作后,我們成功地將兩個(gè)大型項(xiàng)目進(jìn)行了深度整合,取得了顯著的階段性成果。通過(guò)這一融合過(guò)程,我們不僅統(tǒng)一了項(xiàng)目的代碼規(guī)范和架構(gòu),還顯著提升了組件的復(fù)用率。盡管在這個(gè)過(guò)程中我們遇到了諸多挑戰(zhàn)和曲折,但最終的成果——用戶體驗(yàn)的顯著提升——使一切努力都顯得彌足珍貴。
我們深知,每一個(gè)成功的項(xiàng)目背后都有無(wú)數(shù)次的嘗試和優(yōu)化。在這個(gè)過(guò)程中,我們不斷學(xué)習(xí)、適應(yīng)和完善,最終實(shí)現(xiàn)了項(xiàng)目的無(wú)縫融合。我們相信,這段經(jīng)歷和我們所取得的成果,不僅為我們團(tuán)隊(duì)帶來(lái)了寶貴的經(jīng)驗(yàn)和教訓(xùn),也可能為那些正在經(jīng)歷類似挑戰(zhàn)的同學(xué)提供了一些啟示和幫助。





























