BuryPoint.m 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. //
  2. // BuryPoint.m
  3. // BuryPoint
  4. //
  5. // Created by Felix on 2018/8/10.
  6. // Copyright © 2018年 Felix. All rights reserved.
  7. //
  8. #import "BuryPoint.h"
  9. #import "VclSystemInfo.h"
  10. #import <Foundation/NSURLSession.h>
  11. #import <UIKit/UIKit.h>
  12. #import "Domains.h"
  13. #import "BuryPointModel.h"
  14. #import <MJExtension.h>
  15. #import "UseAccountManage.h"
  16. #import "CloudPhoneAPI.h"
  17. #import "HWBuryPointModel.h"
  18. #import "FMDB.h"
  19. #define LOG_TIME_FORMAT @"yyyy-MM-dd HH:mm:ss.SSS"
  20. #define BuryPoint_FORM_BOUNDARY @"BuryPoint_FORM_BOUNDARY"
  21. #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"
  22. // 目前開發用的域名是 dev env的域名 請在正式環境改成 stag env的域名
  23. // stag env 下
  24. // 國內地區存儲在 qcloud的logfiles-stag bucket
  25. // 海外地區存儲在 gcp的logfiles-stag bucket
  26. // 正式版 國內:https://stag.ipc.Vcl.optjoy.cn:30011/apis/v1 海外:https://stag.ipc.Vcl.optjoy.io:30011/apis/v1
  27. #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"
  28. #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"
  29. #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"
  30. //
  31. FOUNDATION_EXPORT BuriedPointType const CloudPhoneTypeWelfare;
  32. //
  33. FOUNDATION_EXPORT BuriedPointType const CloudPhoneTypeCloudGame;
  34. //
  35. FOUNDATION_EXPORT BuriedPointType const CloudPhoneTypeMime;
  36. /**埋点字段value值*/
  37. // 云手机
  38. NSString *const BuriedPointTypeCloudPhone = @"dt_tab_iOS_云手机";
  39. // 云盘
  40. NSString *const BuriedPointTypeCloudStore = @"dt_tab_iOS_云盘";
  41. // 福利社区
  42. NSString *const BuriedPointTypeWelfare = @"dt_tab_iOS_福利社区";
  43. // 云游戏
  44. NSString *const BuriedPointTypeCloudGame = @"dt_tab_iOS_云游试玩";
  45. // 我的
  46. NSString *const BuriedPointTypeMime = @"dt_tab_iOS_我的";
  47. // 裂变活动-免费领机
  48. NSString *const BuriedPointTypeReceivePhone = @"dt_裂变_iOS_免费领机";
  49. // 双旦活动-前往分配时长
  50. // 支付结果页面进入周年庆活动
  51. NSString *const BuriedPointTypePaySuccessEnter = @"znq_2022_iOS_支付按钮";
  52. // 周年庆活动-分配确认
  53. NSString *const BuriedPointTypeClickDistribute = @"znq_2022_iOS_分配确认按钮";
  54. // 周年庆活动-分配确认 弹窗确认
  55. NSString *const BuriedPointTypeSureDistribute = @"znq_2022_iOS_确认弹窗确认";
  56. // 周年庆活动-分配确认 弹窗取消
  57. NSString *const BuriedPointTypeCancelDistribute = @"znq_2022_iOS_确认弹窗取消";
  58. // 周年庆活动-未分配返回
  59. NSString *const BuriedPointTypeDistributeBack = @"znq_2022_iOS_未分配返回";
  60. // 快捷方式创建埋点
  61. NSString *const BuriedPointTypeShortcutCreat = @"shortcut_iOS_create";
  62. // 快捷方式打开埋点
  63. NSString *const BuriedPointTypeShortcutOpen = @"shortcut_iOS_click";
  64. // 云手机试用续费弹窗
  65. NSString *const BuriedPointTypePlayActCodeForceCloseBuy = @"激活码-强制关闭-去购买";
  66. NSString *const BuriedPointTypePlayActCodeForceCloseCancel = @"激活码-强制关闭-取消";
  67. NSString *const BuriedPointTypePlayActCodeForceCloseGiveUp = @"激活码-强制关闭-放弃";
  68. NSString *const BuriedPointTypePlayActCodeForceCloseContinue = @"激活码-强制关闭-继续观看";
  69. NSString *const BuriedPointTypePlayActCodeCloseBuy = @"激活码-关闭-去购买";
  70. NSString *const BuriedPointTypePlayActCodeCloseCancel = @"激活码-关闭-取消";
  71. NSString *const BuriedPointTypePlayActCodeBreakBuy = @"激活码-断线-去购买";
  72. NSString *const BuriedPointTypePlayActCodeBreakCancel = @"激活码-断线-取消";
  73. NSString *const BuriedPointTypePlayFreeForceCloseBuy = @"免费试用-强制关闭-去购买";
  74. NSString *const BuriedPointTypePlayFreeForceCloseCancel = @"免费试用-强制关闭-取消";
  75. NSString *const BuriedPointTypePlayFreeForceCloseGiveUp = @"免费试用-强制关闭-放弃";
  76. NSString *const BuriedPointTypePlayFreeForceCloseContinue = @"免费试用-强制关闭-继续观看";
  77. NSString *const BuriedPointTypePlayFreeCloseBuy = @"免费试用-关闭-去购买";
  78. NSString *const BuriedPointTypePlayFreeCloseCancel = @"免费试用-关闭-取消";
  79. @interface BuryPoint () <NSURLSessionDelegate>
  80. @property (nonatomic, copy) NSMutableArray<NSString *> *pendingUploads; // 待上传的文件所在路径;
  81. @property (nonatomic, assign) BOOL isFormDataEmpty; // 是否为空表单上传,默认YES
  82. @property (nonatomic, copy) NSString *currentReport; // 程序本次启动新创建的文件名,上方待上传的文件pendingUploads不包含新创建的文件
  83. @end
  84. static BuryPoint *_instance = nil;
  85. @implementation BuryPoint
  86. + (instancetype)sharedInstance {
  87. static dispatch_once_t onceToken;
  88. dispatch_once(&onceToken, ^{
  89. _instance = [[BuryPoint alloc] init];
  90. });
  91. return _instance;
  92. }
  93. + (instancetype)allocWithZone:(struct _NSZone *)zone {
  94. static dispatch_once_t onceToken;
  95. dispatch_once(&onceToken, ^{
  96. _instance = [super allocWithZone:zone];
  97. });
  98. return _instance;
  99. }
  100. - (instancetype)copyWithZone:(NSZone *)zone {
  101. return _instance;
  102. }
  103. - (instancetype)init {
  104. return [self initWithBasePath:self.getBasePath];
  105. }
  106. - (instancetype)initWithBasePath:(NSString *)basePath {
  107. if (self = [super init]) {
  108. // 0.initialized data
  109. self.isFormDataEmpty = YES;
  110. // 2.获取已存在的异常文件的文件路径
  111. [self getPendingUploadFiles];
  112. // 3.创建本次的启动日志文件
  113. [self createNewReportFile];
  114. // 4.上传已存在的日志文件
  115. [self uploadReportFiles]; /*获取数据上传到服务器*/
  116. }
  117. return self;
  118. }
  119. // 将信息写入到
  120. - (void)writeContent:(NSString *)content {
  121. [self write:content filename:self.currentReport];
  122. }
  123. - (void)write:(NSString *)content filename:(NSString *)filename {
  124. if (!filename) {
  125. return;
  126. }
  127. @try {
  128. NSString *path = [self getReportsDirectory];
  129. if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
  130. // 创建日志文件的绝对路径
  131. [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
  132. }
  133. NSString *filePath = [path stringByAppendingPathComponent:filename];
  134. if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
  135. [content writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
  136. return;
  137. }
  138. NSFileHandle *fileHandle = [NSFileHandle fileHandleForUpdatingAtPath:filePath];
  139. // 将写入文件操作的指针跳到文件的末端
  140. [fileHandle seekToEndOfFile];
  141. // 追加写入数据
  142. [fileHandle writeData:[[NSString stringWithFormat:@"\n%@", content] dataUsingEncoding:NSUTF8StringEncoding]];
  143. [fileHandle closeFile];
  144. }
  145. @catch (NSException *exception) {
  146. }
  147. @finally {
  148. }
  149. }
  150. - (void)clearCrashFile {
  151. [self clearFile:self.currentReport];
  152. }
  153. - (void)clearFile:(NSString *)filename {
  154. @try {
  155. NSString *path = self.getReportsDirectory;
  156. if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
  157. // 创建日志文件的绝对路径
  158. [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
  159. }
  160. NSString *filePath = [path stringByAppendingPathComponent:filename];
  161. if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
  162. NSFileHandle *fileHandle = [NSFileHandle fileHandleForUpdatingAtPath:filePath];
  163. // 将写入文件操作的指针跳到文件的末端
  164. [fileHandle truncateFileAtOffset:0];
  165. [fileHandle closeFile];
  166. }
  167. }
  168. @catch (NSException *exception) {
  169. HLog(@"[BuryPoint] clear local file error with exception:\n%@", exception);
  170. }
  171. @finally {
  172. }
  173. }
  174. /**
  175. 根据本地文件的全路径,删除文件
  176. @param fullPath 全路径
  177. */
  178. - (BOOL)removeLocalFileWithFullPath:(NSString *)fullPath {
  179. BOOL result = NO;
  180. NSError *error = nil;
  181. if ([[NSFileManager defaultManager] fileExistsAtPath:fullPath]) {
  182. result = [[NSFileManager defaultManager] removeItemAtPath:fullPath error:&error];
  183. }
  184. if (error) {
  185. HLog(@"[BuryPoint] remove local file with error: %@.", error);
  186. }
  187. return result;
  188. }
  189. - (void)uploadReportFiles {
  190. NSArray *paths = [self.pendingUploads copy];
  191. if (paths.count == 0) {
  192. return;
  193. }
  194. [self uploadBuryPointData:paths];
  195. }
  196. - (void)uploadBuryPointData:(NSArray<NSString *> *)filePaths
  197. {
  198. NSMutableArray *emptyFileFullPaths = [NSMutableArray array];
  199. NSMutableArray *uploadFileFullPaths = [NSMutableArray array];
  200. NSMutableArray *dataArray = [[NSMutableArray alloc] init];
  201. for (NSString *obj in filePaths) {
  202. NSData *data = [NSData dataWithContentsOfFile:obj];
  203. NSString *dataStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  204. NSArray *strARy = [dataStr componentsSeparatedByString:@"__"];
  205. for (NSString *subStr in strARy) {
  206. // HLog(@"subStr %@ subStrCount %lu",subStr, (unsigned long)subStr.length);
  207. if (subStr.length > 0) { //去除换行或者空数据
  208. BuryPointModel *model = [subStr mj_JSONObject];
  209. NSDictionary *dic = [model mj_keyValues];
  210. [dataArray addObject:dic];
  211. }
  212. }
  213. // HLog(@"dataStr %@ strARy %@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding], strARy);
  214. // 如果是有数据才进行拼接并且上传
  215. if (data.length) {
  216. self.isFormDataEmpty = NO;
  217. [uploadFileFullPaths addObject:obj];
  218. }
  219. else { // 否则本地的空文件直接删除
  220. if ([self.pendingUploads containsObject:obj]) {
  221. [self.pendingUploads removeObject:obj];
  222. [emptyFileFullPaths addObject:obj];
  223. }
  224. }
  225. }
  226. // 如果上传的文件都为空,则不进行下方的上传操作
  227. if (self.isFormDataEmpty) {
  228. return;
  229. }
  230. for (NSString *path in emptyFileFullPaths) {
  231. [self removeLocalFileWithFullPath:path];
  232. }
  233. for (NSString *path in uploadFileFullPaths) {
  234. [self removeLocalFileWithFullPath:path];
  235. }
  236. // [[UseAccountManage shareInstance] extensionPublicToBuryPointPostCallBackCode:PostBurialSiteLogAPI Parameters:dataArray success:^(id _Nonnull responseObject) {
  237. // SuperModel *mod = [[SuperModel alloc] initWithDictionary:responseObject error:nil];
  238. // if (mod.status && mod.status.integerValue == 0) {
  239. // //上传成功后把文件缓存清除
  240. // for (NSString *path in uploadFileFullPaths) {
  241. // [self removeLocalFileWithFullPath:path];
  242. // }
  243. // }
  244. // } failure:^(NSError * _Nonnull error) {
  245. //
  246. // }];
  247. }
  248. /**
  249. 获取应用程序的BundleName
  250. */
  251. - (NSString *)getBundleName {
  252. NSString *bundleName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"];
  253. if (!bundleName) {
  254. bundleName = @"Unknown";
  255. }
  256. return bundleName;
  257. }
  258. /**
  259. 获取Log日志存储的位置
  260. ~/Library/Caches/BuryPoint/JoyLite/Reports/...
  261. @return 日志文件所在路径
  262. */
  263. - (NSString *)getBasePath {
  264. NSArray *directories = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
  265. if (0 == directories.count) {
  266. HLog(@"Could not locate caches directory path.");
  267. return nil;
  268. }
  269. NSString *cachesPath = [directories firstObject];
  270. if (0 == cachesPath.length) {
  271. HLog(@"Could not locate caches directory path.");
  272. return nil;
  273. }
  274. NSString *path = [@"BuryPoint" stringByAppendingPathComponent:[self getBundleName]];
  275. return [cachesPath stringByAppendingPathComponent:path];
  276. }
  277. /**
  278. 获取日志文件存储所在的目录文件路径
  279. ~/Library/Caches/BuryPoint/双子星云手机/Reports/...
  280. */
  281. - (NSString *)getReportsDirectory {
  282. return [self.getBasePath stringByAppendingPathComponent:@"Reports"];
  283. }
  284. /**
  285. 获取待上传的文件列表
  286. */
  287. - (void)getPendingUploadFiles {
  288. // 所查找文件夹的路径
  289. NSString *reportPath = [self getReportsDirectory];
  290. // 目录迭代器
  291. NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager] enumeratorAtPath:reportPath];
  292. NSMutableArray<NSString *> *array = [NSMutableArray array];
  293. for (NSString *name in enumerator) {
  294. if ([name.pathExtension isEqualToString:@"txt"]) {
  295. [array addObject:[[self getReportsDirectory] stringByAppendingPathComponent:name]];
  296. }
  297. }
  298. [self.pendingUploads addObjectsFromArray:array];
  299. //修改本地文件保存最大数量
  300. while ([self.pendingUploads count] > 50)
  301. {
  302. NSString *path = [self.pendingUploads firstObject];
  303. [self removeLocalFileWithFullPath:path];
  304. [self.pendingUploads removeObject:path];
  305. }
  306. }
  307. /**
  308. 每次崩溃时,信息保存的文件名称
  309. */
  310. - (void)createNewReportFile {
  311. NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
  312. dateFormatter.dateFormat = @"yyyy.MM.dd_HH.mm.ss.ssss";
  313. NSString *filename = [NSString stringWithFormat:@"log_%@_ios.txt", [dateFormatter stringFromDate:[NSDate date]]];
  314. [[NSFileManager defaultManager] createFileAtPath:[[self getReportsDirectory] stringByAppendingPathComponent:filename] contents:nil attributes:nil];
  315. self.currentReport = [filename copy];
  316. }
  317. - (NSString *)getHttpRequestURLString {
  318. NSString *result = [HWDataManager getStringWithKey:@"chinaArea"];
  319. if ([result isEqualToString:@"1"]) { // 国内
  320. return HTTP_REQUEST_URL_STRING_CN;
  321. }
  322. else { // 国外
  323. NSString *appID = [[NSBundle mainBundle] bundleIdentifier];
  324. if ([appID isEqualToString:@"com.jjyang.dadaCamera"] ||
  325. [appID isEqualToString:@"com.dafeng.dadasmartcam"]) { // 大丰项目
  326. return HTTP_REQUEST_URL_STRING_IO_DF;
  327. }
  328. else {
  329. return HTTP_REQUEST_URL_STRING_IO;
  330. }
  331. }
  332. }
  333. - (NSMutableArray<NSString *> *)pendingUploads {
  334. if (!_pendingUploads) {
  335. _pendingUploads = [NSMutableArray array];
  336. }
  337. return _pendingUploads;
  338. }
  339. // 类似:2EAA6CEC-6BAC-43B2-84B2-E528D03C01FD,总长36.
  340. - (NSString *)fetchDeviceUUID {
  341. NSString *uuid = [[UIDevice currentDevice] identifierForVendor].UUIDString;
  342. return uuid;
  343. }
  344. + (void)reportBuriedPointWithType:(NSString *)type {
  345. NSMutableDictionary *params = [NSMutableDictionary dictionary];
  346. [params setObject:type forKey:@"pointName"];
  347. [[UseAccountManage shareInstance] CommonPostCallBackCode:TabBarItemClickAPI Parameters:params success:^(id _Nonnull responseObject) {
  348. } failure:^(NSError * _Nonnull error) {
  349. HLog(@"%@", error);
  350. }];
  351. }
  352. #pragma mark - 全新埋点 7.1
  353. /** 保存或更新埋点数组缓存数据 */
  354. + (void)saveHWBuryPointModelListWithModel:(HWBuryPointModel *)model {
  355. // NSArray *dataSource = [HWBuryPointModel bg_findAll:DB_HWBuryPointModelList_TableName];
  356. // 更新数据库
  357. model.bg_tableName = DB_HWBuryPointModelList_TableName;
  358. [model bg_saveOrUpdateAsync:^(BOOL isSuccess) {
  359. HLog(@"HWBuryPointModel 更新: %@", isSuccess ? @"成功":@"失败");
  360. }];
  361. }
  362. + (void)updateBuryPointModelListWithModel:(HWBuryPointModel *)model{
  363. NSMutableArray *dictArr = [NSMutableArray array];
  364. NSDictionary *dict = [model mj_keyValues];
  365. [dictArr addObject:dict];
  366. [[UseAccountManage shareInstance] extensionPublicToBuryPointPostCallBackCode:NewBuryPointAPI Parameters:dictArr success:^(id _Nonnull responseObject) {
  367. if (CODE == 0) {
  368. HLog(@"\n实时上报完成")
  369. }
  370. } failure:^(NSError * _Nonnull error) {
  371. HLog(@"%@",error)
  372. }];
  373. }
  374. /** 读取埋点数组缓存数据 */
  375. + (NSMutableArray *)readHWBuryPointModelList {
  376. NSMutableArray *array = [NSMutableArray array];
  377. NSArray *dataSource = [HWBuryPointModel bg_findAll:DB_HWBuryPointModelList_TableName];
  378. for (HWBuryPointModel *model in dataSource) {
  379. [array addObject:model];
  380. }
  381. return array;
  382. }
  383. /** 清空埋点数组缓存数据 */
  384. + (void)clearHWBuryPointModelList {
  385. [HWBuryPointModel bg_clearAsync:DB_HWBuryPointModelList_TableName complete:^(BOOL isSuccess) {
  386. HLog(@"HWBuryPointModel 数据清空: %@", isSuccess ? @"成功":@"失败");
  387. }];
  388. }
  389. @end