// // PYHTTPManager.m // Food // // Created by ko1o, on 2019/7/27. // Copyright © 2019年 ko1o. All rights reserved. // #import #import "PYHTTPManager.h" #import "AFHTTPSessionManager.h" //#import "PYAccountService.h" //#import "PYLoginViewController.h" //#import "PYNewLoginViewController.h" #import #if IS_DEBUG static NSString * const salt = @"FFAsdaAoXidicS"; #else static NSString * const salt = @"sdfsaAoXidiZs"; #endif const NSInteger kNoLoginCode = 100; //static BOSClient *bosClient = nil; //static NSString *bucketname = nil; //static NSDictionary *typeConfigDic = nil; static OSSClient *_aliOSSClient; static NSString *_expirationTimeInGMTFormat; static NSString *kBucketName = nil; //static NSString *kAccessKeyId = @"LTAI4GJ3K5yB7xnatvAQdEi5"; //static NSString *kAccessKeySecret = @"6dbahs8GFIZQtslSbQZa6xn2R1pdOA"; //static NSString *kEndpoint = @"https://oss-cn-shenzhen.aliyuncs.com"; static NSString *kSSOCacheAvatarDir = @"avatar"; // 头像目录 static NSString *kSSOCacheAvatarBgDir = @"cover"; // 头像背景目录 static NSString *kSSOCacheReport = @"report"; // 举报 static NSString *kSSOCacheProfilePhoto = @"profile_photo"; // 照片墙 static NSString *kSSOCacheBottlePhoto = @"bottle_photo"; // 瓶子图片 static NSString *kSSOCacheBottleVoice = @"bottle_voice"; // 瓶子语音 static NSString *kSSOCacheiOSTestDir = @"iostest"; static dispatch_queue_t uploadQueue; // 上传队列 void runInMainThread(void(^action)(void)) { dispatch_async(dispatch_get_main_queue(), action); } ///网络状态改变通知Key #define NotificationName_ReachabilityStatusChange @"TH_ReachabilityStatusChange" @implementation PYHTTPManager static AFHTTPSessionManager *_manager; + (void)load { _manager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:PYHttpBaseUrl]]; _manager.securityPolicy.allowInvalidCertificates =YES; _manager.requestSerializer = [AFJSONRequestSerializer serializer]; // _manager.responseSerializer = [AFJSONResponseSerializer serializer]; _manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", @"text/html", nil]; AFSecurityPolicy *securityPolicy = [[AFSecurityPolicy alloc] init]; //如果是需要验证自建证书,需要设置为YES [securityPolicy setAllowInvalidCertificates:NO]; [_manager setSecurityPolicy:securityPolicy]; uploadQueue = dispatch_queue_create("com.youth.uploadfile", DISPATCH_QUEUE_SERIAL); // [self getSetupOSSClientWithCompletion:^(BOOL succeed) { // // }]; } + (void)showLoginPage { // UINavigationController *currectNav = [PYAppService currentNavigationController]; // if ([currectNav.viewControllers.firstObject isKindOfClass:[PYNewLoginViewController class]]) { // return; // } // PYNavigationViewController *nav = [[PYNavigationViewController alloc] initWithRootViewController:[PYNewLoginViewController new]]; // 取消所有请求 [_manager.operationQueue cancelAllOperations]; // [[PYAppService currentViewController] presentViewController:nav animated:YES completion:nil]; } + (void)setReachabilityStatusChangeBlock:(nullable void (^)(AFNetworkReachabilityStatus status))block { [_manager.reachabilityManager startMonitoring]; [_manager.reachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) { //发送网络状态修改通知, 可在全局调用一次就好 [NSNotificationCenter.defaultCenter postNotificationName:NotificationName_ReachabilityStatusChange object:nil userInfo:@{NotificationName_ReachabilityStatusChange : @(status)}]; //兼容旧代码 if (block) { block(status); } }]; } ///监听网络状态改变通知 + (void)addObserverForReachabilityStatusChange:(nullable void (^)(AFNetworkReachabilityStatus status))changeBlock { [NSNotificationCenter.defaultCenter addObserverForName:NotificationName_ReachabilityStatusChange object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) { if ([note.userInfo.allKeys containsObject:NotificationName_ReachabilityStatusChange]) { NSNumber *statusNum = [note.userInfo objectForKey:NotificationName_ReachabilityStatusChange]; changeBlock(statusNum.integerValue); } }]; } + (BOOL)isNetReachable { return [_manager.reachabilityManager isReachable]; } /// 直接显示后台返回的message 服务列表 + (NSArray *)showRspMessageServerNames { return @[ @"activebm/", @"userwsub/", @"invoiceaddc/", @"invoiceadd/" ]; } + (NSArray *)ignoreErrorMsg { return @[@""]; } + (NSArray *)handleErrorCodeWithPath:(NSString *)path { if ([path isEqualToString:@"learnroomdetail"]) { return @[@(1200), @(1201), @(1202)]; } return nil; } + (NSError *)errorWithPath:(NSString *)path forCode:(int)code userInfo:(NSDictionary *)userInfo { if ([[self handleErrorCodeWithPath:path] containsObject:@(code)]) { return [NSError errorWithDomain:NSCocoaErrorDomain code:code userInfo:userInfo]; } return nil; } + (NSDictionary *)headers { NSMutableDictionary *headersM = [NSMutableDictionary dictionary]; if ([LoginService token]) { headersM[@"Authorization"] = [@"Bearer " stringByAppendingString:[LoginService token]]; } return headersM; } + (NSMutableDictionary *)commonParamsM { NSMutableDictionary *commonParamsM = [NSMutableDictionary dictionary]; // * post请求的统一基础参数,内含: // * device_brand: 手机厂商(系统 api 获取) // * device_model: 手机型号(系统 api 获取) // * app_version_name: 应用版本名,3个数字(4.1.12) // * app_version_code:应用开发版本号,客户端自己的 bundle id(1/123) // * interface_version: 请求的接口版本号(1.0) // * device_platform: 操作系统(iOS / Android) UIDevice *device = [UIDevice currentDevice]; commonParamsM[@"device_brand"] = @"Apple"; commonParamsM[@"device_model"] = device.model ?: @"Unknown"; NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];//获取app版本信息 commonParamsM[@"app_version_name"] = infoDictionary[@"CFBundleShortVersionString"] ?: @"Unknown"; NSInteger interVersionCode = (NSInteger)infoDictionary[@"CFBundleVersion"]; NSString *versionCodeStr = [NSString stringWithFormat:@"%lu",interVersionCode]; commonParamsM[@"app_version_code"] = versionCodeStr; // commonParamsM[@"app_version_code"] = infoDictionary[@"CFBundleVersion"] ?: @"Unknown"; commonParamsM[@"interface_version"] = infoDictionary[@"CFBundleShortVersionString"] ?: @"Unknown"; commonParamsM[@"device_platform"] = @"iOS"; commonParamsM[@"os_version"] = [UIDevice currentDevice].systemVersion; return commonParamsM; } + (NSString *)generateSignWithParams:(NSDictionary *)params { NSArray *sortedKeys = [params.allKeys sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) { return obj1 > obj2; }]; NSMutableString *signStrM = [NSMutableString stringWithFormat:@"%@,", salt]; for (NSString *key in sortedKeys) { NSString *value = params[key]; if (key && value) { [signStrM appendFormat:@"%@:%@,", key, value]; } } [signStrM appendString:salt]; return [signStrM md5]; } + (void)handleRsp:(id)responseObject path:(NSString *)path callback:(PYHTTPManagerCallback)callback { int code = [responseObject[@"code"] intValue]; // NSLog(@"code==%d",code); //9999封号 if (code == 9998 || code == 9999) { // 接口认证失败,APP需要重新登录 [SVProgressHUD dismiss]; [PYAppService logout]; return; } if (code == 10) { // 需要购买会员 [SVProgressHUD dismiss]; [PYAppService showVipAlertWithPath:path title:responseObject[@"title"] msg:responseObject[@"message"] source:nil]; return; } NSError *handleError = [self errorWithPath:path forCode:code userInfo:responseObject]; if (code != 0 && !handleError) { NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:code userInfo:responseObject]; if (![[self ignoreErrorMsg] containsObject:path]) { if (![responseObject[@"message"] isEqualToString:@"auth header is empty"]) { [SVProgressHUD showErrorWithStatus:responseObject[@"message"] ?: @"网络异常,请稍后再试~"]; } } if (callback) { callback(nil, error); } return; } id result = responseObject[@"data"]; if ([result isKindOfClass:NSNull.class]) { result = nil; } if (callback) { callback(result, handleError); } } + (void)postWithPath:(NSString *)path params:(NSDictionary *)params callback:(PYHTTPManagerCallback)callback{ NSLog(@"path===%@,[self headers]%@",path,[self headers]); NSMutableDictionary *args = [self commonParamsM]; [args addEntriesFromDictionary:params]; args[@"sign"] = [self generateSignWithParams:args]; [_manager POST:path parameters:args headers:[self headers] progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { [self handleRsp:responseObject path:path callback:callback]; } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { if (callback) { if (error) { [SVProgressHUD showErrorWithStatus:error.localizedDescription ?: @"网络异常,请稍后再试~"]; } callback(nil, error); } }]; } + (void)getWithPath:(NSString *)path params:(NSDictionary *)params callback:(PYHTTPManagerCallback)callback { NSMutableDictionary *args = [self commonParamsM]; [args addEntriesFromDictionary:params]; [_manager GET:path parameters:args headers:[self headers] progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { [self handleRsp:responseObject path:path callback:callback]; } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { if (error.code == kNoLoginCode) { [self showLoginPage]; return ; } if (callback) { if (error) { [SVProgressHUD showErrorWithStatus:error.localizedDescription ?: @"网络异常,请稍后再试~"]; } callback(nil, error); } }]; } + (void)post:(NSString *)url params:(NSDictionary *)params imageses:(NSArray *> *)imageses imageNames:(NSArray *)imageNames callback:(PYHTTPManagerCallback)callback { AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:PYHttpBaseUrl]]; // [manager.requestSerializer setValue:@"multipart/form-data;charset=utf-8" forHTTPHeaderField:@"Content-Type"]; manager.responseSerializer = [AFJSONResponseSerializer serializer]; manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"text/plain", @"multipart/form-data", @"application/json", @"text/html", @"image/jpeg", @"image/png", @"application/octet-stream", @"text/json", nil]; NSMutableDictionary *args = [NSMutableDictionary dictionary]; [args addEntriesFromDictionary:params]; // NSLog(@"PYHttpBaseUrl===%@",PYHttpBaseUrl); [manager POST:url parameters:args headers:nil constructingBodyWithBlock:^(id _Nonnull formData) { for (int j = 0; j < imageses.count; j++) { NSArray *images = imageses[j]; NSString *imageName = imageNames[j]; for (int i = 0 ;i < images.count; i++) { UIImage *image = images[i]; NSData *data = UIImageJPEGRepresentation(image, 1.0); [formData appendPartWithFileData:data name:imageName fileName:[NSString stringWithFormat:@"%@.jpg", [[NSUUID UUID]UUIDString]] mimeType:@"image/jpeg"]; } } } progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { if ([responseObject[@"code"] integerValue] != 200) { NSError *error = [NSError errorWithDomain:@"" code:[responseObject[@"code"] integerValue] userInfo:responseObject]; callback(nil, error); [SVProgressHUD showErrorWithStatus:responseObject[@"message"]]; } else { callback(responseObject[@"data"], nil); } } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { [SVProgressHUD showErrorWithStatus:[error localizedDescription]]; callback(nil, error); }]; } + (NSString *)subfixForScene:(AliOSSUploadScene)scene { switch (scene) { case AliOSSUploadSceneAvatar: case AliOSSUploadSceneAvatarBg: case AliOSSUploadSceneReport: case AliOSSUploadSceneiOSTest: case AliOSSUploadSceneProfilePhoto: case AliOSSUploadSceneBottlePhoto: return @"jpg"; case AliOSSUploadSceneBottleVoice: return @"m4a"; } return @"jpg"; } + (NSString *)cachePathForScene:(AliOSSUploadScene)scene { switch (scene) { case AliOSSUploadSceneAvatar : return kSSOCacheAvatarDir; case AliOSSUploadSceneAvatarBg: return kSSOCacheAvatarBgDir; case AliOSSUploadSceneReport: return kSSOCacheReport; case AliOSSUploadSceneProfilePhoto: return kSSOCacheProfilePhoto; case AliOSSUploadSceneBottlePhoto: return kSSOCacheBottlePhoto; case AliOSSUploadSceneBottleVoice: return kSSOCacheBottleVoice; } return kSSOCacheiOSTestDir; } + (void)getSetupOSSClientWithCompletion:(void(^)(BOOL succeed))completion { [self postWithPath:@"ossauth" params:nil callback:^(id _Nullable rsp, NSError * _Nullable error) { if (!error && rsp) { id credential = [[OSSFederationCredentialProvider alloc] initWithFederationTokenGetter:^OSSFederationToken * { // 您需要在此处实现获取一个FederationToken,并构造成OSSFederationToken对象返回。 // 如果由于某种原因获取失败,直接返回nil。 OSSFederationToken *token = [OSSFederationToken new]; // 从STS服务获取的临时访问密钥(AccessKey ID和AccessKey Secret)。 token.tAccessKey = rsp[@"access_key_id"]; token.tSecretKey = rsp[@"access_key_secret"]; // 从STS服务获取的安全令牌(SecurityToken)。 token.tToken = rsp[@"security_token"]; // 临时访问凭证的过期时间。 token.expirationTimeInGMTFormat = rsp[@"expiration"]; return token; }]; _expirationTimeInGMTFormat = rsp[@"expiration"]; _aliOSSClient = [[OSSClient alloc] initWithEndpoint:rsp[@"end_point"] credentialProvider:credential]; kBucketName = rsp[@"bucket_name"] ?: @"xidi-official-website"; runInMainThread(^{ if (completion) { completion(YES); } }); } else { runInMainThread(^{ if (completion) { completion(NO); } }); } }]; } + (void)uploadFileWithScene:(AliOSSUploadScene)scene data:(NSData *)data completion:(PYHTTPManagerCallback)completion { dispatch_async(uploadQueue, ^{ @synchronized (self) { BOOL needNewOssClient = YES; if (_expirationTimeInGMTFormat != nil && _aliOSSClient != nil) { NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; dateFormatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"]; //2022-06-06T10:01:27Z [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'Z'"]; NSDate *date = [dateFormatter dateFromString:_expirationTimeInGMTFormat]; if (date != nil && date.timeIntervalSinceNow > 60) { needNewOssClient = NO; } } if (needNewOssClient == YES || _aliOSSClient == nil) { dispatch_semaphore_t sema = dispatch_semaphore_create(0); [self getSetupOSSClientWithCompletion:^(BOOL succeed) { dispatch_semaphore_signal(sema); }]; dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); } if (!_aliOSSClient) { runInMainThread(^{ [SVProgressHUD showErrorWithStatus:@"上传失败"]; completion(nil, [NSError errorWithDomain:@"com.youth" code:-1 userInfo:nil]); }); return; } } OSSPutObjectRequest * put = [OSSPutObjectRequest new]; put.bucketName = kBucketName; NSString *userID = [LoginService tokenInfo][@"id"] ?: [NSString stringWithFormat:@"random_%d", arc4random()]; NSString *prefix = [NSString stringWithFormat:@"%@_%.0f", userID, [[NSDate date] timeIntervalSince1970] * 100000.0]; // 毫秒 NSString *fileName = [NSString stringWithFormat:@"%@.%@", prefix, [self subfixForScene:scene]]; //objectKey等同于objectName,表示上传文件到OSS时需要指定包含文件后缀在内的完整路径,例如abc/efg/123.jpg。 NSString *cacheDir = [self cachePathForScene:scene]; put.objectKey = [NSString stringWithFormat:@"%@/%@", cacheDir, fileName]; // 直接上传NSData。 put.uploadingData = data; // put.uploadProgress = ^(int64_t bytesSent, int64_t totalByteSent, int64_t totalBytesExpectedToSend) { // NSLog(@"%lld, %lld, %lld", bytesSent, totalByteSent, totalBytesExpectedToSend); // }; // [SVProgressHUD showWithStatus:@"文件上传中..."]; OSSTask * putTask = [_aliOSSClient putObject:put]; [putTask continueWithBlock:^id(OSSTask *task) { if (completion) { if (!task.error) { // [SVProgressHUD dismiss]; NSLog(@"upload object success!"); // sign public url OSSTask *task = [_aliOSSClient presignPublicURLWithBucketName:kBucketName withObjectKey:put.objectKey]; if (!task.error) { runInMainThread(^{ completion(task.result, nil); }); } else { NSLog(@"sign url error: %@", task.error); runInMainThread(^{ completion(nil, task.error); [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"上传失败 sign %@", [task.error.userInfo objectForKey:@"message"]]]; }); } } else { NSLog(@"upload object failed, error: %@" , task.error); runInMainThread(^{ completion(nil, task.error); [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"上传失败%@", [task.error.userInfo objectForKey:@"message"]]]; }); } } return nil; }]; }); } + (void)uploadFileWithScene:(AliOSSUploadScene)scene datas:(NSArray *)datas completion:(PYHTTPManagerCallback)completion { NSInteger totalTaskCount = datas.count; __block NSInteger finishTaskCount = 0; NSMutableArray *fileUrls = [NSMutableArray array]; for (NSData *data in datas) { [self uploadFileWithScene:scene data:data completion:^(id _Nullable rsp, NSError * _Nullable error) { @synchronized (self) { finishTaskCount++; if (rsp && !error && completion) { [fileUrls addObject:rsp]; if (finishTaskCount >= totalTaskCount && fileUrls.count == finishTaskCount) { // 排序 [fileUrls sortUsingComparator:^NSComparisonResult(NSString *url1, NSString *url2) { return [url1 compare:url2]; }]; runInMainThread(^{ completion(fileUrls, nil); }); } } if (error) { runInMainThread(^{ completion(nil, error); }); } } }]; usleep(10000); // 10毫秒 } } @end