热更新是一个很是方便的方案。在应对大量用户和深度定制的时候必定不能使用开源的方案。
通常第三方的这种方案,服务器带宽较小,或者不够灵活,不能知足本身的想法。
这里推荐本身实现对应的热更新方案。只须要少许代码便可支持。
下面推荐一种灵活的热更新方案。包括客户端的改造、接口设计、界面开发,同时是开源的!能够自由改造。
体验地址: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进制的方式转化数据。拿到的就是真实的百分比了。
前台页面地址:前台代码
后台接口地址:后台代码
数据库地址:数据库代码