chore: 更新 .gitignore 文件,移除不必要的包文件类型;新增邮箱验证码登录流程和 OAuth/Ticket 认证系统 API 文档;更新勋章相关逻辑,优化用户信息获取失败处理;调整勋章展示逻辑,支持多等级高亮功能。
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -82,12 +82,6 @@ iOSInjectionProject/
|
||||
# Mobile Tools for Java (J2ME)
|
||||
.mtj.tmp/
|
||||
|
||||
# Package Files #
|
||||
*.war
|
||||
*.ear
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
|
434
Email-VerificationCode-Login-Flow.md
Normal file
434
Email-VerificationCode-Login-Flow.md
Normal file
@@ -0,0 +1,434 @@
|
||||
# 邮箱验证码登录流程文档
|
||||
|
||||
## 概述
|
||||
|
||||
本文档详细描述了 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. **日志记录**: 添加详细的操作日志
|
||||
|
||||
## 总结
|
||||
|
||||
邮箱验证码登录流程是一个完整的用户认证系统,包含了界面展示、验证码获取、用户登录、数据存储等完整环节。该流程具有良好的安全性、用户体验和错误处理机制,符合现代移动应用的认证标准。
|
||||
|
||||
通过本文档,开发人员可以全面了解邮箱登录的实现细节,便于后续的功能扩展和维护工作。
|
262
OAuth_Ticket_API_Documentation.md
Normal file
262
OAuth_Ticket_API_Documentation.md
Normal file
@@ -0,0 +1,262 @@
|
||||
# OAuth/Ticket 认证系统 API 文档
|
||||
|
||||
## 概述
|
||||
|
||||
本文档描述了 YuMi 应用中 OAuth 认证和 Ticket 会话管理的完整流程。系统采用两阶段认证机制:
|
||||
1. **OAuth 阶段**:用户登录获取 `access_token`
|
||||
2. **Ticket 阶段**:使用 `access_token` 获取业务会话 `ticket`
|
||||
|
||||
## 认证流程架构
|
||||
|
||||
### 核心组件
|
||||
- **AccountInfoStorage**: 负责账户信息和 ticket 的本地存储
|
||||
- **HttpRequestHelper**: 网络请求管理,自动添加认证头
|
||||
- **Api+Login**: 登录相关 API 接口
|
||||
- **Api+Main**: Ticket 获取相关 API 接口
|
||||
|
||||
### 认证数据模型
|
||||
|
||||
#### AccountModel
|
||||
```objc
|
||||
@interface AccountModel : PIBaseModel
|
||||
@property (nonatomic, assign) NSString *uid; // 用户唯一标识
|
||||
@property (nonatomic, copy) NSString *jti; // JWT ID
|
||||
@property (nonatomic, copy) NSString *token_type; // Token 类型
|
||||
@property (nonatomic, copy) NSString *refresh_token; // 刷新令牌
|
||||
@property (nonatomic, copy) NSString *netEaseToken; // 网易云信令牌
|
||||
@property (nonatomic, copy) NSString *access_token; // OAuth 访问令牌
|
||||
@property (nonatomic, assign) NSNumber *expires_in; // 过期时间
|
||||
@end
|
||||
```
|
||||
|
||||
## API 接口详情
|
||||
|
||||
### 1. OAuth 登录接口
|
||||
|
||||
#### 1.1 手机验证码登录
|
||||
```objc
|
||||
+ (void)loginWithCode:(HttpRequestHelperCompletion)completion
|
||||
phone:(NSString *)phone
|
||||
code:(NSString *)code
|
||||
client_secret:(NSString *)client_secret
|
||||
version:(NSString *)version
|
||||
client_id:(NSString *)client_id
|
||||
grant_type:(NSString *)grant_type
|
||||
phoneAreaCode:(NSString *)phoneAreaCode;
|
||||
```
|
||||
|
||||
**接口路径**: `POST /oauth/token`
|
||||
|
||||
**请求参数**:
|
||||
| 参数名 | 类型 | 必填 | 描述 |
|
||||
|--------|------|------|------|
|
||||
| phone | String | 是 | 手机号(DES加密) |
|
||||
| code | String | 是 | 验证码 |
|
||||
| client_secret | String | 是 | 客户端密钥,固定值:"uyzjdhds" |
|
||||
| version | String | 是 | 版本号,固定值:"1" |
|
||||
| client_id | String | 是 | 客户端ID,固定值:"erban-client" |
|
||||
| grant_type | String | 是 | 授权类型,验证码登录为:"sms_code" |
|
||||
| phoneAreaCode | String | 是 | 手机区号 |
|
||||
|
||||
**返回数据**: AccountModel 对象
|
||||
|
||||
#### 1.2 手机密码登录
|
||||
```objc
|
||||
+ (void)loginWithPassword:(HttpRequestHelperCompletion)completion
|
||||
phone:(NSString *)phone
|
||||
password:(NSString *)password
|
||||
client_secret:(NSString *)client_secret
|
||||
version:(NSString *)version
|
||||
client_id:(NSString *)client_id
|
||||
grant_type:(NSString *)grant_type;
|
||||
```
|
||||
|
||||
**接口路径**: `POST /oauth/token`
|
||||
|
||||
**请求参数**:
|
||||
| 参数名 | 类型 | 必填 | 描述 |
|
||||
|--------|------|------|------|
|
||||
| phone | String | 是 | 手机号(DES加密) |
|
||||
| password | String | 是 | 密码(DES加密) |
|
||||
| client_secret | String | 是 | 客户端密钥 |
|
||||
| version | String | 是 | 版本号 |
|
||||
| client_id | String | 是 | 客户端ID |
|
||||
| grant_type | String | 是 | 授权类型,密码登录为:"password" |
|
||||
|
||||
#### 1.3 第三方登录
|
||||
```objc
|
||||
+ (void)loginWithThirdPart:(HttpRequestHelperCompletion)completion
|
||||
openid:(NSString *)openid
|
||||
unionid:(NSString *)unionid
|
||||
access_token:(NSString *)access_token
|
||||
type:(NSString *)type;
|
||||
```
|
||||
|
||||
**接口路径**: `POST /acc/third/login`
|
||||
|
||||
**请求参数**:
|
||||
| 参数名 | 类型 | 必填 | 描述 |
|
||||
|--------|------|------|------|
|
||||
| openid | String | 是 | 第三方平台用户唯一标识 |
|
||||
| unionid | String | 是 | 第三方平台联合ID |
|
||||
| access_token | String | 是 | 第三方平台访问令牌 |
|
||||
| type | String | 是 | 第三方平台类型(1:Apple, 2:Facebook, 3:Google等) |
|
||||
|
||||
### 2. Ticket 获取接口
|
||||
|
||||
#### 2.1 获取 Ticket
|
||||
```objc
|
||||
+ (void)requestTicket:(HttpRequestHelperCompletion)completion
|
||||
access_token:(NSString *)accessToken
|
||||
issue_type:(NSString *)issueType;
|
||||
```
|
||||
|
||||
**接口路径**: `POST /oauth/ticket`
|
||||
|
||||
**请求参数**:
|
||||
| 参数名 | 类型 | 必填 | 描述 |
|
||||
|--------|------|------|------|
|
||||
| access_token | String | 是 | OAuth 登录获取的访问令牌 |
|
||||
| issue_type | String | 是 | 签发类型,固定值:"multi" |
|
||||
|
||||
**返回数据**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"tickets": [
|
||||
{
|
||||
"ticket": "eyJhbGciOiJIUzI1NiJ9..."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. HTTP 请求头配置
|
||||
|
||||
所有业务 API 请求都会自动添加以下请求头:
|
||||
|
||||
```objc
|
||||
// 在 HttpRequestHelper 中自动配置
|
||||
- (void)setupHeader {
|
||||
AFHTTPSessionManager *client = [HttpRequestHelper requestManager];
|
||||
|
||||
// 用户ID头
|
||||
if ([[AccountInfoStorage instance] getUid].length > 0) {
|
||||
[client.requestSerializer setValue:[[AccountInfoStorage instance] getUid]
|
||||
forHTTPHeaderField:@"pub_uid"];
|
||||
}
|
||||
|
||||
// Ticket 认证头
|
||||
if ([[AccountInfoStorage instance] getTicket].length > 0) {
|
||||
[client.requestSerializer setValue:[[AccountInfoStorage instance] getTicket]
|
||||
forHTTPHeaderField:@"pub_ticket"];
|
||||
}
|
||||
|
||||
// 其他公共头
|
||||
[client.requestSerializer setValue:[NSBundle uploadLanguageText]
|
||||
forHTTPHeaderField:@"Accept-Language"];
|
||||
[client.requestSerializer setValue:PI_App_Version
|
||||
forHTTPHeaderField:@"App-Version"];
|
||||
}
|
||||
```
|
||||
|
||||
## 使用流程
|
||||
|
||||
### 完整登录流程示例
|
||||
|
||||
```objc
|
||||
// 1. 用户登录获取 access_token
|
||||
[Api loginWithCode:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
|
||||
if (code == 200) {
|
||||
// 保存账户信息
|
||||
AccountModel *accountModel = [AccountModel modelWithDictionary:data.data];
|
||||
[[AccountInfoStorage instance] saveAccountInfo:accountModel];
|
||||
|
||||
// 2. 使用 access_token 获取 ticket
|
||||
[Api requestTicket:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
|
||||
if (code == 200) {
|
||||
NSArray *tickets = [data.data valueForKey:@"tickets"];
|
||||
NSString *ticket = [tickets[0] valueForKey:@"ticket"];
|
||||
|
||||
// 保存 ticket
|
||||
[[AccountInfoStorage instance] saveTicket:ticket];
|
||||
|
||||
// 3. 登录成功,可以进行业务操作
|
||||
[self navigateToMainPage];
|
||||
}
|
||||
} access_token:accountModel.access_token issue_type:@"multi"];
|
||||
}
|
||||
} phone:encryptedPhone
|
||||
code:verificationCode
|
||||
client_secret:@"uyzjdhds"
|
||||
version:@"1"
|
||||
client_id:@"erban-client"
|
||||
grant_type:@"sms_code"
|
||||
phoneAreaCode:areaCode];
|
||||
```
|
||||
|
||||
### 自动登录流程
|
||||
|
||||
```objc
|
||||
- (void)autoLogin {
|
||||
// 检查本地是否有账户信息
|
||||
AccountModel *accountModel = [[AccountInfoStorage instance] getCurrentAccountInfo];
|
||||
if (accountModel == nil || accountModel.access_token == nil) {
|
||||
[self tokenInvalid]; // 跳转到登录页
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否有有效的 ticket
|
||||
if ([[AccountInfoStorage instance] getTicket].length > 0) {
|
||||
[[self getView] autoLoginSuccess];
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用 access_token 重新获取 ticket
|
||||
[Api requestTicket:^(BaseModel * _Nonnull data) {
|
||||
NSArray *tickets = [data.data valueForKey:@"tickets"];
|
||||
NSString *ticket = [tickets[0] valueForKey:@"ticket"];
|
||||
[[AccountInfoStorage instance] saveTicket:ticket];
|
||||
[[self getView] autoLoginSuccess];
|
||||
} fail:^(NSInteger code, NSString * _Nullable msg) {
|
||||
[self logout]; // ticket 获取失败,重新登录
|
||||
} access_token:accountModel.access_token issue_type:@"multi"];
|
||||
}
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
### 401 未授权错误
|
||||
当接收到 401 状态码时,系统会自动处理:
|
||||
|
||||
```objc
|
||||
// 在 HttpRequestHelper 中
|
||||
if (response && response.statusCode == 401) {
|
||||
failure(response.statusCode, YMLocalizedString(@"HttpRequestHelper7"));
|
||||
// 通常需要重新登录
|
||||
}
|
||||
```
|
||||
|
||||
### Ticket 过期处理
|
||||
- Ticket 过期时服务器返回 401 错误
|
||||
- 客户端应该使用保存的 `access_token` 重新获取 ticket
|
||||
- 如果 `access_token` 也过期,则需要用户重新登录
|
||||
|
||||
## 安全注意事项
|
||||
|
||||
1. **数据加密**: 敏感信息(手机号、密码)使用 DES 加密传输
|
||||
2. **本地存储**:
|
||||
- `access_token` 存储在文件系统中
|
||||
- `ticket` 存储在内存中,应用重启需重新获取
|
||||
3. **请求头**: 所有业务请求自动携带 `pub_uid` 和 `pub_ticket` 头
|
||||
4. **错误处理**: 建立完善的 401 错误重试机制
|
||||
|
||||
## 相关文件
|
||||
|
||||
- `YuMi/Structure/MVP/Model/AccountInfoStorage.h/m` - 账户信息存储管理
|
||||
- `YuMi/Modules/YMLogin/Api/Api+Login.h/m` - 登录相关接口
|
||||
- `YuMi/Modules/YMTabbar/Api/Api+Main.h/m` - Ticket 获取接口
|
||||
- `YuMi/Network/HttpRequestHelper.h/m` - 网络请求管理
|
||||
- `YuMi/Structure/MVP/Model/AccountModel.h/m` - 账户数据模型
|
File diff suppressed because it is too large
Load Diff
@@ -46,7 +46,7 @@ isPhoneXSeries = [[UIApplication sharedApplication] delegate].window.safeAreaIns
|
||||
#define kFontHeavy(font) [UIFont systemFontOfSize:kGetScaleWidth(font) weight:UIFontWeightHeavy]
|
||||
|
||||
///内置版本号
|
||||
#define PI_App_Version @"1.0.28.1"
|
||||
#define PI_App_Version @"1.0.29"
|
||||
///渠道
|
||||
#define PI_App_Source @"appstore"
|
||||
#define PI_Test_Flight @"TestFlight"
|
||||
|
BIN
YuMi/Library/SudMGP.framework/plugin.zip
Normal file
BIN
YuMi/Library/SudMGP.framework/plugin.zip
Normal file
Binary file not shown.
@@ -25,7 +25,9 @@
|
||||
UserInfoModel * infoModel = [UserInfoModel modelWithDictionary:data.data];
|
||||
[[self getView] onGetUserInfoSuccess:infoModel];
|
||||
} fail:^(NSInteger code, NSString * _Nullable msg) {
|
||||
|
||||
if ([self.getView respondsToSelector:@selector(onGetUserFailure:)]) {
|
||||
[self.getView onGetUserFailure:code];
|
||||
}
|
||||
}] uid:uid];
|
||||
}
|
||||
|
||||
|
@@ -17,6 +17,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
///获取用户信息成功
|
||||
- (void)onGetSessionUserInfoSuccess:(UserInfoModel *)userInfo;
|
||||
|
||||
- (void)onGetUserFailure:(NSInteger)code;
|
||||
|
||||
///获取粉丝喜欢成功
|
||||
- (void)getFansLikeSuccess:(BOOL)isLike;
|
||||
///关注成功
|
||||
|
@@ -573,10 +573,27 @@
|
||||
make.top.mas_equalTo(self.sessionNavView.mas_bottom).mas_offset(0);
|
||||
}];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onGetUserFailure:(NSInteger)code {
|
||||
if (code == 3009) {
|
||||
if (self.openType == SessionListOpenTypeRoom) {
|
||||
CATransition *transition = [CATransition animation];
|
||||
transition.duration = 0.3f;
|
||||
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
|
||||
transition.type = kCATransitionPush;
|
||||
transition.subtype = kCATransitionFromLeft;
|
||||
[self.view.superview.layer addAnimation:transition forKey:nil];
|
||||
[self willMoveToParentViewController:nil]; //1
|
||||
[self.view removeFromSuperview]; //2
|
||||
[self removeFromParentViewController]; //3
|
||||
return;
|
||||
}
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)getFansLikeSuccess:(BOOL)isLike {
|
||||
self.sessionNavView.isLike = isLike;
|
||||
if(self.isAttention == YES && isLike == NO){
|
||||
|
@@ -26,7 +26,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
- (void)rankListFailure;
|
||||
|
||||
- (void)useMedalSuccess;
|
||||
- (void)userMedalsFailure;
|
||||
- (void)useMedalFailure;
|
||||
|
||||
@end
|
||||
|
||||
|
@@ -14,7 +14,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
+ (void)registerTo:(UICollectionView *)collectionView;
|
||||
+ (instancetype)cellFor:(UICollectionView *)collectionView atIndexPath:(NSIndexPath *)index;
|
||||
|
||||
- (void)updateCell:(MedalSeriesItemVo *)model isForSquare:(BOOL)isSquare;
|
||||
- (void)updateCell:(MedalSeriesItemVo *)model isForSquare:(BOOL)isSquare isForMine:(BOOL)isMine;
|
||||
|
||||
/**
|
||||
* 当 cell 将要显示时调用
|
||||
|
@@ -162,7 +162,6 @@
|
||||
self.displayModel = nil; // 重置数据模型
|
||||
self.currentItemVo = nil; // 重置当前项
|
||||
|
||||
|
||||
// 清空文本
|
||||
self.titleLabel.text = @"";
|
||||
self.subLabel.text = @"";
|
||||
@@ -175,8 +174,7 @@
|
||||
self.imageView.imageUrl = @"";
|
||||
}
|
||||
|
||||
- (void)updateCell:(MedalSeriesItemVo *)model isForSquare:(BOOL)isSquare {
|
||||
// MedalSeriesItemVo *itemVos = [model.medalSeries xpSafeObjectAtIndex:0];
|
||||
- (void)updateCell:(MedalSeriesItemVo *)model isForSquare:(BOOL)isSquare isForMine:(BOOL)isMine {
|
||||
self.currentItemVo = model;
|
||||
|
||||
// 设置指示器类型为不带图片
|
||||
@@ -190,13 +188,16 @@
|
||||
self.displayModel = [model.medalVos xpSafeObjectAtIndex:model.medalVos.count - 1];
|
||||
[self.levelIndicatorView setSelectedLevel:model.medalLevel animated:NO];
|
||||
} else {
|
||||
self.subLabel.hidden = !isMine;
|
||||
self.levelIndicatorView.userInteractionEnabled = YES;
|
||||
self.displayModel = [model.medalVos xpSafeObjectAtIndex:0];
|
||||
NSMutableArray *arr = @[].mutableCopy;
|
||||
for (MedalVo *vo in model.medalVos) {
|
||||
[arr addObject:@(vo.level)];
|
||||
if (vo.level > self.displayModel.level) {
|
||||
self.displayModel = vo;
|
||||
if (vo.hasGain) {
|
||||
[arr addObject:@(vo.level)];
|
||||
if (vo.level > self.displayModel.level) {
|
||||
self.displayModel = vo;
|
||||
}
|
||||
}
|
||||
}
|
||||
[self.levelIndicatorView setHighlightLevels:arr animated:NO];
|
||||
|
@@ -11,7 +11,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface MedalsDetailView : UIView
|
||||
|
||||
@property (nonatomic, strong) MedalSeriesItemVo *detailItemVo;
|
||||
- (void)updateSeriesItem:(MedalSeriesItemVo *)detailItem isMine:(BOOL)isMine;
|
||||
|
||||
@end
|
||||
|
||||
|
@@ -26,6 +26,8 @@
|
||||
@property (nonatomic, strong) MedalSeriesItemVo *currentSeriesItemVO;
|
||||
@property (nonatomic, strong) MedalVo *displayModel;
|
||||
|
||||
@property (nonatomic, strong) MedalSeriesItemVo *detailItemVo;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MedalsDetailView
|
||||
@@ -129,6 +131,23 @@
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)updateSeriesItem:(MedalSeriesItemVo *)detailItem isMine:(BOOL)isMine {
|
||||
self.detailItemVo = detailItem;
|
||||
if (isMine) {
|
||||
MedalVo *tempVo = [detailItem.medalVos xpSafeObjectAtIndex:0];
|
||||
for (MedalVo *vo in detailItem.medalVos) {
|
||||
if (vo.hasGain) {
|
||||
if (vo.level > tempVo.level) {
|
||||
tempVo = vo;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.displayModel = tempVo;
|
||||
[self.levelIndicatorView setSelectedLevel:self.displayModel.level animated:NO];
|
||||
[self updateDisplayWithCurrentModel];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setDetailItemVo:(MedalSeriesItemVo *)detailItemVo {
|
||||
// _detailItemVo = detailItemVo;
|
||||
self.currentSeriesItemVO = detailItemVo;
|
||||
|
@@ -425,11 +425,11 @@
|
||||
header.lastUpdatedTimeLabel.textColor = [DJDKMIMOMColor secondTextColor];
|
||||
self.rankTableView.mj_header = header;
|
||||
|
||||
// 设置上拉加载更多
|
||||
MJRefreshBackNormalFooter *footer = [MJRefreshBackNormalFooter footerWithRefreshingTarget:self refreshingAction:@selector(footerRefresh)];
|
||||
footer.stateLabel.textColor = [DJDKMIMOMColor secondTextColor];
|
||||
footer.stateLabel.font = [UIFont systemFontOfSize:10.0];
|
||||
self.rankTableView.mj_footer = footer;
|
||||
// // 设置上拉加载更多
|
||||
// MJRefreshBackNormalFooter *footer = [MJRefreshBackNormalFooter footerWithRefreshingTarget:self refreshingAction:@selector(footerRefresh)];
|
||||
// footer.stateLabel.textColor = [DJDKMIMOMColor secondTextColor];
|
||||
// footer.stateLabel.font = [UIFont systemFontOfSize:10.0];
|
||||
// self.rankTableView.mj_footer = footer;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
@@ -450,7 +450,7 @@
|
||||
self.mineRankModel = model.mine;
|
||||
self.avatarImageView.imageUrl = self.mineRankModel.avatar;
|
||||
self.nameLabel.text = self.mineRankModel.nick;
|
||||
self.indexLabel.text = self.mineRankModel.rank == 0 ? @"-" : @(self.mineRankModel.rank).stringValue;
|
||||
self.indexLabel.text = self.mineRankModel.rank == 0 ? @"10+" : @(self.mineRankModel.rank).stringValue;
|
||||
self.countLabel.text = @(self.mineRankModel.medalCount).stringValue;
|
||||
|
||||
self.rankList = [NSMutableArray array];
|
||||
|
@@ -37,7 +37,7 @@ typedef enum : NSInteger {
|
||||
@property (nonatomic, strong) UIButton *emptyUserMedalButton;
|
||||
@property (nonatomic, strong) TYCyclePagerView *medalsCyclePagerView;
|
||||
@property (nonatomic, strong) UIView *emptyView;
|
||||
@property (nonatomic, copy) UICollectionView *medalsCollectionView;
|
||||
@property (nonatomic, strong) UICollectionView *medalsCollectionView;
|
||||
@property (nonatomic, strong) UIStackView *centerTabsStackView;
|
||||
|
||||
@property (nonatomic, strong) NSMutableArray <MedalSeriesItemVo *>*datasourceTaskMedals;
|
||||
@@ -53,9 +53,9 @@ typedef enum : NSInteger {
|
||||
@property (nonatomic, assign) MedalsCenterDisplayType displayType;
|
||||
|
||||
@property (nonatomic, strong) UserMedalsModel *userMedalsModel;
|
||||
@property (nonatomic, copy) NSMutableArray <MedalSeriesVo *>*taskSquareMedalVo;
|
||||
@property (nonatomic, copy) NSMutableArray <MedalSeriesVo *>*activitySquareMedalVo;
|
||||
@property (nonatomic, copy) NSMutableArray <MedalSeriesVo *>*glorySquareMedalVo;
|
||||
@property (nonatomic, strong) NSMutableArray <MedalSeriesVo *>*taskSquareMedalVo;
|
||||
@property (nonatomic, strong) NSMutableArray <MedalSeriesVo *>*activitySquareMedalVo;
|
||||
@property (nonatomic, strong) NSMutableArray <MedalSeriesVo *>*glorySquareMedalVo;
|
||||
|
||||
@property (nonatomic, strong) UIImageView *otherBG;
|
||||
@property (nonatomic, strong) NetImageView *otherAvatar;
|
||||
@@ -807,21 +807,25 @@ typedef enum : NSInteger {
|
||||
|
||||
- (void)_updateDataSource:(NSArray <MedalSeriesVo *>*)models {
|
||||
|
||||
if (models.count < [self.presenter pageSize]) {
|
||||
NSArray *targetArr = models;
|
||||
NSMutableArray *itemDataSources = [NSMutableArray array];
|
||||
|
||||
MedalSeriesVo *series = [models xpSafeObjectAtIndex:0];
|
||||
if (series) {
|
||||
targetArr = series.medalSeries;
|
||||
}
|
||||
itemDataSources = targetArr.mutableCopy;
|
||||
|
||||
if (targetArr.count < [self.presenter pageSize]) {
|
||||
[self.medalsCollectionView.mj_footer endRefreshingWithNoMoreData];
|
||||
} else {
|
||||
[self.medalsCollectionView.mj_footer resetNoMoreData];
|
||||
}
|
||||
|
||||
NSMutableArray *itemDataSources = [NSMutableArray array];
|
||||
for (MedalSeriesVo *vo in models) {
|
||||
[itemDataSources addObjectsFromArray:vo.medalSeries];
|
||||
}
|
||||
|
||||
switch (self.currentTabType) {
|
||||
case MedalsCenterTab_TaskMedals:
|
||||
if (self.currentPageTaskMedals == 1) {
|
||||
self.emptyView.hidden = (models.count != 0);
|
||||
self.emptyView.hidden = (targetArr.count != 0);
|
||||
self.datasourceTaskMedals = itemDataSources;
|
||||
} else {
|
||||
[self.datasourceTaskMedals addObjectsFromArray:itemDataSources];
|
||||
@@ -829,7 +833,7 @@ typedef enum : NSInteger {
|
||||
break;
|
||||
case MedalsCenterTab_ActivityMedals:
|
||||
if (self.currentPageActivityMedals == 1) {
|
||||
self.emptyView.hidden = (models.count != 0);
|
||||
self.emptyView.hidden = (targetArr.count != 0);
|
||||
self.datasourceActivityMedals = itemDataSources;
|
||||
} else {
|
||||
[self.datasourceActivityMedals addObjectsFromArray:itemDataSources];
|
||||
@@ -837,7 +841,7 @@ typedef enum : NSInteger {
|
||||
break;
|
||||
case MedalsCenterTab_GloryMedals:
|
||||
if (self.currentPageGloryMedals == 1) {
|
||||
self.emptyView.hidden = (models.count != 0);
|
||||
self.emptyView.hidden = (targetArr.count != 0);
|
||||
self.datasourceGloryMedals = itemDataSources;
|
||||
} else {
|
||||
[self.datasourceGloryMedals addObjectsFromArray:itemDataSources];
|
||||
@@ -938,7 +942,9 @@ typedef enum : NSInteger {
|
||||
|
||||
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
MedalsCollectionViewCell *cell = [MedalsCollectionViewCell cellFor:collectionView atIndexPath:indexPath];
|
||||
[cell updateCell:[self loadModel:indexPath.item] isForSquare:self.displayType == MedalsCenterDisplayType_Square];
|
||||
[cell updateCell:[self loadModel:indexPath.item]
|
||||
isForSquare:self.displayType == MedalsCenterDisplayType_Square
|
||||
isForMine:self.displayType == MedalsCenterDisplayType_Mine];
|
||||
return cell;
|
||||
}
|
||||
|
||||
@@ -964,7 +970,8 @@ typedef enum : NSInteger {
|
||||
return;
|
||||
}
|
||||
MedalsDetailView *view = [[MedalsDetailView alloc] initWithFrame:self.view.bounds];
|
||||
view.detailItemVo = [self loadModel:indexPath.item];
|
||||
[view updateSeriesItem:[self loadModel:indexPath.item]
|
||||
isMine:self.displayType == MedalsCenterDisplayType_Mine || self.displayType == MedalsCenterDisplayType_Other];
|
||||
[self.view addSubview:view];
|
||||
}
|
||||
|
||||
|
@@ -31,7 +31,9 @@
|
||||
@property (nonatomic, copy) void(^contactCustomerService)(NSString *uid);
|
||||
|
||||
// 记录交易重试次数
|
||||
@property (nonatomic, strong) NSMutableDictionary *retryCountMap;
|
||||
@property (nonatomic, strong) NSMutableDictionary *retryCountDict;
|
||||
// 线程安全队列
|
||||
@property (nonatomic, strong) dispatch_queue_t retryCountQueue;
|
||||
|
||||
@end
|
||||
|
||||
@@ -46,7 +48,10 @@
|
||||
|
||||
proxy.recheckIndex = 0;
|
||||
proxy.recheckInterval = 1.0;
|
||||
proxy.retryCountMap = [NSMutableDictionary dictionary];
|
||||
proxy.retryCountDict = [NSMutableDictionary dictionary];
|
||||
proxy.retryCountQueue = dispatch_queue_create("com.iapmanager.retrycount.queue", DISPATCH_QUEUE_CONCURRENT);
|
||||
proxy.isProcessing = NO;
|
||||
proxy.isLogin = NO;
|
||||
});
|
||||
|
||||
return proxy;
|
||||
@@ -96,6 +101,9 @@
|
||||
self.recheckTimer = nil;
|
||||
}
|
||||
|
||||
// 清理过期交易
|
||||
[self cleanupStaleTransactions];
|
||||
|
||||
// 设置最大重试间隔
|
||||
NSTimeInterval interval = MIN(self.recheckInterval, 300.0);
|
||||
|
||||
@@ -105,112 +113,178 @@
|
||||
selector:@selector(handleRetryCheckReceipt)
|
||||
userInfo:nil
|
||||
repeats:NO];
|
||||
|
||||
// 确保定时器在主运行循环中运行
|
||||
[[NSRunLoop mainRunLoop] addTimer:self.recheckTimer forMode:NSRunLoopCommonModes];
|
||||
}
|
||||
|
||||
// 验单逻辑
|
||||
- (void)handleRetryCheckReceipt {
|
||||
// NSLog(@"[YuMi IAP] 用户触发补单检查 - Retry checking receipts");
|
||||
NSLog(@"[YuMi IAP] 用户触发补单检查 - Retry checking receipts");
|
||||
|
||||
// 如果已经在处理中或未登录,则不执行,直接等待下次定时器触发
|
||||
if (self.isProcessing || !self.isLogin) {
|
||||
// 只销毁当前定时器,不递归创建新定时器,避免无限循环
|
||||
if (self.recheckTimer) {
|
||||
[self.recheckTimer invalidate];
|
||||
self.recheckTimer = nil;
|
||||
}
|
||||
// 重新安排下一次检查(延迟更长时间,防止频繁空转)
|
||||
self.recheckInterval = MIN(self.recheckInterval * 1.5, 300.0);
|
||||
[self retryCheckAllReceipt];
|
||||
return;
|
||||
}
|
||||
|
||||
NSArray *array = [RechargeStorage getAllReceiptsWithUid:[AccountInfoStorage instance].getUid];
|
||||
// NSLog(@" ------------.------------ 尝试:%@", array);
|
||||
@synchronized (array) {
|
||||
if (array.count == 0 || self.isProcessing) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self.isLogin) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.recheckIndex >= array.count) {
|
||||
self.recheckIndex = 0;
|
||||
}
|
||||
#if DEBUG
|
||||
// [self requestAPPOrderData:@"com.hflighting.yumi.gold.1_7000" isFroRecheck:YES];
|
||||
#endif
|
||||
self.isProcessing = YES;
|
||||
NSDictionary *dic = [array xpSafeObjectAtIndex:self.recheckIndex];
|
||||
NSString *transactionId = dic[@"transactionId"];
|
||||
|
||||
[self _logToBugly:transactionId oID:dic[@"orderId"] status:0];
|
||||
|
||||
NSInteger retryCount = [self getRetryCountForTransaction:transactionId];
|
||||
|
||||
if (retryCount > MAX_RETRY_COUNT) {
|
||||
// 超过最大重试次数,记录并清理
|
||||
[self _logToBugly:transactionId oID:dic[@"orderId"] status:4]; // 新状态:重试次数过多
|
||||
[RechargeStorage delegateTransactionId:transactionId uid:[AccountInfoStorage instance].getUid];
|
||||
self.isProcessing = NO;
|
||||
return;
|
||||
}
|
||||
|
||||
// 增加重试计数
|
||||
[self incrementRetryCountForTransaction:transactionId];
|
||||
|
||||
@kWeakify(self);
|
||||
[self backgroundCheckReceiptWithTransactionID:transactionId
|
||||
orderID:dic[@"orderId"]
|
||||
next:^(BOOL isSuccess){
|
||||
@kStrongify(self);
|
||||
if (isSuccess) {
|
||||
[RechargeStorage delegateTransactionId:transactionId
|
||||
uid:[AccountInfoStorage instance].getUid];
|
||||
self.recheckInterval = MIN(self.recheckInterval * 2, 300.0);
|
||||
|
||||
[self _logToBugly:transactionId oID:dic[@"orderId"] status:1];
|
||||
|
||||
// 成功后移除重试记录
|
||||
[self removeRetryCountForTransaction:transactionId];
|
||||
|
||||
} else {
|
||||
self.recheckInterval = self.recheckInterval * 2;
|
||||
[self _logToBugly:transactionId oID:dic[@"orderId"] status:2];
|
||||
}
|
||||
|
||||
self.recheckIndex += 1;
|
||||
|
||||
self.isProcessing = NO;
|
||||
|
||||
NSLog(@" ------------.------------ 尝试:%@", array);
|
||||
|
||||
// 如果没有需要处理的收据,定时器继续以较长间隔轮询
|
||||
if (array.count == 0) {
|
||||
NSLog(@" ------------.------------ 没有需要验单的数据");
|
||||
if (self.recheckTimer) {
|
||||
[self.recheckTimer invalidate];
|
||||
[self retryCheckAllReceipt];
|
||||
}];
|
||||
self.recheckTimer = nil;
|
||||
}
|
||||
// 设为较长间隔(如10分钟)
|
||||
self.recheckInterval = 600.0;
|
||||
[self retryCheckAllReceipt];
|
||||
return;
|
||||
}
|
||||
|
||||
// 确保索引在有效范围内
|
||||
if (self.recheckIndex >= array.count) {
|
||||
self.recheckIndex = 0;
|
||||
}
|
||||
|
||||
// 标记为处理中
|
||||
self.isProcessing = YES;
|
||||
|
||||
// 安全地获取当前需要处理的收据
|
||||
NSDictionary *dic = [array xpSafeObjectAtIndex:self.recheckIndex];
|
||||
NSString *transactionId = dic[@"transactionId"];
|
||||
NSString *orderId = dic[@"orderId"];
|
||||
|
||||
// 记录尝试验单
|
||||
[self _logToBugly:transactionId oID:orderId status:0];
|
||||
|
||||
// 检查重试次数
|
||||
NSInteger retryCount = [self getRetryCountForTransaction:transactionId];
|
||||
|
||||
if (retryCount > MAX_RETRY_COUNT) {
|
||||
// 超过最大重试次数,记录并清理
|
||||
[self _logToBugly:transactionId oID:orderId status:4]; // 新状态:重试次数过多
|
||||
[RechargeStorage delegateTransactionId:transactionId uid:[AccountInfoStorage instance].getUid];
|
||||
[self removeRetryCountForTransaction:transactionId];
|
||||
|
||||
// 重置处理状态
|
||||
self.isProcessing = NO;
|
||||
|
||||
// 增加索引并安排下一次检查
|
||||
self.recheckIndex = (self.recheckIndex + 1) % array.count;
|
||||
if (self.recheckTimer) {
|
||||
[self.recheckTimer invalidate];
|
||||
self.recheckTimer = nil;
|
||||
}
|
||||
[self retryCheckAllReceipt];
|
||||
return;
|
||||
}
|
||||
|
||||
// 增加重试计数
|
||||
[self incrementRetryCountForTransaction:transactionId];
|
||||
|
||||
@kWeakify(self);
|
||||
[self backgroundCheckReceiptWithTransactionID:transactionId
|
||||
orderID:orderId
|
||||
next:^(BOOL isSuccess){
|
||||
@kStrongify(self);
|
||||
if (isSuccess) {
|
||||
// 成功处理
|
||||
[RechargeStorage delegateTransactionId:transactionId
|
||||
uid:[AccountInfoStorage instance].getUid];
|
||||
|
||||
// 成功后减少重试间隔,加快处理速度
|
||||
self.recheckInterval = MAX(self.recheckInterval / 2, 1.0);
|
||||
|
||||
[self _logToBugly:transactionId oID:orderId status:1];
|
||||
|
||||
// 成功后移除重试记录
|
||||
[self removeRetryCountForTransaction:transactionId];
|
||||
|
||||
} else {
|
||||
// 失败后增加重试间隔,避免频繁请求
|
||||
self.recheckInterval = MIN(self.recheckInterval * 1.5, 300.0);
|
||||
[self _logToBugly:transactionId oID:orderId status:2];
|
||||
}
|
||||
|
||||
// 增加索引,准备处理下一个收据
|
||||
self.recheckIndex = (self.recheckIndex + 1) % array.count;
|
||||
|
||||
// 重置处理状态
|
||||
self.isProcessing = NO;
|
||||
|
||||
// 安排下一次检查
|
||||
if (self.recheckTimer) {
|
||||
[self.recheckTimer invalidate];
|
||||
self.recheckTimer = nil;
|
||||
}
|
||||
[self retryCheckAllReceipt];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)_logToBugly:(NSString *)tid oID:(NSString *)oid status:(NSInteger)status {
|
||||
|
||||
NSMutableDictionary *logDic = [NSMutableDictionary dictionary];
|
||||
[logDic setObject:tid forKey:@"内购 transactionId"];
|
||||
[logDic setObject:oid forKey:@"内购 orderId"];
|
||||
[logDic setObject:[AccountInfoStorage instance].getUid forKey:@"内购 用户id"];
|
||||
// 安全处理交易ID
|
||||
if ([NSString isEmpty:tid]) {
|
||||
[logDic setObject:@"" forKey:@"内购 transactionId"];
|
||||
tid = @"";
|
||||
} else {
|
||||
[logDic setObject:tid forKey:@"内购 transactionId"];
|
||||
}
|
||||
// 安全处理订单ID
|
||||
if ([NSString isEmpty:oid]) {
|
||||
[logDic setObject:@"" forKey:@"内购 orderId"];
|
||||
} else {
|
||||
[logDic setObject:oid forKey:@"内购 orderId"];
|
||||
}
|
||||
|
||||
// 安全获取用户ID
|
||||
NSString *uid = [AccountInfoStorage instance].getUid ?: @"未知用户";
|
||||
[logDic setObject:uid forKey:@"内购 用户id"];
|
||||
|
||||
// 添加重试次数信息
|
||||
[logDic setObject:@([self getRetryCountForTransaction:tid]) forKey:@"重试次数"];
|
||||
[logDic setObject:@(self.recheckInterval) forKey:@"重试间隔"];
|
||||
[logDic setObject:@(self.recheckIndex) forKey:@"当前索引"];
|
||||
[logDic setObject:@(self.isProcessing) forKey:@"处理中状态"];
|
||||
[logDic setObject:@(self.isLogin) forKey:@"登录状态"];
|
||||
|
||||
NSString *statusMsg = @"";
|
||||
NSInteger code = -20000;
|
||||
switch (status) {
|
||||
case 0:
|
||||
statusMsg = [NSString stringWithFormat:@"UID: %@, 尝试验单",
|
||||
[AccountInfoStorage instance].getUid];
|
||||
statusMsg = [NSString stringWithFormat:@"UID: %@, 尝试验单", uid];
|
||||
break;
|
||||
case 1:
|
||||
statusMsg = [NSString stringWithFormat:@"UID: %@, 验单-补单成功",
|
||||
[AccountInfoStorage instance].getUid];
|
||||
statusMsg = [NSString stringWithFormat:@"UID: %@, 验单-补单成功", uid];
|
||||
code = -20001;
|
||||
break;
|
||||
case 2:
|
||||
statusMsg = [NSString stringWithFormat:@"UID: %@, 验单-补单失败",
|
||||
[AccountInfoStorage instance].getUid];
|
||||
statusMsg = [NSString stringWithFormat:@"UID: %@, 验单-补单失败", uid];
|
||||
code = -20002;
|
||||
break;
|
||||
case 3:
|
||||
statusMsg = [NSString stringWithFormat:@"UID: %@, 验单-补单 id 异常",
|
||||
[AccountInfoStorage instance].getUid];
|
||||
statusMsg = [NSString stringWithFormat:@"UID: %@, 验单-补单 id 异常", uid];
|
||||
code = -20002;
|
||||
break;
|
||||
case 4:
|
||||
statusMsg = [NSString stringWithFormat:@"UID: %@, 重试次数过多",
|
||||
[AccountInfoStorage instance].getUid];
|
||||
statusMsg = [NSString stringWithFormat:@"UID: %@, 重试次数过多", uid];
|
||||
code = -20003;
|
||||
break;
|
||||
case 5:
|
||||
statusMsg = [NSString stringWithFormat:@"UID: %@, 过期交易清理", uid];
|
||||
code = -20004;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -299,29 +373,29 @@
|
||||
@kStrongify(self);
|
||||
switch (state) {
|
||||
case StoreConditionResultStart:
|
||||
// NSLog(@"~~~```~~~ Purchase started");
|
||||
NSLog(@"~~~```~~~ Purchase started");
|
||||
break;
|
||||
case StoreConditionResultPay:
|
||||
// NSLog(@"~~~```~~~ Processing payment");
|
||||
NSLog(@"~~~```~~~ Processing payment");
|
||||
break;
|
||||
case StoreConditionResultVerifiedServer:
|
||||
// NSLog(@"~~~```~~~ Verified by server");
|
||||
NSLog(@"~~~```~~~ Verified by server");
|
||||
[self handleIAPSuccess:param];
|
||||
break;
|
||||
case StoreConditionResultUserCancelled:
|
||||
// NSLog(@"~~~```~~~ User cancelled purchase");
|
||||
NSLog(@"~~~```~~~ User cancelled purchase");
|
||||
[self handleFailurePurchase:@""];
|
||||
break;
|
||||
case StoreConditionResultNoProduct:
|
||||
// NSLog(@"~~~```~~~ No product found");
|
||||
NSLog(@"~~~```~~~ No product found");
|
||||
[self handleFailurePurchase:[NSString stringWithFormat:@"%@ No product found", YMLocalizedString(@"XPIAPRechargeViewController1")]];
|
||||
break;
|
||||
case StoreConditionResultFailedVerification:
|
||||
// NSLog(@"~~~```~~~ Verification failed");
|
||||
NSLog(@"~~~```~~~ Verification failed");
|
||||
[self handleFailurePurchase:YMLocalizedString(@"XPIAPRechargeViewController1")];
|
||||
break;
|
||||
case StoreConditionResultUnowned:
|
||||
// NSLog(@"~~~```~~~ Result Unowned");
|
||||
NSLog(@"~~~```~~~ Result Unowned");
|
||||
[self handleFailurePurchase:YMLocalizedString(@"XPIAPRechargeViewController1")];
|
||||
break;
|
||||
default:
|
||||
@@ -421,7 +495,7 @@
|
||||
- (void)checkReceiptWithTransactionID:(NSString *)tID
|
||||
orderID:(NSString *)orderID {
|
||||
|
||||
// NSLog(@" ------------.------------ 后端验单:%@ | %@", tID, orderID);
|
||||
NSLog(@" ------------.------------ 后端验单:%@ | %@", tID, orderID);
|
||||
|
||||
@kWeakify(self);
|
||||
[Api checkReceipt:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
|
||||
@@ -435,7 +509,7 @@
|
||||
// 参数异常,暂不处理
|
||||
}
|
||||
}
|
||||
// NSLog(@" ------------.------------ 后端验单结果:%@ ",msg);
|
||||
NSLog(@" ------------.------------ 后端验单结果:%@ ",msg);
|
||||
}
|
||||
chooseEnv:@"true"
|
||||
chargeRecordId:orderID
|
||||
@@ -449,6 +523,14 @@
|
||||
orderID:(NSString *)orderID
|
||||
next:(void(^)(BOOL isSuccess))next {
|
||||
|
||||
// 验证参数有效性
|
||||
if (![self isValidTransactionID:tID orderID:orderID]) {
|
||||
if (next) {
|
||||
next(NO);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建后台任务标识
|
||||
UIBackgroundTaskIdentifier backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithName:@"VerifyReceipt"
|
||||
expirationHandler:^{
|
||||
@@ -459,13 +541,24 @@
|
||||
[[UIApplication sharedApplication] endBackgroundTask:backgroundTask];
|
||||
}];
|
||||
|
||||
// 原验证代码...
|
||||
|
||||
// 完成后务必结束后台任务
|
||||
@kWeakify(self);
|
||||
[Api checkReceipt:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
|
||||
@kStrongify(self);
|
||||
// 原有验证代码...
|
||||
|
||||
BOOL isSuccess = (code == 200);
|
||||
|
||||
if (isSuccess) {
|
||||
[self handleCheckReceiptSuccess:tID isFromRecheck:YES];
|
||||
NSLog(@" ------------.------------ 后台验单成功:%@ | %@", tID, orderID);
|
||||
} else {
|
||||
NSLog(@" ------------.------------ 后台验单失败:%@ | %@ | %@", tID, orderID, msg);
|
||||
}
|
||||
|
||||
// 确保回调被调用
|
||||
if (next) {
|
||||
next(isSuccess);
|
||||
}
|
||||
|
||||
// 结束后台任务
|
||||
[[UIApplication sharedApplication] endBackgroundTask:backgroundTask];
|
||||
@@ -480,13 +573,19 @@
|
||||
// 查找在缓存中的 transactionID 并告知 apple 订单已完成
|
||||
- (void)handleCheckReceiptSuccess:(NSString *)tID
|
||||
isFromRecheck:(BOOL)isFromRecheck {
|
||||
// 验证参数
|
||||
if (tID.length == 0) {
|
||||
NSLog(@" ------------.------------ apple 验单失败:交易ID为空");
|
||||
return;
|
||||
}
|
||||
|
||||
if (@available(iOS 15.0, *)) {
|
||||
@kWeakify(self);
|
||||
[[PIIAPRegulate shared] verifyBusinessAccomplishWithTransactionID:tID
|
||||
completionHandler:^(BOOL success, NSError * _Nullable error) {
|
||||
@kStrongify(self);
|
||||
if (success) {
|
||||
// NSLog(@" ------------.------------ apple 验单成功");
|
||||
NSLog(@" ------------.------------ apple 验单成功");
|
||||
// 流程完成,移除本地缓存账单
|
||||
[RechargeStorage delegateTransactionId:tID
|
||||
uid:[AccountInfoStorage instance].getUid];
|
||||
@@ -494,7 +593,7 @@
|
||||
[self removeRetryCountForTransaction:tID];
|
||||
} else {
|
||||
// 出现异常
|
||||
// NSLog(@" ------------.------------ apple 验单成功:%@ ",error);
|
||||
NSLog(@" ------------.------------ apple 验单失败:%@ ",error);
|
||||
if (error == nil) {
|
||||
// 该订单在 appstore 已无法找到,不必再重试
|
||||
[RechargeStorage delegateTransactionId:tID
|
||||
@@ -502,7 +601,10 @@
|
||||
// 清理不存在的交易记录
|
||||
[self removeRetryCountForTransaction:tID];
|
||||
} else {
|
||||
[self retryCheckAllReceipt];
|
||||
// 只有在重试检查时才安排下一次重试
|
||||
if (isFromRecheck) {
|
||||
// 不立即重试,让定时器自然触发下一次
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -549,48 +651,92 @@
|
||||
// 清理长时间未处理的交易记录
|
||||
- (void)cleanupStaleTransactions {
|
||||
NSArray *receipts = [RechargeStorage getAllReceiptsWithUid:[AccountInfoStorage instance].getUid];
|
||||
if (!receipts || receipts.count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSDate *now = [NSDate date];
|
||||
NSInteger cleanupDays = 70; // 70天后清理
|
||||
|
||||
for (NSDictionary *receipt in receipts) {
|
||||
// 检查交易时间,如果超过7天还未成功,则清理
|
||||
// 检查交易时间,如果超过指定天数还未成功,则清理
|
||||
NSString *timestamp = receipt[@"timestamp"];
|
||||
if (timestamp) {
|
||||
if (!timestamp || timestamp.length == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
NSDate *transactionDate = nil;
|
||||
|
||||
// 尝试多种日期格式解析
|
||||
NSArray *dateFormats = @[
|
||||
@"yyyy-MM-dd HH:mm:ss Z",
|
||||
@"yyyy-MM-dd'T'HH:mm:ssZ",
|
||||
@"EEE MMM dd HH:mm:ss Z yyyy"
|
||||
];
|
||||
|
||||
for (NSString *format in dateFormats) {
|
||||
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
|
||||
[dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss Z"]; // 根据实际时间格式调整
|
||||
NSDate *transactionDate = [dateFormatter dateFromString:timestamp];
|
||||
[dateFormatter setDateFormat:format];
|
||||
[dateFormatter setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]];
|
||||
|
||||
if (transactionDate && [now timeIntervalSinceDate:transactionDate] > 70 * 24 * 60 * 60) {
|
||||
NSString *tID = receipt[@"transactionId"];
|
||||
[RechargeStorage delegateTransactionId:tID uid:[AccountInfoStorage instance].getUid];
|
||||
[self _logToBugly:tID oID:receipt[@"orderId"] status:5]; // 新状态:过期清理
|
||||
|
||||
// 清理过期交易的重试记录
|
||||
[self removeRetryCountForTransaction:tID];
|
||||
transactionDate = [dateFormatter dateFromString:timestamp];
|
||||
if (transactionDate) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果无法解析日期,使用当前时间减去60天作为估计值
|
||||
if (!transactionDate) {
|
||||
transactionDate = [now dateByAddingTimeInterval:-60 * 24 * 60 * 60];
|
||||
}
|
||||
|
||||
// 检查是否超过清理期限
|
||||
if ([now timeIntervalSinceDate:transactionDate] > cleanupDays * 24 * 60 * 60) {
|
||||
NSString *tID = receipt[@"transactionId"];
|
||||
NSString *oID = receipt[@"orderId"];
|
||||
|
||||
// 记录清理日志
|
||||
[self _logToBugly:tID oID:oID status:5]; // 状态5:过期清理
|
||||
|
||||
// 从存储中删除
|
||||
[RechargeStorage delegateTransactionId:tID uid:[AccountInfoStorage instance].getUid];
|
||||
|
||||
// 清理过期交易的重试记录
|
||||
[self removeRetryCountForTransaction:tID];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取交易的重试次数
|
||||
- (NSInteger)getRetryCountForTransaction:(NSString *)transactionId {
|
||||
NSNumber *countNumber = self.retryCountMap[transactionId];
|
||||
return countNumber ? [countNumber integerValue] : 0;
|
||||
__block NSInteger count = 0;
|
||||
dispatch_sync(self.retryCountQueue, ^{
|
||||
NSNumber *countNumber = self.retryCountDict[transactionId];
|
||||
count = countNumber ? [countNumber integerValue] : 0;
|
||||
});
|
||||
return count;
|
||||
}
|
||||
|
||||
// 增加交易的重试次数
|
||||
- (void)incrementRetryCountForTransaction:(NSString *)transactionId {
|
||||
NSInteger currentCount = [self getRetryCountForTransaction:transactionId];
|
||||
self.retryCountMap[transactionId] = @(currentCount + 1);
|
||||
dispatch_barrier_async(self.retryCountQueue, ^{
|
||||
NSInteger currentCount = [self.retryCountDict[transactionId] integerValue];
|
||||
self.retryCountDict[transactionId] = @(currentCount + 1);
|
||||
});
|
||||
}
|
||||
|
||||
// 移除交易的重试记录
|
||||
- (void)removeRetryCountForTransaction:(NSString *)transactionId {
|
||||
[self.retryCountMap removeObjectForKey:transactionId];
|
||||
dispatch_barrier_async(self.retryCountQueue, ^{
|
||||
[self.retryCountDict removeObjectForKey:transactionId];
|
||||
});
|
||||
}
|
||||
|
||||
// 清理所有重试记录
|
||||
- (void)cleanAllRetryCount {
|
||||
[self.retryCountMap removeAllObjects];
|
||||
dispatch_barrier_async(self.retryCountQueue, ^{
|
||||
[self.retryCountDict removeAllObjects];
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
|
@@ -195,10 +195,23 @@ XPIAPRechargeHeadCellDelegate>
|
||||
@kStrongify(self);
|
||||
[self hideHUD];
|
||||
[self getUserWalletBalanceInfo];
|
||||
|
||||
// 更新首充状态
|
||||
FirstRechargeManager *manager = [FirstRechargeManager sharedManager];
|
||||
// 使用新添加的方法更新首充状态
|
||||
[manager updateChargeStatusToCompleted];
|
||||
[self.collectionView reloadData];
|
||||
|
||||
// 由于服务器可能会返回不同的状态,我们在延迟后再次确认状态已更新
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[[FirstRechargeManager sharedManager] updateChargeStatusToCompleted];
|
||||
});
|
||||
|
||||
if(self.delegate && [self.delegate respondsToSelector:@selector(paySuccess)]){
|
||||
[self.delegate paySuccess];
|
||||
}
|
||||
self.rechargeBtn.userInteractionEnabled = YES;
|
||||
|
||||
} failure:^(NSError * _Nonnull error) {
|
||||
@kStrongify(self);
|
||||
[self hideHUD];
|
||||
@@ -280,9 +293,6 @@ XPIAPRechargeHeadCellDelegate>
|
||||
return CGSizeMake(kGetScaleWidth(108), kGetScaleWidth(121));
|
||||
}
|
||||
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
|
||||
|
||||
XPIAPRechargeCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass(XPIAPRechargeCollectionViewCell.self) forIndexPath:indexPath];
|
||||
NSInteger count = indexPath.item;
|
||||
cell.rechargeModel = [self.dataSource xpSafeObjectAtIndex:count];
|
||||
@@ -300,7 +310,6 @@ XPIAPRechargeHeadCellDelegate>
|
||||
|
||||
}
|
||||
#pragma mark - 懒加载
|
||||
|
||||
- (UICollectionView *)collectionView {
|
||||
if (!_collectionView) {
|
||||
MSBaseRTLFlowLayout *layout = [[MSBaseRTLFlowLayout alloc] init];
|
||||
@@ -317,10 +326,7 @@ XPIAPRechargeHeadCellDelegate>
|
||||
|
||||
_collectionView.delegate = self;
|
||||
_collectionView.dataSource = self;
|
||||
_collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
|
||||
|
||||
// _collectionView.contentInset = UIEdgeInsetsMake(kHeaderViewHeight, 0, 0, 0);
|
||||
|
||||
_collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
|
||||
}
|
||||
return _collectionView;
|
||||
}
|
||||
|
@@ -284,16 +284,16 @@
|
||||
@kStrongify(self);
|
||||
[UIView animateWithDuration:0.5 animations:^{
|
||||
self.firstChargeBackgroundView.alpha = 1;
|
||||
} completion:^(BOOL finished) {
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[self removeFirstChargePopup];
|
||||
});
|
||||
}];
|
||||
}];
|
||||
[self.firstChargeWebVC setDidTapCharge:^{
|
||||
@kStrongify(self);
|
||||
[self removeFirstChargePopup];
|
||||
}];
|
||||
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[self removeFirstChargePopup];
|
||||
});
|
||||
}
|
||||
|
||||
// 处理背景点击事件
|
||||
|
@@ -353,4 +353,16 @@
|
||||
return _goButton;
|
||||
}
|
||||
|
||||
// 在主实现体内添加事件穿透逻辑
|
||||
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
|
||||
if (!self.userInteractionEnabled || self.hidden || self.alpha <= 0.01) {
|
||||
return nil;
|
||||
}
|
||||
CGPoint goButtonPoint = [self.goButton convertPoint:point fromView:self];
|
||||
if ([self.goButton pointInside:goButtonPoint withEvent:event]) {
|
||||
return self.goButton;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
@@ -401,4 +401,16 @@
|
||||
return _goButton;
|
||||
}
|
||||
|
||||
// ========== 事件穿透实现 ==========
|
||||
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
|
||||
if (!self.userInteractionEnabled || self.hidden || self.alpha <= 0.01) {
|
||||
return nil;
|
||||
}
|
||||
CGPoint goButtonPoint = [self.goButton convertPoint:point fromView:self];
|
||||
if ([self.goButton pointInside:goButtonPoint withEvent:event]) {
|
||||
return self.goButton;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
@@ -52,11 +52,13 @@ static const NSInteger kItemsPerRow = 5;
|
||||
[self.blurEffectView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.trailing.equalTo(self).inset(0);
|
||||
make.bottom.equalTo(self).offset(12);
|
||||
make.height.mas_equalTo(self.collectionView.mas_height).multipliedBy(1.05);
|
||||
make.height.mas_equalTo(KScreenHeight*2/3);
|
||||
}];
|
||||
[self.collectionView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.bottom.leading.trailing.equalTo(self).inset(kGetScaleWidth(0));
|
||||
make.height.mas_equalTo(kGetScaleWidth(246));
|
||||
make.bottom.equalTo(self);
|
||||
make.leading.trailing.equalTo(self).inset(16);
|
||||
// make.height.mas_equalTo(kGetScaleWidth(246));
|
||||
make.height.mas_equalTo(KScreenHeight*2/3 - 30);
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -69,7 +71,6 @@ static const NSInteger kItemsPerRow = 5;
|
||||
-(void)setPlayList:(NSMutableArray *)playList {
|
||||
_playList = playList;
|
||||
[self.dataSource addObjectsFromArray:playList];
|
||||
[self updateViewHeightWithItemCount:self.dataSource.count];
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[self.collectionView reloadData];
|
||||
});
|
||||
@@ -78,7 +79,6 @@ static const NSInteger kItemsPerRow = 5;
|
||||
- (void)setLittleGameList:(NSMutableArray<LittleGameInfoModel *> *)littleGameList {
|
||||
_littleGameList = littleGameList;
|
||||
[self.dataSource addObjectsFromArray:littleGameList];
|
||||
[self updateViewHeightWithItemCount:self.dataSource.count];
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[self.collectionView reloadData];
|
||||
});
|
||||
@@ -90,39 +90,18 @@ static const NSInteger kItemsPerRow = 5;
|
||||
self.dataSource = [NSMutableArray array];
|
||||
[self.dataSource addObjectsFromArray:playList];
|
||||
[self.dataSource addObjectsFromArray:littleGameList];
|
||||
[self updateViewHeightWithItemCount:self.dataSource.count];
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[self.collectionView reloadData];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)updateViewHeightWithItemCount:(NSInteger)count {
|
||||
if (count == 0) {
|
||||
return;
|
||||
}
|
||||
NSInteger lineNum = (count + kItemsPerRow - 1)/kItemsPerRow;// MIN(3, (count + kItemsPerRow - 1)/kItemsPerRow);
|
||||
|
||||
CGFloat calHeight = self.itemHeight;
|
||||
CGFloat height = 20 + 56 * lineNum + 44 * (lineNum - 1) + 70;
|
||||
height = calHeight * lineNum;
|
||||
// [self.ms_bgView mas_updateConstraints:^(MASConstraintMaker *make) {
|
||||
// make.height.mas_equalTo(kGetScaleWidth(height + 12));
|
||||
// }];
|
||||
if (height > KScreenHeight*2/3) {
|
||||
height = KScreenHeight*2/3;
|
||||
}
|
||||
[self.collectionView mas_updateConstraints:^(MASConstraintMaker *make) {
|
||||
make.height.mas_equalTo(kGetScaleWidth(height));
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSInteger)countOfCurrentType {
|
||||
return self.dataSource.count;// self.littleGameList.count > 0 ? self.littleGameList.count : self.playList.count;
|
||||
}
|
||||
|
||||
#pragma mark- UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout
|
||||
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{
|
||||
CGFloat width = (KScreenWidth-kGetScaleWidth(6))/4;
|
||||
CGFloat width = (KScreenWidth - 64)/4;
|
||||
return [self countOfCurrentType] > 0 ? CGSizeMake(width, self.itemHeight) : CGSizeMake(KScreenWidth, self.emptyHeight);
|
||||
}
|
||||
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section{
|
||||
@@ -183,16 +162,16 @@ static const NSInteger kItemsPerRow = 5;
|
||||
if (!_collectionView) {
|
||||
MSBaseRTLFlowLayout *layout = [[MSBaseRTLFlowLayout alloc] init];
|
||||
|
||||
layout.minimumLineSpacing = 10;
|
||||
layout.minimumInteritemSpacing = 0;
|
||||
layout.minimumLineSpacing = 0;
|
||||
layout.minimumInteritemSpacing = 8;
|
||||
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
|
||||
_collectionView.showsHorizontalScrollIndicator = NO;
|
||||
_collectionView.showsVerticalScrollIndicator = NO;
|
||||
_collectionView.dataSource = self;
|
||||
_collectionView.delegate = self;
|
||||
_collectionView.backgroundColor = [UIColor clearColor];
|
||||
[_collectionView registerClass:[MSRoomMenuGameCell class] forCellWithReuseIdentifier:NSStringFromClass([MSRoomMenuGameCell class])];
|
||||
[_collectionView registerClass:[MSRoomMenuGameEmptyCell class] forCellWithReuseIdentifier:NSStringFromClass([MSRoomMenuGameEmptyCell class])];
|
||||
|
||||
}
|
||||
return _collectionView;
|
||||
}
|
||||
|
@@ -70,7 +70,7 @@
|
||||
}
|
||||
|
||||
- (void)gameSetUp {
|
||||
// nslog(@"小游戏的版本号是:%@", [SudMGP getVersion]);
|
||||
NSLog(@"小游戏的版本号是:%@", [SudMGP getVersion]);
|
||||
BOOL isTestEnv = NO;
|
||||
#ifdef DEBUG
|
||||
isTestEnv = YES;
|
||||
@@ -90,7 +90,7 @@
|
||||
}
|
||||
NSString * userId = [AccountInfoStorage instance].getUid;
|
||||
NSString * roomId = self.gameModel.roomId;
|
||||
// nslog(@"用户ID:%@,房间ID:%@, 游戏ID:%lld, code:%@", userId, roomId, self.currentmgId, self.code);
|
||||
NSLog(@"用户ID:%@,房间ID:%@, 游戏ID:%lld, code:%@", userId, roomId, self.currentmgId, self.code);
|
||||
|
||||
NSString *language = [NSBundle getLanguageText];
|
||||
if ([language hasPrefix:@"zh"]) {
|
||||
@@ -105,7 +105,7 @@
|
||||
self.fsmAPP2MG = [SudMGP loadMG:userId roomId:roomId code:self.code mgId:self.currentmgId language:language fsmMG:self rootView:self];
|
||||
} else {
|
||||
/// 初始化失败, 可根据业务重试
|
||||
// nslog(@"ISudFSMMG:initGameSDKWithAppID:初始化sdk失败 :%@",retMsg);
|
||||
NSLog(@"ISudFSMMG:initGameSDKWithAppID:初始化sdk失败 :%@",retMsg);
|
||||
}
|
||||
}];
|
||||
}
|
||||
@@ -157,7 +157,7 @@
|
||||
* 游戏日志
|
||||
*/
|
||||
-(void)onGameLog:(NSString*)dataJson {
|
||||
// nslog(@"ISudFSMMG:onGameLog:%@", dataJson);
|
||||
NSLog(@"ISudFSMMG:onGameLog:%@", dataJson);
|
||||
NSDictionary * dic = [SudCommon turnStringToDictionary:dataJson];
|
||||
NSString * msg_string = [dic objectForKey:@"msg"];
|
||||
if (!msg_string) {
|
||||
@@ -169,7 +169,7 @@
|
||||
* 游戏开始
|
||||
*/
|
||||
-(void)onGameStarted {
|
||||
// nslog(@"ISudFSMMG:onGameStarted:游戏开始");
|
||||
NSLog(@"ISudFSMMG:onGameStarted:游戏开始");
|
||||
[self notifySelfInState:YES seatIndex:-1];///加入房间
|
||||
[self handleSelfReadyEvent];///准备游戏
|
||||
}
|
||||
@@ -178,7 +178,7 @@
|
||||
* 游戏销毁
|
||||
*/
|
||||
-(void)onGameDestroyed {
|
||||
// nslog(@"ISudFSMMG:onGameDestroyed:游戏销毁");
|
||||
NSLog(@"ISudFSMMG:onGameDestroyed:游戏销毁");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,14 +186,14 @@
|
||||
* @param dataJson {"code":"value"}
|
||||
*/
|
||||
-(void)onExpireCode:(id<ISudFSMStateHandle>)handle dataJson:(NSString*)dataJson {
|
||||
// nslog(@"ISudFSMMG:onExpireCode:Code过期");
|
||||
NSLog(@"ISudFSMMG:onExpireCode:Code过期");
|
||||
// 请求业务服务器刷新令牌
|
||||
[Api getSudGameCode:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
|
||||
if (code == 200) {
|
||||
NSString * gameCode = data.data[@"code"];
|
||||
self.code = gameCode;
|
||||
[self.fsmAPP2MG updateCode:gameCode listener:^(int retCode, const NSString *retMsg, const NSString *dataJson) {
|
||||
// nslog(@"ISudFSMMG:updateGameCode retCode=%@ retMsg=%@ dataJson=%@", @(retCode), retMsg, dataJson);
|
||||
NSLog(@"ISudFSMMG:updateGameCode retCode=%@ retMsg=%@ dataJson=%@", @(retCode), retMsg, dataJson);
|
||||
}];
|
||||
// 回调结果
|
||||
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:@(0), @"ret_code", @"return form APP onExpireCode", @"ret_msg", nil];
|
||||
@@ -251,15 +251,15 @@
|
||||
|
||||
// 检查是否解析成功
|
||||
if (error) {
|
||||
// nslog(@"JSON 解析失败: %@", error.localizedDescription);
|
||||
NSLog(@"JSON 解析失败: %@", error.localizedDescription);
|
||||
} else {
|
||||
// nslog(@"解析后的字典: %@", dictionary);
|
||||
NSLog(@"解析后的字典: %@", dictionary);
|
||||
// 禁止模式选择
|
||||
NSMutableDictionary *ui = [[NSMutableDictionary alloc] initWithDictionary:dictionary[@"ui"]];
|
||||
[ui setObject: @{@"hide" : @(YES)}
|
||||
forKey:@"lobby_game_setting"];
|
||||
[dictionary setObject:ui forKey:@"ui"];
|
||||
// nslog(@"更新后的字典: %@", dictionary);
|
||||
NSLog(@"更新后的字典: %@", dictionary);
|
||||
}
|
||||
|
||||
// dict[@"ui"] = @{
|
||||
@@ -290,11 +290,11 @@
|
||||
* @param dataJson 回调json
|
||||
*/
|
||||
-(void)onGameStateChange:(id<ISudFSMStateHandle>) handle state:(NSString*) state dataJson:(NSString*) dataJson {
|
||||
// nslog(@"onGameStateChange 回调: %@ \n%@", state, dataJson);
|
||||
NSLog(@"onGameStateChange 回调: %@ \n%@", state, dataJson);
|
||||
if ([state isEqualToString:MG_COMMON_SELF_CLICK_READY_BTN]) {
|
||||
// [self addAI];
|
||||
} else if ([state isEqualToString:@"mg_common_game_add_ai_players"]) {
|
||||
// nslog(@" ????????????????????????????????????????????? ");
|
||||
NSLog(@" ????????????????????????????????????????????? ");
|
||||
} else if([state isEqualToString:MG_COMMON_GAME_SETTLE]){
|
||||
NSDictionary *data = @{@"value0":self.gameModel.data.matchRoundId ?: @""};
|
||||
NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:[data mj_JSONString] , @"reportGameInfoExtras",@"value0" , @"reportGameInfoKey" ,nil];
|
||||
@@ -308,7 +308,7 @@
|
||||
[self.delegate getGameRsultsWithList:list];
|
||||
}
|
||||
}else if ([state isEqualToString:MG_COMMON_PUBLIC_MESSAGE]) {
|
||||
// nslog(@"ISudFSMMG:onGameStateChange:游戏->APP:公屏消息");
|
||||
NSLog(@"ISudFSMMG:onGameStateChange:游戏->APP:公屏消息");
|
||||
} else if ([state isEqualToString:MG_COMMON_KEY_WORD_TO_HIT]) {
|
||||
|
||||
}else if ([state isEqualToString:MG_COMMON_SELF_CLICK_JOIN_BTN]) {//加入游戏按钮点击
|
||||
@@ -318,9 +318,9 @@
|
||||
seatIndex = [[dic objectForKey:@"seatIndex"] intValue];
|
||||
}
|
||||
if (seatIndex == -1) {
|
||||
// nslog(@"来自加入按钮%d",seatIndex);
|
||||
NSLog(@"来自加入按钮%d",seatIndex);
|
||||
}else {
|
||||
// nslog(@"来自麦位+入%d",seatIndex);
|
||||
NSLog(@"来自麦位+入%d",seatIndex);
|
||||
}
|
||||
[self notifySelfInState:YES seatIndex:-1];
|
||||
} else if([state isEqualToString:MG_COMMON_SELF_CLICK_START_BTN]) {//开始游戏按钮点击
|
||||
@@ -330,7 +330,7 @@
|
||||
[self handleSelfInExitEvent];
|
||||
} else {
|
||||
/// 其他状态
|
||||
// nslog(@"ISudFSMMG:onGameStateChange:游戏->APP:state:%@",MG_COMMON_PUBLIC_MESSAGE);
|
||||
NSLog(@"ISudFSMMG:onGameStateChange:游戏->APP:state:%@",MG_COMMON_PUBLIC_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,7 +342,7 @@
|
||||
* @param dataJson 回调JSON
|
||||
*/
|
||||
-(void)onPlayerStateChange:(nullable id<ISudFSMStateHandle>) handle userId:(NSString*) userId state:(NSString*) state dataJson:(NSString*) dataJson {
|
||||
// nslog(@"ISudFSMMG:onPlayerStateChange:游戏->APP:游戏玩家状态变化:userId: %@ --state: %@ --dataJson: %@", userId, state, dataJson);
|
||||
NSLog(@"ISudFSMMG:onPlayerStateChange:游戏->APP:游戏玩家状态变化:userId: %@ --state: %@ --dataJson: %@", userId, state, dataJson);
|
||||
/// 状态解析
|
||||
NSString *dataStr = @"";
|
||||
if ([state isEqualToString:MG_COMMON_PLAYER_IN]) {
|
||||
@@ -382,9 +382,9 @@
|
||||
dataStr = YMLocalizedString(@"XPRoomLittleGameContainerView8");
|
||||
[self handleState_MG_DG_SCORE_WithUserId:userId dataJson:dataJson];
|
||||
}else {
|
||||
// nslog(@"ISudFSMMG:onPlayerStateChange:未做解析状态:%@", MG_DG_SCORE);
|
||||
NSLog(@"ISudFSMMG:onPlayerStateChange:未做解析状态:%@", MG_DG_SCORE);
|
||||
}
|
||||
// nslog(@"ISudFSMMG:onPlayerStateChange:dataStr:%@", dataStr);
|
||||
NSLog(@"ISudFSMMG:onPlayerStateChange:dataStr:%@", dataStr);
|
||||
/// 回调
|
||||
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:@(0), @"ret_code", @"return form APP onPlayerStateChange", @"ret_msg", nil];
|
||||
[handle success:[SudCommon dictionaryToJson:dict]];
|
||||
@@ -443,9 +443,9 @@
|
||||
/// @param state 状态名称
|
||||
/// @param dataJson 需传递的json
|
||||
- (void)notifyStateChange:(NSString *) state dataJson:(NSString*) dataJson {
|
||||
// nslog(@"ISudFSMMG: START notifyStateChange:state=%@ \ndataJson=%@", state, dataJson);
|
||||
NSLog(@"ISudFSMMG: START notifyStateChange:state=%@ \ndataJson=%@", state, dataJson);
|
||||
[self.fsmAPP2MG notifyStateChange:state dataJson:dataJson listener:^(int retCode, const NSString *retMsg, const NSString *dataJson) {
|
||||
// nslog(@"ISudFSMMG:notifyStateChange:retCode=%@ retMsg=%@ dataJson=%@", @(retCode), retMsg, dataJson);
|
||||
NSLog(@"ISudFSMMG:notifyStateChange:retCode=%@ retMsg=%@ dataJson=%@", @(retCode), retMsg, dataJson);
|
||||
|
||||
if (retCode == 0 && [state isEqualToString:APP_COMMON_SELF_PLAYING]) {//开始游戏
|
||||
//上报游戏开始
|
||||
@@ -606,11 +606,11 @@
|
||||
}
|
||||
|
||||
- (void)handleState_MG_DG_SELECTING_WithUserId:(NSString *)userId dataJson:(NSString *)dataJson {
|
||||
// nslog(@"handleState_MG_DG_SELECTING_WithUserId%@",dataJson);
|
||||
NSLog(@"handleState_MG_DG_SELECTING_WithUserId%@",dataJson);
|
||||
}
|
||||
|
||||
- (void)handleState_MG_DG_PAINTING_WithUserId:(NSString *)userId dataJson:(NSString *)dataJson {
|
||||
// nslog(@"handleState_MG_DG_PAINTING_WithUserId%@",dataJson);
|
||||
NSLog(@"handleState_MG_DG_PAINTING_WithUserId%@",dataJson);
|
||||
/// 设置麦位状态为作画中
|
||||
NSDictionary * dic = [SudCommon turnStringToDictionary:dataJson];
|
||||
bool isPainting = NO;
|
||||
@@ -621,18 +621,18 @@
|
||||
|
||||
- (void)handleState_MG_DG_ERRORANSWER_WithUserId:(NSString *)userId dataJson:(NSString *)dataJson {
|
||||
/// 错误答案
|
||||
// nslog(@"handleState_MG_DG_ERRORANSWER_WithUserId%@",dataJson);
|
||||
NSLog(@"handleState_MG_DG_ERRORANSWER_WithUserId%@",dataJson);
|
||||
|
||||
}
|
||||
|
||||
- (void)handleState_MG_DG_TOTALSCORE_WithUserId:(NSString *)userId dataJson:(NSString *)dataJson {
|
||||
/// 总积分
|
||||
// nslog(@"handleState_MG_DG_TOTALSCORE_WithUserId%@",dataJson);
|
||||
NSLog(@"handleState_MG_DG_TOTALSCORE_WithUserId%@",dataJson);
|
||||
}
|
||||
|
||||
- (void)handleState_MG_DG_SCORE_WithUserId:(NSString *)userId dataJson:(NSString *)dataJson {
|
||||
/// 本次积分
|
||||
// nslog(@"handleState_MG_DG_SCORE_WithUserId%@",dataJson);
|
||||
NSLog(@"handleState_MG_DG_SCORE_WithUserId%@",dataJson);
|
||||
}
|
||||
|
||||
/// 销毁MG
|
||||
|
@@ -1447,13 +1447,13 @@ XPCandyTreeInsufficientBalanceViewDelegate>
|
||||
self.userInfo = userInfo;
|
||||
|
||||
[self requestBoomData];
|
||||
[self getOnlineCount];
|
||||
|
||||
@kWeakify(self);
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{
|
||||
@kStrongify(self);
|
||||
//获取一下红包信息
|
||||
[self.presenter getRedPacket:[NSString stringWithFormat:@"%ld", self.roomInfo.uid]];
|
||||
[self getOnlineCount];
|
||||
});
|
||||
|
||||
switch (roomInfo.type) {
|
||||
@@ -1842,7 +1842,7 @@ XPCandyTreeInsufficientBalanceViewDelegate>
|
||||
continue;
|
||||
}
|
||||
|
||||
NSLog(@" --- Message Raw Attach Content: %@, %@, %ld", @(message.senderClientType), message.rawAttachContent, (long)message.messageType);
|
||||
// NSLog(@" --- Message Raw Attach Content: %@, %@, %ld", @(message.senderClientType), message.rawAttachContent, (long)message.messageType);
|
||||
|
||||
if (message.messageType == NIMMessageTypeNotification) {
|
||||
NIMNotificationObject *notiMsg = (NIMNotificationObject *)message.messageObject;
|
||||
|
@@ -52,7 +52,9 @@
|
||||
return manager;
|
||||
}
|
||||
+(NSString *)getHostUrl{
|
||||
#ifdef DEBUG
|
||||
#if 0
|
||||
// return @"https://api.epartylive.com";
|
||||
|
||||
NSString *isProduction = [[NSUserDefaults standardUserDefaults]valueForKey:@"kIsProductionEnvironment"];
|
||||
if([isProduction isEqualToString:@"YES"]){
|
||||
return API_HOST_URL;
|
||||
@@ -93,13 +95,31 @@
|
||||
editParam = [MSParamsDecode msDecodeParams:editParam];
|
||||
params = [self configBaseParmars:editParam];
|
||||
|
||||
#ifdef DEBUG
|
||||
NSLog(@"\nmethod:\n%@\nparameter:\n%@\n", method, params);
|
||||
#if 0
|
||||
// 构建完整的 URL
|
||||
NSString *baseUrl = [HttpRequestHelper getHostUrl];
|
||||
NSString *fullUrl = [NSString stringWithFormat:@"%@/%@", baseUrl, method];
|
||||
|
||||
// 构建查询字符串
|
||||
NSMutableArray *queryItems = [NSMutableArray array];
|
||||
[params enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
|
||||
[queryItems addObject:[NSString stringWithFormat:@"%@=%@", key, obj]];
|
||||
}];
|
||||
NSString *queryString = [queryItems componentsJoinedByString:@"&"];
|
||||
if (queryString.length > 0) {
|
||||
fullUrl = [NSString stringWithFormat:@"%@?%@", fullUrl, queryString];
|
||||
}
|
||||
|
||||
NSLog(@"\n🌐 API Request Info:");
|
||||
NSLog(@"📍 Full URL: %@", fullUrl);
|
||||
NSLog(@"🔧 Method: %@", method);
|
||||
NSLog(@"📦 Parameter Type: queryParameters");
|
||||
NSLog(@"📋 Parameters: %@\n", params);
|
||||
#endif
|
||||
@kWeakify(self);
|
||||
[manager GET:method parameters:params headers:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
|
||||
BaseModel *baseModel = [BaseModel modelWithDictionary:responseObject];
|
||||
#ifdef DEBUG
|
||||
#if 0
|
||||
NSLog(@"%@ - \n%@\n", method, [baseModel toJSONString]);
|
||||
#else
|
||||
#endif
|
||||
@@ -226,14 +246,22 @@
|
||||
|
||||
params = [self configBaseParmars:params];
|
||||
|
||||
#ifdef DEBUG
|
||||
NSLog(@"\nmethod:\n%@\nparameter:\n%@", method, params);
|
||||
#if 0
|
||||
// 构建完整的 URL
|
||||
NSString *baseUrl = [HttpRequestHelper getHostUrl];
|
||||
NSString *fullUrl = [NSString stringWithFormat:@"%@/%@", baseUrl, method];
|
||||
|
||||
NSLog(@"\n🌐 API Request Info:");
|
||||
NSLog(@"📍 Full URL: %@", fullUrl);
|
||||
NSLog(@"🔧 Method: %@", method);
|
||||
NSLog(@"📦 Parameter Type: bodyParameters");
|
||||
NSLog(@"📋 Parameters: %@\n", params);
|
||||
#else
|
||||
#endif
|
||||
|
||||
[manager POST:method parameters:params headers:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
|
||||
BaseModel *baseModel = [BaseModel modelWithDictionary:responseObject];
|
||||
#ifdef DEBUG
|
||||
#if 0
|
||||
NSLog(@"\n%@", [baseModel toJSONString]);
|
||||
#else
|
||||
#endif
|
||||
@@ -259,7 +287,7 @@
|
||||
params = [MSParamsDecode msDecodeParams:[params mutableCopy] ];
|
||||
params = [self configBaseParmars:params];
|
||||
|
||||
#ifdef DEBUG
|
||||
#if 0
|
||||
NSLog(@"\nmethod:\n%@\nparameter:\n%@\n 超時:%@",
|
||||
method,
|
||||
params,
|
||||
@@ -282,13 +310,13 @@ constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
|
||||
progress:nil
|
||||
success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
|
||||
BaseModel *baseModel = [BaseModel modelWithDictionary:responseObject];
|
||||
#ifdef DEBUG
|
||||
#if 0
|
||||
NSLog(@"%@ - \n%@\n", method, [baseModel toJSONString]);
|
||||
#else
|
||||
#endif
|
||||
success(baseModel);
|
||||
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
|
||||
#ifdef DEBUG
|
||||
#if 0
|
||||
NSLog(@"%@ - \n%@\n", method, error);
|
||||
#else
|
||||
#endif
|
||||
@@ -311,14 +339,32 @@ constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
|
||||
params = [MSParamsDecode msDecodeParams:[params mutableCopy] ];
|
||||
params = [self configBaseParmars:params];
|
||||
AFHTTPSessionManager *manager = [HttpRequestHelper requestManager];
|
||||
#ifdef DEBUG
|
||||
NSLog(@"\nmethod:\n%@\nparameter:\n%@\n", method, params);
|
||||
#if 0
|
||||
// 构建完整的 URL
|
||||
NSString *baseUrl = [HttpRequestHelper getHostUrl];
|
||||
NSString *fullUrl = [NSString stringWithFormat:@"%@/%@", baseUrl, method];
|
||||
|
||||
// 构建查询字符串
|
||||
NSMutableArray *queryItems = [NSMutableArray array];
|
||||
[params enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
|
||||
[queryItems addObject:[NSString stringWithFormat:@"%@=%@", key, obj]];
|
||||
}];
|
||||
NSString *queryString = [queryItems componentsJoinedByString:@"&"];
|
||||
if (queryString.length > 0) {
|
||||
fullUrl = [NSString stringWithFormat:@"%@?%@", fullUrl, queryString];
|
||||
}
|
||||
|
||||
NSLog(@"\n🌐 API Request Info:");
|
||||
NSLog(@"📍 Full URL: %@", fullUrl);
|
||||
NSLog(@"🔧 Method: %@", method);
|
||||
NSLog(@"📦 Parameter Type: queryParameters");
|
||||
NSLog(@"📋 Parameters: %@\n", params);
|
||||
#else
|
||||
#endif
|
||||
@kWeakify(self);
|
||||
[manager DELETE:method parameters:params headers:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
|
||||
BaseModel *baseModel = [BaseModel modelWithDictionary:responseObject];
|
||||
#ifdef DEBUG
|
||||
#if 0
|
||||
NSLog(@"\n%@\n", [baseModel toJSONString]);
|
||||
#else
|
||||
#endif
|
||||
@@ -456,7 +502,7 @@ constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
|
||||
failure(error.code, error.localizedDescription.length > 0 ? error.localizedDescription : YMLocalizedString(@"HttpRequestHelper4"));
|
||||
}
|
||||
}
|
||||
#ifdef DEBUG
|
||||
#if 0
|
||||
NSLog(@"\n%@", error);
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
NSDictionary *allHeaders = response.allHeaderFields;
|
||||
@@ -491,7 +537,7 @@ constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
|
||||
AFHTTPSessionManager *manager = [HttpRequestHelper requestManager];
|
||||
NSString *url = [self getHostUrl];
|
||||
NSString *urlPath = [NSString stringWithFormat:@"%@/%@", url ,path];
|
||||
#ifdef DEBUG
|
||||
#if 0
|
||||
NSLog(@"\nmethod:\n%@\nparameter:\n%@", path, params);
|
||||
dispatch_async(dispatch_get_global_queue(0, 0), ^{
|
||||
[BSNetListenModel addHttpReq:urlPath header:manager.requestSerializer.HTTPRequestHeaders param:[params copy] time:[NSDate getCurrentTimeWithFormat:@"yyyy-MM-dd HH:mm:ss"]];
|
||||
@@ -536,12 +582,12 @@ constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
|
||||
if (responseObject) {
|
||||
BaseModel *baseModel = [BaseModel modelWithDictionary:responseObject];
|
||||
|
||||
#ifdef DEBUG
|
||||
#if 0
|
||||
NSLog(@"\n%@", [baseModel toJSONString]);
|
||||
#else
|
||||
#endif
|
||||
if (baseModel.code == 200) {
|
||||
#ifdef DEBUG
|
||||
#if 0
|
||||
NSHTTPURLResponse *urlresponse = (NSHTTPURLResponse *)response;
|
||||
NSDictionary *allHeaders = urlresponse.allHeaderFields;
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
|
@@ -11,13 +11,13 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface AccountModel : PIBaseModel
|
||||
|
||||
@property (nonatomic , assign) NSString *uid;
|
||||
@property (nonatomic , copy) NSString *uid;
|
||||
@property (nonatomic , copy) NSString *jti;
|
||||
@property (nonatomic , copy) NSString *token_type;
|
||||
@property (nonatomic , copy) NSString *refresh_token;
|
||||
@property (nonatomic , copy) NSString *netEaseToken;
|
||||
@property (nonatomic , copy) NSString *access_token;
|
||||
@property (nonatomic , assign) NSNumber *expires_in;
|
||||
@property (nonatomic , copy) NSNumber *expires_in;
|
||||
|
||||
@end
|
||||
@interface HomeUserModel : PIBaseModel
|
||||
|
@@ -98,8 +98,8 @@
|
||||
[[self getView] completeUserInfo];
|
||||
return;
|
||||
case 3009: // 账号已注销
|
||||
[[self getView] accountCanceled:data.model2dictionary];
|
||||
return;
|
||||
// [[self getView] accountCanceled:data.model2dictionary];
|
||||
// return;
|
||||
case 31005: {//余额不足
|
||||
}
|
||||
break;
|
||||
|
@@ -13,6 +13,7 @@
|
||||
- (void)stopMonitoring;
|
||||
- (void)manualCheckFirstRecharge;
|
||||
- (void)markTodayShown;
|
||||
- (void)updateChargeStatusToCompleted;
|
||||
|
||||
- (FirstRechargeModel *)loadCurrentModel;
|
||||
|
||||
@@ -29,4 +30,4 @@
|
||||
- (void)firstRechargeManager:(FirstRechargeManager *)manager
|
||||
didFailWithError:(NSError *)error;
|
||||
|
||||
@end
|
||||
@end
|
||||
|
@@ -106,6 +106,19 @@ static NSString * const kCurrentUserIdKey = @"FirstRechargeCurrentUserId";
|
||||
[self setTodayShownForCurrentUser:today];
|
||||
}
|
||||
|
||||
- (void)updateChargeStatusToCompleted {
|
||||
if (self.currentFirstRechargeData && !self.currentFirstRechargeData.chargeStatus) {
|
||||
// 更新首充状态为已完成
|
||||
self.currentFirstRechargeData.chargeStatus = YES;
|
||||
|
||||
// 保存更新后的模型
|
||||
[self saveCachedModel:self.currentFirstRechargeData];
|
||||
|
||||
// 通知代理(如果需要)
|
||||
[self notifyDelegatesWithModel:self.currentFirstRechargeData shouldShow:NO];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Private Methods
|
||||
|
||||
- (void)setupDailyTimer {
|
||||
|
@@ -127,7 +127,7 @@
|
||||
"LoginBindPhoneViewController2" = "ربط رقم الجوال";
|
||||
|
||||
"MvpViewController0" = "وقت تسجيل الخروج: %@";
|
||||
"MvpViewController1" = "%@\n\nيرجى الاتصال بخدمة العملاء (WeChat: mxyz2050) للاستفسارات.";
|
||||
"MvpViewController1" = "%@\n\nيرجى الاتصال بخدمة العملاء للاستفسارات.";
|
||||
"MvpViewController2" = "تم تسجيل الخروج من هذا الحساب";
|
||||
"MvpViewController3" = "من أجل خلق بيئة عمل
|
||||
|
||||
@@ -2941,8 +2941,8 @@ ineHeadView12" = "الحمل";
|
||||
"LoginBindPhoneViewController2" = "ربط رقم الهاتف المحمول";
|
||||
|
||||
"MvpViewController0" = "وقت الإلغاء: %@";
|
||||
"MvpViewController1.1" = "@%\n\nيرجى الاتصال بخدمة العملاء (WeChat: mxyz2050) للحصول على استفسارات";
|
||||
"MvpViewController1.2" = "يرجى الاتصال بخدمة العملاء (WeChat: mxyz2050) للحصول على استفسارات";
|
||||
"MvpViewController1.1" = "@%\n\nيرجى الاتصال بخدمة العملاء للحصول على استفسارات";
|
||||
"MvpViewController1.2" = "يرجى الاتصال بخدمة العملاء للحصول على استفسارات";
|
||||
"MvpViewController3" = "من أجل خلق بيئة إنترنت آمنة أكثر\nحماية ممتلكاتك وممتلكات الآخرين\nيرجى تقديم التحقق الهوية أولا";
|
||||
|
||||
"MvpViewController5" = "التحقق من الهوية";
|
||||
|
@@ -2548,9 +2548,9 @@
|
||||
"LoginBindPhoneViewController2" = "Bind Mobile Number";
|
||||
|
||||
"MvpViewController0" = "Logoff time: %@";
|
||||
"MvpViewController1" = "%@\n\nPlease contact customer service (WeChat: mxyz2050) for inquiries.";
|
||||
"MvpViewController1.1" = "%@\n\nPlease contact customer service (WeChat: mxyz2050) for inquiries.";
|
||||
"MvpViewController1.2" = "Please contact customer service (WeChat: mxyz2050) for inquiries.";
|
||||
"MvpViewController1" = "%@\n\nPlease contact customer service for inquiries.";
|
||||
"MvpViewController1.1" = "%@\n\nPlease contact customer service for inquiries.";
|
||||
"MvpViewController1.2" = "Please contact customer service for inquiries.";
|
||||
"MvpViewController2" = "This account has been logged off";
|
||||
"MvpViewController3" = "In order to create a safer online environment\nand protect the property safety of you and others,\nplease complete real-name authentication first";
|
||||
|
||||
|
@@ -2223,9 +2223,9 @@
|
||||
"LoginBindPhoneViewController2" = "Telefon numarasını bağla";
|
||||
|
||||
"MvpViewController0" = "Oturumu kapatma zamanı: %@";
|
||||
"MvpViewController1" = "%@\n\nMüşteri hizmetleriyle iletişime geçin (WeChat: mxyz2050) danışın";
|
||||
"MvpViewController1.1" = "%@\n\nMüşteri hizmetleriyle iletişime geçin (WeChat: mxyz2050) danışın";
|
||||
"MvpViewController1.2" = "Müşteri hizmetleriyle iletişime geçin (WeChat: mxyz2050) danışın";
|
||||
"MvpViewController1" = "%@\n\nMüşteri hizmetleriyle iletişime geçin danışın";
|
||||
"MvpViewController1.1" = "%@\n\nMüşteri hizmetleriyle iletişime geçin danışın";
|
||||
"MvpViewController1.2" = "Müşteri hizmetleriyle iletişime geçin danışın";
|
||||
"MvpViewController2" = "Bu hesap kapatıldı";
|
||||
"MvpViewController3" = "Daha güvenli bir ağ ortamı oluşturmak ve sizin ve diğerlerinin mülk güvenliğini korumak için\nlütfen önce kimlik doğrulaması yapın";
|
||||
|
||||
|
@@ -2263,8 +2263,8 @@
|
||||
"LoginBindPhoneViewController2" = "綁定手機號";
|
||||
|
||||
"MvpViewController0" = "註銷時間: %@";
|
||||
"MvpViewController1.1" = "%@\n\n請聯繫客服(微信:mxyz2050)咨詢哦";
|
||||
"MvpViewController1.2" = "請聯繫客服(微信:mxyz2050)咨詢哦";
|
||||
"MvpViewController1.1" = "%@\n\n請聯繫客服咨詢哦";
|
||||
"MvpViewController1.2" = "請聯繫客服咨詢哦";
|
||||
"MvpViewController2" = "該賬號已註銷";
|
||||
"MvpViewController3" = "為了營造更安全的網絡環境\n保護您和他人的財產安全\n請先進行實名認證";
|
||||
|
||||
|
Reference in New Issue
Block a user