Files
e-party-iOS/yana/APIs/email login flow.md
edwinQQQ e45ad3bad5 feat: 增强邮箱登录功能和密码恢复流程
- 更新邮箱登录相关功能,新增邮箱验证码获取和登录API端点。
- 添加AccountModel以管理用户认证信息,支持会话票据的存储和更新。
- 实现密码恢复功能,支持通过邮箱获取验证码和重置密码。
- 增加本地化支持,更新相关字符串以适应新功能。
- 引入ValidationHelper以验证邮箱和密码格式,确保用户输入的有效性。
- 更新视图以支持邮箱登录和密码恢复的用户交互。
2025-07-10 14:00:58 +08:00

14 KiB
Raw Blame History

邮箱验证码登录流程文档

概述

本文档详细描述了 YuMi iOS 应用中 LoginTypesViewControllerLoginDisplayType_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: 网络请求管理

扩展和维护

新增功能建议

  1. 邮箱格式验证: 添加正则表达式验证邮箱格式
  2. 验证码长度限制: 限制验证码输入长度
  3. 自动填充: 支持系统邮箱自动填充
  4. 记住邮箱: 保存最近使用的邮箱地址

性能优化

  1. 请求去重: 防止短时间内重复请求验证码
  2. 缓存机制: 缓存验证码倒计时状态
  3. 网络优化: 添加请求超时和重试机制

代码维护

  1. 常量管理: 将硬编码字符串提取为常量
  2. 错误码统一: 统一管理API错误码
  3. 日志记录: 添加详细的操作日志

总结

邮箱验证码登录流程是一个完整的用户认证系统,包含了界面展示、验证码获取、用户登录、数据存储等完整环节。该流程具有良好的安全性、用户体验和错误处理机制,符合现代移动应用的认证标准。

通过本文档,开发人员可以全面了解邮箱登录的实现细节,便于后续的功能扩展和维护工作。