第三方授权的应用苹果审核被驳回解决方案和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个小时上架的)。当然这个你的用户密码功能没有开发完的情况。若你的这个功能完全,直接不显示第三方授权登录的按钮,保留用户名密码按钮就可以。


目录
相关文章
|
3月前
|
小程序 JavaScript 前端开发
uni-app开发微信小程序:四大解决方案,轻松应对主包与vendor.js过大打包难题
uni-app开发微信小程序:四大解决方案,轻松应对主包与vendor.js过大打包难题
828 1
|
8天前
|
存储 监控 API
app开发之安卓Android+苹果ios打包所有权限对应解释列表【长期更新】-以及默认打包自动添加权限列表和简化后的基本打包权限列表以uniapp为例-优雅草央千澈
app开发之安卓Android+苹果ios打包所有权限对应解释列表【长期更新】-以及默认打包自动添加权限列表和简化后的基本打包权限列表以uniapp为例-优雅草央千澈
|
20天前
|
供应链 搜索推荐 API
1688APP原数据API接口的开发、应用与收益(一篇文章全明白)
1688作为全球知名的B2B电商平台,通过开放的原数据API接口,为开发者提供了丰富的数据资源,涵盖商品信息、交易数据、店铺信息、物流信息和用户信息等。本文将深入探讨1688 APP原数据API接口的开发、应用及其带来的商业收益,包括提升流量、优化库存管理、增强用户体验等方面。
94 6
|
2月前
|
人工智能 小程序 搜索推荐
uni app下开发AI运动小程序解决方案
本文介绍了在小程序中实现AI运动识别的解决方案。该方案依托于UNI平台,通过高效便捷的插件形式,实现包括相机抽帧控制、人体识别、姿态识别等在内的多项功能,无需依赖后台服务器,大幅提高识别效率和用户体验。方案内置多种运动模式,支持自定义扩展,适用于AI健身、云上赛事、AI体测等多场景,适合新开发和存量改造项目。
|
2月前
|
开发框架 监控 .NET
【Azure App Service】部署在App Service上的.NET应用内存消耗不能超过2GB的情况分析
x64 dotnet runtime is not installed on the app service by default. Since we had the app service running in x64, it was proxying the request to a 32 bit dotnet process which was throwing an OutOfMemoryException with requests >100MB. It worked on the IaaS servers because we had the x64 runtime install
|
3月前
|
缓存 小程序 索引
uni-app开发微信小程序时vant组件van-tabs的使用陷阱及解决方案
uni-app开发微信小程序时vant组件van-tabs的使用陷阱及解决方案
274 1
|
4月前
|
移动开发 Android开发 数据安全/隐私保护
移动应用与系统的技术演进:从开发到操作系统的全景解析随着智能手机和平板电脑的普及,移动应用(App)已成为人们日常生活中不可或缺的一部分。无论是社交、娱乐、购物还是办公,移动应用都扮演着重要的角色。而支撑这些应用运行的,正是功能强大且复杂的移动操作系统。本文将深入探讨移动应用的开发过程及其背后的操作系统机制,揭示这一领域的技术演进。
本文旨在提供关于移动应用与系统技术的全面概述,涵盖移动应用的开发生命周期、主要移动操作系统的特点以及它们之间的竞争关系。我们将探讨如何高效地开发移动应用,并分析iOS和Android两大主流操作系统的技术优势与局限。同时,本文还将讨论跨平台解决方案的兴起及其对移动开发领域的影响。通过这篇技术性文章,读者将获得对移动应用开发及操作系统深层理解的钥匙。
115 12
|
3月前
|
机器学习/深度学习 搜索推荐 数据挖掘
北邮移动互联网应用大作业实验报告《云账本app》开发
北邮移动互联网应用大作业实验报告《云账本app》开发
54 0
|
9天前
|
开发框架 小程序 前端开发
圈子社交app前端+后端源码,uniapp社交兴趣圈子开发,框架php圈子小程序安装搭建
本文介绍了圈子社交APP的源码获取、分析与定制,PHP实现的圈子框架设计及代码编写,以及圈子小程序的安装搭建。涵盖环境配置、数据库设计、前后端开发与接口对接等内容,确保平台的安全性、性能和功能完整性。通过详细指导,帮助开发者快速搭建稳定可靠的圈子社交平台。
91 18
|
5天前
|
JSON 供应链 搜索推荐
淘宝APP分类API接口:开发、运用与收益全解析
淘宝APP作为国内领先的购物平台,拥有丰富的商品资源和庞大的用户群体。分类API接口是实现商品分类管理、查询及个性化推荐的关键工具。通过开发和使用该接口,商家可以构建分类树、进行商品查询与搜索、提供个性化推荐,从而提高销售额、增加商品曝光、提升用户体验并降低运营成本。此外,它还能帮助拓展业务范围,满足用户的多样化需求,推动电商业务的发展和创新。
22 5