
- 更新邮箱登录相关功能,新增邮箱验证码获取和登录API端点。 - 添加AccountModel以管理用户认证信息,支持会话票据的存储和更新。 - 实现密码恢复功能,支持通过邮箱获取验证码和重置密码。 - 增加本地化支持,更新相关字符串以适应新功能。 - 引入ValidationHelper以验证邮箱和密码格式,确保用户输入的有效性。 - 更新视图以支持邮箱登录和密码恢复的用户交互。
434 lines
14 KiB
Markdown
434 lines
14 KiB
Markdown
# 邮箱验证码登录流程文档
|
||
|
||
## 概述
|
||
|
||
本文档详细描述了 YuMi iOS 应用中 `LoginTypesViewController` 在 `LoginDisplayType_email` 模式下的邮箱验证码登录流程。该流程实现了基于邮箱和验证码的用户认证机制。
|
||
|
||
## 系统架构
|
||
|
||
### 核心组件
|
||
- **LoginTypesViewController**: 登录类型控制器,负责 UI 展示和用户交互
|
||
- **LoginPresenter**: 登录业务逻辑处理器,负责与 API 交互
|
||
- **LoginInputItemView**: 输入组件,提供邮箱和验证码输入界面
|
||
- **Api+Login**: 登录相关 API 接口封装
|
||
- **AccountInfoStorage**: 账户信息本地存储管理
|
||
|
||
### 数据模型
|
||
|
||
#### LoginDisplayType 枚举
|
||
```objc
|
||
typedef NS_ENUM(NSUInteger, LoginDisplayType) {
|
||
LoginDisplayType_id, // ID 登录
|
||
LoginDisplayType_email, // 邮箱登录 ✓
|
||
LoginDisplayType_phoneNum, // 手机号登录
|
||
LoginDisplayType_email_forgetPassword, // 邮箱忘记密码
|
||
LoginDisplayType_phoneNum_forgetPassword, // 手机号忘记密码
|
||
};
|
||
```
|
||
|
||
#### LoginInputType 枚举
|
||
```objc
|
||
typedef NS_ENUM(NSUInteger, LoginInputType) {
|
||
LoginInputType_email, // 邮箱输入
|
||
LoginInputType_verificationCode, // 验证码输入
|
||
LoginInputType_login, // 登录按钮
|
||
// ... 其他类型
|
||
};
|
||
```
|
||
|
||
#### GetSmsType 验证码类型
|
||
```objc
|
||
typedef NS_ENUM(NSUInteger, GetSmsType) {
|
||
GetSmsType_Regist = 1, // 注册(邮箱登录使用此类型)
|
||
GetSmsType_Login = 2, // 登录
|
||
GetSmsType_Reset_Password = 3, // 重设密码
|
||
// ... 其他类型
|
||
};
|
||
```
|
||
|
||
## 登录流程详解
|
||
|
||
### 1. 界面初始化流程
|
||
|
||
#### 1.1 控制器初始化
|
||
```objc
|
||
// 在 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 输入区域设置
|
||
```objc
|
||
- (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 用户交互触发
|
||
```objc
|
||
// 用户点击"获取验证码"按钮
|
||
[self.secondLineInputView setHandleItemAction:^(LoginInputType inputType) {
|
||
if (inputType == LoginInputType_verificationCode) {
|
||
if (self.type == LoginDisplayType_email) {
|
||
[self handleTapGetMailVerificationCode];
|
||
}
|
||
}
|
||
}];
|
||
```
|
||
|
||
#### 2.2 邮箱验证码获取处理
|
||
```objc
|
||
- (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 层处理
|
||
```objc
|
||
- (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 接口调用
|
||
```objc
|
||
+ (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 获取验证码成功处理
|
||
```objc
|
||
- (void)emailCodeSucess:(NSString *)message type:(GetSmsType)type {
|
||
[self showSuccessToast:YMLocalizedString(@"XPLoginPhoneViewController2")]; // "验证码已发送"
|
||
[self.secondLineInputView startVerificationCountDown]; // 开始倒计时
|
||
[self.secondLineInputView displayKeyboard]; // 显示键盘
|
||
}
|
||
```
|
||
|
||
#### 2.6 获取验证码失败处理
|
||
```objc
|
||
- (void)emailCodeFailure {
|
||
[self.secondLineInputView endVerificationCountDown]; // 结束倒计时
|
||
}
|
||
```
|
||
|
||
### 3. 邮箱登录流程
|
||
|
||
#### 3.1 登录按钮状态检查
|
||
```objc
|
||
- (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 登录按钮点击处理
|
||
```objc
|
||
- (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 层登录处理
|
||
```objc
|
||
- (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 接口调用
|
||
```objc
|
||
+ (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 登录成功处理
|
||
```objc
|
||
- (void)loginSuccess {
|
||
[self showSuccessToast:YMLocalizedString(@"XPLoginPhoneViewController1")]; // "登录成功"
|
||
[PILoginManager loginWithVC:self isLoginPhone:NO]; // 执行登录后续处理
|
||
}
|
||
```
|
||
|
||
#### 3.6 登录失败处理
|
||
```objc
|
||
- (void)loginFailWithMsg:(NSString *)msg {
|
||
[self showSuccessToast:msg]; // 显示错误信息
|
||
}
|
||
```
|
||
|
||
## 数据流时序图
|
||
|
||
```mermaid
|
||
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 算法加密邮箱地址后传输
|
||
```objc
|
||
NSString *desEmail = [DESEncrypt encryptUseDES:email key:KeyWithType(KeyType_PasswordEncode)];
|
||
```
|
||
|
||
### 2. 输入验证
|
||
- **邮箱格式验证**: 通过 `UIKeyboardTypeEmailAddress` 键盘类型引导正确输入
|
||
- **非空验证**: 邮箱和验证码都必须非空才能执行登录
|
||
|
||
### 3. 验证码安全
|
||
- **时效性**: 验证码具有倒计时机制,防止重复获取
|
||
- **类型标识**: 使用 `GetSmsType_Regist = 1` 标识登录验证码
|
||
|
||
### 4. 网络安全
|
||
- **错误处理**: 完整的成功/失败回调机制
|
||
- **加载状态**: `showLoading:YES` 防止重复请求
|
||
- **错误提示**: `errorToast:YES` 显示网络错误
|
||
|
||
## 错误处理机制
|
||
|
||
### 1. 邮箱验证码获取错误
|
||
```objc
|
||
- (void)emailCodeFailure {
|
||
[self.secondLineInputView endVerificationCountDown]; // 停止倒计时
|
||
// 用户可以重新获取验证码
|
||
}
|
||
```
|
||
|
||
### 2. 登录失败处理
|
||
```objc
|
||
- (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**: 网络请求管理
|
||
|
||
## 扩展和维护
|
||
|
||
### 新增功能建议
|
||
1. **邮箱格式验证**: 添加正则表达式验证邮箱格式
|
||
2. **验证码长度限制**: 限制验证码输入长度
|
||
3. **自动填充**: 支持系统邮箱自动填充
|
||
4. **记住邮箱**: 保存最近使用的邮箱地址
|
||
|
||
### 性能优化
|
||
1. **请求去重**: 防止短时间内重复请求验证码
|
||
2. **缓存机制**: 缓存验证码倒计时状态
|
||
3. **网络优化**: 添加请求超时和重试机制
|
||
|
||
### 代码维护
|
||
1. **常量管理**: 将硬编码字符串提取为常量
|
||
2. **错误码统一**: 统一管理API错误码
|
||
3. **日志记录**: 添加详细的操作日志
|
||
|
||
## 总结
|
||
|
||
邮箱验证码登录流程是一个完整的用户认证系统,包含了界面展示、验证码获取、用户登录、数据存储等完整环节。该流程具有良好的安全性、用户体验和错误处理机制,符合现代移动应用的认证标准。
|
||
|
||
通过本文档,开发人员可以全面了解邮箱登录的实现细节,便于后续的功能扩展和维护工作。 |