我们先讨论登录操作,回头再来看注册。
首先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。
额外的讨论:
番茄计时器是在用户登录状态下才能使用,所以每次登出,任务列表清空,此时必须保证番茄计时器的页面无法打开,同时确保其他使用番茄计时器的功能全部停止。
每次登录后,同步任务列表,番茄计时器可以开始工作。