// // JXMemberService.m // AICity // // Feature: 003-ios-api-https // Task: T051 - iOS会员状态检查 // Created on 2025-10-14. // #import "JXMemberService.h" #import "JXNetworkManager.h" // 通知名称 NSString * const JXMemberStatusDidChangeNotification = @"JXMemberStatusDidChangeNotification"; // 缓存超时时间(5分钟) static const NSTimeInterval kCacheTimeout = 5 * 60; @implementation JXMemberStatus - (BOOL)isExpired { return self.expireAt <= [[NSDate date] timeIntervalSince1970]; } - (BOOL)isVip { return ![self.localMemberType isEqualToString:@"free"] && ![self isExpired]; } - (NSInteger)getRemainingDays { if ([self isExpired]) return 0; NSTimeInterval remainingSeconds = self.expireAt - [[NSDate date] timeIntervalSince1970]; return (NSInteger)(remainingSeconds / (24 * 60 * 60)); } - (NSString *)getDisplayName { if ([self.localMemberType isEqualToString:@"free"]) { return @"免费用户"; } else if ([self.localMemberType isEqualToString:@"basic"]) { return @"基础会员"; } else if ([self.localMemberType isEqualToString:@"premium"]) { return @"高级会员"; } else if ([self.localMemberType isEqualToString:@"vip"]) { return @"VIP会员"; } else { return @"未知类型"; } } @end @implementation JXAccessCheckResult @end @interface JXMemberService () @property (nonatomic, strong) JXMemberStatus *currentMemberStatus; @property (nonatomic, strong) NSMutableDictionary *accessCache; @property (nonatomic, strong) JXNetworkManager *networkManager; @end @implementation JXMemberService #pragma mark - Singleton + (instancetype)sharedService { static JXMemberService *sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[self alloc] init]; }); return sharedInstance; } - (instancetype)init { self = [super init]; if (self) { _accessCache = [[NSMutableDictionary alloc] init]; _networkManager = [JXNetworkManager sharedManager]; } return self; } #pragma mark - Public Methods - (void)getCurrentMemberStatusWithForceRefresh:(BOOL)forceRefresh completion:(void(^)(JXMemberStatus * _Nullable memberStatus, NSError * _Nullable error))completion { // 如果有缓存且不强制刷新,返回缓存 if (!forceRefresh && self.currentMemberStatus && ![self isMemberStatusExpired:self.currentMemberStatus]) { if (completion) { completion(self.currentMemberStatus, nil); } return; } // 从服务器获取最新状态 [self.networkManager getMemberStatusWithCompletion:^(NSDictionary * _Nullable response, NSError * _Nullable error) { if (error) { // 网络异常时返回缓存的状态 if (self.currentMemberStatus) { if (completion) { completion(self.currentMemberStatus, nil); } } else { if (completion) { completion(nil, error); } } return; } // 解析响应数据 if (response && [response[@"code"] integerValue] == 0 && response[@"data"]) { JXMemberStatus *memberStatus = [self parseMemberStatusFromResponse:response[@"data"]]; self.currentMemberStatus = memberStatus; // 清除过期的权限缓存 [self clearExpiredAccessCache]; // 发送状态变更通知 [[NSNotificationCenter defaultCenter] postNotificationName:JXMemberStatusDidChangeNotification object:memberStatus]; if (completion) { completion(memberStatus, nil); } } else { NSString *message = response[@"message"] ?: @"获取会员状态失败"; NSError *apiError = [NSError errorWithDomain:@"JXMemberService" code:-1 userInfo:@{NSLocalizedDescriptionKey: message}]; if (completion) { completion(nil, apiError); } } }]; } - (void)checkContentAccessWithContentType:(NSString *)contentType requiredLevel:(NSInteger)requiredLevel contentId:(nullable NSString *)contentId completion:(void(^)(JXAccessCheckResult * _Nullable result, NSError * _Nullable error))completion { NSString *cacheKey = [NSString stringWithFormat:@"%@_%ld_%@", contentType, (long)requiredLevel, contentId ?: @"default"]; // 检查缓存 JXAccessCheckResult *cached = self.accessCache[cacheKey]; if (cached && ![self isAccessCacheExpired:cached]) { if (completion) { completion(cached, nil); } return; } // 获取会员状态 [self getCurrentMemberStatusWithForceRefresh:NO completion:^(JXMemberStatus * _Nullable memberStatus, NSError * _Nullable error) { if (error || !memberStatus) { if (completion) { completion(nil, error ?: [NSError errorWithDomain:@"JXMemberService" code:-1 userInfo:@{NSLocalizedDescriptionKey: @"获取会员状态失败"}]); } return; } // 本地权限检查 BOOL hasAccess = memberStatus.accessLevel >= requiredLevel; JXAccessCheckResult *result = [[JXAccessCheckResult alloc] init]; result.hasAccess = hasAccess; result.memberStatus = memberStatus; result.contentType = contentType; result.requiredLevel = requiredLevel; result.contentId = contentId; result.checkTime = [[NSDate date] timeIntervalSince1970]; result.reason = hasAccess ? @"权限验证通过" : @"会员等级不足"; // 缓存结果 self.accessCache[cacheKey] = result; if (completion) { completion(result, nil); } }]; } - (void)checkEpisodeAccessWithEpisodeId:(NSString *)episodeId requiredLevel:(NSInteger)requiredLevel completion:(void(^)(JXAccessCheckResult * _Nullable result, NSError * _Nullable error))completion { [self checkContentAccessWithContentType:@"episode" requiredLevel:requiredLevel contentId:episodeId completion:completion]; } - (void)checkDramaAccessWithDramaId:(NSString *)dramaId requiredLevel:(NSInteger)requiredLevel completion:(void(^)(JXAccessCheckResult * _Nullable result, NSError * _Nullable error))completion { [self checkContentAccessWithContentType:@"drama" requiredLevel:requiredLevel contentId:dramaId completion:completion]; } - (void)syncMemberStatusWithCompletion:(void(^)(BOOL success, NSError * _Nullable error))completion { [self.networkManager syncMemberStatusWithCompletion:^(NSDictionary * _Nullable response, NSError * _Nullable error) { if (error) { if (completion) { completion(NO, error); } return; } if (response && [response[@"code"] integerValue] == 0) { // 同步成功后刷新本地状态 [self getCurrentMemberStatusWithForceRefresh:YES completion:^(JXMemberStatus * _Nullable memberStatus, NSError * _Nullable error) { if (completion) { completion(error == nil, error); } }]; } else { NSString *message = response[@"message"] ?: @"同步会员状态失败"; NSError *apiError = [NSError errorWithDomain:@"JXMemberService" code:-1 userInfo:@{NSLocalizedDescriptionKey: message}]; if (completion) { completion(NO, apiError); } } }]; } - (void)isVipUserWithCompletion:(void(^)(BOOL isVip, NSError * _Nullable error))completion { [self getCurrentMemberStatusWithForceRefresh:NO completion:^(JXMemberStatus * _Nullable memberStatus, NSError * _Nullable error) { if (error) { if (completion) { completion(NO, error); } return; } BOOL isVip = memberStatus && [memberStatus isVip]; if (completion) { completion(isVip, nil); } }]; } - (BOOL)isMemberExpired:(JXMemberStatus *)memberStatus { return [memberStatus isExpired]; } - (void)getMemberExpireTimeWithCompletion:(void(^)(NSTimeInterval expireTime, NSError * _Nullable error))completion { [self getCurrentMemberStatusWithForceRefresh:NO completion:^(JXMemberStatus * _Nullable memberStatus, NSError * _Nullable error) { if (error) { if (completion) { completion(0, error); } return; } NSTimeInterval expireTime = memberStatus ? memberStatus.expireAt : 0; if (completion) { completion(expireTime, nil); } }]; } - (NSString *)getMemberTypeDisplayName:(NSString *)memberType { if ([memberType isEqualToString:@"free"]) { return @"免费用户"; } else if ([memberType isEqualToString:@"basic"]) { return @"基础会员"; } else if ([memberType isEqualToString:@"premium"]) { return @"高级会员"; } else if ([memberType isEqualToString:@"vip"]) { return @"VIP会员"; } else { return @"未知类型"; } } - (NSString *)getAccessLevelDisplayName:(NSInteger)accessLevel { switch (accessLevel) { case 0: return @"免费内容"; case 1: return @"付费内容"; case 2: return @"高级内容"; case 3: return @"VIP内容"; default: return @"未知级别"; } } - (void)clearMemberStatusCache { self.currentMemberStatus = nil; [self.accessCache removeAllObjects]; } - (void)updateMemberStatus:(JXMemberStatus *)memberStatus { self.currentMemberStatus = memberStatus; // 清除权限缓存,强制重新检查 [self.accessCache removeAllObjects]; // 发送状态变更通知 [[NSNotificationCenter defaultCenter] postNotificationName:JXMemberStatusDidChangeNotification object:memberStatus]; } - (void)addMemberStatusObserver:(id)observer selector:(SEL)selector { [[NSNotificationCenter defaultCenter] addObserver:observer selector:selector name:JXMemberStatusDidChangeNotification object:nil]; } - (void)removeMemberStatusObserver:(id)observer { [[NSNotificationCenter defaultCenter] removeObserver:observer name:JXMemberStatusDidChangeNotification object:nil]; } #pragma mark - Private Methods - (JXMemberStatus *)parseMemberStatusFromResponse:(NSDictionary *)data { JXMemberStatus *memberStatus = [[JXMemberStatus alloc] init]; memberStatus.userId = [data[@"user_id"] integerValue]; memberStatus.localMemberType = data[@"local_member_type"] ?: @"free"; memberStatus.juXingMemberType = data[@"juxing_member_type"] ?: @"0"; memberStatus.accessLevel = [data[@"access_level"] integerValue]; memberStatus.expireAt = [data[@"expire_at"] doubleValue]; memberStatus.syncStatus = data[@"sync_status"] ?: @"pending"; memberStatus.lastSyncAt = [data[@"last_sync_at"] doubleValue]; memberStatus.retryCount = [data[@"retry_count"] integerValue]; memberStatus.createdAt = [data[@"created_at"] doubleValue]; memberStatus.updatedAt = [data[@"updated_at"] doubleValue]; return memberStatus; } - (BOOL)isMemberStatusExpired:(JXMemberStatus *)memberStatus { // 缓存5分钟 NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970]; return currentTime - memberStatus.updatedAt > kCacheTimeout; } - (BOOL)isAccessCacheExpired:(JXAccessCheckResult *)result { NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970]; return currentTime - result.checkTime > kCacheTimeout; } - (void)clearExpiredAccessCache { NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970]; NSMutableArray *keysToRemove = [[NSMutableArray alloc] init]; for (NSString *key in self.accessCache.allKeys) { JXAccessCheckResult *result = self.accessCache[key]; if (currentTime - result.checkTime > kCacheTimeout) { [keysToRemove addObject:key]; } } for (NSString *key in keysToRemove) { [self.accessCache removeObjectForKey:key]; } } @end