JXAPIService.m 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. //
  2. // JXAPIService.m
  3. // AICity
  4. //
  5. // Created by TogetherWatch on 2025-10-13.
  6. //
  7. #import "JXAPIService.h"
  8. #import "RequestTool.h"
  9. @interface JXAPIService ()
  10. @property (nonatomic, strong) JXNetworkManager *networkManager;
  11. @end
  12. @implementation JXAPIService
  13. + (instancetype)sharedService {
  14. static JXAPIService *instance = nil;
  15. static dispatch_once_t onceToken;
  16. dispatch_once(&onceToken, ^{
  17. instance = [[self alloc] init];
  18. });
  19. return instance;
  20. }
  21. - (instancetype)init {
  22. self = [super init];
  23. if (self) {
  24. _networkManager = [JXNetworkManager sharedManager];
  25. }
  26. return self;
  27. }
  28. #pragma mark - 首页相关API
  29. - (void)getHomeHeaderDataWithSuccess:(void (^)(JXHomeHeaderData *))success
  30. failure:(JXFailureBlock)failure {
  31. [RequestTool post:@"home/homePage" params:@{} success:^(id responseObject) {
  32. NSLog(@"[JXAPIService] home/homePage response: %@", responseObject);
  33. // 防护检查:responseObject 不能为 nil
  34. if (!responseObject) {
  35. NSLog(@"[JXAPIService] home/homePage response is nil, degradation mode");
  36. if (failure) {
  37. NSError *error = [NSError errorWithDomain:@"JXAPIService"
  38. code:-1
  39. userInfo:@{NSLocalizedDescriptionKey: @"Empty response"}];
  40. failure(error);
  41. }
  42. return;
  43. }
  44. if ([responseObject[@"code"] intValue] == 0 || [responseObject[@"code"] intValue] == 200) {
  45. // 解析数据
  46. NSDictionary *data = responseObject[@"data"];
  47. JXHomeHeaderData *headerData = [JXHomeHeaderData modelWithDictionary:data];
  48. // 确保返回有效的数据对象
  49. if (!headerData) {
  50. headerData = [[JXHomeHeaderData alloc] init];
  51. headerData.banners = @[];
  52. headerData.hotMovies = @[];
  53. headerData.hotDramas = @[];
  54. }
  55. if (success) {
  56. success(headerData);
  57. }
  58. } else {
  59. // 服务器返回错误码
  60. NSLog(@"[JXAPIService] home/homePage server error: code=%@, msg=%@", responseObject[@"code"], responseObject[@"msg"]);
  61. if (failure) {
  62. NSError *error = [NSError errorWithDomain:@"JXAPIService"
  63. code:[responseObject[@"code"] intValue]
  64. userInfo:@{NSLocalizedDescriptionKey: responseObject[@"msg"] ?: @"Unknown error"}];
  65. failure(error);
  66. }
  67. }
  68. } failure:^(NSError *error) {
  69. NSLog(@"[JXAPIService] home/homePage network error: %@ (code: %ld)", error.localizedDescription, (long)error.code);
  70. if (failure) {
  71. failure(error);
  72. }
  73. }];
  74. }
  75. - (void)getDailyRecommendWithPage:(NSInteger)page
  76. pageSize:(NSInteger)pageSize
  77. success:(JXSuccessBlock)success
  78. failure:(JXFailureBlock)failure {
  79. NSDictionary *params = @{
  80. @"page": @{
  81. @"page": @(page),
  82. @"pageSize": @(pageSize)
  83. }
  84. };
  85. [RequestTool post:@"home/dailyRecommend" params:params success:^(id responseObject) {
  86. NSLog(@"[JXAPIService] home/dailyRecommend response: page=%ld, response=%@", (long)page, responseObject);
  87. // 防护检查:responseObject 不能为 nil
  88. if (!responseObject) {
  89. NSLog(@"[JXAPIService] home/dailyRecommend response is nil, degradation mode");
  90. if (failure) {
  91. NSError *error = [NSError errorWithDomain:@"JXAPIService"
  92. code:-1
  93. userInfo:@{NSLocalizedDescriptionKey: @"Empty response"}];
  94. failure(error);
  95. }
  96. return;
  97. }
  98. // 降级处理:即使响应不完整也返回可用的数据结构
  99. NSMutableDictionary *degradedResponse = [NSMutableDictionary dictionaryWithDictionary:responseObject];
  100. if (!degradedResponse[@"code"]) {
  101. degradedResponse[@"code"] = @(200);
  102. }
  103. // 确保 data 是一个可变字典
  104. if (!degradedResponse[@"data"] || ![degradedResponse[@"data"] isKindOfClass:[NSMutableDictionary class]]) {
  105. NSLog(@"[JXAPIService] data is not NSMutableDictionary, type: %@",
  106. degradedResponse[@"data"] ? NSStringFromClass([degradedResponse[@"data"] class]) : @"nil");
  107. degradedResponse[@"data"] = [NSMutableDictionary dictionary];
  108. }
  109. // 安全地访问 data 字典
  110. NSMutableDictionary *dataDict = degradedResponse[@"data"];
  111. if (!dataDict) {
  112. NSLog(@"[JXAPIService] ERROR: dataDict is still nil after initialization!");
  113. dataDict = [NSMutableDictionary dictionary];
  114. degradedResponse[@"data"] = dataDict;
  115. }
  116. // 确保 list 是一个数组
  117. if (!dataDict[@"list"] || ![dataDict[@"list"] isKindOfClass:[NSArray class]]) {
  118. NSLog(@"[JXAPIService] list is not NSArray, type: %@",
  119. dataDict[@"list"] ? NSStringFromClass([dataDict[@"list"] class]) : @"nil");
  120. dataDict[@"list"] = @[];
  121. }
  122. // 安全地获取列表计数
  123. NSArray *list = dataDict[@"list"];
  124. NSUInteger listCount = [list isKindOfClass:[NSArray class]] ? list.count : 0;
  125. NSLog(@"[JXAPIService] home/dailyRecommend degraded response: code=%@, list_count=%lu",
  126. degradedResponse[@"code"],
  127. (unsigned long)listCount);
  128. if (success) {
  129. success(degradedResponse);
  130. }
  131. } failure:^(NSError *error) {
  132. NSLog(@"[JXAPIService] home/dailyRecommend network error: page=%ld, error=%@ (code: %ld)",
  133. (long)page, error.localizedDescription, (long)error.code);
  134. if (failure) {
  135. failure(error);
  136. }
  137. }];
  138. }
  139. #pragma mark - 内容相关API
  140. - (void)getCategoriesWithSuccess:(JXSuccessBlock)success
  141. failure:(JXFailureBlock)failure {
  142. [self.networkManager GET:@"juxing/categories"
  143. parameters:nil
  144. success:success
  145. failure:failure];
  146. }
  147. - (void)getDramaListWithCategoryId:(NSString *)categoryId
  148. page:(NSInteger)page
  149. pageSize:(NSInteger)pageSize
  150. success:(JXSuccessBlock)success
  151. failure:(JXFailureBlock)failure {
  152. NSMutableDictionary *params = [NSMutableDictionary dictionary];
  153. if (categoryId && categoryId.length > 0) {
  154. params[@"category_id"] = categoryId;
  155. }
  156. params[@"page"] = @(page);
  157. params[@"page_size"] = @(pageSize);
  158. [self.networkManager GET:@"juxing/dramas"
  159. parameters:params
  160. success:success
  161. failure:failure];
  162. }
  163. - (void)getDramaDetailWithDramaId:(NSString *)dramaId
  164. success:(JXSuccessBlock)success
  165. failure:(JXFailureBlock)failure {
  166. NSString *path = [NSString stringWithFormat:@"juxing/dramas/%@", dramaId];
  167. [self.networkManager GET:path
  168. parameters:@{@"include_episodes": @YES}
  169. success:success
  170. failure:failure];
  171. }
  172. - (void)searchDramasWithKeyword:(NSString *)keyword
  173. page:(NSInteger)page
  174. pageSize:(NSInteger)pageSize
  175. success:(JXSuccessBlock)success
  176. failure:(JXFailureBlock)failure {
  177. NSDictionary *params = @{
  178. @"keyword": keyword,
  179. @"page": @(page),
  180. @"page_size": @(pageSize)
  181. };
  182. [self.networkManager GET:@"juxing/dramas/search"
  183. parameters:params
  184. success:success
  185. failure:failure];
  186. }
  187. - (void)getInteractionDataWithDramaId:(NSString *)dramaId
  188. episodeId:(NSString *)episodeId
  189. success:(JXSuccessBlock)success
  190. failure:(JXFailureBlock)failure {
  191. // 交互数据包含在短剧详情中
  192. [self getDramaDetailWithDramaId:dramaId success:^(id responseObject) {
  193. NSDictionary *data = responseObject[@"data"];
  194. if (success) {
  195. success(data);
  196. }
  197. } failure:failure];
  198. }
  199. #pragma mark - 播放相关API
  200. - (void)getPlayURLWithEpisodeId:(NSString *)episodeId
  201. success:(JXSuccessBlock)success
  202. failure:(JXFailureBlock)failure {
  203. NSString *path = [NSString stringWithFormat:@"juxing/playback/url/%@", episodeId];
  204. [self.networkManager GET:path
  205. parameters:nil
  206. success:success
  207. failure:failure];
  208. }
  209. - (void)reportPlaybackProgressWithEpisodeId:(NSString *)episodeId
  210. position:(NSInteger)position
  211. duration:(NSInteger)duration
  212. isCompleted:(BOOL)isCompleted
  213. success:(JXSuccessBlock)success
  214. failure:(JXFailureBlock)failure {
  215. NSDictionary *params = @{
  216. @"episodeId": episodeId,
  217. @"position": @(position),
  218. @"duration": @(duration),
  219. @"isCompleted": @(isCompleted)
  220. };
  221. [self.networkManager POST:@"juxing/playback/progress"
  222. parameters:params
  223. success:success
  224. failure:failure];
  225. }
  226. - (void)getPlaybackProgressWithEpisodeId:(NSString *)episodeId
  227. success:(JXSuccessBlock)success
  228. failure:(JXFailureBlock)failure {
  229. NSString *path = [NSString stringWithFormat:@"juxing/playback/progress/%@", episodeId];
  230. [self.networkManager GET:path
  231. parameters:nil
  232. success:success
  233. failure:failure];
  234. }
  235. #pragma mark - 交互相关API
  236. - (void)toggleLikeWithDramaId:(NSString *)dramaId
  237. episodeId:(NSString *)episodeId
  238. success:(JXSuccessBlock)success
  239. failure:(JXFailureBlock)failure {
  240. NSDictionary *params = @{
  241. @"dramaId": dramaId,
  242. @"episodeId": episodeId
  243. };
  244. [self.networkManager POST:@"juxing/interaction/like"
  245. parameters:params
  246. success:success
  247. failure:failure];
  248. }
  249. - (void)toggleFavoriteWithDramaId:(NSString *)dramaId
  250. success:(JXSuccessBlock)success
  251. failure:(JXFailureBlock)failure {
  252. NSDictionary *params = @{
  253. @"dramaId": dramaId
  254. };
  255. [self.networkManager POST:@"juxing/interaction/favorite"
  256. parameters:params
  257. success:success
  258. failure:failure];
  259. }
  260. - (void)toggleFollowWithDramaId:(NSString *)dramaId
  261. success:(JXSuccessBlock)success
  262. failure:(JXFailureBlock)failure {
  263. NSDictionary *params = @{
  264. @"dramaId": dramaId
  265. };
  266. [self.networkManager POST:@"juxing/interaction/follow"
  267. parameters:params
  268. success:success
  269. failure:failure];
  270. }
  271. #pragma mark - 评论相关API
  272. - (void)getCommentsWithDramaId:(NSString *)dramaId
  273. page:(NSInteger)page
  274. pageSize:(NSInteger)pageSize
  275. success:(JXSuccessBlock)success
  276. failure:(JXFailureBlock)failure {
  277. NSString *path = [NSString stringWithFormat:@"juxing/dramas/%@/comments", dramaId];
  278. NSDictionary *params = @{
  279. @"page": @(page),
  280. @"page_size": @(pageSize)
  281. };
  282. [self.networkManager GET:path
  283. parameters:params
  284. success:success
  285. failure:failure];
  286. }
  287. - (void)submitCommentWithDramaId:(NSString *)dramaId
  288. content:(NSString *)content
  289. success:(JXSuccessBlock)success
  290. failure:(JXFailureBlock)failure {
  291. NSString *path = [NSString stringWithFormat:@"juxing/dramas/%@/comments", dramaId];
  292. NSDictionary *params = @{
  293. @"content": content
  294. };
  295. [self.networkManager POST:path
  296. parameters:params
  297. success:success
  298. failure:failure];
  299. }
  300. - (void)likeCommentWithCommentId:(long long)commentId
  301. success:(JXSuccessBlock)success
  302. failure:(JXFailureBlock)failure {
  303. NSString *path = [NSString stringWithFormat:@"juxing/comments/%lld/like", commentId];
  304. [self.networkManager POST:path
  305. parameters:nil
  306. success:success
  307. failure:failure];
  308. }
  309. - (void)replyCommentWithCommentId:(long long)commentId
  310. content:(NSString *)content
  311. success:(JXSuccessBlock)success
  312. failure:(JXFailureBlock)failure {
  313. NSString *path = [NSString stringWithFormat:@"juxing/comments/%lld/reply", commentId];
  314. NSDictionary *params = @{
  315. @"content": content
  316. };
  317. [self.networkManager POST:path
  318. parameters:params
  319. success:success
  320. failure:failure];
  321. }
  322. #pragma mark - 合集相关API
  323. - (void)getEpisodesWithDramaId:(NSString *)dramaId
  324. success:(JXSuccessBlock)success
  325. failure:(JXFailureBlock)failure {
  326. // 调用正确的剧集列表接口:/api/v1/dramas/{dramaId}/episodes
  327. NSString *path = [NSString stringWithFormat:@"juxing/dramas/%@/episodes", dramaId];
  328. NSLog(@"[JXAPIService] 开始获取剧集列表: path=%@, dramaId=%@", path, dramaId);
  329. [[JXNetworkManager sharedManager] GET:path
  330. parameters:@{
  331. @"range_start": @(0),
  332. @"range_end": @(100) // 获取前100集
  333. }
  334. success:^(id responseObject) {
  335. NSLog(@"[JXAPIService] 获取剧集列表响应: %@", responseObject);
  336. // 检查 responseObject 和 code
  337. if (!responseObject || ![responseObject isKindOfClass:[NSDictionary class]]) {
  338. NSLog(@"[JXAPIService] ERROR: 剧集列表响应无效");
  339. if (success) {
  340. success(@{@"data": @{@"episodes": @[]}});
  341. }
  342. return;
  343. }
  344. NSInteger code = [responseObject[@"code"] integerValue];
  345. NSString *msg = responseObject[@"msg"] ?: @"";
  346. NSLog(@"[JXAPIService] 响应 code=%ld, msg=%@", (long)code, msg);
  347. if (code != 0) {
  348. NSLog(@"[JXAPIService] ERROR: 获取剧集列表失败,code=%ld, msg=%@", (long)code, msg);
  349. if (success) {
  350. success(@{@"data": @{@"episodes": @[]}});
  351. }
  352. return;
  353. }
  354. // 响应格式: { code: 0, data: { list: [...], total: N }, msg: "..." }
  355. NSDictionary *data = responseObject[@"data"];
  356. NSLog(@"[JXAPIService] data 类型: %@, data=%@", NSStringFromClass([data class]), data);
  357. NSArray *episodes = data[@"list"] ?: @[]; // 剧集列表在 "list" 字段
  358. NSLog(@"[JXAPIService] 获取到 %lu 集", (unsigned long)episodes.count);
  359. if (episodes.count > 0) {
  360. NSDictionary *firstEp = episodes[0];
  361. NSLog(@"[JXAPIService] 第一集数据: %@", firstEp.allKeys);
  362. NSLog(@"[JXAPIService] - tcplayer_app_id: %@", firstEp[@"tcplayer_app_id"]);
  363. NSLog(@"[JXAPIService] - tcplayer_file_id: %@", firstEp[@"tcplayer_file_id"]);
  364. NSLog(@"[JXAPIService] - tcplayer_sign 长度: %lu", (unsigned long)[firstEp[@"tcplayer_sign"] length]);
  365. }
  366. if (success) {
  367. // 返回完整的 episodes 数据,包含 tcplayer 字段
  368. success(@{
  369. @"data": @{
  370. @"episodes": episodes
  371. }
  372. });
  373. }
  374. } failure:^(NSError *error) {
  375. NSLog(@"[JXAPIService] 获取剧集列表 HTTP 错误: %@", error.localizedDescription);
  376. if (failure) {
  377. failure(error);
  378. }
  379. }];
  380. }
  381. - (void)incrementPlayCountWithDramaId:(NSString *)dramaId
  382. success:(JXSuccessBlock)success
  383. failure:(JXFailureBlock)failure {
  384. // 增加播放次数接口
  385. NSString *path = [NSString stringWithFormat:@"juxing/dramas/%@/play", dramaId];
  386. [self.networkManager POST:path
  387. parameters:@{}
  388. success:success
  389. failure:failure];
  390. }
  391. @end