// // RQHTTPService.m // RQCommon // // Created by 张嵘 on 2018/11/16. // Copyright © 2018 张嵘. All rights reserved. // #import "RQHTTPService.h" #import #import #import /** * 知识点 //- (RACSignal *)replayLast 就是用Capacity为1的RACReplaySubject来替换- (RACSignal *)replay的`subject。 其作用是使后来订阅者只收到最后的历史值。 //- (RACSignal *)replayLazily和- (RACSignal *)replay的区别就是replayLazily会在第一次订阅的时候才订阅sourceSignal。 // replay、replayLast、replayLazily的区别 ReactiveCocoa提供了这三个简便的方法允许多个订阅者订阅一个信号,却不会重复执行订阅代码,并且能给新加的订阅者提供订阅前的值。 replay和replayLast使信号变成热信号,且会提供所有值(-replay) 或者最新的值(-replayLast) 给订阅者。 replayLazily 会提供所有的值给订阅者 replayLazily还是冷信号 避免了冷信号的副作用 * */ /// The Http request error domain NSString *const RQHTTPServiceErrorDomain = @"RQHTTPServiceErrorDomain"; /// 请求成功,但statusCode != 0 NSString *const RQHTTPServiceErrorResponseCodeKey = @"RQHTTPServiceErrorResponseCodeKey"; NSString * const RQHTTPServiceErrorRequestURLKey = @"RQHTTPServiceErrorRequestURLKey"; NSString * const RQHTTPServiceErrorHTTPStatusCodeKey = @"RQHTTPServiceErrorHTTPStatusCodeKey"; NSString * const RQHTTPServiceErrorDescriptionKey = @"RQHTTPServiceErrorDescriptionKey"; NSString * const RQHTTPServiceErrorMessagesKey = @"RQHTTPServiceErrorMessagesKey"; NSString * const RQNetworkingReachabilityDidChangeNotification = @"RQNetworkingReachabilityDidChangeNotification"; /// 连接服务器失败 default NSInteger const RQHTTPServiceErrorConnectionFailed = 668; NSInteger const RQHTTPServiceErrorJSONParsingFailed = 669; NSInteger const RQHTTPServiceErrorBadRequest = 670; NSInteger const RQHTTPServiceErrorRequestForbidden = 671; /// 服务器请求失败 NSInteger const RQHTTPServiceErrorServiceRequestFailed = 672; NSInteger const RQHTTPServiceErrorSecureConnectionFailed = 673; /// 登录相关 NSString * const RQWeChatOnRespNotification = @"RQWeChatOnRespNotification"; @interface RQHTTPService () @property (nonatomic, assign, readwrite) AFNetworkReachabilityStatus networkReachabilityStatus; @end @implementation RQHTTPService static id service_ = nil; #pragma mark - HTTPService +(instancetype) sharedInstance { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ service_ = [[self alloc] initWithBaseURL:[NSURL URLWithString:[RQConfigureManager requestBaseUrl]] sessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; }); return service_; } + (id)allocWithZone:(struct _NSZone *)zone{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ service_ = [super allocWithZone:zone]; }); return service_; } - (id)copyWithZone:(NSZone *)zone { return service_; } - (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(nullable NSURLSessionConfiguration *)configuration{ if (self = [super initWithBaseURL:url sessionConfiguration:configuration]) { /// 配置 [self _configHTTPService]; } return self; } /// config service - (void)_configHTTPService{ AFJSONResponseSerializer *responseSerializer = [AFJSONResponseSerializer serializer]; #if DEBUG responseSerializer.removesKeysWithNullValues = NO; #else responseSerializer.removesKeysWithNullValues = YES; #endif responseSerializer.readingOptions = NSJSONReadingAllowFragments; /// config self.responseSerializer = responseSerializer; self.requestSerializer = [AFJSONRequestSerializer serializer]; /// 安全策略 AFSecurityPolicy *securityPolicy = [AFSecurityPolicy defaultPolicy]; //allowInvalidCertificates 是否允许无效证书(也就是自建的证书),默认为NO //如果是需要验证自建证书,需要设置为YES securityPolicy.allowInvalidCertificates = YES; //validatesDomainName 是否需要验证域名,默认为YES; //假如证书的域名与你请求的域名不一致,需把该项设置为NO //主要用于这种情况:客户端请求的是子域名,而证书上的是另外一个域名。因为SSL证书上的域名是独立的,假如证书上注册的域名是www.google.com,那么mail.google.com是无法验证通过的;当然,有钱可以注册通配符的域名*.google.com,但这个还是比较贵的。 securityPolicy.validatesDomainName = NO; self.securityPolicy = securityPolicy; /// 支持解析 self.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", @"text/html", @"text/plain", @"text/html; charset=UTF-8", nil]; /// 开启网络监测 [[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES]; [self.reachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) { switch (status) { case AFNetworkReachabilityStatusUnknown: // 未知网络 NSLog(@"当前网络:未知网络"); break; case AFNetworkReachabilityStatusNotReachable: // 无网络 NSLog(@"当前网络:无网络"); break; case AFNetworkReachabilityStatusReachableViaWWAN: // 蜂窝数据 NSLog(@"当前网络:蜂窝数据"); break; case AFNetworkReachabilityStatusReachableViaWiFi: // 无线网络 NSLog(@"当前网络:无线网络"); break; default: break; } if (_networkReachabilityStatus != status) { _networkReachabilityStatus = status; // 网络改变通知 [[NSNotificationCenter defaultCenter] postNotificationName:RQNetworkingReachabilityDidChangeNotification object:[NSNumber numberWithInteger:status]]; } }]; [self.reachabilityManager startMonitoring]; } #pragma mark - Request - (RACSignal *)enqueueRequest:(RQHTTPRequest *) request resultClass:(Class /*subclass of RQObject*/) resultClass{ /// request 必须的有值 if (!request) return [RACSignal error:[NSError errorWithDomain:RQHTTPServiceErrorDomain code:-1 userInfo:nil]]; @weakify(self); // if ([request.urlParameters.method isEqualToString:RQ_HTTTP_METHOD_GET] || [request.urlParameters.path isEqualToString:RQ_POST_Code]) { // self.requestSerializer = [self _requestSerializerWithRequest:request]; // } else if ([request.urlParameters.method isEqualToString:RQ_HTTTP_METHOD_POST] && ![request.urlParameters.path isEqualToString:RQ_POST_Code]) { // self.requestSerializer = [self _requestJSONSerializerWithRequest:request]; // } else if ([request.urlParameters.method isEqualToString:RQ_HTTTP_METHOD_DELETE]) { // NSString *deletePath = [NSString stringWithFormat:@"%@/%@",request.urlParameters.path,request.urlParameters.parameters.allValues.firstObject]; // request.urlParameters.path = deletePath; // request.urlParameters.parameters = @{}; // self.requestSerializer = [self _requestSerializerWithRequest:request]; // } else { // self.requestSerializer = [self _requestJSONSerializerWithRequest:request]; // } // self.requestSerializer.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", nil]; // if (RQ_USER_MANAGER.isLogin) { // [self.requestSerializer setValue:RQ_USER_MANAGER.currentUser.token forHTTPHeaderField:@"Authorization"]; // } if ([request.urlParameters.method isEqualToString:RQ_HTTTP_METHOD_GET] && ([request.urlParameters.path containsString:@"/userInfo/vip/info"])) { NSString *newPath = [NSString stringWithFormat:@"%@/%@",request.urlParameters.path,request.urlParameters.parameters.allValues.firstObject]; request.urlParameters.path = newPath; request.urlParameters.parameters = @{}; self.requestSerializer = [self _requestSerializerWithRequest:request]; } else { /// 覆盖manager请求序列化 self.requestSerializer = [self _requestJSONSerializerWithRequest:request]; /// @RQ-MARK:请求参数 request.urlParameters.path = [NSString stringWithFormat:@"%@?%@",request.urlParameters.path, [self _signWithParameters:request.urlParameters.parameters]]; } // request.urlParameters.path = [NSString stringWithFormat:@"%@",request.urlParameters.path]; /// 发起请求 /// concat:按一定顺序拼接信号,当多个信号发出的时候,有顺序的接收信号。 这里传进去的参数,不是parameters而是之前通过 /// urlParametersWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters;穿进去的参数 return [[[self enqueueRequestWithPath:request.urlParameters.path parameters:request.urlParameters.parameters method:request.urlParameters.method] reduceEach:^RACStream *(NSURLResponse *response, NSDictionary * responseObject){ @strongify(self); /// 请求成功 这里解析数据 return [[self parsedResponseOfClass:resultClass fromJSON:responseObject] map:^(id parsedResult) { RQHTTPResponse *parsedResponse = [[RQHTTPResponse alloc] initWithResponseObject:responseObject parsedResult:parsedResult]; NSAssert(parsedResponse != nil, @"Could not create RQHTTPResponse with response %@ and parsedResult %@", response, parsedResult); return parsedResponse; }]; }] concat]; } /// 请求数据 - (RACSignal *)enqueueRequestWithPath:(NSString *)path parameters:(id)parameters method:(NSString *)method{ @weakify(self); /// 创建信号 RACSignal *signal = [RACSignal createSignal:^(id subscriber) { @strongify(self); /// 获取request NSError *serializationError = nil; NSMutableURLRequest *request; // NSDictionary *dic = parameters; // YYCache *cache = [[YYCache alloc] initWithName:@"getTeachingVideoByTypeId"]; // NSString *cacheResponseKeyName = [NSString stringWithFormat:@"Response-getTeachingVideoByTypeId=%@",dic[@"videoTypeId"]]; // NSString *cacheResponseObjectKeyName = [NSString stringWithFormat:@"ResponseObject-getTeachingVideoByTypeId=%@",dic[@"videoTypeId"]]; // if ([path containsString:@"getTeachingVideoByTypeId"]) { // if ([cache objectForKey:cacheResponseObjectKeyName] && [cache objectForKey:cacheResponseKeyName]) { // /// 打包成元祖 回调数据 // NSURLResponse *response = (NSURLResponse *)[cache objectForKey:cacheResponseKeyName]; // NSDictionary *responseObject = (NSDictionary *)[cache objectForKey:cacheResponseObjectKeyName]; // dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{ // [subscriber sendNext:RACTuplePack(response , responseObject)]; // [subscriber sendCompleted]; // }); // return [RACDisposable disposableWithBlock:^{ // // }]; // } // } if ([RQ_CACHE_MANAGER isNeedCacheWithPath:path]) { NSDictionary *dic = [RQ_CACHE_MANAGER getCacheWithPath:path parameters:parameters]; if (!RQObjectIsNil(dic)) { NSURLResponse *response = (NSURLResponse *)dic[@"response"]; NSDictionary *responseObject = (NSDictionary *)dic[@"responseObject"]; dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{ [subscriber sendNext:RACTuplePack(response , responseObject)]; [subscriber sendCompleted]; }); return [RACDisposable disposableWithBlock:^{ }]; } } if ([path containsString:@"zzjs.zzxcx.net"]) { request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:path relativeToURL:[NSURL URLWithString:@""]] absoluteString] parameters:parameters error:&serializationError]; } else if ([path containsString:@"jsjp-admin.zzxcx.net"] || [path containsString:@"jsjp-admin1.zzxcx.net"] || [path containsString:@"192.168.8.63:8080"]) { request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:path relativeToURL:[NSURL URLWithString:@""]] absoluteString] parameters:parameters error:&serializationError]; } else { if ([path isEqualToString:RQ_POST_AddCollections]) { NSDictionary *dic = parameters; request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:path relativeToURL:self.baseURL] absoluteString] parameters:dic[@"list"] error:&serializationError]; } else { request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:path relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError]; } } if (serializationError) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{ [subscriber sendError:serializationError]; }); #pragma clang diagnostic pop return [RACDisposable disposableWithBlock:^{ }]; } /// 获取请求任务 __block NSURLSessionDataTask *task = nil; task = [self dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:^(NSURLResponse *response, NSDictionary * responseObject, NSError *error) { if (error) { NSError *parseError = [self _errorFromRequestWithTask:task httpResponse:(NSHTTPURLResponse *)response responseObject:responseObject error:error]; [self HTTPRequestLog:task body:parameters error:parseError]; [subscriber sendError:parseError]; } else { /// 断言 NSAssert([responseObject isKindOfClass:NSDictionary.class], @"responseObject is not an NSDictionary: %@", responseObject); /// 在这里判断数据是否正确 /// 判断 NSInteger statusCode = [responseObject[RQHTTPServiceResponseCodeKey] integerValue]; if (statusCode == RQHTTPResponseCodeSuccess || statusCode == RQHTTPResponseCodeJSJPSuccess) { // NSHTTPURLResponse *httpUrlResponse = (NSHTTPURLResponse *)response; // /// 存储token // NSString *token = [[[httpUrlResponse allHeaderFields] valueForKey:RQHTTPRequestTokenKey] rq_stringValueExtension]; // [self saveToken:token]; [self HTTPRequestLog:task body:parameters error:nil]; /// 打包成元祖 回调数据 [subscriber sendNext:RACTuplePack(response , responseObject)]; // if ([path containsString:@"getTeachingVideoByTypeId"]) { // [cache setObject:response forKey:cacheResponseKeyName]; // [cache setObject:responseObject forKey:cacheResponseObjectKeyName]; // } if ([RQ_CACHE_MANAGER isNeedCacheWithPath:path]) { [RQ_CACHE_MANAGER saveCacheWithPath:path parameters:parameters response:response responseObject:responseObject]; } [subscriber sendCompleted]; }else{ if (([path isEqualToString:RQ_GET_Userinfo] || [path isEqualToString:RQ_GET_Refresh_token]) && statusCode == 0) { /// 打包成元祖 回调数据 NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithDictionary:responseObject]; [dic setValue:path forKey:@"path"]; [subscriber sendNext:RACTuplePack(response , dic.copy)]; [subscriber sendCompleted]; } if (statusCode == RQHTTPResponseCodeNotLogin) { // /// 删除账号 // [SAMKeychain deleteRawLogin]; // /// 先退出用户 // [RQ_USER_MANAGER logoutUser]; // // /// 延迟一段时间 // dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // /// 这里切换 到账号登录的界面 // [RQNotificationCenter postNotificationName:RQSwitchRootViewControllerNotification object:nil userInfo:@{RQSwitchRootViewControllerUserInfoKey:@(RQSwitchRootViewControllerFromTypeLogout)}]; // RQ_USER_MANAGER.isShouldLogin; // }); RACSignal *signalA = [[[RACSignal createSignal:^RACDisposable *(id subscriber) { /// 删除账号 [SAMKeychain deleteRawLogin]; /// 先退出用户 [RQ_USER_MANAGER logoutUser]; [subscriber sendNext:@1]; [subscriber sendCompleted]; return nil; }] delay:1] deliverOnMainThread]; RACSignal *signalB = [[[RACSignal createSignal:^RACDisposable *(id subscriber) { /// 这里切换 主页面 [RQNotificationCenter postNotificationName:RQSwitchRootViewControllerNotification object:nil userInfo:@{RQSwitchRootViewControllerUserInfoKey:@(RQSwitchRootViewControllerFromTypeLogout)}]; [subscriber sendNext:@2]; return nil; }] delay:1] deliverOnMainThread]; // 把signalA拼接到signalB后,signalA发送完成,signalB才会被激活。 RACSignal *concatSignal = [signalA concat:signalB]; // 以后只需要面对拼接信号开发。 // 订阅拼接的信号,不需要单独订阅signalA,signalB // 内部会自动订阅。 // 注意:第一个信号必须发送完成,第二个信号才会被激活 [concatSignal subscribeNext:^(id x) { NSLog(@"%@",x); if ([x integerValue] == 2) { RQ_USER_MANAGER.isShouldLogin; } }]; // [[[[[RACSignal createSignal:^RACDisposable *(id subscriber) { // // /// 延迟一段时间 // [subscriber sendNext:@1]; // return nil; // }] delay:1] deliverOnMainThread] then:^RACSignal * _Nonnull{ // return [[[RACSignal createSignal:^RACDisposable *(id subscriber) { // /// 这里切换 主页面 // [RQNotificationCenter postNotificationName:RQSwitchRootViewControllerNotification object:nil userInfo:@{RQSwitchRootViewControllerUserInfoKey:@(RQSwitchRootViewControllerFromTypeLogout)}]; // /// 延迟一段时间 // [subscriber sendNext:@2]; // return nil; // }] delay:1] deliverOnMainThread]; // }] subscribeNext:^(id x) { // /// 这里切换 到账号登录的界面 // NSLog(@"%@",x); // RQ_USER_MANAGER.isShouldLogin; // }]; // [self login:^{ // /// 这里需要重新配置序列化 // self.requestSerializer = [self _requestSerializerWithRequest:[RQHTTPRequest requestWithParameters:[RQURLParameters urlParametersWithMethod:method path:path parameters:parameters]]]; // /// 重新发起请求 // [[self enqueueRequestWithPath:path parameters:parameters method:method] subscribe:subscriber]; // } cancel:^{ // NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; // userInfo[RQHTTPServiceErrorResponseCodeKey] = @(statusCode); // NSString *msgTips = responseObject[RQHTTPServiceResponseMsgKey]; //#if defined(DEBUG)||defined(_DEBUG) // msgTips = RQStringIsNotEmpty(msgTips)?[NSString stringWithFormat:@"%@(%zd)",msgTips,statusCode]:[NSString stringWithFormat:@"服务器出错了,请稍后重试(%zd)~",statusCode]; /// 调试模式 //#else // msgTips = RQStringIsNotEmpty(msgTips)?msgTips:@"服务器出错了,请稍后重试~"; /// 发布模式 //#endif // userInfo[RQHTTPServiceErrorMessagesKey] = msgTips; // if (task.currentRequest.URL != nil) userInfo[RQHTTPServiceErrorRequestURLKey] = task.currentRequest.URL.absoluteString; // if (task.error != nil) userInfo[NSUnderlyingErrorKey] = task.error; // NSError *requestError = [NSError errorWithDomain:RQHTTPServiceErrorDomain code:statusCode userInfo:userInfo]; // [self HTTPRequestLog:task body:parameters error:requestError]; // [subscriber sendError:requestError]; // }]; } else { NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; userInfo[RQHTTPServiceErrorResponseCodeKey] = @(statusCode); NSString *msgTips = responseObject[RQHTTPServiceResponseMsgKey]; if ([responseObject[RQHTTPServiceResponseDataKey] isKindOfClass:[NSString class]] && RQStringIsEmpty(msgTips)) { msgTips = responseObject[RQHTTPServiceResponseDataKey]; #if defined(DEBUG)||defined(_DEBUG) msgTips = RQStringIsNotEmpty(msgTips)?[NSString stringWithFormat:@"%@(%zd)",msgTips,statusCode]:[NSString stringWithFormat:@"服务器出错了,请稍后重试(%zd)~",statusCode]; /// 调试模式 #else msgTips = RQStringIsNotEmpty(msgTips)?msgTips:@"服务器出错了,请稍后重试~";/// 发布模式 #endif } else if ([responseObject[RQHTTPServiceResponseBodyKey] isKindOfClass:[NSString class]] && RQStringIsEmpty(msgTips)) { msgTips = responseObject[RQHTTPServiceResponseBodyKey]; #if defined(DEBUG)||defined(_DEBUG) msgTips = RQStringIsNotEmpty(msgTips)?[NSString stringWithFormat:@"%@(%zd)",msgTips,statusCode]:[NSString stringWithFormat:@"服务器出错了,请稍后重试(%zd)~",statusCode]; /// 调试模式 #else msgTips = RQStringIsNotEmpty(msgTips)?msgTips:@"服务器出错了,请稍后重试~";/// 发布模式 #endif } else { #if defined(DEBUG)||defined(_DEBUG) msgTips = RQObjectIsNil(msgTips)? [NSString stringWithFormat:@"服务器出错了,请稍后重试(%zd)~",statusCode] : (RQStringIsNotEmpty(msgTips)?[NSString stringWithFormat:@"%@(%zd)",msgTips,statusCode]:[NSString stringWithFormat:@"服务器出错了,请稍后重试(%zd)~",statusCode]); /// 调试模式 #else msgTips = RQStringIsNotEmpty(msgTips)?msgTips:@"服务器出错了,请稍后重试~";/// 发布模式 #endif } userInfo[RQHTTPServiceErrorMessagesKey] = msgTips; if (task.currentRequest.URL != nil) userInfo[RQHTTPServiceErrorRequestURLKey] = task.currentRequest.URL.absoluteString; if (task.error != nil) userInfo[NSUnderlyingErrorKey] = task.error; NSError *requestError = [NSError errorWithDomain:RQHTTPServiceErrorDomain code:statusCode userInfo:userInfo]; [self HTTPRequestLog:task body:parameters error:requestError]; [subscriber sendError:requestError]; } } } }]; /// 开启请求任务 [task resume]; return [RACDisposable disposableWithBlock:^{ [task cancel]; }]; }]; /// replayLazily:replayLazily会在第一次订阅的时候才订阅sourceSignal /// 会提供所有的值给订阅者 replayLazily还是冷信号 避免了冷信号的副作用 return [[signal replayLazily] setNameWithFormat:@"-enqueueRequestWithPath: %@ parameters: %@ method: %@", path, parameters , method]; } #pragma mark - Upload - (RACSignal *)enqueueUploadRequest:(RQHTTPRequest *)request resultClass:(Class)resultClass fileDatas:(NSArray *)fileDatas name:(NSString *)name mimeType:(NSString *)mimeType{ /// request 必须的有值 if (!request) return [RACSignal error:[NSError errorWithDomain:RQHTTPServiceErrorDomain code:-1 userInfo:nil]]; /// 断言 NSAssert(RQStringIsNotEmpty(name), @"name is empty: %@", name); @weakify(self); /// 覆盖manager 请求序列化 self.requestSerializer = [self _requestSerializerWithRequest:request]; /// 发起请求 /// concat:按一定顺序拼接信号,当多个信号发出的时候,有顺序的接收信号。 这里传进去的参数,不是parameters而是之前通过 /// urlParametersWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters;穿进去的参数 return [[[self enqueueUploadRequestWithPath:request.urlParameters.path parameters:request.urlParameters.parameters constructingBodyWithBlock:^(id formData) { NSInteger count = fileDatas.count; for (int i = 0; i< count; i++) { /// 取出fileData NSData *fileData = fileDatas[i]; /// 断言 NSAssert([fileData isKindOfClass:NSData.class], @"fileData is not an NSData class: %@", fileData); // 在网络开发中,上传文件时,是文件不允许被覆盖,文件重名 // 要解决此问题, // 可以在上传时使用当前的系统事件作为文件名 static NSDateFormatter *formatter = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ formatter = [[NSDateFormatter alloc] init]; formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; }); // 设置时间格式 [formatter setDateFormat:@"yyyyMMddHHmmss"]; NSString *dateString = [formatter stringFromDate:[NSDate date]]; NSString *fileName = [NSString stringWithFormat:@"senba_empty_%@_%d.jpg", dateString , i]; [formData appendPartWithFileData:fileData name:name fileName:fileName mimeType:RQStringIsNotEmpty(mimeType)?mimeType:@"application/octet-stream"]; } }] reduceEach:^RACStream *(NSURLResponse *response, NSDictionary * responseObject){ @strongify(self); /// 请求成功 这里解析数据 return [[self parsedResponseOfClass:resultClass fromJSON:responseObject] map:^(id parsedResult) { RQHTTPResponse *parsedResponse = [[RQHTTPResponse alloc] initWithResponseObject:responseObject parsedResult:parsedResult]; NSAssert(parsedResponse != nil, @"Could not create RQHTTPResponse with response %@ and parsedResult %@", response, parsedResult); return parsedResponse; }]; }] concat];; } - (RACSignal *)enqueueUploadRequestWithPath:(NSString *)path parameters:(id)parameters constructingBodyWithBlock:(void (^)(id formData))block{ @weakify(self); /// 创建信号 RACSignal *signal = [RACSignal createSignal:^(id subscriber) { @strongify(self); /// 获取request NSError *serializationError = nil; NSMutableURLRequest *request = [self.requestSerializer multipartFormRequestWithMethod:@"POST" URLString:[[NSURL URLWithString:path relativeToURL:self.baseURL] absoluteString] parameters:parameters constructingBodyWithBlock:block error:&serializationError]; if (serializationError) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{ [subscriber sendError:serializationError]; }); #pragma clang diagnostic pop return [RACDisposable disposableWithBlock:^{ }]; } __block NSURLSessionDataTask *task = [self uploadTaskWithStreamedRequest:request progress:nil completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) { if (error) { NSError *parseError = [self _errorFromRequestWithTask:task httpResponse:(NSHTTPURLResponse *)response responseObject:responseObject error:error]; [self HTTPRequestLog:task body:parameters error:parseError]; [subscriber sendError:parseError]; } else { /// 断言 NSAssert([responseObject isKindOfClass:NSDictionary.class], @"responseObject is not an NSDictionary: %@", responseObject); /// 在这里判断数据是否正确 /// 判断 NSInteger statusCode = [responseObject[RQHTTPServiceResponseCodeKey] integerValue]; if (statusCode == RQHTTPResponseCodeSuccess || statusCode == RQHTTPResponseCodeJSJPSuccess) { /// 打包成元祖 回调数据 [subscriber sendNext:RACTuplePack(response , responseObject)]; [subscriber sendCompleted]; }else{ if (statusCode == RQHTTPResponseCodeNotLogin) { /// 删除账号 [SAMKeychain deleteRawLogin]; /// 先退出用户 [RQ_USER_MANAGER logoutUser]; /// 延迟一段时间 [[[[RACSignal createSignal:^RACDisposable *(id subscriber) { [subscriber sendNext:@1]; return nil; }] delay:1] deliverOnMainThread] subscribeNext:^(id x) { /// 这里切换 到账号登录的界面 NSLog(@"%@",x); // RQ_USER_MANAGER.isShouldLogin; }]; /// 需要登录 // [self login:^{ // /// 这里需要重新配置序列化 // self.requestSerializer = [self _requestSerializerWithRequest:[RQHTTPRequest requestWithParameters:[RQURLParameters urlParametersWithMethod:@"POST" path:path parameters:parameters]]]; // /// 重新发起请求 // [self enqueueUploadRequestWithPath:path parameters:parameters constructingBodyWithBlock:block]; // } cancel:^{ // NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; // userInfo[RQHTTPServiceErrorResponseCodeKey] = @(statusCode); // NSString *msgTips = responseObject[RQHTTPServiceResponseMsgKey]; //#if defined(DEBUG)||defined(_DEBUG) // msgTips = RQStringIsNotEmpty(msgTips)?[NSString stringWithFormat:@"%@(%zd)",msgTips,statusCode]:[NSString stringWithFormat:@"服务器出错了,请稍后重试(%zd)~",statusCode]; /// 调试模式 //#else // msgTips = RQStringIsNotEmpty(msgTips)?msgTips:@"服务器出错了,请稍后重试~"; /// 发布模式 //#endif // userInfo[RQHTTPServiceErrorMessagesKey] = msgTips; // if (task.currentRequest.URL != nil) userInfo[RQHTTPServiceErrorRequestURLKey] = task.currentRequest.URL.absoluteString; // if (task.error != nil) userInfo[NSUnderlyingErrorKey] = task.error; // [subscriber sendError:[NSError errorWithDomain:RQHTTPServiceErrorDomain code:statusCode userInfo:userInfo]]; // }]; }else{ NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; userInfo[RQHTTPServiceErrorResponseCodeKey] = @(statusCode); NSString *msgTips = responseObject[RQHTTPServiceResponseMsgKey]; #if defined(DEBUG)||defined(_DEBUG) msgTips = RQStringIsNotEmpty(msgTips)?[NSString stringWithFormat:@"%@(%zd)",msgTips,statusCode]:[NSString stringWithFormat:@"服务器出错了,请稍后重试(%zd)~",statusCode]; /// 调试模式 #else msgTips = RQStringIsNotEmpty(msgTips)?msgTips:@"服务器出错了,请稍后重试~"; /// 发布模式 #endif userInfo[RQHTTPServiceErrorMessagesKey] = msgTips; if (task.currentRequest.URL != nil) userInfo[RQHTTPServiceErrorRequestURLKey] = task.currentRequest.URL.absoluteString; if (task.error != nil) userInfo[NSUnderlyingErrorKey] = task.error; [subscriber sendError:[NSError errorWithDomain:RQHTTPServiceErrorDomain code:statusCode userInfo:userInfo]]; } } } }]; [task resume]; return [RACDisposable disposableWithBlock:^{ [task cancel]; }]; }]; /// replayLazily:replayLazily会在第一次订阅的时候才订阅sourceSignal /// 会提供所有的值给订阅者 replayLazily还是冷信号 避免了冷信号的副作用 return [[signal replayLazily] setNameWithFormat:@"-enqueueUploadRequestWithPath: %@ parameters: %@", path, parameters]; } #pragma mark Parsing (数据解析) - (NSError *)parsingErrorWithFailureReason:(NSString *)localizedFailureReason { NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; userInfo[NSLocalizedDescriptionKey] = NSLocalizedString(@"Could not parse the service response.", @""); if (localizedFailureReason != nil) userInfo[NSLocalizedFailureReasonErrorKey] = localizedFailureReason; return [NSError errorWithDomain:RQHTTPServiceErrorDomain code:RQHTTPServiceErrorJSONParsingFailed userInfo:userInfo]; } /// 解析数据 - (RACSignal *)parsedResponseOfClass:(Class)resultClass fromJSON:(NSDictionary *)responseObject { /// 必须是RQBaseModel的子类 且 最外层responseObject必须是字典 NSParameterAssert((resultClass == nil || [resultClass isSubclassOfClass:RQBaseModel.class])); /// 这里主要解析的是 data:对应的数据 if ([responseObject.allKeys containsObject:RQHTTPServiceResponseDataKey] && !RQObjectIsNil(responseObject[RQHTTPServiceResponseDataKey])) { responseObject = responseObject[RQHTTPServiceResponseDataKey]; } else if ([responseObject.allKeys containsObject:RQHTTPServiceResponseBodyKey] && !RQObjectIsNil(responseObject[RQHTTPServiceResponseBodyKey])) { responseObject = responseObject[RQHTTPServiceResponseBodyKey]; } /// 解析 return [RACSignal createSignal:^ id (id subscriber) { /// 解析字典 void (^parseJSONDictionary)(NSDictionary *) = ^(NSDictionary *JSONDictionary) { if (resultClass == nil) { [subscriber sendNext:JSONDictionary]; return; } /// 这里继续取出数据 data{"list":[]} NSArray * JSONArray = JSONDictionary[RQHTTPServiceResponseDataListKey]; if ([JSONArray isKindOfClass:[NSArray class]]) { /// 字典数组 转对应的模型 NSArray *parsedObjects = [NSArray yy_modelArrayWithClass:resultClass.class json:JSONArray]; /// 这里还需要解析是否是RQObject的子类 for (id parsedObject in parsedObjects) { /// 确保解析出来的类 也是 RQObject ((RQBaseModel *)parsedObject).total = [JSONDictionary[RQHTTPServiceResponseDataTotalKey] integerValue]; NSAssert([parsedObject isKindOfClass:RQBaseModel.class], @"Parsed model object is not an RQObject: %@", parsedObject); } [subscriber sendNext:parsedObjects]; }else{ /// 字典转模型 RQBaseModel *parsedObject = [resultClass yy_modelWithDictionary:JSONDictionary]; if (parsedObject == nil) { // Don't treat "no class found" errors as real parsing failures. // In theory, this makes parsing code forward-compatible with // API additions. // 模型解析失败 NSError *error = [NSError errorWithDomain:@"" code:2222 userInfo:@{}]; [subscriber sendError:error]; return; } /// 确保解析出来的类 也是 BaseModel NSAssert([parsedObject isKindOfClass:RQBaseModel.class], @"Parsed model object is not an BaseModel: %@", parsedObject); /// 发送数据 [subscriber sendNext:parsedObject]; } }; if ([responseObject isKindOfClass:NSArray.class]) { if (resultClass == nil) { [subscriber sendNext:responseObject]; }else{ /// 数组 保证数组里面装的是同一种 NSDcitionary for (NSDictionary *JSONDictionary in responseObject) { if (![JSONDictionary isKindOfClass:NSDictionary.class]) { NSString *failureReason = [NSString stringWithFormat:NSLocalizedString(@"Invalid JSON array element: %@", @""), JSONDictionary]; [subscriber sendError:[self parsingErrorWithFailureReason:failureReason]]; return nil; } } /// 字典数组 转对应的模型 NSArray *parsedObjects = [NSArray yy_modelArrayWithClass:resultClass.class json:responseObject]; /// 这里还需要解析是否是RQObject的子类 for (id parsedObject in parsedObjects) { /// 确保解析出来的类 也是 BaseModel NSAssert([parsedObject isKindOfClass:RQBaseModel.class], @"Parsed model object is not an BaseModel: %@", parsedObject); } [subscriber sendNext:parsedObjects]; } [subscriber sendCompleted]; } else if ([responseObject isKindOfClass:NSDictionary.class]) { /// 解析字典 parseJSONDictionary(responseObject); [subscriber sendCompleted]; } else if (responseObject == nil || [responseObject isKindOfClass:[NSNull class]]) { [subscriber sendNext:nil]; [subscriber sendCompleted]; } else if ([responseObject isKindOfClass:[NSString class]]) { /// RQ-MARK: 如果responseObject是NSString类型进入该条件 [subscriber sendNext:responseObject]; [subscriber sendCompleted]; } else if ([responseObject isKindOfClass:[NSNumber class]]) { /// RQ-MARK: 如果responseObject是NSNumber类型进入该条件 [subscriber sendNext:responseObject]; [subscriber sendCompleted]; } else { NSString *failureReason = [NSString stringWithFormat:NSLocalizedString(@"Response wasn't an array or dictionary (%@): %@", @""), [responseObject class], responseObject]; [subscriber sendError:[self parsingErrorWithFailureReason:failureReason]]; } return nil; }]; } #pragma mark - Error Handling /// 请求错误解析 - (NSError *)_errorFromRequestWithTask:(NSURLSessionTask *)task httpResponse:(NSHTTPURLResponse *)httpResponse responseObject:(NSDictionary *)responseObject error:(NSError *)error { /// 不一定有值,则HttpCode = 0; NSInteger HTTPCode = httpResponse.statusCode; NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; /// default errorCode is RQHTTPServiceErrorConnectionFailed,意味着连接不上服务器 NSInteger errorCode = RQHTTPServiceErrorConnectionFailed; NSString *errorDesc = @"服务器出错了,请稍后重试~"; /// 其实这里需要处理后台数据错误,一般包在 responseObject /// HttpCode错误码解析 https://www.guhei.net/post/jb1153 /// 1xx : 请求消息 [100 102] /// 2xx : 请求成功 [200 206] /// 3xx : 请求重定向[300 307] /// 4xx : 请求错误 [400 417] 、[422 426] 、449、451 /// 5xx 、600: 服务器错误 [500 510] 、600 NSInteger httpFirstCode = HTTPCode/100; if (httpFirstCode>0) { if (httpFirstCode==4) { /// 请求出错了,请稍后重试 if (HTTPCode == 408) { #if defined(DEBUG)||defined(_DEBUG) errorDesc = @"请求超时,请稍后再试(408)~"; /// 调试模式 #else errorDesc = @"请求超时,请稍后再试~"; /// 发布模式 #endif }else{ #if defined(DEBUG)||defined(_DEBUG) errorDesc = [NSString stringWithFormat:@"请求出错了,请稍后重试(%zd)~",HTTPCode]; /// 调试模式 #else errorDesc = @"请求出错了,请稍后重试~"; /// 发布模式 #endif } }else if (httpFirstCode == 5 || httpFirstCode == 6){ /// 服务器出错了,请稍后重试 #if defined(DEBUG)||defined(_DEBUG) errorDesc = [NSString stringWithFormat:@"服务器出错了,请稍后重试(%zd)~",HTTPCode]; /// 调试模式 #else errorDesc = @"服务器出错了,请稍后重试~"; /// 发布模式 #endif }else if (!self.reachabilityManager.isReachable){ /// 网络不给力,请检查网络 errorDesc = @"网络开小差了,请稍后重试~"; } }else{ if (!self.reachabilityManager.isReachable){ /// 网络不给力,请检查网络 errorDesc = @"网络开小差了,请稍后重试~"; } } switch (HTTPCode) { case 400:{ errorCode = RQHTTPServiceErrorBadRequest; /// 请求失败 break; } case 403:{ errorCode = RQHTTPServiceErrorRequestForbidden; /// 服务器拒绝请求 break; } case 422:{ errorCode = RQHTTPServiceErrorServiceRequestFailed; /// 请求出错 break; } default: /// 从error中解析 if ([error.domain isEqual:NSURLErrorDomain]) { #if defined(DEBUG)||defined(_DEBUG) errorDesc = [NSString stringWithFormat:@"请求出错了,请稍后重试(%zd)~",error.code]; /// 调试模式 #else errorDesc = @"请求出错了,请稍后重试~"; /// 发布模式 #endif switch (error.code) { case NSURLErrorSecureConnectionFailed: case NSURLErrorServerCertificateHasBadDate: case NSURLErrorServerCertificateHasUnknownRoot: case NSURLErrorServerCertificateUntrusted: case NSURLErrorServerCertificateNotYetValid: case NSURLErrorClientCertificateRejected: case NSURLErrorClientCertificateRequired: errorCode = RQHTTPServiceErrorSecureConnectionFailed; /// 建立安全连接出错了 break; case NSURLErrorTimedOut:{ #if defined(DEBUG)||defined(_DEBUG) errorDesc = @"请求超时,请稍后再试(-1001)~"; /// 调试模式 #else errorDesc = @"请求超时,请稍后再试~"; /// 发布模式 #endif break; } case NSURLErrorNotConnectedToInternet:{ #if defined(DEBUG)||defined(_DEBUG) errorDesc = @"网络开小差了,请稍后重试(-1009)~"; /// 调试模式 #else errorDesc = @"网络开小差了,请稍后重试~"; /// 发布模式 #endif break; } } } } userInfo[RQHTTPServiceErrorHTTPStatusCodeKey] = @(HTTPCode); userInfo[RQHTTPServiceErrorDescriptionKey] = errorDesc; if (task.currentRequest.URL != nil) userInfo[RQHTTPServiceErrorRequestURLKey] = task.currentRequest.URL.absoluteString; if (task.error != nil) userInfo[NSUnderlyingErrorKey] = task.error; return [NSError errorWithDomain:RQHTTPServiceErrorDomain code:errorCode userInfo:userInfo]; } #pragma mark - 打印请求日志 - (void)HTTPRequestLog:(NSURLSessionTask *)task body:params error:(NSError *)error { NSLog(@">>>>>>>>>>>>>>>>>>>>>👇 REQUEST FINISH 👇>>>>>>>>>>>>>>>>>>>>>>>>>>"); NSLog(@"Request%@=======>:%@", error?@"失败":@"成功", task.currentRequest.URL.absoluteString); NSLog(@"requestBody======>:%@", params); NSLog(@"requstHeader=====>:%@", task.currentRequest.allHTTPHeaderFields); NSLog(@"response=========>:%@", task.response); NSLog(@"error============>:%@", error); NSLog(@"<<<<<<<<<<<<<<<<<<<<<👆 REQUEST FINISH 👆<<<<<<<<<<<<<<<<<<<<<<<<<<"); #if defined(DEBUG)||defined(_DEBUG) if([QMUIConsole sharedInstance].canShow){ NSMutableString *log = [NSMutableString stringWithFormat:@">>>>>>>>>>>>>>>>>>>>>👇 REQUEST FINISH 👇>>>>>>>>>>>>>>>>>>>>>>>>>>"]; [log appendFormat:@"Request%@=======>:%@", error?@"失败":@"成功", task.currentRequest.URL.absoluteString]; [log appendFormat:@"requestBody======>:%@", params]; [log appendFormat:@"requstHeader=====>:%@", task.currentRequest.allHTTPHeaderFields]; [log appendFormat:@"response=========>:%@", task.response]; [log appendFormat:@"error============>:%@", error]; [log appendString:@"<<<<<<<<<<<<<<<<<<<<<👆 REQUEST FINISH 👆<<<<<<<<<<<<<<<<<<<<<<<<<<"]; [QMUIConsole log:log]; } #endif } #pragma mark - Parameter 签名 MD5 生成一个 sign ,这里请根据实际项目来定 /// 基础的请求参数 - (NSMutableDictionary *)_parametersWithRequest:(RQHTTPRequest *)request{ NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; /// 模型转字典 NSDictionary *extendsUrlParams = [request.urlParameters.extendsParameters mj_keyValues].copy; if ([extendsUrlParams count]) { [parameters addEntriesFromDictionary:extendsUrlParams]; } if ([request.urlParameters.parameters count]) { [parameters addEntriesFromDictionary:request.urlParameters.parameters]; } return parameters; } /// 带签名的请求参数 - (NSString *)_signWithParameters:(NSDictionary *) parameters { /// 按照ASCII码排序 NSArray *sortedKeys = [[parameters allKeys] sortedArrayUsingSelector:@selector(compare:)]; NSMutableArray *kvs = [NSMutableArray array]; for (id key in sortedKeys) { /// value 为 empty 跳过 if(RQObjectIsNil(parameters[key])) continue; NSString * value = [parameters[key] rq_stringValueExtension]; // if (RQObjectIsNil(value)||!RQStringIsNotEmpty(value)) continue; value = [value rq_removeBothEndsWhitespaceAndNewline]; // value = [value rq_URLEncoding]; [kvs addObject:[NSString stringWithFormat:@"%@=%@",key,value]]; } NSString *paramString = [kvs componentsJoinedByString:@"&"]; /// 拼接私钥 // NSString *keyValue = RQHTTPServiceKeyValue; // NSString *signedString = [NSString stringWithFormat:@"%@&%@=%@",paramString,RQHTTPServiceKey,keyValue]; /// 拼接时间戳 NSString *ts = [NSString stringWithFormat:@"%.f", [NSDate date].timeIntervalSince1970 * 1000]; NSString *sign = [NSString stringWithFormat:@"%@&key=%@",paramString ,ts]; /// md5 sign = [CocoaSecurity md5:sign].hex; NSString *signedStr = [NSString stringWithFormat:@"ts=%@&sign=%@&user=ios&v=jsjp",ts ,sign]; return signedStr; } /// 序列化 - (AFHTTPRequestSerializer *)_requestSerializerWithRequest:(RQHTTPRequest *) request { /// 获取基础参数(参数+拓展参数) NSMutableDictionary *parameters = [self _parametersWithRequest:request]; /// 获取带签名的参数 NSString *sign = [self _signWithParameters:parameters]; /// 赋值 parameters[RQHTTPServiceSignKey] = [sign length]?sign:@""; /// 请求序列化 AFHTTPRequestSerializer *requestSerializer = [AFHTTPRequestSerializer serializer]; /// 配置请求头 for (NSString *key in parameters) { NSString *value = [[parameters[key] rq_stringValueExtension] copy]; if (value.length==0) continue; /// value只能是字符串,否则崩溃 [requestSerializer setValue:value forHTTPHeaderField:key]; } return requestSerializer; } - (AFJSONRequestSerializer *)_requestJSONSerializerWithRequest:(RQHTTPRequest *) request { /// 获取基础参数(参数+拓展参数) NSMutableDictionary *parameters = [self _parametersWithRequest:request]; /// 获取带签名的参数 NSString *sign = [self _signWithParameters:parameters]; /// 赋值 parameters[RQHTTPServiceSignKey] = [sign length]?sign:@""; /// 请求序列化 AFJSONRequestSerializer *requestSerializer = [AFJSONRequestSerializer serializer]; /// 配置请求头 if ([request.urlParameters.path containsString:@"jsjp-admin.zzxcx.net"] || [request.urlParameters.path containsString:@"jsjp-admin1.zzxcx.net"] || [request.urlParameters.path containsString:@"192.168.8.63:8080"]) { } else { for (NSString *key in parameters) { NSString *value = [[parameters[key] rq_stringValueExtension] copy]; if (value.length==0) continue; /// value只能是字符串,否则崩溃 [requestSerializer setValue:value forHTTPHeaderField:key]; } } return requestSerializer; } @end