Files
xj-ios/XinjiangProject/XinjiangProject/Manager/XJHealthKnowledgeManager.m
T
jiayangyangandClaude Opus 4.7 6337454956 feat(home): 首页增加健康知识/资讯模块,含双视图列表及H5跳转
- 新增健康知识视图(标签切换+搜索+列表)和健康资讯视图(纯列表无搜索)
- 对接健康栏目列表和文章分页接口,资讯无子分类直接用父级ID
- 接入更多/搜索/详情H5跳转,统一用通用加密串传参
- 键盘弹起时自动避让,从H5回来刷新文章收藏数

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-29 18:46:58 +08:00

223 lines
8.9 KiB
Objective-C

//
// XJHealthKnowledgeManager.m
// XinjiangProject
//
// Created by Apple on 2026/6/29.
//
#import "XJHealthKnowledgeManager.h"
#import <CommonCrypto/CommonHMAC.h>
#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<XJHealthCategoryModel *> *))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<XJHealthArticleModel *> *, 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