旭日是搜狗重要的商業(yè)廣告平臺(tái)泣侮,目前舊版是基于backbone開發(fā)涂滴,新版則是使用React+redux。
像旭日這種大型的web應(yīng)用烤低,很多功能都需要區(qū)分使用角色,以前的方法是在每個(gè)模塊需要請(qǐng)求權(quán)限時(shí)笆载,通過checkPermission接口把模塊相關(guān)功能的接口名作為參數(shù)傳給后端扑馁,后端依次返回結(jié)果,這種異步方式有許多痛點(diǎn)凉驻。
- 異步編程不優(yōu)雅
每次都通過發(fā)請(qǐng)求來獲取權(quán)限腻要,代碼則需要以異步的方式編寫,并不優(yōu)雅涝登,而且代碼執(zhí)行效率很受接口返回速度的約束雄家,頁面渲染速度也會(huì)受到影響。 - 請(qǐng)求次數(shù)過多
由于旭日平臺(tái)功能模塊很多缀拭,每個(gè)模塊都要請(qǐng)求權(quán)限的話咳短,加載頁面時(shí)得同時(shí)發(fā)大量checkPermission請(qǐng)求,造成大量網(wǎng)絡(luò)開銷蛛淋。
比如旭日推廣管理頁咙好,第一次頁面加載就有22個(gè)checkPermission請(qǐng)求。
image.png
3.影響頁面渲染性能
過多異步請(qǐng)求會(huì)嚴(yán)重阻塞頁面渲染狀態(tài)褐荷,特別是多個(gè)組件串行渲染勾效,需要一級(jí)一級(jí)等到組件異步請(qǐng)求權(quán)限的結(jié)果才能繼續(xù)渲染,渲染性能很差叛甫。
改造思路
- 初始權(quán)限的獲取
我們開發(fā)時(shí)使用權(quán)限最理想的場(chǎng)景是同步的方式來使用层宫,可是已經(jīng)不是jsp時(shí)代了,我們無法在頁面的初始化狀態(tài)就拿到該角色的所有功能權(quán)限其监,所以我們必須得向后端請(qǐng)求萌腿,最好在頁面初始化時(shí)一次請(qǐng)求完所有的權(quán)限,所以我新建了一個(gè)配置文件抖苦,里面填寫了平臺(tái)所有需要請(qǐng)求的權(quán)限參數(shù)毁菱,后續(xù)有需要繼續(xù)在該配置文件中添加即可,然后一次性的去請(qǐng)求锌历。 - 頁面空白問題
但是這樣有個(gè)大問題贮庞,這樣還是異步的方式,而且checkPermission接口在一次性請(qǐng)求過多參數(shù)(200+)時(shí)究西,性能極其不穩(wěn)定窗慎,有時(shí)候會(huì)需要幾百ms才返回?cái)?shù)據(jù),這樣頁面會(huì)有一段時(shí)間明顯的白屏,這樣的用戶體驗(yàn)顯然是不可接受的 - 巧用緩存
由于旭日已經(jīng)丟下了ie8以下的兼容包袱遮斥,我們可以用一些更好的技術(shù)來解決業(yè)務(wù)問題峦失,一個(gè)角色的用戶并不會(huì)經(jīng)常變化,為什么我們把他存在用戶本地呢伏伐?所以我將請(qǐng)求回來的權(quán)限結(jié)果存放在瀏覽器的localStorage中宠进,這樣就可以直接使用緩存中的結(jié)果而不需要依靠請(qǐng)求回來的結(jié)果了。 - 權(quán)限更正
但是這樣還是有明顯缺陷的藐翎,沒法保證用戶的權(quán)限真的不會(huì)改變材蹬,即localstorage的結(jié)果有可能是錯(cuò)的;而且沒有l(wèi)ocalstorage時(shí)的那次的頁面長(zhǎng)時(shí)間空白問題也沒有解決吝镣。
由于新版旭日是基于react+redux搭建的堤器,我們只要把權(quán)限的值作為個(gè)props傳入頁面組件中,那么在權(quán)限變化的時(shí)候末贾,傳入的props發(fā)生了變化闸溃,頁面自動(dòng)會(huì)進(jìn)行渲染更新成正確權(quán)限的狀態(tài)
完整攻略
所以,下面有了新版旭日權(quán)限改造的完整攻略:
- 配置一個(gè)基礎(chǔ)的權(quán)限值拱撵,全為false辉川,即無權(quán)限狀態(tài),傳入應(yīng)用中作為props拴测,并以此props來判斷權(quán)限乓旗,這樣頁面可以同步渲染出頁面來,即使是錯(cuò)的
//傳入初始化的permission值進(jìn)入store中集索,初始值為localstorage中的值或者為空對(duì)象
const store = configureStore({
permissions: JSON.parse(localStorage.getItem(LocalStoragePKey)) || {}
});
- 在頁面初始化時(shí)屿愚,同時(shí)一次全量請(qǐng)求應(yīng)用所需要的所有權(quán)限,在收到結(jié)果后與localstorage的值進(jìn)行對(duì)比务荆,如果沒有l(wèi)ocalstorage或者與localstorage的值不一致妆距,則將localstorage更新為正確的結(jié)果,并dispatch一個(gè)PERMISSION_CHANGED的action出來
Auth.checkURLPermissions(permissions, (ret)=> {
const permissionMap = {};
permissions.map((item, index)=>{
permissionMap[item] = ret[index]
});
//對(duì)比請(qǐng)求結(jié)果與localstorage中的值是否相同
if (localStorage.getItem(LocalStoragePKey) !== JSON.stringify(permissionMap)) {
localStorage.setItem(LocalStoragePKey, JSON.stringify(permissionMap));
store.dispatch({type: ACTION_PERMISSIONS_CHANGED, payload: permissionMap});
} else {
return;
}
});
- 頁面各組件在收到PERMISSION_CHANGED的action后函匕,會(huì)自動(dòng)根據(jù)新的permission重新渲染相關(guān)的頁面娱据,頁面渲染為正確的狀態(tài)
//render方法中直接同步使用permission,在permission發(fā)生變化時(shí)盅惜,自動(dòng)重新執(zhí)行render方法
render() {
this.selectArr = this.state.selectArr || [];
let disabled = this.selectArr.length === 0;
const {permissions} = this.props;
return (
<div styleName="container">
<div styleName="top-buttons">
<span styleName="inline-item">
<Button type="minor" onClick={this.doSeizeGoldenPosition.bind(this)}
disabled={disabled}>搶占黃金位</Button>
</span>
{permissions[MONITOR_CREATE_MONITOR_KEY] ?
<span styleName="inline-item">
<Button type="minor" onClick={this.addMonitorKey.bind(this)}>添加監(jiān)控關(guān)鍵詞</Button>
</span> : ''}
{permissions[MANAGE_MODIFY_KEY_PRICE] ?
<span styleName="inline-item">
<Button type="minor" onClick={this.batchModifyKeyPrice.bind(this)}
disabled={disabled}>修改出價(jià)</Button>
</span> : ''}
{permissions[MANAGE_MODIFY_KEY_MATCH] ?
<span styleName="inline-item">
<Button type="minor" onClick={this.doModifyMatchType.bind(this)}
disabled={disabled}>修改匹配方式</Button>
</span> : ''}
{permissions[MANAGE_MODIFY_KEY_IS_PAUSE] ?
<span styleName="inline-item">
<Button type="minor" onClick={this.statusChange.bind(this, 0)}
disabled={disabled}>取消暫停</Button>
</span> : ''}
{permissions[MANAGE_MODIFY_KEY_IS_PAUSE] ?
<span styleName="inline-item">
<Button type="minor" onClick={this.statusChange.bind(this, 1)}
disabled={disabled}>暫停</Button>
</span> : ''}
{permissions[MONITOR_STOP_MONITOR_KEY] ?
<span styleName="inline-item">
<Button type="minor" onClick={this.stopMonitor.bind(this)}
disabled={disabled}>停止監(jiān)控</Button>
</span> : ''}
</div>
</div>
);
}
總結(jié)
這樣頁面就可以無阻塞中剩、快速、正確的渲染酷窥,而且開發(fā)也可以優(yōu)雅的以同步方式來使用權(quán)限了!