
- 更新邮箱登录相关功能,新增邮箱验证码获取和登录API端点。 - 添加AccountModel以管理用户认证信息,支持会话票据的存储和更新。 - 实现密码恢复功能,支持通过邮箱获取验证码和重置密码。 - 增加本地化支持,更新相关字符串以适应新功能。 - 引入ValidationHelper以验证邮箱和密码格式,确保用户输入的有效性。 - 更新视图以支持邮箱登录和密码恢复的用户交互。
14 KiB
14 KiB
邮箱验证码登录流程文档
概述
本文档详细描述了 YuMi iOS 应用中 LoginTypesViewController
在 LoginDisplayType_email
模式下的邮箱验证码登录流程。该流程实现了基于邮箱和验证码的用户认证机制。
系统架构
核心组件
- LoginTypesViewController: 登录类型控制器,负责 UI 展示和用户交互
- LoginPresenter: 登录业务逻辑处理器,负责与 API 交互
- LoginInputItemView: 输入组件,提供邮箱和验证码输入界面
- Api+Login: 登录相关 API 接口封装
- AccountInfoStorage: 账户信息本地存储管理
数据模型
LoginDisplayType 枚举
typedef NS_ENUM(NSUInteger, LoginDisplayType) {
LoginDisplayType_id, // ID 登录
LoginDisplayType_email, // 邮箱登录 ✓
LoginDisplayType_phoneNum, // 手机号登录
LoginDisplayType_email_forgetPassword, // 邮箱忘记密码
LoginDisplayType_phoneNum_forgetPassword, // 手机号忘记密码
};
LoginInputType 枚举
typedef NS_ENUM(NSUInteger, LoginInputType) {
LoginInputType_email, // 邮箱输入
LoginInputType_verificationCode, // 验证码输入
LoginInputType_login, // 登录按钮
// ... 其他类型
};
GetSmsType 验证码类型
typedef NS_ENUM(NSUInteger, GetSmsType) {
GetSmsType_Regist = 1, // 注册(邮箱登录使用此类型)
GetSmsType_Login = 2, // 登录
GetSmsType_Reset_Password = 3, // 重设密码
// ... 其他类型
};
登录流程详解
1. 界面初始化流程
1.1 控制器初始化
// 在 LoginViewController 中点击邮箱登录按钮
- (void)didTapEntrcyButton:(UIButton *)sender {
if (sender.tag == LoginType_Email) {
LoginTypesViewController *vc = [[LoginTypesViewController alloc] init];
[self.navigationController pushViewController:vc animated:YES];
[vc updateLoginType:LoginDisplayType_email]; // 设置为邮箱登录模式
}
}
1.2 输入区域设置
- (void)setupEmailInputArea {
[self setupInpuArea:LoginInputType_email // 第一行:邮箱输入
second:LoginInputType_verificationCode // 第二行:验证码输入
third:LoginInputType_none // 第三行:无
action:LoginInputType_login // 操作按钮:登录
showForgetPassword:NO]; // 不显示忘记密码
}
1.3 UI 组件配置
-
第一行输入框: 邮箱地址输入
- 占位符: "请输入邮箱地址"
- 键盘类型:
UIKeyboardTypeEmailAddress
- 回调:
handleFirstInputContentUpdate
-
第二行输入框: 验证码输入
- 占位符: "请输入验证码"
- 键盘类型:
UIKeyboardTypeDefault
- 附带"获取验证码"按钮
- 回调:
handleSecondInputContentUpdate
2. 验证码获取流程
2.1 用户交互触发
// 用户点击"获取验证码"按钮
[self.secondLineInputView setHandleItemAction:^(LoginInputType inputType) {
if (inputType == LoginInputType_verificationCode) {
if (self.type == LoginDisplayType_email) {
[self handleTapGetMailVerificationCode];
}
}
}];
2.2 邮箱验证码获取处理
- (void)handleTapGetMailVerificationCode {
NSString *email = [self.firstLineInputView inputContent];
// 邮箱地址验证
if (email.length == 0) {
[self.secondLineInputView endVerificationCountDown];
return;
}
// 调用 Presenter 发送验证码
[self.presenter sendMailVerificationCode:email type:GetSmsType_Regist];
}
2.3 Presenter 层处理
- (void)sendMailVerificationCode:(NSString *)emailAddress type:(NSInteger)type {
// DES 加密邮箱地址
NSString *desEmail = [DESEncrypt encryptUseDES:emailAddress
key:KeyWithType(KeyType_PasswordEncode)];
@kWeakify(self);
[Api emailGetCode:[self createHttpCompletion:^(BaseModel *data) {
@kStrongify(self);
if ([[self getView] respondsToSelector:@selector(emailCodeSucess:type:)]) {
[[self getView] emailCodeSucess:@"" type:type];
}
} fail:^(NSInteger code, NSString *msg) {
@kStrongify(self);
if ([[self getView] respondsToSelector:@selector(emailCodeFailure)]) {
[[self getView] emailCodeFailure];
}
} showLoading:YES errorToast:YES]
emailAddress:desEmail
type:@(type)];
}
2.4 API 接口调用
+ (void)emailGetCode:(HttpRequestHelperCompletion)completion
emailAddress:(NSString *)emailAddress
type:(NSNumber *)type {
[self makeRequest:@"email/getCode"
method:HttpRequestHelperMethodPOST
completion:completion, __FUNCTION__, emailAddress, type, nil];
}
API 详情:
- 接口路径:
POST /email/getCode
- 请求参数:
emailAddress
: 邮箱地址(DES 加密)type
: 验证码类型(1=注册)
2.5 获取验证码成功处理
- (void)emailCodeSucess:(NSString *)message type:(GetSmsType)type {
[self showSuccessToast:YMLocalizedString(@"XPLoginPhoneViewController2")]; // "验证码已发送"
[self.secondLineInputView startVerificationCountDown]; // 开始倒计时
[self.secondLineInputView displayKeyboard]; // 显示键盘
}
2.6 获取验证码失败处理
- (void)emailCodeFailure {
[self.secondLineInputView endVerificationCountDown]; // 结束倒计时
}
3. 邮箱登录流程
3.1 登录按钮状态检查
- (void)checkActionButtonStatus {
switch (self.type) {
case LoginDisplayType_email: {
NSString *accountString = [self.firstLineInputView inputContent]; // 邮箱
NSString *codeString = [self.secondLineInputView inputContent]; // 验证码
// 只有当邮箱和验证码都不为空时才启用登录按钮
if (![NSString isEmpty:accountString] && ![NSString isEmpty:codeString]) {
self.bottomActionButton.enabled = YES;
} else {
self.bottomActionButton.enabled = NO;
}
}
break;
}
}
3.2 登录按钮点击处理
- (void)didTapActionButton {
[self.view endEditing:true];
switch (self.type) {
case LoginDisplayType_email: {
// 调用 Presenter 进行邮箱登录
[self.presenter loginWithEmail:[self.firstLineInputView inputContent]
code:[self.secondLineInputView inputContent]];
}
break;
}
}
3.3 Presenter 层登录处理
- (void)loginWithEmail:(NSString *)email code:(NSString *)code {
// DES 加密邮箱地址
NSString *desMail = [DESEncrypt encryptUseDES:email
key:KeyWithType(KeyType_PasswordEncode)];
@kWeakify(self);
[Api loginWithCode:[self createHttpCompletion:^(BaseModel *data) {
@kStrongify(self);
// 解析账户模型
AccountModel *accountModel = [AccountModel modelWithDictionary:data.data];
// 保存账户信息
if (accountModel && accountModel.access_token.length > 0) {
[[AccountInfoStorage instance] saveAccountInfo:accountModel];
}
// 通知登录成功
if ([[self getView] respondsToSelector:@selector(loginSuccess)]) {
[[self getView] loginSuccess];
}
} fail:^(NSInteger code, NSString *msg) {
@kStrongify(self);
[[self getView] loginFailWithMsg:msg];
} errorToast:NO]
email:desMail
code:code
client_secret:clinet_s // 客户端密钥
version:@"1"
client_id:@"erban-client"
grant_type:@"email"]; // 邮箱登录类型
}
3.4 API 接口调用
+ (void)loginWithCode:(HttpRequestHelperCompletion)completion
email:(NSString *)email
code:(NSString *)code
client_secret:(NSString *)client_secret
version:(NSString *)version
client_id:(NSString *)client_id
grant_type:(NSString *)grant_type {
NSString *fang = [NSString stringFromBase64String:@"b2F1dGgvdG9rZW4="]; // oauth/token
[self makeRequest:fang
method:HttpRequestHelperMethodPOST
completion:completion, __FUNCTION__, email, code, client_secret,
version, client_id, grant_type, nil];
}
API 详情:
- 接口路径:
POST /oauth/token
- 请求参数:
email
: 邮箱地址(DES 加密)code
: 验证码client_secret
: 客户端密钥version
: 版本号 "1"client_id
: 客户端ID "erban-client"grant_type
: 授权类型 "email"
3.5 登录成功处理
- (void)loginSuccess {
[self showSuccessToast:YMLocalizedString(@"XPLoginPhoneViewController1")]; // "登录成功"
[PILoginManager loginWithVC:self isLoginPhone:NO]; // 执行登录后续处理
}
3.6 登录失败处理
- (void)loginFailWithMsg:(NSString *)msg {
[self showSuccessToast:msg]; // 显示错误信息
}
数据流时序图
sequenceDiagram
participant User as 用户
participant VC as LoginTypesViewController
participant IV as LoginInputItemView
participant P as LoginPresenter
participant API as Api+Login
participant Storage as AccountInfoStorage
Note over User,Storage: 1. 初始化邮箱登录界面
User->>VC: 选择邮箱登录
VC->>VC: updateLoginType(LoginDisplayType_email)
VC->>VC: setupEmailInputArea()
VC->>IV: 创建邮箱输入框
VC->>IV: 创建验证码输入框
Note over User,Storage: 2. 获取邮箱验证码
User->>IV: 输入邮箱地址
User->>IV: 点击"获取验证码"
IV->>VC: handleTapGetMailVerificationCode
VC->>VC: 验证邮箱地址非空
VC->>P: sendMailVerificationCode(email, GetSmsType_Regist)
P->>P: DES加密邮箱地址
P->>API: emailGetCode(encryptedEmail, type=1)
API-->>P: 验证码发送结果
P-->>VC: emailCodeSucess / emailCodeFailure
VC->>IV: startVerificationCountDown / endVerificationCountDown
VC->>User: 显示成功/失败提示
Note over User,Storage: 3. 邮箱验证码登录
User->>IV: 输入验证码
IV->>VC: 输入内容变化回调
VC->>VC: checkActionButtonStatus()
VC->>User: 启用/禁用登录按钮
User->>VC: 点击登录按钮
VC->>VC: didTapActionButton()
VC->>P: loginWithEmail(email, code)
P->>P: DES加密邮箱地址
P->>API: loginWithCode(email, code, ...)
API-->>P: OAuth Token 响应
P->>P: 解析 AccountModel
P->>Storage: saveAccountInfo(accountModel)
P-->>VC: loginSuccess / loginFailWithMsg
VC->>User: 显示登录结果
VC->>User: 跳转到主界面
安全机制
1. 数据加密
- 邮箱地址加密: 使用 DES 算法加密邮箱地址后传输
NSString *desEmail = [DESEncrypt encryptUseDES:email key:KeyWithType(KeyType_PasswordEncode)];
2. 输入验证
- 邮箱格式验证: 通过
UIKeyboardTypeEmailAddress
键盘类型引导正确输入 - 非空验证: 邮箱和验证码都必须非空才能执行登录
3. 验证码安全
- 时效性: 验证码具有倒计时机制,防止重复获取
- 类型标识: 使用
GetSmsType_Regist = 1
标识登录验证码
4. 网络安全
- 错误处理: 完整的成功/失败回调机制
- 加载状态:
showLoading:YES
防止重复请求 - 错误提示:
errorToast:YES
显示网络错误
错误处理机制
1. 邮箱验证码获取错误
- (void)emailCodeFailure {
[self.secondLineInputView endVerificationCountDown]; // 停止倒计时
// 用户可以重新获取验证码
}
2. 登录失败处理
- (void)loginFailWithMsg:(NSString *)msg {
[self showSuccessToast:msg]; // 显示具体错误信息
// 用户可以重新尝试登录
}
3. 网络请求错误
- 自动重试: 用户可以手动重新点击获取验证码或登录
- 错误提示: 通过 Toast 显示具体错误信息
- 状态恢复: 失败后恢复按钮可点击状态
本地化支持
关键文本资源
@"20.20.51_text_1"
: "邮箱登录"@"20.20.51_text_4"
: "请输入邮箱地址"@"20.20.51_text_7"
: "请输入验证码"@"XPLoginPhoneViewController2"
: "验证码已发送"@"XPLoginPhoneViewController1"
: "登录成功"
多语言支持
- 简体中文 (
zh-Hant.lproj
) - 英文 (
en.lproj
) - 阿拉伯语 (
ar.lproj
) - 土耳其语 (
tr.lproj
)
依赖组件
外部框架
- MASConstraintMaker: 自动布局
- ReactiveObjC: 响应式编程(部分组件使用)
内部组件
- YMLocalizedString: 本地化字符串管理
- DESEncrypt: DES 加密工具
- AccountInfoStorage: 账户信息存储
- HttpRequestHelper: 网络请求管理
扩展和维护
新增功能建议
- 邮箱格式验证: 添加正则表达式验证邮箱格式
- 验证码长度限制: 限制验证码输入长度
- 自动填充: 支持系统邮箱自动填充
- 记住邮箱: 保存最近使用的邮箱地址
性能优化
- 请求去重: 防止短时间内重复请求验证码
- 缓存机制: 缓存验证码倒计时状态
- 网络优化: 添加请求超时和重试机制
代码维护
- 常量管理: 将硬编码字符串提取为常量
- 错误码统一: 统一管理API错误码
- 日志记录: 添加详细的操作日志
总结
邮箱验证码登录流程是一个完整的用户认证系统,包含了界面展示、验证码获取、用户登录、数据存储等完整环节。该流程具有良好的安全性、用户体验和错误处理机制,符合现代移动应用的认证标准。
通过本文档,开发人员可以全面了解邮箱登录的实现细节,便于后续的功能扩展和维护工作。