From 62c3544f105c27abb22e33d6651fbbec50ebe748 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=AD=E5=9F=8E=E5=B0=8F=E5=88=98?= Date: Sat, 11 Apr 2020 02:42:38 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20Python=20=E8=84=9A=E6=9C=AC=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E7=88=AC=E5=8F=96=E7=96=AB=E6=83=85=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=B9=B6=E7=94=A8=20Markdown=20=E9=A2=84=E8=A7=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Chapter1 - iOS/1.74.md | 299 +++++++++++++++++++++++++- Chapter1 - iOS/1.83.md | 6 +- Chapter3 - Server/3.9.md | 106 +++++++++ Chapter3 - Server/chapter3.md | 1 + SUMMARY.md | 1 + assets/2020-04-09-methodSwizzling.png | Bin 0 -> 30861 bytes 6 files changed, 403 insertions(+), 10 deletions(-) create mode 100644 Chapter3 - Server/3.9.md create mode 100644 assets/2020-04-09-methodSwizzling.png diff --git a/Chapter1 - iOS/1.74.md b/Chapter1 - iOS/1.74.md index 106aa10..40ec969 100644 --- a/Chapter1 - iOS/1.74.md +++ b/Chapter1 - iOS/1.74.md @@ -1861,7 +1861,7 @@ iOS 网络现状是由4层组成的:最底层的 BSD Sockets、SecureTransport -#### 2.1 NSURLProtocol 监控 App 网络请求 +#### 2.1 NSURLProtocol 监控 App 网络请求 NSURLProtocol 作为上层接口,使用较为简单,但 NSURLProtocol 属于 URL Loading System 体系中。应用协议的支持程度有限,支持 FTP、HTTP、HTTPS 等几个应用层协议,对于其他的协议则无法监控,存在一定的局限性。如果监控底层网络库 CFNetwork 则没有这个限制。 @@ -2341,9 +2341,9 @@ API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)) -#### 2.2 骚操作篇 +#### 2.2 骚操作篇 -在 2.1 分析到了 NSURLSessionTaskMetrics 由于兼容性问题,对于网络监控来说似乎不太完美,但是自后在搜资料的时候看到了一篇[文章](https://www.jianshu.com/p/1c34147030d1)。文章在分析 WebView 的网络监控的时候分析 Webkit 源码的时候发现了下面代码 +文章上面 [2.1 ](#network-2.1)分析到了 NSURLSessionTaskMetrics 由于兼容性问题,对于网络监控来说似乎不太完美,但是自后在搜资料的时候看到了一篇[文章](https://www.jianshu.com/p/1c34147030d1)。文章在分析 WebView 的网络监控的时候分析 Webkit 源码的时候发现了下面代码 ```objective-c #if !HAVE(TIMINGDATAOPTIONS) @@ -2441,9 +2441,9 @@ NSURLSession 在 iOS9 之前使用 `_setCollectsTimingData:` 就可以使用 Tim 在 iOS 中 AOP 的实现是基于 Runtime 机制,目前由3种方式:Method Swizzling、NSProxy、FishHook(主要用用于 hook c 代码)。 -2.1 讨论了满足大多数的需求的场景,NSURLProtocol 监控了 NSURLConnection、NSURLSession 的网络请求,自身代理后可以发起网络请求并得到诸如请求开始时间、请求结束时间、header 信息等,但是无法得到非常详细的网络性能数据,比如 DNS 开始解析时间、DNS 解析用了多久、reponse 开始返回的时间、返回了多久等。 iOS10 之后 NSURLSessionTaskDelegate 增加了一个代理方法 `- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));`,可以获取到精确的各项网络数据。但是具有兼容性。2.2 讨论了一个 Webkit 源码中得到的信息,通过私有方法 `_setCollectsTimingData:` 、`_timingData` 可以获取到 TimingData。 +文章上面 [2.1 ](#network-2.1)讨论了满足大多数的需求的场景,NSURLProtocol 监控了 NSURLConnection、NSURLSession 的网络请求,自身代理后可以发起网络请求并得到诸如请求开始时间、请求结束时间、header 信息等,但是无法得到非常详细的网络性能数据,比如 DNS 开始解析时间、DNS 解析用了多久、reponse 开始返回的时间、返回了多久等。 iOS10 之后 NSURLSessionTaskDelegate 增加了一个代理方法 `- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));`,可以获取到精确的各项网络数据。但是具有兼容性。文章上面 [2.2 ](#network-2.2)讨论了从 Webkit 源码中得到的信息,通过私有方法 `_setCollectsTimingData:` 、`_timingData` 可以获取到 TimingData。 -但是如果需要监全部的网络请求就不能满足需求了,查阅资料后发现了阿里百川有 APM 的解决方案,对于网络监控需要做如下的 hook +但是如果需要监全部的网络请求就不能满足需求了,查阅资料后发现了阿里百川有 APM 的解决方案,于是有了方案3,对于网络监控需要做如下的处理 ![network hook](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2020-04-04-network_monitor.png) @@ -2546,7 +2546,292 @@ void printResponseData (CFDataRef responseData) { } ``` -Method Swizzling 方法替换需要知道类名,我们在开发阶段使用 NSURLConnection、NSURLSession 都需要指定代理对象,且代理对象在该阶段没有办法确定,所以在此处的思路是在 NSURLConnection、NSURLSession 设置代理的时候替换掉代理对象, +我们知道 NSURLSession、NSURLConnection、CFNetwork 的使用都需要调用一堆方法进行设置然后需要设置代理对象,实现代理方法。所以针对这种情况进行监控首先想到的是使用 runtime hook 掉方法层级。但是针对设置的代理对象的代理方法没办法 hook,因为不知道代理对象是哪个类。所以想办法可以 hook 设置代理对象这个步骤,将代理对象替换成我们设计好的某个类,然后让这个类去实现 NSURLConnection、NSURLSession、CFNetwork 相关的代理方法。然后在这些方法的内部都去调用一下原代理对象的方法实现。所以我们的需求得以满足,我们在相应的方法里面可以拿到监控数据,比如请求开始时间、结束时间、状态码、内容大小等。 + +具体步骤及其关键代码如下,以 NSURLConnection 举例 + +- 因为要 Hook 挺多地方,所以写一个 method swizzling 的工具类 + + ```objective-c + #import + + NS_ASSUME_NONNULL_BEGIN + + @interface NSObject (hook) + + /** + hook对象方法 + + @param originalSelector 需要hook的原始对象方法 + @param swizzledSelector 需要替换的对象方法 + */ + + (void)apm_swizzleMethod:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector; + + /** + hook类方法 + + @param originalSelector 需要hook的原始类方法 + @param swizzledSelector 需要替换的类方法 + */ + + (void)apm_swizzleClassMethod:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector; + + @end + + NS_ASSUME_NONNULL_END + + + (void)apm_swizzleMethod:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector + { + class_swizzleInstanceMethod(self, originalSelector, swizzledSelector); + } + + + (void)apm_swizzleClassMethod:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector + { + //类方法实际上是储存在类对象的类(即元类)中,即类方法相当于元类的实例方法,所以只需要把元类传入,其他逻辑和交互实例方法一样。 + Class class2 = object_getClass(self); + class_swizzleInstanceMethod(class2, originalSelector, swizzledSelector); + } + + void class_swizzleInstanceMethod(Class class, SEL originalSEL, SEL replacementSEL) + { + Method originMethod = class_getInstanceMethod(class, originalSEL); + Method replaceMethod = class_getInstanceMethod(class, replacementSEL); + + if(class_addMethod(class, originalSEL, method_getImplementation(replaceMethod),method_getTypeEncoding(replaceMethod))) + { + class_replaceMethod(class,replacementSEL, method_getImplementation(originMethod), method_getTypeEncoding(originMethod)); + }else { + method_exchangeImplementations(originMethod, replaceMethod); + } + } + ``` + +- 建立一个继承自 NSProxy 抽象类的类,实现相应方法。 + + ```objective-c + #import + + NS_ASSUME_NONNULL_BEGIN + + // 为 NSURLConnection、NSURLSession、CFNetwork 代理设置代理转发 + @interface NetworkDelegateProxy : NSProxy + + + (instancetype)setProxyForObject:(id)originalTarget withNewDelegate:(id)newDelegate; + + @end + + NS_ASSUME_NONNULL_END + + // .m + @interface NetworkDelegateProxy () { + id _originalTarget; + id _NewDelegate; + } + + @end + + + @implementation NetworkDelegateProxy + + #pragma mark - life cycle + + + (instancetype)sharedInstance { + static NetworkDelegateProxy *_sharedInstance = nil; + + static dispatch_once_t onceToken; + + dispatch_once(&onceToken, ^{ + _sharedInstance = [NetworkDelegateProxy alloc]; + }); + + return _sharedInstance; + } + + + #pragma mark - public Method + + + (instancetype)setProxyForObject:(id)originalTarget withNewDelegate:(id)newDelegate + { + NetworkDelegateProxy *instance = [NetworkDelegateProxy sharedInstance]; + instance->_originalTarget = originalTarget; + instance->_NewDelegate = newDelegate; + return instance; + } + + - (void)forwardInvocation:(NSInvocation *)invocation + { + if ([_originalTarget respondsToSelector:invocation.selector]) { + [invocation invokeWithTarget:_originalTarget]; + [((NSURLSessionAndConnectionImplementor *)_NewDelegate) invoke:invocation]; + } + } + + - (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel + { + return [_originalTarget methodSignatureForSelector:sel]; + } + + @end + ``` + +- 创建一个对象,实现 NSURLConnection、NSURLSession、NSIuputStream 代理方法 + + ```objective-c + // NetworkImplementor.m + + #pragma mark-NSURLConnectionDelegate + - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { + NSLog(@"%s", __func__); + } + + - (nullable NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(nullable NSURLResponse *)response { + NSLog(@"%s", __func__); + return request; + } + + #pragma mark-NSURLConnectionDataDelegate + - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { + NSLog(@"%s", __func__); + } + + - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { + NSLog(@"%s", __func__); + } + + - (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten + totalBytesWritten:(NSInteger)totalBytesWritten + totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite { + NSLog(@"%s", __func__); + } + + - (void)connectionDidFinishLoading:(NSURLConnection *)connection { + NSLog(@"%s", __func__); + } + + #pragma mark-NSURLConnectionDownloadDelegate + - (void)connection:(NSURLConnection *)connection didWriteData:(long long)bytesWritten totalBytesWritten:(long long)totalBytesWritten expectedTotalBytes:(long long) expectedTotalBytes { + NSLog(@"%s", __func__); + } + + - (void)connectionDidResumeDownloading:(NSURLConnection *)connection totalBytesWritten:(long long)totalBytesWritten expectedTotalBytes:(long long) expectedTotalBytes { + NSLog(@"%s", __func__); + } + + - (void)connectionDidFinishDownloading:(NSURLConnection *)connection destinationURL:(NSURL *) destinationURL { + NSLog(@"%s", __func__); + } + // 根据需求自己去写需要监控的数据项 + ``` + +- 给 NSURLConnection 添加 Category,专门设置 hook 代理对象、hook NSURLConnection 对象方法 + + ```objective-c + // NSURLConnection+Monitor.m + @implementation NSURLConnection (Monitor) + + + (void)load + { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + @autoreleasepool { + [[self class] apm_swizzleMethod:@selector(apm_initWithRequest:delegate:) swizzledSelector:@selector(initWithRequest: delegate:)]; + } + }); + } + + - (_Nonnull instancetype)apm_initWithRequest:(NSURLRequest *)request delegate:(nullable id)delegate + { + /* + 1. 在设置 Delegate 的时候替换 delegate。 + 2. 因为要在每个代理方法里面,监控数据,所以需要将代理方法都 hook 下 + 3. 在原代理方法执行的时候,让新的代理对象里面,去执行方法的转发, + */ + NSString *traceId = @"traceId"; + NSMutableURLRequest *rq = [request mutableCopy]; + NSString *preTraceId = [request.allHTTPHeaderFields valueForKey:@"head_key_traceid"]; + if (preTraceId) { + // 调用 hook 之前的初始化方法,返回 NSURLConnection + return [self apm_initWithRequest:rq delegate:delegate]; + } else { + [rq setValue:traceId forHTTPHeaderField:@"head_key_traceid"]; + + NSURLSessionAndConnectionImplementor *mockDelegate = [NSURLSessionAndConnectionImplementor new]; + [self registerDelegateMethod:@"connection:didFailWithError:" originalDelegate:delegate newDelegate:mockDelegate flag:"v@:@@"]; + + [self registerDelegateMethod:@"connection:didReceiveResponse:" originalDelegate:delegate newDelegate:mockDelegate flag:"v@:@@"]; + [self registerDelegateMethod:@"connection:didReceiveData:" originalDelegate:delegate newDelegate:mockDelegate flag:"v@:@@"]; + [self registerDelegateMethod:@"connection:didFailWithError:" originalDelegate:delegate newDelegate:mockDelegate flag:"v@:@@"]; + + [self registerDelegateMethod:@"connectionDidFinishLoading:" originalDelegate:delegate newDelegate:mockDelegate flag:"v@:@"]; + [self registerDelegateMethod:@"connection:willSendRequest:redirectResponse:" originalDelegate:delegate newDelegate:mockDelegate flag:"@@:@@"]; + delegate = [NetworkDelegateProxy setProxyForObject:delegate withNewDelegate:mockDelegate]; + + // 调用 hook 之前的初始化方法,返回 NSURLConnection + return [self apm_initWithRequest:rq delegate:delegate]; + } + } + + - (void)registerDelegateMethod:(NSString *)methodName originalDelegate:(id)originalDelegate newDelegate:(NSURLSessionAndConnectionImplementor *)newDelegate flag:(const char *)flag + { + if ([originalDelegate respondsToSelector:NSSelectorFromString(methodName)]) { + IMP originalMethodImp = class_getMethodImplementation([originalDelegate class], NSSelectorFromString(methodName)); + IMP newMethodImp = class_getMethodImplementation([newDelegate class], NSSelectorFromString(methodName)); + if (originalMethodImp != newMethodImp) { + [newDelegate registerSelector: methodName]; + NSLog(@""); + } + } else { + class_addMethod([originalDelegate class], NSSelectorFromString(methodName), class_getMethodImplementation([newDelegate class], NSSelectorFromString(methodName)), flag); + } + } + + @end + ``` + + + +这样下来就是可以监控到网络信息了,然后将数据交给数据上报 SDK,按照下发的数据上报策略去上报数据。 + +顺道说一句,上面针对 NSURLConnection、NSURLSession、NSInputStream 代理对象的 hook 之后,利用 NSProxy 实现代理对象方法的转发,有另一种方法可以实现,那就是 **isa swizzling**。 + +- Method swizzling 原理 + + ```objective-c + struct old_method { + SEL method_name; + char *method_types; + IMP method_imp; + }; + ``` + + ![method swizzling](./../assets/2020-04-09-methodSwizzling.png) + + method swizzling 改进版如下 + + ```objective-c + Method originalMethod = class_getInstanceMethod(aClass, aSEL); + IMP originalIMP = method_getImplementation(originalMethod); + char *cd = method_getTypeEncoding(originalMethod); + IMP newIMP = imp_implementationWithBlock(^(id self) { + void (*tmp)(id self, SEL _cmd) = originalIMP; + tmp(self, aSEL); + }); + class_replaceMethod(aClass, aSEL, newIMP, cd); + ``` + +- isa swizzling + + ```objective-c + /// Represents an instance of a class. + struct objc_object { + Class _Nonnull isa OBJC_ISA_AVAILABILITY; + }; + + /// A pointer to an instance of a class. + typedef struct objc_object *id; + + ``` + + ![method swizzling](./../assets/2020-04-09-methodSwizzling.png) @@ -2556,7 +2841,7 @@ Method Swizzling 方法替换需要知道类名,我们在开发阶段使用 NS -#### 2.4 监控 App 常见网络请求 +#### 2.4 监控 App 常见网络请求 本着成本的原因,由于现在大多数的项目的网络能力都是通过 [AFNetworking](https://github.com/AFNetworking/AFNetworking) 完成的,所以本文的网络监控可以快速完成。 diff --git a/Chapter1 - iOS/1.83.md b/Chapter1 - iOS/1.83.md index 0e6bed0..f8162d9 100644 --- a/Chapter1 - iOS/1.83.md +++ b/Chapter1 - iOS/1.83.md @@ -407,10 +407,10 @@ if ([cls respondsToSelector:sel]) { 6. 网络请求完成后,通过 NetworkProtocolClient 将请求结果返回给 WKWebView。 +### 5. 拦截 WebView 内 Ajax 请求 - - - +其实上述的方法也是可行,不过使用私有 API 的方式不是很推荐,一般在穷途末路的时候才选择私有 API,所以另一种思路是 hook Web 端的 ajax 请求。在执行 hook 后的 ajax 请求的时候将 ajax 的请求相关信息(请求方式、header、body 等)以 messageHandler 的方式告诉 Native,然后起到监控的效果。 +参考: https://www.jianshu.com/p/7337ac624b8e;https://github.com/wendux/Ajax-hook diff --git a/Chapter3 - Server/3.9.md b/Chapter3 - Server/3.9.md new file mode 100644 index 0000000..2854542 --- /dev/null +++ b/Chapter3 - Server/3.9.md @@ -0,0 +1,106 @@ +# 爬取疫情数据并用 Markdown 预览 + +> 周五不困,无聊写了一个 Python 脚本,功能很简单:获取新浪关于各个国家疫情数据,并写入 md 文件并预览,定时去获取数据,有新数据则生成新的 markdown 内容拼接在文件最后。 + + + + +## 一、 代码 + +由于功能和代码都很简单,直接上代码 + +```Python +# -*-coding:utf8-*- +import re,requests,json,pprint,time +import os + +pattern=re.compile(r'^try{sinajp_15844213244528328543098388435\((.*?)\);}catch\(e\){};') +lasttimes='00:00:00' + +while True: + res=requests.get('https://gwpre.sina.cn/ncp/foreign?_=1584421324452&callback=sinajp_15844213244528328543098388435') + match=pattern.search(res.text) + + if match: + obj=json.loads(match.group(1)) + resultObj=obj['result'] + times=resultObj['times'] # 截止时间 + timesMatch=re.search(r'截至(\d{2})月(\d{2})日(\d{2})时(\d{2})分',times) + if timesMatch: + times=timesMatch.group(1)+'月'+timesMatch.group(2)+'日 '+timesMatch.group(3)+':'+timesMatch.group(4) + + if times==lasttimes: + continue + else: + lasttimes=times + totalObj=resultObj['total'] + certain=totalObj['certain'] # 累计确诊 + die=totalObj['die'] # 死亡 + recure=totalObj['recure'] # 治愈 + certain_inc=totalObj['certain_inc'] # 确诊增加 + die_inc=totalObj['die_inc'] # 死亡增加 + recure_inc=totalObj['recure_inc'] # 治愈增加 + # 各国数据列表 + worldlistArr=resultObj['worldlist'] + worldlistArr.sort(key=lambda x: int(x.get('conNum','0')),reverse=True) + + fo=open('./coronavirus.md','a') + fo.writelines('\n# '+times+'\n') + fo.writelines('感染国家总数:'+str(len(worldlistArr))+'\n') + fo.writelines('```\n累计确诊:'+certain.rjust(10,' ')+' 较昨日:'+certain_inc+'\n'+'累计死亡:'+die.rjust(10,' ')+' 较昨日:'+die_inc+'\n'+'累计治愈:'+recure.rjust(10,' ')+' 较昨日:'+recure_inc+'\n```\n') + + fo.writelines('|国家|新增确诊|累计确诊|新增死亡|累计死亡|累计治愈|'+'\n') + fo.writelines('|:--:|---:|---:|---:|---:|---:|'+'\n') + + top15=worldlistArr[:15] + pattient_countrys=['澳大利亚','加拿大','巴西','印度','丹麦','越南','新加坡','俄罗斯','塞尔维亚','巴基斯坦',] + pattient=[c for c in worldlistArr if c['name'] in pattient_countrys] + + for countryObj in top15: + name=countryObj['name'] # 国家 + if name=='中国': + continue + conadd=countryObj['conadd'] # 新增确诊 + conNum=countryObj['conNum'] # 累计确诊 + deathadd=countryObj['deathadd'] # 新增死亡 + deathNum=countryObj['deathNum'] # 累计死亡 + cureNum=countryObj['cureNum'] # 累计治愈 + fo.writelines('|'+name+'|'+conadd+'|'+conNum+'|'+deathadd+'|'+deathNum+'|'+cureNum+'|\n') + + fo.writelines('\n特别关心'+'\n') + fo.writelines('|国家|新增确诊|累计确诊|新增死亡|累计死亡|累计治愈|'+'\n') + fo.writelines('|:--:|---:|---:|---:|---:|---:|'+'\n') + for countryObj in pattient: + name=countryObj['name'] # 国家 + conadd=countryObj['conadd'] # 新增确诊 + conNum=countryObj['conNum'] # 累计确诊 + deathadd=countryObj['deathadd'] # 新增死亡 + deathNum=countryObj['deathNum'] # 累计死亡 + cureNum=countryObj['cureNum'] # 累计治愈 + fo.writelines('|'+name+'|'+conadd+'|'+conNum+'|'+deathadd+'|'+deathNum+'|'+cureNum+'|\n') + fo.close() + + # 用 Markdown IDE 打开 .md 文件进行预览 + os.system('open -a "/Applications/Typora.app" ./coronavirus.md') + + for i in range(1,61): + time.sleep(10) + print(i*10)2 +``` + + + + +## 二、 如何使用 + +1. 安装 requests +```shell +pip3 install requests +``` + +2. 修改 Markdown 的打开方式。由于我电脑安装 Marodown 编辑器是 `Typora`,所以脚本是 `open -a "/Applications/Typora.app" ./coronavirus.md`。修改这里的 `***.app` 为自己的 ide + +3. 终端运行即可 +```shell +python3 coronavirus.py +``` diff --git a/Chapter3 - Server/chapter3.md b/Chapter3 - Server/chapter3.md index c8d1ef5..34368a6 100644 --- a/Chapter3 - Server/chapter3.md +++ b/Chapter3 - Server/chapter3.md @@ -11,3 +11,4 @@ * [6、YAML](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter3%20-%20Server/3.6.md) * [7、Node单元测试](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter3%20-%20Server/3.7.md) * [8、数据安全(反爬虫)之「防重放」策略](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter3%20-%20Server/3.8.md) +* [9、爬取疫情数据并用 Markdown 预览](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter3%20-%20Server/3.9.md) \ No newline at end of file diff --git a/SUMMARY.md b/SUMMARY.md index 7c80e6b..f4435a4 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -138,6 +138,7 @@ * [6、YAML](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter3%20-%20Server/3.6.md) * [7、Node单元测试](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter3%20-%20Server/3.7.md) * [8、数据安全(反爬虫)之「防重放」策略](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter3%20-%20Server/3.8.md) + * [9、爬取疫情数据并用 Markdown 预览](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter3%20-%20Server/3.9.md) diff --git a/assets/2020-04-09-methodSwizzling.png b/assets/2020-04-09-methodSwizzling.png new file mode 100644 index 0000000000000000000000000000000000000000..4d3f3039aa7a06203c8db453befe2c8ab198c147 GIT binary patch literal 30861 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 literal 0 HcmV?d00001