3
This commit is contained in:
32
keyBoard/Class/Login/VM/KBLoginVM.h
Normal file
32
keyBoard/Class/Login/VM/KBLoginVM.h
Normal file
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// KBLoginVM.h
|
||||
// 登录相关的 ViewModel:封装 Apple 登录与服务端校验、登录态落盘。
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class KBUser;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// 登录完成回调
|
||||
typedef void(^KBLoginCompletion)(BOOL success, NSError * _Nullable error);
|
||||
|
||||
@interface KBLoginVM : NSObject
|
||||
|
||||
+ (instancetype)shared;
|
||||
|
||||
/// 最近一次登录/拉取到的用户信息(仅内存缓存),可选
|
||||
@property (atomic, strong, readonly, nullable) KBUser *currentUser; // 最近一次解析得到的用户
|
||||
|
||||
/// 调起 Apple 登录并在服务端完成校验;成功后会将 token 写入共享钥匙串(KBAuthManager),作为“已登录”的判断依据。
|
||||
- (void)signInWithAppleFromViewController:(UIViewController *)presenter
|
||||
completion:(KBLoginCompletion)completion;
|
||||
|
||||
/// 是否已登录:由 KBAuthManager 判断(是否存在有效 token)
|
||||
- (BOOL)isLoggedIn;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
99
keyBoard/Class/Login/VM/KBLoginVM.m
Normal file
99
keyBoard/Class/Login/VM/KBLoginVM.m
Normal file
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// KBLoginVM.m
|
||||
//
|
||||
|
||||
#import "KBLoginVM.h"
|
||||
#import "AppleSignInManager.h"
|
||||
#import "KBNetworkManager.h"
|
||||
#import "KBAuthManager.h"
|
||||
#import "KBAPI.h"
|
||||
#import "KBUser.h"
|
||||
|
||||
@interface KBLoginVM ()
|
||||
@property (atomic, strong, readwrite, nullable) KBUser *currentUser;
|
||||
@end
|
||||
|
||||
@implementation KBLoginVM
|
||||
|
||||
+ (instancetype)shared {
|
||||
static KBLoginVM *vm; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ vm = [KBLoginVM new]; });
|
||||
return vm;
|
||||
}
|
||||
|
||||
- (BOOL)isLoggedIn {
|
||||
return [[KBAuthManager shared] isLoggedIn];
|
||||
}
|
||||
|
||||
- (void)signInWithAppleFromViewController:(UIViewController *)presenter
|
||||
completion:(KBLoginCompletion)completion {
|
||||
// 调起 Apple 登录
|
||||
[[AppleSignInManager shared] signInFromViewController:presenter completion:^(ASAuthorizationAppleIDCredential * _Nullable credential, NSError * _Nullable error) {
|
||||
if (error) { if (completion) completion(NO, error); return; }
|
||||
if (![credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {
|
||||
if (completion) completion(NO, [NSError errorWithDomain:@"KBLogin" code:-1 userInfo:@{NSLocalizedDescriptionKey: @"无效的登录凭证"}]);
|
||||
return;
|
||||
}
|
||||
|
||||
ASAuthorizationAppleIDCredential *cred = (ASAuthorizationAppleIDCredential *)credential;
|
||||
// identityToken/authorizationCode 均可按后端要求传递;本项目后端使用 identityToken 映射为 code
|
||||
NSString *identityToken = cred.identityToken ? [[NSString alloc] initWithData:cred.identityToken encoding:NSUTF8StringEncoding] : nil;
|
||||
NSString *authorizationCode = cred.authorizationCode ? [[NSString alloc] initWithData:cred.authorizationCode encoding:NSUTF8StringEncoding] : nil;
|
||||
|
||||
NSMutableDictionary *params = [NSMutableDictionary dictionary];
|
||||
if (identityToken.length) params[@"code"] = identityToken;
|
||||
if (authorizationCode.length) params[@"accessCode"] = authorizationCode; // 仅供后端需要时使用
|
||||
if (cred.user.length) params[@"userID"] = cred.user; // 可选
|
||||
|
||||
// 向服务端发起校验
|
||||
[[KBNetworkManager shared] POST:API_APPLE_LOGIN jsonBody:params headers:nil completion:^(id _Nullable jsonOrData, NSURLResponse * _Nullable response, NSError * _Nullable error) {
|
||||
if (error) { if (completion) completion(NO, error); return; }
|
||||
|
||||
// 从返回 JSON 中提取 token(支持常见命名与层级 data/user)
|
||||
// 先解析用户模型(使用 MJExtension)
|
||||
KBUser *user = [KBUser userFromResponseObject:jsonOrData];
|
||||
self.currentUser = user;
|
||||
NSString *token = user.token ?: [self.class tokenFromResponseObject:jsonOrData];
|
||||
if (token.length == 0) {
|
||||
if (completion) completion(NO, [NSError errorWithDomain:@"KBLogin" code:-2 userInfo:@{NSLocalizedDescriptionKey: @"未返回 token"}]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 保存登录态到共享钥匙串;供 App 与扩展共享
|
||||
BOOL ok = [[KBAuthManager shared] saveAccessToken:token
|
||||
refreshToken:nil
|
||||
expiryDate:nil
|
||||
userIdentifier:cred.user];
|
||||
if (completion) completion(ok, ok ? nil : [NSError errorWithDomain:@"KBLogin" code:-3 userInfo:@{NSLocalizedDescriptionKey: @"保存登录态失败"}]);
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Helpers
|
||||
|
||||
// 宽松解析:token / access_token / accessToken,支持顶层或 data/user 层
|
||||
+ (NSString *)tokenFromResponseObject:(id)obj {
|
||||
if (![obj isKindOfClass:[NSDictionary class]]) return nil;
|
||||
NSDictionary *dict = (NSDictionary *)obj;
|
||||
|
||||
NSString *(^pick)(NSDictionary *) = ^NSString *(NSDictionary *d) {
|
||||
NSArray *keys = @[ @"token", @"access_token", @"accessToken" ];
|
||||
for (NSString *k in keys) {
|
||||
id v = d[k]; if ([v isKindOfClass:NSString.class] && ((NSString *)v).length > 0) return v;
|
||||
}
|
||||
return nil;
|
||||
};
|
||||
|
||||
NSString *t = pick(dict);
|
||||
if (t.length) return t;
|
||||
|
||||
id data = dict[@"data"]; if ([data isKindOfClass:NSDictionary.class]) { t = pick(data); if (t.length) return t; }
|
||||
id user = dict[@"user"]; if ([user isKindOfClass:NSDictionary.class]) { t = pick(user); if (t.length) return t; }
|
||||
|
||||
// 扩展:允许后端将 token 放在 data.session.token
|
||||
NSDictionary *d2 = dict[@"data"]; if ([d2 isKindOfClass:NSDictionary.class]) {
|
||||
NSDictionary *session = d2[@"session"]; if ([session isKindOfClass:NSDictionary.class]) { t = pick(session); if (t.length) return t; }
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user