// // JXAPIService.m // AICity // // Created by TogetherWatch on 2025-10-13. // #import "JXAPIService.h" #import "RequestTool.h" @interface JXAPIService () @property (nonatomic, strong) JXNetworkManager *networkManager; @end @implementation JXAPIService + (instancetype)sharedService { static JXAPIService *instance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[self alloc] init]; }); return instance; } - (instancetype)init { self = [super init]; if (self) { _networkManager = [JXNetworkManager sharedManager]; } return self; } #pragma mark - 首页相关API - (void)getHomeHeaderDataWithSuccess:(void (^)(JXHomeHeaderData *))success failure:(JXFailureBlock)failure { [RequestTool post:@"home/homePage" params:@{} success:^(id responseObject) { NSLog(@"[JXAPIService] home/homePage response: %@", responseObject); // 防护检查:responseObject 不能为 nil if (!responseObject) { NSLog(@"[JXAPIService] home/homePage response is nil, degradation mode"); if (failure) { NSError *error = [NSError errorWithDomain:@"JXAPIService" code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Empty response"}]; failure(error); } return; } if ([responseObject[@"code"] intValue] == 0 || [responseObject[@"code"] intValue] == 200) { // 解析数据 NSDictionary *data = responseObject[@"data"]; JXHomeHeaderData *headerData = [JXHomeHeaderData modelWithDictionary:data]; // 确保返回有效的数据对象 if (!headerData) { headerData = [[JXHomeHeaderData alloc] init]; headerData.banners = @[]; headerData.hotMovies = @[]; headerData.hotDramas = @[]; } if (success) { success(headerData); } } else { // 服务器返回错误码 NSLog(@"[JXAPIService] home/homePage server error: code=%@, msg=%@", responseObject[@"code"], responseObject[@"msg"]); if (failure) { NSError *error = [NSError errorWithDomain:@"JXAPIService" code:[responseObject[@"code"] intValue] userInfo:@{NSLocalizedDescriptionKey: responseObject[@"msg"] ?: @"Unknown error"}]; failure(error); } } } failure:^(NSError *error) { NSLog(@"[JXAPIService] home/homePage network error: %@ (code: %ld)", error.localizedDescription, (long)error.code); if (failure) { failure(error); } }]; } - (void)getDailyRecommendWithPage:(NSInteger)page pageSize:(NSInteger)pageSize success:(JXSuccessBlock)success failure:(JXFailureBlock)failure { NSDictionary *params = @{ @"page": @{ @"page": @(page), @"pageSize": @(pageSize) } }; [RequestTool post:@"home/dailyRecommend" params:params success:^(id responseObject) { NSLog(@"[JXAPIService] home/dailyRecommend response: page=%ld, response=%@", (long)page, responseObject); // 防护检查:responseObject 不能为 nil if (!responseObject) { NSLog(@"[JXAPIService] home/dailyRecommend response is nil, degradation mode"); if (failure) { NSError *error = [NSError errorWithDomain:@"JXAPIService" code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Empty response"}]; failure(error); } return; } // 降级处理:即使响应不完整也返回可用的数据结构 NSMutableDictionary *degradedResponse = [NSMutableDictionary dictionaryWithDictionary:responseObject]; if (!degradedResponse[@"code"]) { degradedResponse[@"code"] = @(200); } // 确保 data 是一个可变字典 if (!degradedResponse[@"data"] || ![degradedResponse[@"data"] isKindOfClass:[NSMutableDictionary class]]) { NSLog(@"[JXAPIService] data is not NSMutableDictionary, type: %@", degradedResponse[@"data"] ? NSStringFromClass([degradedResponse[@"data"] class]) : @"nil"); degradedResponse[@"data"] = [NSMutableDictionary dictionary]; } // 安全地访问 data 字典 NSMutableDictionary *dataDict = degradedResponse[@"data"]; if (!dataDict) { NSLog(@"[JXAPIService] ERROR: dataDict is still nil after initialization!"); dataDict = [NSMutableDictionary dictionary]; degradedResponse[@"data"] = dataDict; } // 确保 list 是一个数组 if (!dataDict[@"list"] || ![dataDict[@"list"] isKindOfClass:[NSArray class]]) { NSLog(@"[JXAPIService] list is not NSArray, type: %@", dataDict[@"list"] ? NSStringFromClass([dataDict[@"list"] class]) : @"nil"); dataDict[@"list"] = @[]; } // 安全地获取列表计数 NSArray *list = dataDict[@"list"]; NSUInteger listCount = [list isKindOfClass:[NSArray class]] ? list.count : 0; NSLog(@"[JXAPIService] home/dailyRecommend degraded response: code=%@, list_count=%lu", degradedResponse[@"code"], (unsigned long)listCount); if (success) { success(degradedResponse); } } failure:^(NSError *error) { NSLog(@"[JXAPIService] home/dailyRecommend network error: page=%ld, error=%@ (code: %ld)", (long)page, error.localizedDescription, (long)error.code); if (failure) { failure(error); } }]; } #pragma mark - 内容相关API - (void)getCategoriesWithSuccess:(JXSuccessBlock)success failure:(JXFailureBlock)failure { [self.networkManager GET:@"juxing/categories" parameters:nil success:success failure:failure]; } - (void)getDramaListWithCategoryId:(NSString *)categoryId page:(NSInteger)page pageSize:(NSInteger)pageSize success:(JXSuccessBlock)success failure:(JXFailureBlock)failure { NSMutableDictionary *params = [NSMutableDictionary dictionary]; if (categoryId && categoryId.length > 0) { params[@"category_id"] = categoryId; } params[@"page"] = @(page); params[@"page_size"] = @(pageSize); [self.networkManager GET:@"juxing/dramas" parameters:params success:success failure:failure]; } - (void)getDramaDetailWithDramaId:(NSString *)dramaId success:(JXSuccessBlock)success failure:(JXFailureBlock)failure { NSString *path = [NSString stringWithFormat:@"juxing/dramas/%@", dramaId]; [self.networkManager GET:path parameters:@{@"include_episodes": @YES} success:success failure:failure]; } - (void)searchDramasWithKeyword:(NSString *)keyword page:(NSInteger)page pageSize:(NSInteger)pageSize success:(JXSuccessBlock)success failure:(JXFailureBlock)failure { NSDictionary *params = @{ @"keyword": keyword, @"page": @(page), @"page_size": @(pageSize) }; [self.networkManager GET:@"juxing/dramas/search" parameters:params success:success failure:failure]; } - (void)getInteractionDataWithDramaId:(NSString *)dramaId episodeId:(NSString *)episodeId success:(JXSuccessBlock)success failure:(JXFailureBlock)failure { // 交互数据包含在短剧详情中 [self getDramaDetailWithDramaId:dramaId success:^(id responseObject) { NSDictionary *data = responseObject[@"data"]; if (success) { success(data); } } failure:failure]; } #pragma mark - 播放相关API - (void)getPlayURLWithEpisodeId:(NSString *)episodeId success:(JXSuccessBlock)success failure:(JXFailureBlock)failure { NSString *path = [NSString stringWithFormat:@"juxing/playback/url/%@", episodeId]; [self.networkManager GET:path parameters:nil success:success failure:failure]; } - (void)reportPlaybackProgressWithEpisodeId:(NSString *)episodeId position:(NSInteger)position duration:(NSInteger)duration isCompleted:(BOOL)isCompleted success:(JXSuccessBlock)success failure:(JXFailureBlock)failure { NSDictionary *params = @{ @"episodeId": episodeId, @"position": @(position), @"duration": @(duration), @"isCompleted": @(isCompleted) }; [self.networkManager POST:@"juxing/playback/progress" parameters:params success:success failure:failure]; } - (void)getPlaybackProgressWithEpisodeId:(NSString *)episodeId success:(JXSuccessBlock)success failure:(JXFailureBlock)failure { NSString *path = [NSString stringWithFormat:@"juxing/playback/progress/%@", episodeId]; [self.networkManager GET:path parameters:nil success:success failure:failure]; } #pragma mark - 交互相关API - (void)toggleLikeWithDramaId:(NSString *)dramaId episodeId:(NSString *)episodeId success:(JXSuccessBlock)success failure:(JXFailureBlock)failure { NSDictionary *params = @{ @"dramaId": dramaId, @"episodeId": episodeId }; [self.networkManager POST:@"juxing/interaction/like" parameters:params success:success failure:failure]; } - (void)toggleFavoriteWithDramaId:(NSString *)dramaId success:(JXSuccessBlock)success failure:(JXFailureBlock)failure { NSDictionary *params = @{ @"dramaId": dramaId }; [self.networkManager POST:@"juxing/interaction/favorite" parameters:params success:success failure:failure]; } - (void)toggleFollowWithDramaId:(NSString *)dramaId success:(JXSuccessBlock)success failure:(JXFailureBlock)failure { NSDictionary *params = @{ @"dramaId": dramaId }; [self.networkManager POST:@"juxing/interaction/follow" parameters:params success:success failure:failure]; } #pragma mark - 评论相关API - (void)getCommentsWithDramaId:(NSString *)dramaId page:(NSInteger)page pageSize:(NSInteger)pageSize success:(JXSuccessBlock)success failure:(JXFailureBlock)failure { NSString *path = [NSString stringWithFormat:@"juxing/dramas/%@/comments", dramaId]; NSDictionary *params = @{ @"page": @(page), @"page_size": @(pageSize) }; [self.networkManager GET:path parameters:params success:success failure:failure]; } - (void)submitCommentWithDramaId:(NSString *)dramaId content:(NSString *)content success:(JXSuccessBlock)success failure:(JXFailureBlock)failure { NSString *path = [NSString stringWithFormat:@"juxing/dramas/%@/comments", dramaId]; NSDictionary *params = @{ @"content": content }; [self.networkManager POST:path parameters:params success:success failure:failure]; } - (void)likeCommentWithCommentId:(long long)commentId success:(JXSuccessBlock)success failure:(JXFailureBlock)failure { NSString *path = [NSString stringWithFormat:@"juxing/comments/%lld/like", commentId]; [self.networkManager POST:path parameters:nil success:success failure:failure]; } - (void)replyCommentWithCommentId:(long long)commentId content:(NSString *)content success:(JXSuccessBlock)success failure:(JXFailureBlock)failure { NSString *path = [NSString stringWithFormat:@"juxing/comments/%lld/reply", commentId]; NSDictionary *params = @{ @"content": content }; [self.networkManager POST:path parameters:params success:success failure:failure]; } #pragma mark - 合集相关API - (void)getEpisodesWithDramaId:(NSString *)dramaId success:(JXSuccessBlock)success failure:(JXFailureBlock)failure { // 调用正确的剧集列表接口:/api/v1/dramas/{dramaId}/episodes NSString *path = [NSString stringWithFormat:@"juxing/dramas/%@/episodes", dramaId]; NSLog(@"[JXAPIService] 开始获取剧集列表: path=%@, dramaId=%@", path, dramaId); [[JXNetworkManager sharedManager] GET:path parameters:@{ @"range_start": @(0), @"range_end": @(100) // 获取前100集 } success:^(id responseObject) { NSLog(@"[JXAPIService] 获取剧集列表响应: %@", responseObject); // 检查 responseObject 和 code if (!responseObject || ![responseObject isKindOfClass:[NSDictionary class]]) { NSLog(@"[JXAPIService] ERROR: 剧集列表响应无效"); if (success) { success(@{@"data": @{@"episodes": @[]}}); } return; } NSInteger code = [responseObject[@"code"] integerValue]; NSString *msg = responseObject[@"msg"] ?: @""; NSLog(@"[JXAPIService] 响应 code=%ld, msg=%@", (long)code, msg); if (code != 0) { NSLog(@"[JXAPIService] ERROR: 获取剧集列表失败,code=%ld, msg=%@", (long)code, msg); if (success) { success(@{@"data": @{@"episodes": @[]}}); } return; } // 响应格式: { code: 0, data: { list: [...], total: N }, msg: "..." } NSDictionary *data = responseObject[@"data"]; NSLog(@"[JXAPIService] data 类型: %@, data=%@", NSStringFromClass([data class]), data); NSArray *episodes = data[@"list"] ?: @[]; // 剧集列表在 "list" 字段 NSLog(@"[JXAPIService] 获取到 %lu 集", (unsigned long)episodes.count); if (episodes.count > 0) { NSDictionary *firstEp = episodes[0]; NSLog(@"[JXAPIService] 第一集数据: %@", firstEp.allKeys); NSLog(@"[JXAPIService] - tcplayer_app_id: %@", firstEp[@"tcplayer_app_id"]); NSLog(@"[JXAPIService] - tcplayer_file_id: %@", firstEp[@"tcplayer_file_id"]); NSLog(@"[JXAPIService] - tcplayer_sign 长度: %lu", (unsigned long)[firstEp[@"tcplayer_sign"] length]); } if (success) { // 返回完整的 episodes 数据,包含 tcplayer 字段 success(@{ @"data": @{ @"episodes": episodes } }); } } failure:^(NSError *error) { NSLog(@"[JXAPIService] 获取剧集列表 HTTP 错误: %@", error.localizedDescription); if (failure) { failure(error); } }]; } - (void)incrementPlayCountWithDramaId:(NSString *)dramaId success:(JXSuccessBlock)success failure:(JXFailureBlock)failure { // 增加播放次数接口 NSString *path = [NSString stringWithFormat:@"juxing/dramas/%@/play", dramaId]; [self.networkManager POST:path parameters:@{} success:success failure:failure]; } @end