| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390 |
- //
- // 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<NSString *, JXAccessCheckResult *> *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
|