用户注册/登录/登出/注销需要处理的事情

我们先讨论登录操作,回头再来看注册。

首先Parse Server提供的登录API如下:

PFUser *user = [PFUser logInWithUsername:userName password:password error:error];

这个比较明了,根据用户名密码进行登录,出错则由error进行记录。

那么登录后我们需要处理的内容有什么?

1. 读取FollowInfo

本来觉得这一步不需要,但考虑了下还是需要有容错机制,也就是说如果注册时,改字段没有被初始化,则需要再登录的时候添加,所以这里的处理时读取,如果读取失败则添加。

+ (void)_fetchFollowInfoForUser:(PFUser *)user error:(NSError **)error {
    PFFollowInfoObject *followInfoObject = [user objectForKey:@"followInfo"];
    if (followInfoObject) {
        [followInfoObject fetchIfNeeded:error];
    } else {
        [KQParseServerManager _addFollowInfoForUser:user error:error];
    }
}
1. 删除其他登录Session

为了确保单一登录,在这里,我们需要删除其他设备的登录Session。Parse Server提供"_session"表来进行Session管理,在这里需要查询当前用户的其他Session,并删除掉,代码如下:

// 一个用户仅允许一个session登录。查询当前用户的其他session,然后删除。
+ (void)_removeSessions:(PFUser *)user error:(NSError **)error {
    PFQuery *querySession = [PFQuery queryWithClassName:@"_Session"];
    [querySession whereKey:@"user" equalTo:user];
    [querySession whereKey:@"sessionToken" notEqualTo:user.sessionToken];
    
    NSArray *sessionArray = [querySession findObjects:error];
    for (NSInteger i = 0; i < sessionArray.count; i++) {
        PFSession *sessionObject = sessionArray[i];
        [sessionObject delete:error];
        
        if (*error) {
            break;
        }
    }
}
2. 同步番茄计时器数据

这又是一项需要额外处理的内容,系统添加了蕃茄计时器,在登录时,需要同步蕃茄计时器的任务信息(TomatoTask表),和番茄统计信息(TomatoStatistics表)

TomatoTask表,具体的思路大家可以看代码的注释

+ (void)syncTomatoTask:(PFUser *)user error:(NSError **)error {
    //登录后,先判断task表中是否存在当前的用户
    PFQuery *query = [PFQuery queryWithClassName:kTomatoTaskClassKey];
    [query whereKey:kTomatoTaskUserKey equalTo:user];
    [query orderByAscending:@"createdAt"];
    
    NSArray *tomatoTaskArray = [query findObjects:error];
    if (*error) {
        return;
    }
    
    if (tomatoTaskArray.count > 0) {
        // 先删除
        [[INSTaskTableManager sharedInstance] removeAll];

        // 存在,则用服务器的覆盖本地
        [tomatoTaskArray enumerateObjectsUsingBlock:^(PFTomatoTaskObject * _Nonnull tomatoTaskObject, NSUInteger idx, BOOL * _Nonnull stop) {
            INSTaskModel *taskModel = [[INSTaskModel alloc] initWithTaskDictionary:[tomatoTaskObject toDictionary]];
            [[INSTaskTableManager sharedInstance] addTaskToLocal:taskModel];
        }];
        
        // 最后,设置syncTomatoTaskToServerBlock,该Block执行,当本地Task添加/更新时,同步到服务器
        [INSTaskTableManager sharedInstance].syncTomatoTaskToServerBlock = ^(INSTaskModel * _Nonnull taskModel) {
            NSError *syncError = nil;
            [KQParseServerManager updateTomatoTask:taskModel toUser:user error:&syncError];
        };
    } else {
        // 如果不存在则初始化一个。必须得有,否则番茄计时器功能会崩溃。
        [KQParseServerManager initializeTomatoTaskToUser:user error:error];
    }
}

TomatoStatistics表,思路同样参考注释:

// 仅在登录的时候调用
// 如果是首次注册,那么数据库中必然不存在TomatoStatistics的记录,此时,什么都不用做。
// 如果是登录,那么如果数据库中存在TomatoStatistics的记录,此时,把服务器的数据覆盖到本地。
+ (void)syncTomatoStatistics:(PFUser *)user error:(NSError **)error {
    PFQuery *query = [PFQuery queryWithClassName:kTomatoStatisticsClassKey];
    [query whereKey:kTomatoStatisticsUserKey equalTo:user];
    [query includeKey:kTomatoStatisticsTomatoTaskKey];
    [query orderByAscending:@"createdAt"];
    
    NSArray *statisticsArray = [query findObjects:error];
    if (*error) {
        return;
    }
    
    // 先删除
    [[INSStatisticsTableManager sharedInstance] removeAll];
    
    // 如果数据库中存在,则保存到本地
    if (statisticsArray.count > 0) {
        [statisticsArray enumerateObjectsUsingBlock:^(PFTomatoStatisticsObject * _Nonnull statisticsObject, NSUInteger idx, BOOL * _Nonnull stop) {
            INSStatisticsModel *statisticsModel = [[INSStatisticsModel alloc] initWithTomatoDictionary:[statisticsObject toDictionary]];
            [[INSStatisticsTableManager sharedInstance] addStatistics:statisticsModel];
        }];
    }
    
    // 设置syncStatisticsToServerBlock,用户同步本地数据到服务器。
    [INSStatisticsTableManager sharedInstance].syncStatisticsToServerBlock = ^(INSStatisticsModel * _Nonnull statisticsModel) {
        NSError *error = nil;
        [KQParseServerManager addTomatoStatistics:statisticsModel toUser:user error:&error];
    };
}
4. 绑定用户和设备,用于推送

Parse Server 提供了"_Installation"表,来保存安装了应用的设备信息。我们可以通过[PFInstallation currentInstallation] 来获取当前设备的记录。这里需要做的是,将设备和用户进行绑定。推送发生是,在"_Installation"表中根据用户信息查询到对应的设备,进行推送。代码如下:

+ (void)_linkInstallationWithUser:(PFUser *)user error:(NSError **)error {
    [[PFInstallation currentInstallation] setObject:user forKey:@"user"];
    [[PFInstallation currentInstallation] save:error];
}
5. 同步Badge信息

Parse Server 提供了"_Installation"表,其“badge”字段,保存了推送的条数,因此,当用户登录后,将系统的badge number设置为“badge”字段的值。

注意,其实badge的设计会更复杂一些,推送可以分很多种,例如对所有安装设备的推送,例如对某一类用户的推送,还有就是对特定用户的推送。

目前_Installation表仅提供了一个全局的字段“badge”来管理,这会造成一些问题,例如:

  • 设备A用1账户登录,登录期间,有3条推送给1用户的消息,设备A的_Installation记录中,badge = 3
  • 设备B用2账户登录,登录期间,有10条推送给2用户的消息,设备B的_Installation记录中,badge = 10
  • 然后交换登录,A设备用2账户登录,B设备用1账户登录
  • 此时badge该如何处理?

理论上,badge也应该分多字段进行管理,特定的推送由特定的badge管理。限于篇幅,这个话题我们另开一篇进行讨论。

在这里,统一的处理方案:
由于酷文奇题APP主要是针对特定用户的推送消息,所以统一的处理方案是:
用户登录/登出之后,badge清空

    [[PFInstallation currentInstallation] setBadge:0];
    [[PFInstallation currentInstallation] save:error];

    [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];

上述1-5成功之后,才算登录成功,如果有任何一个失败,则调用退出登录。

登录成功之后,需要做的事情:

  • 发送用户登录通知
  • 发送Badge更新通知
    注意,之前登录的操作应该异步子线程完成,结束之后,推荐在主线程中发送通知。

然后我们来看看登出需要处理的事情

Parse Server提供了下面的API

[PFUser logOut];

非常简单明了,但需要注意的是,登出之前/之后我们需要处理的事情。

登出之前,我们需要处理番茄计时器,如果你在番茄计时器工作期间登出,那么就会存在一个问题,如果番茄计时器继续,直到完成,那么他将生成新的番茄记录并上传到服务器,此时,执行的代码是我们登录时设置的syncStatisticsToServerBlock,而此时的数据是存在风险的,因为user已经不存在了。

所以,必须在登出之前将番茄计时器终止。

    if ([INSTomatoTimer sharedInstance].tomatoTimerStatus != INSTomatoTimerStatusStop) {
        [[INSTomatoTimer sharedInstance] cancelTimer];
    }

接着看登出之后,需要处理的事情

1. 解除设备和用户的绑定,清除Badge信息。关于Badge,上面讨论过了。
    [[PFInstallation currentInstallation] removeObjectForKey:@"user"];
    [[PFInstallation currentInstallation] setBadge:0];
    [[PFInstallation currentInstallation] saveEventually];
    
    [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
2. 删除本地番茄数据
    [[INSTaskTableManager sharedInstance] removeAll];
    [[INSStatisticsTableManager sharedInstance] removeAll];

    [INSTaskTableManager sharedInstance].syncTomatoTaskToServerBlock = nil;
    [INSStatisticsTableManager sharedInstance].syncStatisticsToServerBlock = nil;

登出必然成功,后续需要做的事情:

  • 发送用户登出通知
  • 发送Badge更新通知
    注意,之前登录的操作应该异步子线程完成,结束之后,推荐在主线程中发送通知。

使用Apple ID登录

Parse Server提供了下面的API,使用第三方登录

+ (BFTask<__kindof PFUser *> *)logInWithAuthTypeInBackground:(NSString *)authType
                                           authData:(NSDictionary *)authData

登录调用之后,和普通过的登录过程一样,同样需要做好额外的处理,和最终的通知,一旦中间有任何一个环节出错,调用logout并告知用户错误信息。


用户注册

Parse Server 提供的注册API是signUp,简单来说就是创建一个PFUser的对象,设定他的用户名/密码/邮箱,然后调用signUp函数进行注册。代码如下:

    PFUser *user = [PFUser user];
    user.username = userName;
    user.password = password;
    user.email = email;
    
    [user signUp:error];

注册成功之后,需要处理的事情包括:

1. 添加并初始化followInfo字段
        PFFollowInfoObject *followInfoObject = [[PFFollowInfoObject alloc] init];
        followInfoObject.user = user;
        followInfoObject.followNumber = @0;
        followInfoObject.followedNumber = @0;
        [followInfoObject save:error];
        
        if (!*error) {
            [user setObject:followInfoObject forKey:@"followInfo"];
            [user save:error];
        }
2. 初始化preferredThemeName和preferredSubjectName

3. 和登录一样,绑定用户和设备
4. 和登录一样,同步Badge信息
5. 和登录一样,同步统计数据

最后和登录一样,发送登录通知和Badge更新通知


注销用户

苹果新出的政策是,需要提供注销手段,不知道不提供会不会被拒。

Parse Server没有提供标准的注销用户的API,可以直接使用delete从"_user"表中删除用户记录,即可达到注销效果。

但是,由于其他表的数据,会依赖用户表,当某一条记录删除之后,那么该用户对应的所有内容均将失效,读取时会出现问题。由于Parse Server表的设计中,外键连接以指针的形式,那么最终会导致指向用户表的指针内容为空,可能会引起Crash。

所以,目前的方案,是使用一个随机账户来替换用户的个人信息,保留账户并让其处于未激活状态。

所以,我们在“_user”表中引入了status字段,Actived表示该用户处于激活状态,Unsubscribed表示该用户已经注销。

+ (void)unsubscribe {
    NSString *username = [[NSUUID UUID] UUIDString];
    NSString *password = [[NSUUID UUID] UUIDString];
    NSString *email = [NSString stringWithFormat:@"%@@homtial.com", username];

    [PFUser currentUser].username = username;
    [PFUser currentUser].email = email;
    [PFUser currentUser].password = password;
    [[PFUser currentUser] setObject:@"Unsubscribed" forKey:@"status"];
    [[PFUser currentUser] removeObjectForKey:@"authData"];
    [[PFUser currentUser] save];
    
    // 替换后,退出登录。此时该用户将无法再登录。
    [KQParseServerManager logout];
}

之后有两种处理方案,这个用户相当于被系统回收,其内容依旧可以展示,但无法再继续登录,添加,更新等操作。

如果不希望注销用户的内容进行展示,那么在查询展示的内容时,如果涉及到用户,添加用户条件为激活的用户即可。

最后我们看这两个通知怎么处理
1. 登录/登出通知

对注册的页面来说,基本就是重新加载数据,登出受限的仅仅包括几个TabViewController的根界面,下面一一进行讨论

  • 酷奇专题,包括左侧列表框和主话题页面。左侧列表框和用户无关。主话题则设计成和用户相关,用户列表里保存了preferredThemeName和preferredSubjectName,登录状态则更新为该用户的preferredThemeName和preferredSubjectName。注册状态/退出登录状态,则初始化为默认页面。
  • 酷文共赏,更新Table即可
  • 奇题共解,更新Table即可
2.badge更新通知

更新需要显示的badge。

额外的讨论:
番茄计时器是在用户登录状态下才能使用,所以每次登出,任务列表清空,此时必须保证番茄计时器的页面无法打开,同时确保其他使用番茄计时器的功能全部停止。

每次登录后,同步任务列表,番茄计时器可以开始工作。

你可能感兴趣的:(用户注册/登录/登出/注销需要处理的事情)