JSPatch线上bug实时修复

JSPatch是什么


JSPatch是一个开源项目,只需要在项目里引入极小的引擎文件,就可以使用 JavaScript 调用任何 Objective-C 的原生接口,替换任意 Objective-C 原生方法。目前主要用于下发 JS 脚本替换原生 Objective-C 代码,实时修复线上 bug.

目录


  1. JSPatch接入
  2. 示例代码
  3. 安全问题
  4. 自定义RSA Key
  5. 常见问题:发布补丁后不起作用
  6. 一点儿小总结

JSPatch接入


  1. 注册
    注册jspatch账号并登录.(http://www.jspatch.com).
  1. 添加应用,获取appKey
    在"我的app"中添加新的app,未上线项目无需填写AppStoreID,添加完成后可以看到appKey.
  2. 导入framework和相关类库
    在官网下载SDK,将解压得到的JSPatch.framework导入到项目,并添加依赖的类库:libz.dylibJavaScriptCore.framework即可.
  3. 测试补丁和发布补丁
    新建一个main.js文件,在这个文件中编写js代码(用于替换oc代码的),这个main.js文件如果在项目中,通过[JSPatch testScriptInBundle];方法测试补丁,补丁测试通过后,可以将这个main.js文件上传到jspatch官网,通过[JSPatch startWithAppKey:@"APPKey"];方法调用在线补丁,项目中的appDelega.m文件中的代理方法中写入这个方法,发布的app每次启动时会在线查找补丁,如果有版本号一致的补丁,则会下载这个补丁,用来替换相应的方法.
  4. 发布补丁包
    在app管理中"新建版本",新建版本后,上传main.js文件再点击提交即可.需要注意的是新建版本中的版本,对应项目的版本号,app在线上寻找补丁时,版本号是一个筛选条件.
代码如下:
///本地测试
///AppDelegate.m
#import <JSPatch/JSPatch.h>
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    //本地测试版本
    [JSPatch testScriptInBundle];
    [JSPatch sync];
}
///线上补丁
///AppDelegate.m
#import <JSPatch/JSPatch.h>
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    //本地测试版本
    [JSPatch startWithAppKey:@"APPKey"];
    [JSPatch sync];
}

举个栗子


OC代码如下

///viewController.m
- (void)viewDidLoad {
    [super viewDidLoad];
    self.array = @[@"one",@"two",@"three"];
    UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(135, 250, 100, 50)];
    [button setBackgroundColor:[UIColor blueColor]];
    [button addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
}
//按钮点击事件,模拟数组越界
-(void)buttonClicked:(UIButton *)sender{
    UIAlertView *av = [[UIAlertView alloc] initWithTitle:@"提示" message:self.array[3] delegate:self cancelButtonTitle:nil otherButtonTitles:@"确定", nil];
    [av show];
    NSLog(@"%@",self.array[3]);
}

下边用js替换点击方法buttonClicked

///main.js
defineClass("ViewController", {
            buttonClicked: function(sender) {
            //1.获取/修改 Property: 等于调用这个 Property 的 getter / setter 方法,获取时记得加()
            //2.OC中的nil -->jspatch中的null
            //3.require('className')
            //4.NSLog(@"123") -->console.log("123")
            var message = self.array().objectAtIndex(0);
            var temAlertView = require('UIAlertView').alloc().initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles("提示",message, self, "OK", null);
            temAlertView.show()
            console.log(message);
            }
    })
///AppDelegate.m
#import <JSPatch/JSPatch.h>
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    //本地测试版本
    [JSPatch startWithAppKey:@"APPKey"];
    [JSPatch sync];
}

然后上传这个main.js,发布补丁

关于安全问题


传输安全

JSPatch脚本的执行权限很高,若在传输过程中被中间人篡改,会带来很大的安全问题,为了防止这种情况出现,我们在传输过程中对JS文件进行了RSA签名加密,流程如下:
服务端:
计算 JS 文件 MD5 值。
用 RSA 私钥对 MD5 值进行加密,与JS文件一起下发给客户端。
客户端:
拿到加密数据,用 RSA 公钥解密出 MD5 值。
本地计算返回的 JS 文件 MD5 值。
对比上述的两个 MD5 值,若相等则校验通过,取 JS 文件保存到本地。
由于 RSA 是非对称加密,在没有私钥的情况下第三方无法加密对应的 MD5 值,也就无法伪造 JS 文件,杜绝了 JS 文件在传输过程被篡改的可能。

本地存储

本地存储的脚本被篡改的机会小很多,只在越狱机器上有点风险,对此 JSPatch SDK 在下载完脚本保存到本地时也进行了简单的对称加密,每次读取时解密。

自定义RSA Key


客户端和 JSPatch 后台默认有一对 RSA 密钥,默认会用这对密钥进行加解密验证。
若对安全要求较高,可以自定义 RSA 密钥,然后将公钥通过:
[JSPatch setupRSAPublicKey:@"******"]
方法写入app代码中,而私钥需要开发者自行保留,以后每次发布补丁的时候,需要同时上传这个私钥,如图:(生成秘钥的具体方法看官方文档,点这里)

上传私钥.png

这里上传的 rsa_private_key.pem 只是一次性使用,不会保存在服务端,所以只有通过用户自己保存的 rsa_private_key.pem 文件才可以针对 APP 下发脚本,即使 JSPatch 平台或者七牛云被黑,第三方也无法对你的 APP 下发恶意脚本(可以下发,但验证不过,不会执行),保证安全性。rsa_private_key.pem 请妥善保管,避免泄露。

使用自定义RSA秘钥的作用:

使用自定义的RSA秘钥,可以保障以下两种情况下的安全:

  1. 当jspatch账号被盗时,盗号者没有私钥,即使发布恶意补丁包,该补丁也不会被执行.(因为没有私钥,无法通过验证)
  2. 当jspatch服务器被黑,由于jspatch方面并不保留上传的私钥,所以app依然是安全的.

常见问题:发布补丁后不起作用


原因可能有:
1.APPKey写错了(好好检查).
错误信息:JSPatch: request success {error = "Document not found";}
2.发布的版本号和项目的版本号不一致(一定要一致,1.2版本的项目只能用1.2版本的补丁包)
错误信息:JSPatch: request success {error = "Document not found";}
3.main.js文件错误,是否找对了"类"和"方法"(类名和方法名要是写错,补丁想起作用也做不到啊)
错误信息:不会出现任何"JSPatch: request success {}"提示
4.main.js文件语法错误(main.js文件中的js语法错误,或者是oc方法名错误都会导致编译不通过,从而不执行main.js方法,最可怕的是他还不报错,jspatch的原则是,js文件能用就用,不能用,我不告诉你,就是不用.)(一个很长的oc方法转写成js方法是,是没有提示的,一个字母写错了,整个js文件就废了,最关键的是,这些错误的是隐形的,不提示,难发觉,更难找,所以我建议两点,第一,写补丁时现在本地测试,在本地的main.js文件中写一行测试一行,保证每一行都是正确的.第二,把要改的oc代码,暂时copy到main.js文件中,这样,当你在js中写oc的方法名时,而这个方法名在你copy的那段代码中,xcode就会给提示.)
错误信息:输出成功信息JSPatch: request success {v = 1;},但是依然执行oc代码而不是js代码

一点儿小总结


补丁发布后,app运行时会把补丁下载下来,一切验证通过后,补丁就可以运行了,这时候,如果你在jspatch管理补丁页面停用了补丁,下载过补丁的app还是会执行补丁.

那么问题来了,我们如何让已经下载过补丁的app不执行补丁呢?

最简单的方法是,先停用补丁,然后把app卸载后重新安装.但是总不能让客户先卸载再重新安装吧.
然后我做了以下几个实验:
实验假设: 我们已经发布补丁修改了代码中的方法 funA,下载过补丁的app执行补丁中的方法,不再执行原方法funA,现在如何让下载过补丁的app不再执行补丁,而是执行原来的方法呢?
实验1. 发布一个空白的补丁,也就新建一个空白的main.js文件,然后上传,企图用这个空白的补丁覆盖原来的.
结果: 失败了,下载过补丁的app依然执行补丁.
实验2. 把原来补丁里的方法名改掉,比如把funA 改成 funAXiugai,这样补丁找不到对应的方法,原方法就可以执行了.
结果: 失败了,下载过补丁的app依然执行补丁.
实验3. 在代码中有一个永不执行的(不被调用)方法funForeverNotDo,这个方法就是用来预防这种情况而存在的(好凄惨的备胎啊),然后写一个main.js,里边只修改funForeverNotDo方法,不再修改funA.
结果: 成功啦

更新

擦擦擦擦,打脸了,如果想要已经下载过补丁的app不再执行补丁,直接"删除版本"就可以了:


删除版本.png

删除补丁包.png

再更新

有一个神奇的网站,可以把OC代码翻译成JS代码:
传送门
Github上有本地源码:
Github本地源码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,544评论 25 707
  • JSPatch作为热更新技术的黑科技,已经不是什么前沿的新闻了,像腾讯、美团等大公司也在使用JSPatch。前段时...
    任尔东西南北疯阅读 1,465评论 0 3
  • 大家好! 欢迎大家的参与, 欢迎大家的到来。 大家仔细思考一下, 你就会发现, 这种互粉的模式, 是非常非常先进的...
    仨二俩王阅读 319评论 0 0
  • 文|蓝塔灵罗 其实,九零后的童年没有那么快乐,望子成龙的家长们从小教育孩子学习,城市中的孩子们更喜欢山间村野的快活...
    蓝塔灵罗阅读 231评论 0 0
  • 信任,还是必要的。 但放到生活里,就要在策略上重视,行动中轻视。 不是误导大家不信人,而是不可全信。如果贸然全信,...
    雕琢人生阅读 659评论 2 1