第三方授权的应用苹果审核被驳回解决方案和app版本更新

简介: 第三方授权的应用苹果审核被驳回解决方案和app版本更新

根据应用启动时获取的版本信息来决定显示还是隐藏第三方授权按钮和用户名密码登录方式。

由于现在禁止app自己弹出版本更新的提示框,那么绝大多数应用都需要版本更新的,甚至在强制更新场景下,以前的版本接口或逻辑都废掉了,不更新就落下一地的鸡毛。

苹果商店那么app怎么都监控的到,也就是做做样子,别在它审核你新版本时版本更新就可以了。也可能苹果的目的就是烦你在它审核你的app时,你弹出版本更新,骚扰到它了。但是有的应用很特殊,只允许微信等第三方软件授权登录,没有用户名登录。那么你上架时,苹果商店审核,绝对会给你no pass。因为苹果不允许你的app依赖与其它app,不能跳转到空白页面,你给他一个没有安装微信等提示,它也不领情,仍旧残忍的拒绝。那么怎么解决这种审核时的鸡生蛋,蛋生鸡的问题呢?我们的app就时这样的,被拒绝了三次。

解决方案是:采用启动时版本更新查询的方式。审核前,把新app版本设置为手动发布。把app当前版本号发送给服务器,服务器返回当前最高版本号,是否强更新。最好有两个标志:是否更新标志和是否强制更新标志。我们现在app处理比较简单只有一个是否更新标志,以前我做的曹操专车app是有两个标志的。用两个升级标志的好处是,可以实现小范围试用的需求,当版本大改版本,可能风险时,出一个小范围使用的试用版本,让指定一批用户非强制更新,其它绝大多数用户不更新,一般运行几天,收集客服反馈过来的问题,看是否有重大风险,若有风险就删除该版本,让用户重新下载以前的版本,若没有风险就把该版本对全体没有安装最次版本的用户提升为强制更新或非强制更新版本。毕竟有重大风险的概率有,但是不大,这样也可以控制影响范围。不过这个小范围试用处理机制和我们现在问题有点冲突,不过可以通过增加标志解决。app拿到版本更新相关数据,和app版本号比较,若本地版本号高于服务器的版本号,显示审核用的固定用户名和密码登录,隐藏第三方授权按钮和图标;若查询失败或服务器返回的版本号小于等于本地版本号就显示正常的第三方授权登录按钮。收到苹果苹果审核通过邮件后,手动发布app,并把服务器返回的版本号提高到最新版本,版本更新设置为不更新。收到上架成功的邮件,修改数据库或修改版本更新配置让版本更新查询返回强制更新或非强制更新。

一般app非第一次上架,从手动发布版本到在苹果商店看到该版本,通常需要2小时左右。当然苹果客服的官方说法是24小时都是正常范围内。我一个新应用就出现过30多小时在苹果商店没有看到情况,没有办法只有自己拼接app下载地址,手动激活才看到我的app,具体见我的文章《手动第一次上架的应用如何快速在苹果商店看到》。

服务器响应的日志:

2018-09-04 15:11:52:233 ArtEnjoymentWeChatAuction[57332:5293160] AWUpdateVersionModel.m:AWUpdateVersionModel.m:-[AWUpdateVersionModel setupRACCommand]_block_invoke_3:91 Verbose:data:{
    appCode = agent;
    appId = 4;
    appName = "\U827a\U4eab\U4f18\U9009";
    remark = "";
    type = ios;
    update = N;
    version = "0.0.1";
}

版本更新的部分代码如下:

- (AWUpdateVersionModel *)updateVersionModel
{
    if (_updateVersionModel == nil) {
        _updateVersionModel = [[AWUpdateVersionModel alloc]init];
    }
    return _updateVersionModel;
}

- (AWUpdateVersionEntity *)updateVersionEntity
{
    if (_updateVersionEntity == nil) {
        NSDictionary *localDic = [[NSBundle mainBundle] infoDictionary];
        NSString *localVersion = [localDic objectForKey:@"CFBundleShortVersionString"];
        _updateVersionEntity =[AWUpdateVersionEntity updateVersionEntityWithLocalVersion:localVersion];
    }
    return _updateVersionEntity;
}
- (void)forceUpdte
{
    @weakify(self);
    FLDDLogVerbose(@"requestUrl:%@,params:%@",@"api/version/validateUpdate.ns", self.updateVersionEntity);
    [[self.updateVersionModel.validateUpdateCommand execute:self.updateVersionEntity]subscribeNext:^(AWUpdateVersionEntity *resultUpdateVersionEntity) {
        @strongify(self);
        FLDDLogVerbose(@"resultUpdateVersionEntity:%@", resultUpdateVersionEntity);
        if((resultUpdateVersionEntity == nil) || ![resultUpdateVersionEntity isKindOfClass:[AWUpdateVersionEntity class]])
        {
            return;
        }
        if((!resultUpdateVersionEntity.isNeedUpdate) || !((resultUpdateVersionEntity.updateType == UPDATE_TYPE_REMIND) || (resultUpdateVersionEntity.updateType == UPDATE_TYPE_FORCE)))
        {
            return;
        }
        self.updateVersionEntity = resultUpdateVersionEntity;
        [AWUpdateVersionView initWithUpdateVersionEntity:resultUpdateVersionEntity];
    } error:^(NSError *error) {
    }];
}

AWUpdateVersionEntity.h文件

#import <Foundation/Foundation.h>
#import "AWUpdateVersionMacro.h"

@interface AWUpdateVersionEntity : NSObject
@property(nonatomic, strong) NSString *download;
@property(nonatomic, strong) NSString *appCode;
@property(nonatomic, strong) NSString *appName;
@property(nonatomic, strong) NSString *remark;
@property(nonatomic, strong) NSString *update;
@property(nonatomic, strong) NSString *version;
@property(nonatomic, strong) NSString *localVersion;
@property(nonatomic, strong) NSString *errorInfo;
@property(nonatomic, assign) NSString *appId;
@property(nonatomic, assign) BOOL isNeedUpdate;
@property(nonatomic, assign) BOOL isUpdateData;
@property(nonatomic, assign) BOOL isLargerCurrentVersion;
@property(nonatomic, assign) UPDATE_TYPE updateType;



+ (instancetype)updateVersionEntityWithLocalVersion:(NSString *)localVersion;

- (void)updateVersionEntityWithNewUpdateVersionEntity:(AWUpdateVersionEntity *)newUpdateVersionEntity;
@end

AWUpdateVersionEntity.m文件

#import "AWUpdateVersionEntity.h"

@implementation AWUpdateVersionEntity
+ (instancetype)updateVersionEntityWithLocalVersion:(NSString *)localVersion;
{
    return [[self alloc] initWithLocalVersion:(NSString *)localVersion];
}

- (instancetype)initWithLocalVersion:(NSString *)localVersion
{
    if (self = [super init]) {
        _localVersion= localVersion;
        _errorInfo = nil;
        _appCode = nil;
        _appName = nil;
        _remark = @"";
        _update = nil;
        _version = nil;
        _appId = @"1";
        _download = nil;
        _isNeedUpdate = NO;
        _updateType = UPDATE_TYPE_NO;
        _isUpdateData = NO;
    }
    return self;
}


- (void)updateVersionEntityWithNewUpdateVersionEntity:(AWUpdateVersionEntity *)newUpdateVersionEntity
{
    if(!newUpdateVersionEntity || ![newUpdateVersionEntity isKindOfClass:[AWUpdateVersionEntity class]])
    {
        return;
    }
    _download = newUpdateVersionEntity.download;
    _localVersion= newUpdateVersionEntity.localVersion;
    _errorInfo = newUpdateVersionEntity.errorInfo;
    _appCode = newUpdateVersionEntity.appCode;
    _appName = newUpdateVersionEntity.appName;
    _remark = newUpdateVersionEntity.remark;
    _update = newUpdateVersionEntity.update;
    _version = newUpdateVersionEntity.version;
    _appId = newUpdateVersionEntity.appId;
    _isNeedUpdate = newUpdateVersionEntity.isNeedUpdate;
    _updateType = newUpdateVersionEntity.updateType;
    _isUpdateData = newUpdateVersionEntity.isUpdateData;
}
@end

AWUpdateVersionEntity.m文件

#import "AWUpdateVersionEntity.h"

@implementation AWUpdateVersionEntity
+ (instancetype)updateVersionEntityWithLocalVersion:(NSString *)localVersion;
{
    return [[self alloc] initWithLocalVersion:(NSString *)localVersion];
}

- (instancetype)initWithLocalVersion:(NSString *)localVersion
{
    if (self = [super init]) {
        _localVersion= localVersion;
        _errorInfo = nil;
        _appCode = nil;
        _appName = nil;
        _remark = @"";
        _update = nil;
        _version = nil;
        _appId = @"1";
        _download = nil;
        _isNeedUpdate = NO;
        _updateType = UPDATE_TYPE_NO;
        _isUpdateData = NO;
    }
    return self;
}


- (void)updateVersionEntityWithNewUpdateVersionEntity:(AWUpdateVersionEntity *)newUpdateVersionEntity
{
    if(!newUpdateVersionEntity || ![newUpdateVersionEntity isKindOfClass:[AWUpdateVersionEntity class]])
    {
        return;
    }
    _download = newUpdateVersionEntity.download;
    _localVersion= newUpdateVersionEntity.localVersion;
    _errorInfo = newUpdateVersionEntity.errorInfo;
    _appCode = newUpdateVersionEntity.appCode;
    _appName = newUpdateVersionEntity.appName;
    _remark = newUpdateVersionEntity.remark;
    _update = newUpdateVersionEntity.update;
    _version = newUpdateVersionEntity.version;
    _appId = newUpdateVersionEntity.appId;
    _isNeedUpdate = newUpdateVersionEntity.isNeedUpdate;
    _updateType = newUpdateVersionEntity.updateType;
    _isUpdateData = newUpdateVersionEntity.isUpdateData;
}
@end

AWUpdateVersionModel.h文件:

#import <Foundation/Foundation.h>
@class AWUpdateVersionEntity;
@interface AWUpdateVersionModel : NSObject
/**
 *  版本更新
 */
@property(nonatomic,strong)RACCommand *validateUpdateCommand;

/**
 *  版本更新实体
 */
@property(nonatomic,strong)AWUpdateVersionEntity *updateVersionEntity;

@property (nonatomic, strong) YXRequestApi *validateUpdateInfoApi;
@end

AWUpdateVersionModel.m文件:

#import "AWUpdateVersionModel.h"
#import "AWUpdateVersionEntity.h"
#import "AWAppIconTypeEntity.h"

@implementation AWUpdateVersionModel
- (instancetype)init
{
    if (self = [super init]) {
        [self setupRACCommand];
    }
    return self;
}

-(YXRequestApi *)validateUpdateInfoApi{
    if (!_validateUpdateInfoApi)
    {
        _validateUpdateInfoApi = [[YXRequestApi alloc]init];
        _validateUpdateInfoApi
        .setBaseURL(kBaseURL)
        .setApiPath(@"market/app/version.htm")
        .setShowHUD(NO)
        .setRequestMethodType(YX_Request_POST)
        .setParams(@{});
    }
    return _validateUpdateInfoApi;
}

- (void)setupRACCommand
{
    @weakify(self);
    self.validateUpdateCommand = [[RACCommand alloc]initWithSignalBlock:^RACSignal *(id input) {
        return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            @strongify(self);
            if([input isKindOfClass:[AWUpdateVersionEntity class]])
            {
                self.updateVersionEntity = input;
            }
            else
            {
                [[AWNoticeView currentNotice] showErrorNotice:@"参数错误"];
                self.updateVersionEntity.errorInfo  = @"";
                [subscriber sendError:nil];
                return nil;
            }

            NSMutableDictionary *dic = [NSMutableDictionary new];
            [dic setSafeObject:@"ios" forKey:@"type"];
            [dic setSafeObject:APP_CODE forKey:@"appCode"];
            self.validateUpdateInfoApi.setParams(dic);
            [[YXRequestClient sharedClient] loadDataWithApi:self.validateUpdateInfoApi successBlock:^(NSDictionary *data) {
                @strongify(self);
                if((data == nil) || ![data isKindOfClass:[NSDictionary class]])
                {
                    [[AWNoticeView currentNotice] showErrorNotice:@"参数错误"];
                    self.updateVersionEntity.errorInfo  = @"";
                    [subscriber sendError:nil];
                    return;
                }
                AWUpdateVersionEntity *resultEntity = [AWUpdateVersionEntity yy_modelWithDictionary:data];
                NSString *localVersion = self.updateVersionEntity.localVersion;
                if(localVersion.length == 0)
                {
                    NSDictionary *localDic = [[NSBundle mainBundle] infoDictionary];
                    localVersion = [localDic objectForKey:@"CFBundleShortVersionString"];
                    self.updateVersionEntity.localVersion = localVersion;
                }
                resultEntity.localVersion = self.updateVersionEntity.localVersion;
                resultEntity.isUpdateData = YES;
                FLDDLogVerbose(@"data:%@", data);
//                resultEntity.update =@"N";
                if((resultEntity.update) && [resultEntity.update isKindOfClass:[NSString class]] && (resultEntity.version) && [resultEntity.version isKindOfClass:[NSString class]])
                {
                    NSArray *localVersionArr = [resultEntity.localVersion componentsSeparatedByString:@"."];
                    NSArray *versionArr = [resultEntity.version componentsSeparatedByString:@"."];
                    BOOL sameFlag = YES;
                    for(NSUInteger i = 0; (i < localVersionArr.count) && i < versionArr.count; i++)
                    {
                        if([versionArr[i] compare:localVersionArr[i]] == NSOrderedDescending)
                        {
                            resultEntity.isNeedUpdate = YES;
                            if([resultEntity.update isEqualToString:@"Y"])
                            {
                                resultEntity.updateType = UPDATE_TYPE_FORCE;
                            }
                            else
                            {
                                resultEntity.updateType = UPDATE_TYPE_REMIND;
                            }
                            sameFlag = NO;
                            break;
                        }
                        else if([versionArr[i] compare:localVersionArr[i]] == NSOrderedAscending)
                        {
                            resultEntity.isNeedUpdate = NO;
                            resultEntity.updateType = UPDATE_TYPE_NO;
                            sameFlag = NO;
                            resultEntity.isLargerCurrentVersion = YES;
//                            [[NSNotificationCenter defaultCenter] postNotificationName:updateLoginButtonNotification  object:@{@"isLargerCurrentVersion":@(YES)}];
                            [[NSNotificationCenter defaultCenter] postNotificationName:updateLoginButtonNotification object:nil userInfo:[NSDictionary dictionaryWithObjectsAndKeys: @(YES),@"isLargerCurrentVersion", nil]];
                            break;
                        }
                    }
                    if(!(resultEntity.isNeedUpdate) && sameFlag && (localVersionArr.count > 0) && (localVersionArr.count < versionArr.count) && (!isEmptyString(versionArr[versionArr.count - 1]) && ([versionArr[versionArr.count - 1] integerValue] > 0)))
                    {
                        resultEntity.isNeedUpdate = YES;
                        if((resultEntity.update) && [resultEntity.update isKindOfClass:[NSString class]] && [resultEntity.update isEqualToString:@"Y"])
                        {
                            resultEntity.updateType = UPDATE_TYPE_FORCE;
                        }
                        else
                        {
                            resultEntity.updateType = UPDATE_TYPE_REMIND;
                        }
                        sameFlag = NO;
                    }

                }
                if(UPDATE_TYPE_REMIND == resultEntity.updateType)
                {
                    NSString *lastRemindUpdateTimeStr = [AWGeneralFunction valueForKey:@"remindUpdateTime"];
                    //实例化一个NSDateFormatter对象
                    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
                    //设定时间格式,这里可以设置成自己需要的格式
                    [dateFormatter setDateFormat:@"yyyy-MM-dd"];
                    //用[NSDate date]可以获取系统当前时间
                    NSString *currentDateStr = [dateFormatter stringFromDate:[NSDate date]];

                    if(lastRemindUpdateTimeStr)
                    {
                        if(lastRemindUpdateTimeStr && currentDateStr && [lastRemindUpdateTimeStr isEqualToString:currentDateStr])
                        {
                            resultEntity.updateType = UPDATE_TYPE_NO;
                        }
                    }
                    [AWGeneralFunction setUserDefaultsValue:currentDateStr key:@"remindUpdateTime"];
                }
                self.updateVersionEntity.errorInfo  = @"";
                [self.updateVersionEntity updateVersionEntityWithNewUpdateVersionEntity:resultEntity];
                //self.updateVersionEntity.remark = @"1.全面支持“#”话题,给喜欢的作品打个标签吧\n2.支持查看更多精彩评论\n3.支持查看更多精彩评论\n1.全面支持“#”话题,给喜欢的作品打个标签吧\n2.支持查看更多精彩评论\n3.支持查看更多精彩评论\n1.全面支持“#”话题,给喜欢的作品打个标签吧\n2.支持查看更多精彩评论\n3.支持查看更多精彩评论";
                [subscriber sendNext:self.updateVersionEntity];
                [subscriber sendCompleted];

            } failureBlock:^(NSError *error) {

                [[AWNoticeView currentNotice] showErrorNotice:error.domain];
                [subscriber sendError:error];
            }];
            return nil;
        }];
    }];
}

@end

下面是我当时遇到这个第三方授权登录被驳回,写的分析和报告:

只支持微信等第三方软件授权登录,并且不支持网页授权登录或用户名密码登录。

Guideline 4.2.3 - Design - Minimum Functionality 
We were required to install the WeChat app before we could log in via WeChat. Users should be able to log in with WeChat and access their accounts without having to install any additional apps. 
Next Steps 
If you would like to offer authentication through WeChat, please use a mechanism that allows users to log in with WeChat from within your app without first having to install an additional app. 
We recommend implementing the Safari View Controller API to display web content within your app. The Safari View Controller allows the display of a URL and inspection of the certificate from an embedded browser in an app so that customers can verify the webpage URL and SSL certificate to confirm they are entering their sign in credentials into a legitimate page. 

可以看到流行的软件当发现本地没有装微信时,都不显示微信登录;QQ当没有安装,大都也不显示QQ登录图标,个别的显示QQ图标并且采用网页登录QQ授权,不过他们都有用户名密码登录功能。所以它的常规解决方案时当发现本地没有安装第三方授权软件直接不显示它的按钮(按钮灰化不使能或弹出提示苹果照样给你驳回),就时没有完整的用户名/密码登录功能,至少给他在审核期间可以只有审核人员知道的登录账户和密码。可以当发现服务器返回版本低于app版本时显示临时用户名密码登录,发布时选择手动发布。当审核通过时(苹果会发邮件一般造成7点左右审核过的可能比较多),修改服务器返回的最新版本号并设置不更新,并发布版本,等在所在区域的苹果商店看到最新的app时在,再设置为非强制更新,等一天后等全世界的区域都上架后若是需要强制更新的再设置为强制更新(世界各个苹果市场上架时间并不一致,有不同的负责任,是苹果审核人员手动上架的,一般2小时上架,也有时6个小时上架的)。当然这个你的用户密码功能没有开发完的情况。若你的这个功能完全,直接不显示第三方授权登录的按钮,保留用户名密码按钮就可以。


目录
相关文章
|
6天前
|
XML JSON 数据安全/隐私保护
如何使用Fiddler抓取APP接口和微信授权网页源代码
Fiddler是一款强大的抓包工具,用于捕获HTTP/HTTPS流量,包括手机APP和微信授权页面的数据。下载安装Fiddler后,需设置电脑代理,如端口8888,并在手机上配置相同代理,确保两者在同一局域网。通过安装Fiddler证书,可解密HTTPS请求。在手机上打开目标应用或网页,Fiddler将显示请求详情,便于接口调试和数据查看。
14 0
如何使用Fiddler抓取APP接口和微信授权网页源代码
|
14天前
|
数据采集 JSON 算法
使用Python爬取华为市场APP应用进行分析
这个网站也是作者最近接触到的一个APP应用市场类网站。讲实话,还是蛮适合新手朋友去动手学习的。毕竟爬虫领域要想进步,还是需要多实战、多分析!该网站中的一些小细节也是能够锻炼分析能力的,也有反爬虫处理。甚至是下载APP的话在Web端是无法拿到APK下载的直链,需要去APP端接口数据获取
|
4天前
|
应用服务中间件 Linux 网络安全
PHP应用部署在App Service for Linux环境中,上传文件大于1MB时,遇见了413 Request Entity Too Large 错误的解决方法
在Azure App Service for Linux上部署的PHP应用遇到上传文件超过1MB时出现413 Request Entity Too Large错误的解决之法
|
29天前
|
移动开发 网络协议 安全
HTML5页面被运营商DNS问题及解决方案,app中h5页面源码的获取
HTML5页面被运营商DNS问题及解决方案,app中h5页面源码的获取
85 4
|
29天前
|
存储 机器学习/深度学习 编解码
app版本更新的五种实现方式
app版本更新的五种实现方式
29 2
|
29天前
|
安全 定位技术 网络安全
禁止应用在模拟器上运行的方案及app安全问题
禁止应用在模拟器上运行的方案及app安全问题
47 1
|
10天前
|
Android开发 Kotlin
kotlin开发安卓应用 如何修改app安装后的名称
在 Android 应用中,要修改安装后的显示名称,需更新 AndroidManifest.xml 文件中 application 标签的 android:label 属性。可直接在该属性内设置新名称,或在 res/values/strings.xml 文件中修改 app_name 并在 manifest 中引用。推荐使用 strings.xml 方式,以便支持多语言和集中管理。
|
12天前
|
移动开发 小程序 视频直播
FFmpeg开发笔记(二十七)解决APP无法访问ZLMediaKit的直播链接问题
本文讲述了在使用ZLMediaKit进行视频直播时,遇到移动端通过ExoPlayer和微信小程序播放HLS直播地址失败的问题。错误源于ZLMediaKit对HTTP地址的Cookie校验导致401无权限响应。通过修改ZLMediaKit源码,注释掉相关鉴权代码并重新编译安装,解决了此问题,使得ExoPlayer和小程序能成功播放HLS视频。详细解决方案及FFmpeg集成可参考《FFmpeg开发实战:从零基础到短视频上线》一书。
21 3
FFmpeg开发笔记(二十七)解决APP无法访问ZLMediaKit的直播链接问题
|
2天前
|
开发框架 移动开发 JavaScript
SpringCloud微服务实战——搭建企业级开发框架(四十七):【移动开发】整合uni-app搭建移动端快速开发框架-添加Axios并实现登录功能
在uni-app中,使用axios实现网络请求和登录功能涉及以下几个关键步骤: 1. **安装axios和axios-auth-refresh**: 在项目的`package.json`中添加axios和axios-auth-refresh依赖,可以通过HBuilderX的终端窗口运行`yarn add axios axios-auth-refresh`命令来安装。 2. **配置自定义常量**: 创建`project.config.js`文件,配置全局常量,如API基础URL、TenantId、APP_CLIENT_ID和APP_CLIENT_SECRET等。
|
5天前
|
安全 JavaScript 前端开发
kotlin开发安卓app,JetPack Compose框架,给webview新增一个按钮,点击刷新网页
在Kotlin中开发Android应用,使用Jetpack Compose框架时,可以通过添加一个按钮到TopAppBar来实现WebView页面的刷新功能。按钮位于右上角,点击后调用`webViewState?.reload()`来刷新网页内容。以下是代码摘要: