// // HttpRequestHelper.m // YUMI // // Created by zu on 2021/9/3. // #import "HttpRequestHelper.h" #import "YYUtility.h" #import "AccountInfoStorage.h" #import "YYReachability.h" #import #import "YUMIMacroUitls.h" #import "MSParamsDecode.h" #import "NSData+GZIP.h" #import #import "BuglyManager.h" @implementation HttpRequestHelper +(AFHTTPSessionManager *)requestManager { static AFHTTPSessionManager *manager = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; configuration.HTTPShouldUsePipelining = YES; // 启用 HTTP/2 pipelining configuration.HTTPMaximumConnectionsPerHost = 15; // 提升并发 manager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:[HttpRequestHelper getHostUrl]] sessionConfiguration:configuration]; manager.responseSerializer = [AFJSONResponseSerializer serializer]; manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects: @"application/json", @"text/json", @"text/javascript", @"text/html", @"text/plain", @"image/jpeg", @"image/png", nil]; manager.requestSerializer.HTTPShouldHandleCookies = YES; manager.requestSerializer.timeoutInterval = 60; // 声明开启 gzip 和 br [manager.requestSerializer setValue:@"gzip, br" forHTTPHeaderField:@"Accept-Encoding"]; // 确保所有请求走 HTTPS,避免降级到 HTTP/1.1 manager.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone]; manager.securityPolicy.allowInvalidCertificates = NO; manager.securityPolicy.validatesDomainName = YES; }); return manager; } +(NSString *)getHostUrl{ return API_HOST_URL; #if DEBUG NSString *isProduction = [[NSUserDefaults standardUserDefaults]valueForKey:@"kIsProductionEnvironment"]; if([isProduction isEqualToString:@"YES"]){ return API_HOST_URL; } return API_HOST_TEST_URL; #else return API_HOST_URL; #endif } + (void)GET:(NSString *)method params:(NSDictionary *)params success:(void (^)(BaseModel *data))success failure:(void (^)(NSInteger resCode, NSString *message))failure { if ([self checkNetworkStatusWithFailure:^{ failure(-1, YMLocalizedString(@"HttpRequestHelper0")); }]) { return; } [self configHeaders]; AFHTTPSessionManager *manager = [HttpRequestHelper requestManager]; NSString *key = @"NeedChangeTimeOut"; NSMutableDictionary *editParam = [params mutableCopy]; if ([editParam.allKeys containsObject:key]) { NSInteger timeout = [[editParam objectForKey:key] integerValue]; [editParam removeObjectForKey:key]; if (timeout > 0) { manager.requestSerializer.timeoutInterval = timeout; } } else { manager.requestSerializer.timeoutInterval = 60; } editParam = [MSParamsDecode msDecodeParams:editParam]; params = [self configBaseParmars:editParam]; #if DEBUG // 构建完整的 URL NSString *baseUrl = [HttpRequestHelper getHostUrl]; NSString *fullUrl = [NSString stringWithFormat:@"%@/%@", baseUrl, method]; // 构建查询字符串 NSMutableArray *queryItems = [NSMutableArray array]; [params enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { [queryItems addObject:[NSString stringWithFormat:@"%@=%@", key, obj]]; }]; NSString *queryString = [queryItems componentsJoinedByString:@"&"]; if (queryString.length > 0) { fullUrl = [NSString stringWithFormat:@"%@?%@", fullUrl, queryString]; } NSLog(@"\n🌐 API Request Info:"); NSLog(@"📍 Full URL: %@", fullUrl); NSLog(@"🔧 Method: %@", method); NSLog(@"📦 Parameter Type: queryParameters"); NSLog(@"📋 Parameters: %@\n", params); #endif @kWeakify(self); [manager GET:method parameters:params headers:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { BaseModel *baseModel = [BaseModel modelWithDictionary:responseObject]; #if DEBUG NSLog(@"%@ - \n%@\n", method, [baseModel toJSONString]); #else #endif success(baseModel); } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { @kStrongify(self); [self handleNetError:error method:method failure:failure]; }]; } + (void)POST:(NSString *)method params:(NSDictionary *)params success:(void (^)(BaseModel *data))success failure:(void (^)(NSInteger resCode, NSString *message))failure { if ([AFNetworkReachabilityManager sharedManager].networkReachabilityStatus == 0) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ failure(-1, YMLocalizedString(@"HttpRequestHelper0")); }); return; } // [self configHeaders]; AFHTTPSessionManager *manager = [HttpRequestHelper requestManager]; params = [MSParamsDecode msDecodeParams:[params mutableCopy] ]; params = [self configBaseParmars:params]; #if DEBUG // 构建完整的 URL NSString *baseUrl = [HttpRequestHelper getHostUrl]; NSString *fullUrl = [NSString stringWithFormat:@"%@/%@", baseUrl, method]; NSLog(@"\n🌐 API Request Info:"); NSLog(@"📍 Full URL: %@", fullUrl); NSLog(@"🔧 Method: %@", method); NSLog(@"📦 Parameter Type: bodyParameters"); NSLog(@"📋 Parameters: %@\n", params); #else #endif [manager POST:method parameters:params headers:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { BaseModel *baseModel = [BaseModel modelWithDictionary:responseObject]; #if DEBUG NSLog(@"\n%@", [baseModel toJSONString]); #else #endif success(baseModel); } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { [self handleNetError:error method:method failure:failure]; }]; } + (void)__POST:(NSString *)method params:(NSDictionary *)params success:(void (^)(BaseModel *data))success failure:(void (^)(NSInteger resCode, NSString *message))failure { if ([self checkNetworkStatusWithFailure:^{ failure(-1, YMLocalizedString(@"HttpRequestHelper0")); }]) { return; } [self configHeaders]; AFHTTPSessionManager *manager = [HttpRequestHelper requestManager]; params = [MSParamsDecode msDecodeParams:[params mutableCopy] ]; params = [self configBaseParmars:params]; #if DEBUG NSLog(@"\nmethod:\n%@\nparameter:\n%@\n 超時:%@", method, params, @(manager.session.configuration.timeoutIntervalForRequest)); #else #endif // 将 NSDictionary 转换为 JSON NSData *jsonData = [NSJSONSerialization dataWithJSONObject:params options:0 error:nil]; // Gzip 压缩数据 NSData *gzipData = [jsonData gzippedData]; @kWeakify(self); [manager POST:method parameters:params headers:nil constructingBodyWithBlock:^(id _Nonnull formData) { [formData appendPartWithFormData:gzipData name:@"data"]; } progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { BaseModel *baseModel = [BaseModel modelWithDictionary:responseObject]; #if DEBUG NSLog(@"%@ - \n%@\n", method, [baseModel toJSONString]); #else #endif success(baseModel); } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { #if DEBUG NSLog(@"%@ - \n%@\n", method, error); #else #endif @kStrongify(self); [self handleNetError:error method:method failure:failure]; }]; } + (void)DELETE:(NSString *)method params:(NSDictionary *)params success:(void (^)(BaseModel *data))success failure:(void (^)(NSInteger resCode, NSString *message))failure { if ([self checkNetworkStatusWithFailure:^{ failure(-1, YMLocalizedString(@"HttpRequestHelper0")); }]) { return; } // [self configHeaders]; params = [MSParamsDecode msDecodeParams:[params mutableCopy] ]; params = [self configBaseParmars:params]; AFHTTPSessionManager *manager = [HttpRequestHelper requestManager]; #if DEBUG // 构建完整的 URL NSString *baseUrl = [HttpRequestHelper getHostUrl]; NSString *fullUrl = [NSString stringWithFormat:@"%@/%@", baseUrl, method]; // 构建查询字符串 NSMutableArray *queryItems = [NSMutableArray array]; [params enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { [queryItems addObject:[NSString stringWithFormat:@"%@=%@", key, obj]]; }]; NSString *queryString = [queryItems componentsJoinedByString:@"&"]; if (queryString.length > 0) { fullUrl = [NSString stringWithFormat:@"%@?%@", fullUrl, queryString]; } NSLog(@"\n🌐 API Request Info:"); NSLog(@"📍 Full URL: %@", fullUrl); NSLog(@"🔧 Method: %@", method); NSLog(@"📦 Parameter Type: queryParameters"); NSLog(@"📋 Parameters: %@\n", params); #else #endif @kWeakify(self); [manager DELETE:method parameters:params headers:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { BaseModel *baseModel = [BaseModel modelWithDictionary:responseObject]; #if DEBUG NSLog(@"\n%@\n", [baseModel toJSONString]); #else #endif success(baseModel); } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { @kStrongify(self); [self handleNetError:error method:method failure:failure]; }]; } + (void)request:(NSString *)url method:(HttpRequestHelperMethod)method params:(NSDictionary *)params success:(void (^)(BaseModel *data))success failure:(void (^)(NSInteger resCode, NSString *message))failure { switch (method) { case HttpRequestHelperMethodGET: { [self GET:url params:params success:success failure:failure]; } break; case HttpRequestHelperMethodPOST:{ [self POST:url params:params success:success failure:failure]; } break; case HttpRequestHelperMethodDELETE:{ [self DELETE:url params:params success:success failure:failure]; } break; } } + (void)request:(NSString *)path method:(HttpRequestHelperMethod)method params:(NSDictionary *)params completion:(HttpRequestHelperCompletion)completion { [self request:path method:method params:params success:^(BaseModel *data) { if (completion) { completion(data, data.code, data.message); } } failure:^(NSInteger resCode, NSString *message) { if (completion) { completion(nil, resCode, message); } if (resCode > 500 && resCode < 600) { // 使用 BuglyManager 统一上报网络错误 NSString *uid = [AccountInfoStorage instance].getUid ?: @"未知用户"; NSMutableDictionary *userInfo = [params mutableCopy]; [userInfo setObject:message forKey:@"error message"]; [userInfo setObject:@(method) forKey:@"http method"]; [[BuglyManager sharedManager] reportNetworkError:uid api:path code:resCode userInfo:userInfo]; } }]; } + (void)configHeaders { AFHTTPSessionManager *client = [HttpRequestHelper requestManager]; if ([[AccountInfoStorage instance] getUid].length > 0) { [client.requestSerializer setValue:[[AccountInfoStorage instance] getUid] forHTTPHeaderField:@"pub_uid"]; } else { [client.requestSerializer setValue:nil forHTTPHeaderField:@"pub_uid"]; } if ([[AccountInfoStorage instance] getTicket].length > 0) { [client.requestSerializer setValue:[[AccountInfoStorage instance] getTicket] forHTTPHeaderField:@"pub_ticket"]; }else { [client.requestSerializer setValue:nil forHTTPHeaderField:@"pub_ticket"]; } [client.requestSerializer setValue:[NSBundle uploadLanguageText] forHTTPHeaderField:@"Accept-Language"]; [client.requestSerializer setValue:PI_App_Version forHTTPHeaderField:@"App-Version"]; } + (NSDictionary*)configBaseParmars:(NSDictionary * _Nullable)parmars { NSDictionary *defaultBasciParame = @{ @"Accept-Language":[NSBundle uploadLanguageText], @"os" : @"iOS", @"osVersion" : [YYUtility systemVersion], @"netType" : ([YYUtility networkStatus] == ReachableViaWiFi) ? @2 : @1, @"ispType" : @([YYUtility carrierIdentifier]), @"channel" : [YYUtility getAppSource] ? : @"", @"model" : [YYUtility modelType], @"deviceId" : [YYUtility deviceUniqueIdentification], @"appVersion" : [YYUtility appVersion], @"app" : [YYUtility appName], @"lang" : [YYUtility getLanguage], }; if (!parmars||![parmars isKindOfClass:[NSDictionary class]]){ NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithDictionary:defaultBasciParame]; if(![[YYUtility getMobileCountryCode] isEqualToString:@"65535"]){ [dic setValue:[YYUtility getMobileCountryCode] forKey:@"mcc"]; } return dic; } NSMutableDictionary * dic = [NSMutableDictionary dictionaryWithDictionary:parmars]; if(![[YYUtility getMobileCountryCode] isEqualToString:@"65535"]){ [dic setValue:[YYUtility getMobileCountryCode] forKey:@"mcc"]; } [dic addEntriesFromDictionary:defaultBasciParame]; if ([[AccountInfoStorage instance] getUid].length > 0) { [dic setValue:[[AccountInfoStorage instance] getUid] forKey:@"pub_uid"]; } if ([[AccountInfoStorage instance] getTicket].length > 0) { [dic setValue:[[AccountInfoStorage instance] getTicket] forKey:@"pub_ticket"]; } return dic; } + (void)handleNetError:(NSError *)error method:(NSString *)method failure:(void (^)(NSInteger resCode, NSString *message))failure { // 别问,反正 oauth/ticket 接口要通过这种方式取错误码。 NSHTTPURLResponse *response = error.userInfo[@"com.alamofire.serialization.response.error.response"]; NSString *NETWORK_ERROR = error.description.length > 0 ? error.description : YMLocalizedString(@"HttpRequestHelper5");; if (response && response.statusCode == 401) { failure(response.statusCode, YMLocalizedString(@"HttpRequestHelper7")); } else { if (error.code == -1009 || error.code == -1001 || error.code == -1004 || error.code == -1003 || error.code == -1002 || error.code == 3840) { failure(error.code, @""); } else { failure(error.code, error.localizedDescription.length > 0 ? error.localizedDescription : YMLocalizedString(@"HttpRequestHelper4")); } } #if DEBUG NSLog(@"\n%@", error); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSDictionary *allHeaders = response.allHeaderFields; //3, NSMutableDictionary *blockDict = [[NSMutableDictionary alloc]init]; [blockDict setObject:@(response.statusCode) forKey:@"resultCode"]; [blockDict setObject:NETWORK_ERROR forKey:@"resultDesc"]; [BSNetListenModel addHttpRsp:method header:allHeaders result:blockDict isSuccess:NO time:[NSDate getCurrentTimeWithFormat:@"yyyy-MM-dd HH:mm:ss"]]; }); #else #endif } /// 增加或编辑技能卡专用接口 Post 请求参数放入到 body 里 使用 application/json 类型传递 /// @param path 请求地址 /// @param params 参数 /// @param completion 回调 + (void)postSkillCard:(NSString *)path params:(NSString *)params completion:(HttpRequestHelperCompletion)completion{ if ([self checkNetworkStatusWithFailure:^{ completion(nil, -1, YMLocalizedString(@"HttpRequestHelper0")); }]) { return; } // [self configHeaders]; params = [MSParamsDecode msDecodeParams:[params.mj_JSONObject mutableCopy] ].toJSONString; NSDictionary *baseParams = [self configBaseParmars:nil]; AFHTTPSessionManager *manager = [HttpRequestHelper requestManager]; NSString *url = [self getHostUrl]; NSString *urlPath = [NSString stringWithFormat:@"%@/%@", url ,path]; #if DEBUG NSLog(@"\nmethod:\n%@\nparameter:\n%@", path, params); dispatch_async(dispatch_get_global_queue(0, 0), ^{ [BSNetListenModel addHttpReq:urlPath header:manager.requestSerializer.HTTPRequestHeaders param:[params copy] time:[NSDate getCurrentTimeWithFormat:@"yyyy-MM-dd HH:mm:ss"]]; }); #else #endif __block NSString *requestUrl = @""; [baseParams.allKeys enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { NSString *value = baseParams[obj]; requestUrl = [requestUrl stringByAppendingString:[NSString stringWithFormat:@"%@=%@&", obj, value]]; }]; urlPath = [NSString stringWithFormat:@"%@?%@", urlPath, requestUrl]; urlPath = [urlPath stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; NSMutableURLRequest *request = [[AFJSONRequestSerializer serializer] requestWithMethod:@"POST" URLString:urlPath parameters:baseParams error:nil]; request.timeoutInterval= [[[NSUserDefaults standardUserDefaults] valueForKey:@"timeoutInterval"] longValue]; [request setValue:@"application/json; charset=UTF-8" forHTTPHeaderField:@"Content-Type"]; if ([[AccountInfoStorage instance] getUid].length > 0) { [request setValue:[[AccountInfoStorage instance] getUid] forHTTPHeaderField:@"pub_uid"]; [manager.requestSerializer setValue:[[AccountInfoStorage instance] getUid] forHTTPHeaderField:@"pub_uid"]; } else { [request setValue:nil forHTTPHeaderField:@"pub_uid"]; } if ([[AccountInfoStorage instance] getTicket].length > 0) { [request setValue:[[AccountInfoStorage instance] getTicket] forHTTPHeaderField:@"pub_ticket"]; [manager.requestSerializer setValue:[[AccountInfoStorage instance] getTicket] forHTTPHeaderField:@"pub_ticket"]; }else { [request setValue:nil forHTTPHeaderField:@"pub_ticket"]; } [request setValue:[NSBundle uploadLanguageText] forHTTPHeaderField:@"Accept-Language"]; [request setValue:PI_App_Version forHTTPHeaderField:@"App-Version"]; [manager.requestSerializer setValue:[NSBundle uploadLanguageText] forHTTPHeaderField:@"Accept-Language"]; [manager.requestSerializer setValue:PI_App_Version forHTTPHeaderField:@"App-Version"]; [request setHTTPBody:[params dataUsingEncoding:NSUTF8StringEncoding]]; [[manager dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) { if (responseObject) { BaseModel *baseModel = [BaseModel modelWithDictionary:responseObject]; #if DEBUG NSLog(@"\n%@", [baseModel toJSONString]); #else #endif if (baseModel.code == 200) { #if DEBUG NSHTTPURLResponse *urlresponse = (NSHTTPURLResponse *)response; NSDictionary *allHeaders = urlresponse.allHeaderFields; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [BSNetListenModel addHttpRsp:urlPath header:allHeaders result:baseModel.data isSuccess:YES time:[NSDate getCurrentTimeWithFormat:@"yyyy-MM-dd HH:mm:ss"]]; }); #else #endif if (completion) { completion(baseModel, 200, nil); } } else { if (completion) { completion(nil, baseModel.code, baseModel.message); } } } if (error) { [self handleNetError:error method:path failure:^(NSInteger resCode, NSString *message) { completion(nil, resCode, message); }]; } }] resume]; } + (BOOL)checkNetworkStatusWithFailure:(void (^)(void))failure { if ([AFNetworkReachabilityManager sharedManager].networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable) { if (failure) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ failure(); }); } return YES; } return NO; } @end