// // BuryPoint.m // BuryPoint // // Created by Felix on 2018/8/10. // Copyright © 2018年 Felix. All rights reserved. // #import "BuryPoint.h" #import "VclSystemInfo.h" #import #import #import "Domains.h" #import "BuryPointModel.h" #import #import "UseAccountManage.h" #import "CloudPhoneAPI.h" #import "HWBuryPointModel.h" #import "FMDB.h" #define LOG_TIME_FORMAT @"yyyy-MM-dd HH:mm:ss.SSS" #define BuryPoint_FORM_BOUNDARY @"BuryPoint_FORM_BOUNDARY" #define HTTP_REQUEST_URL_STRING @"http://dev2.ipc.Vcl.optjoy.io:30288/apis/v1/logfile/logfile/upload/app/ios/resource?uid=12345&access_token=9999" // 目前開發用的域名是 dev env的域名 請在正式環境改成 stag env的域名 // stag env 下 // 國內地區存儲在 qcloud的logfiles-stag bucket // 海外地區存儲在 gcp的logfiles-stag bucket // 正式版 國內:https://stag.ipc.Vcl.optjoy.cn:30011/apis/v1 海外:https://stag.ipc.Vcl.optjoy.io:30011/apis/v1 #define HTTP_REQUEST_URL_STRING_CN @"https://stag.ipc.Vcl.optjoy.cn:30011/apis/v1/logfile/logfile/upload/app/ios/resource?uid=12345&access_token=9999" #define HTTP_REQUEST_URL_STRING_IO @"https://stag.ipc.Vcl.optjoy.io:30011/apis/v1/logfile/logfile/upload/app/ios/resource?uid=12345&access_token=9999" #define HTTP_REQUEST_URL_STRING_IO_DF @"https://ipc.dada.p.optjoy.io:30011/apis/v1/logfile/logfile/upload/app/ios/resource?uid=12345&access_token=9999" // FOUNDATION_EXPORT BuriedPointType const CloudPhoneTypeWelfare; // FOUNDATION_EXPORT BuriedPointType const CloudPhoneTypeCloudGame; // FOUNDATION_EXPORT BuriedPointType const CloudPhoneTypeMime; /**埋点字段value值*/ // 云手机 NSString *const BuriedPointTypeCloudPhone = @"dt_tab_iOS_云手机"; // 云盘 NSString *const BuriedPointTypeCloudStore = @"dt_tab_iOS_云盘"; // 福利社区 NSString *const BuriedPointTypeWelfare = @"dt_tab_iOS_福利社区"; // 云游戏 NSString *const BuriedPointTypeCloudGame = @"dt_tab_iOS_云游试玩"; // 我的 NSString *const BuriedPointTypeMime = @"dt_tab_iOS_我的"; // 裂变活动-免费领机 NSString *const BuriedPointTypeReceivePhone = @"dt_裂变_iOS_免费领机"; // 双旦活动-前往分配时长 // 支付结果页面进入周年庆活动 NSString *const BuriedPointTypePaySuccessEnter = @"znq_2022_iOS_支付按钮"; // 周年庆活动-分配确认 NSString *const BuriedPointTypeClickDistribute = @"znq_2022_iOS_分配确认按钮"; // 周年庆活动-分配确认 弹窗确认 NSString *const BuriedPointTypeSureDistribute = @"znq_2022_iOS_确认弹窗确认"; // 周年庆活动-分配确认 弹窗取消 NSString *const BuriedPointTypeCancelDistribute = @"znq_2022_iOS_确认弹窗取消"; // 周年庆活动-未分配返回 NSString *const BuriedPointTypeDistributeBack = @"znq_2022_iOS_未分配返回"; // 快捷方式创建埋点 NSString *const BuriedPointTypeShortcutCreat = @"shortcut_iOS_create"; // 快捷方式打开埋点 NSString *const BuriedPointTypeShortcutOpen = @"shortcut_iOS_click"; // 云手机试用续费弹窗 NSString *const BuriedPointTypePlayActCodeForceCloseBuy = @"激活码-强制关闭-去购买"; NSString *const BuriedPointTypePlayActCodeForceCloseCancel = @"激活码-强制关闭-取消"; NSString *const BuriedPointTypePlayActCodeForceCloseGiveUp = @"激活码-强制关闭-放弃"; NSString *const BuriedPointTypePlayActCodeForceCloseContinue = @"激活码-强制关闭-继续观看"; NSString *const BuriedPointTypePlayActCodeCloseBuy = @"激活码-关闭-去购买"; NSString *const BuriedPointTypePlayActCodeCloseCancel = @"激活码-关闭-取消"; NSString *const BuriedPointTypePlayActCodeBreakBuy = @"激活码-断线-去购买"; NSString *const BuriedPointTypePlayActCodeBreakCancel = @"激活码-断线-取消"; NSString *const BuriedPointTypePlayFreeForceCloseBuy = @"免费试用-强制关闭-去购买"; NSString *const BuriedPointTypePlayFreeForceCloseCancel = @"免费试用-强制关闭-取消"; NSString *const BuriedPointTypePlayFreeForceCloseGiveUp = @"免费试用-强制关闭-放弃"; NSString *const BuriedPointTypePlayFreeForceCloseContinue = @"免费试用-强制关闭-继续观看"; NSString *const BuriedPointTypePlayFreeCloseBuy = @"免费试用-关闭-去购买"; NSString *const BuriedPointTypePlayFreeCloseCancel = @"免费试用-关闭-取消"; @interface BuryPoint () @property (nonatomic, copy) NSMutableArray *pendingUploads; // 待上传的文件所在路径; @property (nonatomic, assign) BOOL isFormDataEmpty; // 是否为空表单上传,默认YES @property (nonatomic, copy) NSString *currentReport; // 程序本次启动新创建的文件名,上方待上传的文件pendingUploads不包含新创建的文件 @end static BuryPoint *_instance = nil; @implementation BuryPoint + (instancetype)sharedInstance { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _instance = [[BuryPoint alloc] init]; }); return _instance; } + (instancetype)allocWithZone:(struct _NSZone *)zone { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _instance = [super allocWithZone:zone]; }); return _instance; } - (instancetype)copyWithZone:(NSZone *)zone { return _instance; } - (instancetype)init { return [self initWithBasePath:self.getBasePath]; } - (instancetype)initWithBasePath:(NSString *)basePath { if (self = [super init]) { // 0.initialized data self.isFormDataEmpty = YES; // 2.获取已存在的异常文件的文件路径 [self getPendingUploadFiles]; // 3.创建本次的启动日志文件 [self createNewReportFile]; // 4.上传已存在的日志文件 [self uploadReportFiles]; /*获取数据上传到服务器*/ } return self; } // 将信息写入到 - (void)writeContent:(NSString *)content { [self write:content filename:self.currentReport]; } - (void)write:(NSString *)content filename:(NSString *)filename { if (!filename) { return; } @try { NSString *path = [self getReportsDirectory]; if (![[NSFileManager defaultManager] fileExistsAtPath:path]) { // 创建日志文件的绝对路径 [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil]; } NSString *filePath = [path stringByAppendingPathComponent:filename]; if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) { [content writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil]; return; } NSFileHandle *fileHandle = [NSFileHandle fileHandleForUpdatingAtPath:filePath]; // 将写入文件操作的指针跳到文件的末端 [fileHandle seekToEndOfFile]; // 追加写入数据 [fileHandle writeData:[[NSString stringWithFormat:@"\n%@", content] dataUsingEncoding:NSUTF8StringEncoding]]; [fileHandle closeFile]; } @catch (NSException *exception) { } @finally { } } - (void)clearCrashFile { [self clearFile:self.currentReport]; } - (void)clearFile:(NSString *)filename { @try { NSString *path = self.getReportsDirectory; if ([[NSFileManager defaultManager] fileExistsAtPath:path]) { // 创建日志文件的绝对路径 [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil]; } NSString *filePath = [path stringByAppendingPathComponent:filename]; if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) { NSFileHandle *fileHandle = [NSFileHandle fileHandleForUpdatingAtPath:filePath]; // 将写入文件操作的指针跳到文件的末端 [fileHandle truncateFileAtOffset:0]; [fileHandle closeFile]; } } @catch (NSException *exception) { HLog(@"[BuryPoint] clear local file error with exception:\n%@", exception); } @finally { } } /** 根据本地文件的全路径,删除文件 @param fullPath 全路径 */ - (BOOL)removeLocalFileWithFullPath:(NSString *)fullPath { BOOL result = NO; NSError *error = nil; if ([[NSFileManager defaultManager] fileExistsAtPath:fullPath]) { result = [[NSFileManager defaultManager] removeItemAtPath:fullPath error:&error]; } if (error) { HLog(@"[BuryPoint] remove local file with error: %@.", error); } return result; } - (void)uploadReportFiles { NSArray *paths = [self.pendingUploads copy]; if (paths.count == 0) { return; } [self uploadBuryPointData:paths]; } - (void)uploadBuryPointData:(NSArray *)filePaths { NSMutableArray *emptyFileFullPaths = [NSMutableArray array]; NSMutableArray *uploadFileFullPaths = [NSMutableArray array]; NSMutableArray *dataArray = [[NSMutableArray alloc] init]; for (NSString *obj in filePaths) { NSData *data = [NSData dataWithContentsOfFile:obj]; NSString *dataStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSArray *strARy = [dataStr componentsSeparatedByString:@"__"]; for (NSString *subStr in strARy) { // HLog(@"subStr %@ subStrCount %lu",subStr, (unsigned long)subStr.length); if (subStr.length > 0) { //去除换行或者空数据 BuryPointModel *model = [subStr mj_JSONObject]; NSDictionary *dic = [model mj_keyValues]; [dataArray addObject:dic]; } } // HLog(@"dataStr %@ strARy %@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding], strARy); // 如果是有数据才进行拼接并且上传 if (data.length) { self.isFormDataEmpty = NO; [uploadFileFullPaths addObject:obj]; } else { // 否则本地的空文件直接删除 if ([self.pendingUploads containsObject:obj]) { [self.pendingUploads removeObject:obj]; [emptyFileFullPaths addObject:obj]; } } } // 如果上传的文件都为空,则不进行下方的上传操作 if (self.isFormDataEmpty) { return; } for (NSString *path in emptyFileFullPaths) { [self removeLocalFileWithFullPath:path]; } for (NSString *path in uploadFileFullPaths) { [self removeLocalFileWithFullPath:path]; } // [[UseAccountManage shareInstance] extensionPublicToBuryPointPostCallBackCode:PostBurialSiteLogAPI Parameters:dataArray success:^(id _Nonnull responseObject) { // SuperModel *mod = [[SuperModel alloc] initWithDictionary:responseObject error:nil]; // if (mod.status && mod.status.integerValue == 0) { // //上传成功后把文件缓存清除 // for (NSString *path in uploadFileFullPaths) { // [self removeLocalFileWithFullPath:path]; // } // } // } failure:^(NSError * _Nonnull error) { // // }]; } /** 获取应用程序的BundleName */ - (NSString *)getBundleName { NSString *bundleName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"]; if (!bundleName) { bundleName = @"Unknown"; } return bundleName; } /** 获取Log日志存储的位置 ~/Library/Caches/BuryPoint/JoyLite/Reports/... @return 日志文件所在路径 */ - (NSString *)getBasePath { NSArray *directories = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); if (0 == directories.count) { HLog(@"Could not locate caches directory path."); return nil; } NSString *cachesPath = [directories firstObject]; if (0 == cachesPath.length) { HLog(@"Could not locate caches directory path."); return nil; } NSString *path = [@"BuryPoint" stringByAppendingPathComponent:[self getBundleName]]; return [cachesPath stringByAppendingPathComponent:path]; } /** 获取日志文件存储所在的目录文件路径 ~/Library/Caches/BuryPoint/双子星云手机/Reports/... */ - (NSString *)getReportsDirectory { return [self.getBasePath stringByAppendingPathComponent:@"Reports"]; } /** 获取待上传的文件列表 */ - (void)getPendingUploadFiles { // 所查找文件夹的路径 NSString *reportPath = [self getReportsDirectory]; // 目录迭代器 NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager] enumeratorAtPath:reportPath]; NSMutableArray *array = [NSMutableArray array]; for (NSString *name in enumerator) { if ([name.pathExtension isEqualToString:@"txt"]) { [array addObject:[[self getReportsDirectory] stringByAppendingPathComponent:name]]; } } [self.pendingUploads addObjectsFromArray:array]; //修改本地文件保存最大数量 while ([self.pendingUploads count] > 50) { NSString *path = [self.pendingUploads firstObject]; [self removeLocalFileWithFullPath:path]; [self.pendingUploads removeObject:path]; } } /** 每次崩溃时,信息保存的文件名称 */ - (void)createNewReportFile { NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; dateFormatter.dateFormat = @"yyyy.MM.dd_HH.mm.ss.ssss"; NSString *filename = [NSString stringWithFormat:@"log_%@_ios.txt", [dateFormatter stringFromDate:[NSDate date]]]; [[NSFileManager defaultManager] createFileAtPath:[[self getReportsDirectory] stringByAppendingPathComponent:filename] contents:nil attributes:nil]; self.currentReport = [filename copy]; } - (NSString *)getHttpRequestURLString { NSString *result = [HWDataManager getStringWithKey:@"chinaArea"]; if ([result isEqualToString:@"1"]) { // 国内 return HTTP_REQUEST_URL_STRING_CN; } else { // 国外 NSString *appID = [[NSBundle mainBundle] bundleIdentifier]; if ([appID isEqualToString:@"com.jjyang.dadaCamera"] || [appID isEqualToString:@"com.dafeng.dadasmartcam"]) { // 大丰项目 return HTTP_REQUEST_URL_STRING_IO_DF; } else { return HTTP_REQUEST_URL_STRING_IO; } } } - (NSMutableArray *)pendingUploads { if (!_pendingUploads) { _pendingUploads = [NSMutableArray array]; } return _pendingUploads; } // 类似:2EAA6CEC-6BAC-43B2-84B2-E528D03C01FD,总长36. - (NSString *)fetchDeviceUUID { NSString *uuid = [[UIDevice currentDevice] identifierForVendor].UUIDString; return uuid; } + (void)reportBuriedPointWithType:(NSString *)type { NSMutableDictionary *params = [NSMutableDictionary dictionary]; [params setObject:type forKey:@"pointName"]; [[UseAccountManage shareInstance] CommonPostCallBackCode:TabBarItemClickAPI Parameters:params success:^(id _Nonnull responseObject) { } failure:^(NSError * _Nonnull error) { HLog(@"%@", error); }]; } #pragma mark - 全新埋点 7.1 /** 保存或更新埋点数组缓存数据 */ + (void)saveHWBuryPointModelListWithModel:(HWBuryPointModel *)model { // NSArray *dataSource = [HWBuryPointModel bg_findAll:DB_HWBuryPointModelList_TableName]; // 更新数据库 model.bg_tableName = DB_HWBuryPointModelList_TableName; [model bg_saveOrUpdateAsync:^(BOOL isSuccess) { HLog(@"HWBuryPointModel 更新: %@", isSuccess ? @"成功":@"失败"); }]; } + (void)updateBuryPointModelListWithModel:(HWBuryPointModel *)model{ NSMutableArray *dictArr = [NSMutableArray array]; NSDictionary *dict = [model mj_keyValues]; [dictArr addObject:dict]; [[UseAccountManage shareInstance] extensionPublicToBuryPointPostCallBackCode:NewBuryPointAPI Parameters:dictArr success:^(id _Nonnull responseObject) { if (CODE == 0) { HLog(@"\n实时上报完成") } } failure:^(NSError * _Nonnull error) { HLog(@"%@",error) }]; } /** 读取埋点数组缓存数据 */ + (NSMutableArray *)readHWBuryPointModelList { NSMutableArray *array = [NSMutableArray array]; NSArray *dataSource = [HWBuryPointModel bg_findAll:DB_HWBuryPointModelList_TableName]; for (HWBuryPointModel *model in dataSource) { [array addObject:model]; } return array; } /** 清空埋点数组缓存数据 */ + (void)clearHWBuryPointModelList { [HWBuryPointModel bg_clearAsync:DB_HWBuryPointModelList_TableName complete:^(BOOL isSuccess) { HLog(@"HWBuryPointModel 数据清空: %@", isSuccess ? @"成功":@"失败"); }]; } @end