JSPatch的应用

在项目开发过程中, 为了实现热修复, 项目中集成了JSPatch框架.

为了更好的集成, 对JSPatch相关的操作完全封装到了一个类里面. 思路如下:

1. 首先调用一个类方法, 作为检测是否需要更新的入口.
2. 从服务端请求数据, 请求服务端的补丁文件和对应的key(md5加密字符串, 目的: 安全传输/检测文件内容是否有变化).
3. 请求结果存储到本地数据库. 比对请求到的key, 和本地存储的key对比.
    1> 本地key和服务端请求到的key比对成功: 读取本地文件, 并执行js文件.
    2> 比对失败(文件内容发生变化/本地不存在): 重新下载文件到本地, 并执行下载的js文件.
    3> 本地存在的垃圾文件, 服务器返回结果没有的文件, 不做任何处理.
    4> 统一清理本地的垃圾文件(以前下载产生的, 当前已经不需要的).

加密思路:

服务端:
    1. 对每个文件进行md5加密, 这样, 如果文件发生了任何变化, key值就会发生变化, 移动端通过key值判断, 文件是否发生改变.
    2. 对1的加密结果, 拼接一个约定的token, 再次加密. 因为, 这些js文件是非常敏感的, 万一下载途中被黑客截取, 黑客就能得到app中所有的方法和属性, 是非常不安全的. 加密后, 就算中途被黑客截取, 黑客也无法更改js文件, 从而, 移动端只执行从自己服务端下载的源文件.

移动端:
    1. 使用服务端完全相同的加密方法, 再次加密, 最后比对加密结果, 如果相同, 则执行对应js文件, 如果不同, 不做任何操作.

封装的 JSPatchHandler.h

//
//  JSPathHandler.h
//  Elite
//
//  Created by  www.6dao.cc on 16/10/8.
//  Copyright © 2016年 ledao. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface JSPatchHandler : NSObject

/// 从服务器请求, 是否有新的补丁, 如果有新的, 则下载新的补丁, 然后打补丁
+ (void)checkPatch;

/// 不从服务器请求, 本地重新装载补丁文件
+ (void)reloadingPatch;

@end

JSPatchHandler.m

//
//  JSPathHandler.m
//  Elite
//
//  Created by  www.6dao.cc on 16/10/8.
//  Copyright © 2016年 ledao. All rights reserved.
//

#import "JSPatchHandler.h"
#import "JSPatchModel.h"
#import "WHFileManager.h"

#import "JPEngine.h"

/**
 处理逻辑:
    1. 先调用一个类方法, 然后从服务器请求, 服务器中所有的补丁列表和对应的key. 
    2. 然后和本地存储的key对比:
        1> 本地有 服务器端没有任何变化的文件, 不做任何处理.
        2> 对比,本地文件和服务器文件发生了改变, 重新下载, 重新加载. 修改前和修改后, 相同的方法名, 会覆盖吗? 预测可以覆盖. 待检验.
        3> 服务端新增文件, 重新下载, 重新加载.
 */

static NSString *PatchDir = @"patch";
const static NSString *secrityCode = @"0QEGR9123590u1";

@interface JSPatchHandler ()

@property (nonatomic, strong) NSOperationQueue *queue;

@property (nonatomic, strong) NSArray *executedArray;

@end

@implementation JSPatchHandler

static NSArray *dataArray;

+ (instancetype)sharePatchHandler {
    
    static JSPatchHandler *patchHandler;
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        patchHandler = [[JSPatchHandler alloc] init];
        patchHandler.queue = [[NSOperationQueue alloc] init];
        
        // 开启JPEngine
        [JPEngine startEngine];
    });
    
    return patchHandler;
}


/// 从服务器请求, 是否有新的补丁
/*
 1. 从服务器获取补丁列表, 保存到本地数据库.
 2. 检查列表中每一条信息, 如果本地存在, 则直接读取执行, 如果本地不存在或者有变化, 从网络去读取.
 3. 网络数据, 下载后, 一方面, 保存到指定目录, 另一方面, 执行js文件,
 4. 检查, 数据库中没有的文件名, 本地存在的文件名, 依次删除.
 */
+ (void)checkPatch {
    
    NSLog(@"___________________________________\n %@", NSHomeDirectory());
    
    [AFNTool requestWithUrlString:@"app/file/listData" params:nil success:^(NSDictionary *response, BOOL success, NSString *code) {
        if (!success) {
            return ;
        }
        
        NSArray *patchArray = [AssignToObject customModel:@"JSPatchModel" fromArray:response[@"data"]];
        
        // 请求结果 保存数据库中
        [JSPatchModel delDataBaseTable];
        [patchArray insertRecordFromArray];
        
        // 检测并执行js代码
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [[JSPatchHandler sharePatchHandler] checkAndExcute:patchArray];
        });
        
        // 删除本地存在的垃圾文件
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [[JSPatchHandler sharePatchHandler] clearLocalData];
        });
    }];
    
}





/// 不从服务器请求, 本地重新装载补丁文件
+ (void)reloadingPatch {
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSArray *patchArray = [JSPatchModel getAllRecod];
        [[JSPatchHandler sharePatchHandler] checkAndExcute:patchArray];
    });
}


/// 检查本地是够存在, 如果存在去执行, 不存在去下载
- (void)checkAndExcute:(NSArray *)array {
    
    
    for (JSPatchModel *patchModel in array) {
        
        if ([self verificationData:patchModel]) {
            [self loadPatchFile:patchModel];
        }else{
            [self downloadPatchFile:patchModel];
        }
    }
    
}



/// 下载js文件, 下载完成, 调用 loadingPatchFileWithModel: 加载js文件.
- (void)downloadPatchFile:(JSPatchModel *)patchModel {
    
    NSString *filePath = [[WHFileManager cacheDirWithSubpath:PatchDir] stringByAppendingPathComponent:patchModel.name];
    [WHFileManager deleteFile:filePath];
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    NSURL *url = [NSURL URLWithString:patchModel.downPath];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLSessionDownloadTask *task =
    [manager downloadTaskWithRequest:request
                            progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
                                
                                return [NSURL fileURLWithPath:filePath];
                            }
                   completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
                       
                       if (filePath) {
                           [self loadPatchFile:patchModel];
                       }
                   }];
    [task resume];
    
}



/// 根据 data 数据包, 加载jsPatch
/// 如果data度去过, 会被保存在 model 的data属性中, 如果没有读取, 则需要去读取
- (void)loadPatchFile:(JSPatchModel *)patchModel {
    
    if (![self verificationData:patchModel]) {
        return ;
    }
    
    NSString *script = [[NSString alloc] initWithData:patchModel.data encoding:NSUTF8StringEncoding];
    [JPEngine evaluateScript:script];
}



/// 根据 参数model, 判断参数中数据是否经过自己服务器加密, 如果是, 则可以执行jsdiamante, 如果不是, 不能执行
- (BOOL)verificationData:(JSPatchModel *)patchModel {
    
    NSString *filePath = [[WHFileManager cacheDirWithSubpath:PatchDir] stringByAppendingPathComponent:patchModel.name];
    if (![WHFileManager fileExistsAtPath:filePath]) {
        return NO;
    }
    
    if (!patchModel.data) {
        
        patchModel.data = [WHFileManager readFile:filePath];
    }
    
    NSString *fileString = [[NSString alloc] initWithData:patchModel.data encoding:NSUTF8StringEncoding];
    NSString *secrityKey = [[NSString stringWithFormat:@"%@%@", [fileString md5String], secrityCode] md5String];
    
    return [secrityKey isEqualToString:patchModel.fileKey];
}



#pragma mark - 有空  给FMDBHelper, 加上一个队列操作数据库, 保证操作的同步执行 FMDBDatabaseQueue
// 检查并删除, 本地存在但是数据库中没有记录的 记录
- (void)clearLocalData {
    
    NSString *filePath = [WHFileManager cacheDirWithSubpath:PatchDir];
    
    // 获取数据库中记录
    NSArray *records = [JSPatchModel getAllRecod];
    NSArray *recordFileNames = [records valueForKeyPath:@"name"];
    NSString *targetString = [recordFileNames componentsJoinedByString:@", "];
    
    NSArray *localFiles = [WHFileManager contentsOfDirectoryAtPath:filePath];
    
    for (NSString *fileName in localFiles) {
        if (![targetString containsString:fileName] && ![fileName hasPrefix:@"."]) {
            [WHFileManager deleteFile:[filePath stringByAppendingPathComponent:fileName]];
        }
    }
}

@end

WHFileManager 是自己对文件操作的相关封装, 文件操作还是比较简单的, 只是不经常用到, 经常忘掉 ?, 封装一下, 以后拿来就用.

JSPatchModel是一个数据Model类, 没什么好说的.

相关文件下载地址: https://github.com/hell03W/Files/tree/master/Files/JSPatchHandler

欢迎关注github.

如果您有更好的解决方案, 或者发现任何问题, 请联系: weidf@163.com

转载于:https://my.oschina.net/whforever/blog/759628

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值