熱更新是一個很是方便的方案。在應對大量用戶和深度定製的時候必定不能使用開源的方案。
通常第三方的這種方案,服務器帶寬較小,或者不夠靈活,不能知足本身的想法。
這裏推薦本身實現對應的熱更新方案。只須要少許代碼便可支持。
下面推薦一種靈活的熱更新方案。包括客戶端的改造、接口設計、界面開發,同時是開源的!能夠自由改造。
體驗地址:demo 用戶名密碼都是:adminjava
首先第一點,一個APP若是要支持熱更新,須要在打開APP(或者其餘進入RN頁面以前)就要判斷是否須要更新bundle文件。這裏就是咱們實現熱更新的節點。一旦須要熱更新就開始下載文件,而判斷的接口就是咱們此次文章的核心內容。這裏簡單貼出安卓和ios兩端的下載邏輯。node
請求以前須要在head中附帶上客戶端的幾個重要信息。客戶端版本號version、客戶端惟一id:clientid、客戶端類型platform、客戶端品牌brand。
ios下載的例子ios
-(void)doCheckUpdate { self.upView.viewButtonStart.hidden = YES; if ([XCUploadManager isFileExist:[XCUploadManager bundlePathUrl].path]) {//沙盒裏已經有了下載好的jsbundle,以沙盒文件優先 self.oldSign = [FileHash md5HashOfFileAtPath:[XCUploadManager bundlePathUrl].path]; }else {//真機計算出的包內bundlemd5有變化,多是壓縮了,因此這裏寫死初始化的md5 // NSString *ipPath = [[NSBundle mainBundle] pathForResource:@"main" ofType:@"jsbundle"]; // self.oldSign = [FileHash md5HashOfFileAtPath:ipPath]; self.oldSign = projectBundleMd5; } AFHTTPSessionManager *_sharedClient = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:@"http://test.com"]]; [self initAFNetClient:_sharedClient]; [_sharedClient GET:@"api/check" parameters:nil progress:nil success:^(NSURLSessionDataTask * __unused task, id JSON) { NSDictionary *dic = [JSON valueForKeyPath:@"data"]; BOOL isNeedLoadBundle = YES; if ([dic isKindOfClass:[NSDictionary class]]) { self.updateSign = [dic stringForKey:@"sign"]; self.downLoadUrl = [dic stringForKey:@"downloadUrl"]; if(self.updateSign.length && self.oldSign.length && (![self.updateSign isEqualToString:self.oldSign])) { //須要更新bundle文件了 self.upView.viewUpdate.hidden = NO; [self updateBundleNow]; isNeedLoadBundle = NO; }else { //不須要更新bundle文件,再處理跳過按鈕顯示邏輯 [self.upView showSkipButtonOrNot]; } } if (isNeedLoadBundle) { [self loadBundle]; } } failure:^(NSURLSessionDataTask *__unused task, NSError *error) { [self loadBundle]; }]; }
安卓下載的例子c++
private void requestData() { subscribe = DalingNetwork .getDalingApi() .getBundleVersion() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new BaseSubscriber<BundleVersionResponse>() { @Override public void onError(Throwable e) { startMainActivity(); e.printStackTrace(); } @Override public void onNext(final BundleVersionResponse response) { isJSNeedUpdate = false; if (response.status == 0) { if (response.data != null) { if (MainApplication.getApplication().getBundleMD5().equalsIgnoreCase(response.data.sign)) { //和本地版本相同,直接進入主頁 isJSNeedUpdate = false; tv_skip.setVisibility(View.VISIBLE); startMainActivity(); } else { //下載升級 isJSNeedUpdate = true; downloadSign = response.data.sign; downloadUrl = response.data.downloadUrl; downLoad(response.data.downloadUrl, response.data.sign); } } } else { startMainActivity(); } } }); }
首先來看一下咱們是怎樣設計客戶端獲取更新邏輯的。
git
上面的設計是基礎的邏輯,下面咱們繼續細化邏輯。其中爲了支持更好的性能和分佈式作了一些其餘的方案設計。github
根據邏輯自行設計是徹底能夠的😁web
咱們選擇MySQL做爲基礎數據庫,負責存儲每次發佈以後的數據保存。fe_bundle
表存儲的是每次發佈的bundle信息,主要分3個部分:redis
fe_labels
表就是做爲附加數據存儲的。若是想要在接口上返回一些複雜的操做,好比顯示隱藏某個界面、是否加載某個bundle、是否強制更新等,均可以在這裏設置。這個表自己只支持添加和是否啓用,不支持刪除,防止誤操做。數據庫
根據實際狀況減小字段的長度能夠優化數據庫的查詢性能。好比暱稱的長度不會超過10個字符。
大量數據的狀況下添加索引也會提升數據庫性能。查詢的時候只查詢須要的字段也能夠減小查詢的時間。
使用發佈訂閱模式主要是爲了同步每次發佈的結果。這樣作能夠解耦發佈和本地緩存更新,多個服務器支持也不會出現資源爭奪或者更新不及時的狀況。後端
這裏使用的是redis的發佈訂閱模式,能夠選的其餘方案有MQ的消息隊列等方式。在收到消息的時候主動更新本地緩存。
接口響應速度快不快的關鍵就是在本地緩存這裏了。畢竟在用戶大量訪問的狀況下,一個數據庫是很是難支撐的。這裏利用本地緩存減小數據庫的查詢,不論是面對多少用戶,實際在工做的就只有接口所在的服務器線程。並且這裏利用了nodejs的高併發優點,只要機器抗的住,咱們的服務就不會卡頓或者掛掉。服務能支持的併發數幾乎等於機器支持的併發數。
前臺界面使用React+Mobx+ElementUI實現。這裏選擇這個技術棧主要是爲了方便,畢竟會RN的開發者大機率是能夠很快上手React的。
登陸只須要簡單的一個背景+登陸信息輸入框便可。有興趣的能夠優化一下,讓界面更好看。
這裏利用Mobx將用戶的登陸信息保存在全局緩存中。這個設計比較簡陋,在公司內部用一下還能夠了。若是是開發給更多人用必定要完善一下,把用戶鑑權作的更安全一些。
列表管理只須要顯示關鍵信息便可。列出查詢的幾個參數,方便查詢。在點擊刪除的時候要彈出是否刪除的提示,點擊發布的時候也須要彈出提示。
編輯的時候給出幾個固定選項。若是是灰度的時候還可以選擇不一樣的手機品牌、灰度的比例。若是是白名單模式,須要填入白名單對應的clientid。
標籤的核心就是添加和使用。在添加的時候定義好添加的字段和值類型。只須要一次添加便可完成。客戶端兼容🈚️值狀況下的兼容就行了。
接口分2個部分,一部分是應對後臺的編輯列表等接口,另一個部分是應對大量用戶的查詢接口。
接口開發其實很是簡單,若是對數據庫使用不熟練的能夠看看相應的文檔或者教程。
sequelize簡單教程
接口開發3個步驟:
這裏約定,返回status=0
是查詢成功,全部數據放在data
字段裏。
返回status=1
表明查詢失敗,錯誤信息放在msg
字段裏。
查詢接口分2個線程,一個線程是網絡請求線程,管理來訪的網絡請求和篩選返回。另一個線程管理本地更新,經過redis的訂閱模式觸發對應的數據更新。
當redis通知到須要更新的時候會帶上版本號、平臺的數據庫。咱們本地緩存也是由這2個字段做爲key緩存的。searchFromService
這個方法主要是從數據庫拿對應的數據列表,而且在拿到數據以後手工把數據分爲3個部分,分別用來處理白名單、灰度、全量的數據。他們對應的返回也是N個白名單、N個灰度、1個全量數據。
網絡請求邏輯較複雜,須要首先從緩存中拿數據,同時可能觸發數據庫拿數據並處理到緩存中,備份緩存拿數據並返回。
數據來源肯定以後就開始分階段篩選。
以上判斷所有完成以後就能夠知道本次請求是否有合適的bundle了。沒有的話客戶端也不須要更新。用戶能夠正常打開並瀏覽。
判斷灰度的時候clientid中可能會帶字母。這狀況下須要將字母轉爲數據再判斷。
這裏的轉化是簡單的字母數字對應,具體表現就是百分比前移。前60%的用戶量會大於後40%的用戶量。若是對這個有要求的能夠按照26進制轉10進制的方式轉化數據。拿到的就是真實的百分比了。
前臺頁面地址:前臺代碼
後臺接口地址:後臺代碼
數據庫地址:數據庫代碼