// // XJHealthKnowledgeManager.m // XinjiangProject // // Created by Apple on 2026/6/29. // #import "XJHealthKnowledgeManager.h" #import #pragma mark - ————— Manager ————— @interface XJHealthKnowledgeManager () @property (nonatomic, copy) NSString * cachedToken; @end @implementation XJHealthKnowledgeManager + (instancetype)sharedManager { static XJHealthKnowledgeManager * instance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[XJHealthKnowledgeManager alloc] init]; }); return instance; } #pragma mark - ————— HMAC-SHA256 ————— - (void)clearToken { self.cachedToken = nil; DLog(@"[健康知识] Token 已清除"); } + (NSString *)hmacSHA256:(NSString *)data key:(NSString *)key { const char * cKey = [key cStringUsingEncoding:NSUTF8StringEncoding]; const char * cData = [data cStringUsingEncoding:NSUTF8StringEncoding]; unsigned char result[CC_SHA256_DIGEST_LENGTH]; CCHmac(kCCHmacAlgSHA256, cKey, strlen(cKey), cData, strlen(cData), result); NSMutableString * hash = [[NSMutableString alloc] initWithCapacity:CC_SHA256_DIGEST_LENGTH * 2]; for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) { [hash appendFormat:@"%02x", result[i]]; } return hash; } #pragma mark - ————— 获取 Token ————— - (void)getTokenWithUserId:(NSString *)userId workNo:(NSString *)workNo success:(void (^)(NSString *))success failure:(void (^)(NSString *))failure { NSString * urlString = [NSString stringWithFormat:@"%@%@", kHealthKnowledgeBaseURL, kHealthKnowledgeTokenPath]; // 构建请求体 NSDictionary * bodyDict = @{ @"userId": userId ?: @"", @"workNo": workNo ?: @"" }; NSData * bodyData = [NSJSONSerialization dataWithJSONObject:bodyDict options:0 error:nil]; NSString * bodyString = [[NSString alloc] initWithData:bodyData encoding:NSUTF8StringEncoding]; // 签名 NSString * timestamp = [NSString stringWithFormat:@"%.0f", [[NSDate date] timeIntervalSince1970]]; NSString * stringToSign = [NSString stringWithFormat:@"%@%@", timestamp, bodyString]; NSString * sign = [[self class] hmacSHA256:stringToSign key:kHealthKnowledgeSecretKey]; NSMutableURLRequest * request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:urlString]]; request.HTTPMethod = @"POST"; [request setValue:@"application/json; charset=utf-8" forHTTPHeaderField:@"Content-Type"]; [request setValue:timestamp forHTTPHeaderField:@"X-Timestamp"]; [request setValue:sign forHTTPHeaderField:@"X-Sign"]; request.HTTPBody = bodyData; DLog(@"[健康知识] 请求 Token URL: %@", urlString); DLog(@"[健康知识] 签名串: %@", stringToSign); DLog(@"[健康知识] X-Sign: %@", sign); [self sendRequest:request completion:^(NSDictionary * json) { DLog(@"[健康知识] Token 响应: %@", json); if ([json[@"code"] integerValue] == 200) { NSString * token = json[@"data"][@"token"]; if (!ValidStr(token)) { self.cachedToken = token; DLog(@"[健康知识] Token 获取成功: %@", token); if (success) success(token); } else { DLog(@"[健康知识] Token 为空"); if (failure) failure(@"token 为空"); } } else { DLog(@"[健康知识] Token 失败: %@", json[@"msg"]); if (failure) failure(json[@"msg"] ?: @"获取 token 失败"); } } failure:^(NSString * errorMsg) { DLog(@"[健康知识] Token 请求异常: %@", errorMsg); if (failure) failure(errorMsg); }]; } #pragma mark - ————— 栏目列表 ————— - (void)getCategoryListWithToken:(NSString *)token success:(void (^)(NSArray *))success failure:(void (^)(NSString *))failure { NSString * urlString = [NSString stringWithFormat:@"%@%@", kHealthKnowledgeBaseURL, kHealthKnowledgeCategoryPath]; NSMutableURLRequest * request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:urlString]]; request.HTTPMethod = @"GET"; [request setValue:token forHTTPHeaderField:@"token"]; [self sendRequest:request completion:^(NSDictionary * json) { if ([json[@"code"] integerValue] == 200) { NSArray * dataArray = json[@"data"]; NSMutableArray * categories = [[NSMutableArray alloc] init]; if ([dataArray isKindOfClass:[NSArray class]]) { for (NSDictionary * dict in dataArray) { XJHealthCategoryModel * m = [XJHealthCategoryModel modelWithDictionary:dict]; [categories addObject:m]; } } if (success) success(categories); } else { if (failure) failure(json[@"msg"] ?: @"获取栏目列表失败"); } } failure:^(NSString * errorMsg) { if (failure) failure(errorMsg); }]; } #pragma mark - ————— 分页查询文章 ————— - (void)getArticleListWithToken:(NSString *)token categoryId:(NSInteger)categoryId articleType:(NSString *)articleType pageNum:(NSInteger)pageNum pageSize:(NSInteger)pageSize success:(void (^)(NSArray *, NSInteger))success failure:(void (^)(NSString *))failure { NSMutableString * urlString = [NSMutableString stringWithFormat:@"%@%@", kHealthKnowledgeBaseURL, kHealthKnowledgeArticleListPath]; [urlString appendFormat:@"?categoryId=%ld&pageNum=%ld&pageSize=%ld&showAppHomepage=Y", (long)categoryId, (long)pageNum, (long)pageSize]; if (!ValidStr(articleType)) { [urlString appendFormat:@"&articleType=%@", articleType]; } NSMutableURLRequest * request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:urlString]]; request.HTTPMethod = @"GET"; [request setValue:token forHTTPHeaderField:@"token"]; [self sendRequest:request completion:^(NSDictionary * json) { if ([json[@"code"] integerValue] == 200) { NSArray * rows = json[@"rows"]; NSInteger total = [json[@"total"] integerValue]; NSMutableArray * articles = [[NSMutableArray alloc] init]; if ([rows isKindOfClass:[NSArray class]]) { for (NSDictionary * dict in rows) { XJHealthArticleModel * m = [XJHealthArticleModel modelWithDictionary:dict]; [articles addObject:m]; } } if (success) success(articles, total); } else { if (failure) failure(json[@"msg"] ?: @"获取文章列表失败"); } } failure:^(NSString * errorMsg) { if (failure) failure(errorMsg); }]; } #pragma mark - ————— 通用请求 ————— - (void)sendRequest:(NSURLRequest *)request completion:(void (^)(NSDictionary * json))completion failure:(void (^)(NSString * errorMsg))failure { DLog(@"[健康知识] >>> 请求: %@ %@", request.HTTPMethod, request.URL.absoluteString); NSURLSession * session = [NSURLSession sharedSession]; NSURLSessionDataTask * task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { dispatch_async(dispatch_get_main_queue(), ^{ if (error) { DLog(@"[健康知识] <<< 网络错误: %@", error.localizedDescription); if (failure) failure(error.localizedDescription); return; } NSHTTPURLResponse * httpResp = (NSHTTPURLResponse *)response; NSInteger statusCode = httpResp.statusCode; DLog(@"[健康知识] <<< HTTP %ld", (long)statusCode); if (statusCode != 200) { NSString * msg = [NSString stringWithFormat:@"HTTP %ld", (long)statusCode]; DLog(@"[健康知识] <<< 请求失败: %@", msg); if (failure) failure(msg); return; } NSError * jsonError = nil; NSDictionary * json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; if (jsonError || ![json isKindOfClass:[NSDictionary class]]) { DLog(@"[健康知识] <<< JSON 解析失败"); if (failure) failure(@"数据解析失败"); return; } DLog(@"[健康知识] <<< 响应: %@", json); if (completion) completion(json); }); }]; [task resume]; } @end