二次开发方案
 一、方案的确定及要实现的效果
        首先,最多的信息获取还是官方文档:https://lw.microstrategy.com/msdz/MobileUpdates/941_iOSU5/docs/mergedProjects/mobile_sdk/mobilesdk.htm
在本项目的一小部分,项目需求也是改来改去,最终需要实现的有:
应用图标的替换,应用名称的更改,启动图片的替换。
 自定义登录界面。
 登录时进行VPN验证,通过VPN服务器实现外网访问。
 动态更改app的显示模式(文件夹目录或者默认报表)。
 服务器集群,负载均衡的使用。

 二、实现步骤
       1、需求一的实现很简单,图标更改在Images.xcassets里面直接替换。应用名称在Info_IPad.plist,Bundle display name对应的名字改掉就可以。
Home > Mobile SDK > Mobile SDK for iOS > Customizing MicroStrategy Mobile > Customization scenarios > Adding functionality with a custom Application Delegate。
//
 //  CustomAppDelegate.m
 //  MicroStrategyMobile
 //
 //  Copyright (c) 2014 MicroStrategy Inc. All rights reserved.
 //
#import "CustomAppDelegate.h"
 #import "CustomLoginView.h"
 #import <MicroStrategyMobileSDK/MSIAuthenticationModule.h>
 #import <MicroStrategyMobileSDK/MSIAuthenticationPromptViewController.h>
 #import <MicroStrategyMobileSDK/MSIMobileLoginManager.h>
@implementation CustomAppDelegate
 -(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
     BOOL res = [super application:application didFinishLaunchingWithOptions:launchOptions];
     //add custom logic here
    return res;
 }
 @end
  
       创建自定义的登录界面
 Home > Mobile SDK > Mobile SDK for iOS > Customizing authentication > Client-side customizations > Programmatic customizations > Customizing login screen UI and authentication parameters programmatically
#import "CustomLoginView.h"
@implementation CustomLoginView
- (void) setupWithParameters:(NSDictionary*) promptViewParameters delegate:(id<MSIMobileLoginPromptViewDelegate>) delegate{
    [super setupWithParameters:promptViewParameters delegate:delegate];
     //After calling super, add additional code to setup the login UI, for example, add textfields, login buttons, background image
    
 }
-(void) login{
[self.delegate loginPromptView:self didInputAuthenticationParameters:@{
@"username":([usernameTextField text]?[usernameTextField text]:EMPTY_STRING),
@"password":([passwordTextField text]?[passwordTextField text]:EMPTY_STRING)
}];
}
@end
  
       同时CustomAppDelegate.m文件也要做更改
 //
 //  CustomAppDelegate.m
 //  MicroStrategyMobile
 //
 //  Copyright (c) 2014 MicroStrategy Inc. All rights reserved.
 //
#import "CustomAppDelegate.h"
 #import "CustomLoginView.h"
 #import <MicroStrategyMobileSDK/MSIAuthenticationModule.h>
 #import <MicroStrategyMobileSDK/MSIAuthenticationPromptViewController.h>
 #import <MicroStrategyMobileSDK/MSIMobileLoginManager.h>
 #import <MicroStrategyMobileSDK/ProjectInfo.h>
@implementation CustomAppDelegate
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
CustomLoginView *loginView = [[CustomLoginView alloc] initWithFrame:self.window.frame];
MSIAuthenticationPromptViewController *loginViewController = [[MSIAuthenticationPromptViewController alloc] init];
MSIAuthenticationModule *loginModule = [[MSIAuthenticationModule alloc] init];
[[MSIMobileLoginManager sharedMobileLoginManager] setView:loginView controller:loginViewController module:loginModule forPromptType:AuthenticationPromptType];
BOOL res = [super application:application didFinishLaunchingWithOptions:launchOptions];
    return res;
 }
@end
  
 Home > Mobile SDK > Mobile SDK for iOS > Customizing authentication > Client-side customizations > Programmatic customizations > Adding custom logic during authentication process。
到此,自定义登录界面已经实现。
3、VPN服务器的认证和使用
在iOS平台下,第三方应用程序通过调用移动应用安全认证开发包,使用证书与VPN服务器建立安全通道、以及单点登录等功能的实现。此处以北京国富安提供的开发包为例(具体细节参照国富安提供的开发文档)。
首先在iPad端安装移动认证证书(国富安提供)。
 从Keychain里读取证书的cn。设置指定CN的证书,用此证书来做为身份。根据身份证书去跟vpn建立ssl连接。
 根据VPN上配置的资源,启动本地监听的端口。
 通过VPN调用服务器接口,获取配置文件。
 显示登录界面,点击登录,根据证书和主账号密码进行统一安全认证。
 认证成功返回的json,包含移动平台用户的主从账号信息,并会在Keychain中存储session,附应用可以通过Keychain中存储的session直接登录成功。
 根据json信息,将主账号作为mstr用户账号和mstr用户默认密码一起,在应用内自动进行mstr的认证。成功界面会跳转,不跳转且无提示可以检查所使用mstr用户是否存在。(注:以移动平台主账号为账号,自定义统一的默认密码,提前在mstr创建用户)
       
/**
  *  2、从设备的钥匙串中获取证书,如果获取失败就返回nil;
  *  @return 证书或者nil
  */
 -(NSString *)getCNfromKeychain
 {
     NSMutableArray *outArray = [[NSMutableArray alloc] init];
     //获取证书cn项,因为有可能有多个证书,所以返回的是一个cn项的数组
     BOOL ret = [[CertHelper sharedInstance] getCNfromKeychain:outArray];
     if (!ret)
     {
         NSLog(@"获取证书失败,请联系管理员");
         return nil;
     }
     //一般情况只有一个证书
     NSString *certCN = [outArray objectAtIndex:0];
     return certCN;
 }
if ([[CertHelper sharedInstance] setIndexbyCN:[self getCNfromKeychain]] == -1) //指定身份证书,返回-1表示设置指定证书失败
     {
         NSLog(@"your identity error");
     }
/**
  * 将身份证书和VPN做认证以登录VPN创建ssl连接
  *  @return VPN连接状态
  */
 -(BOOL)connectVPN
 {
     int iResult = -1;
     @try {
         iResult =[[L4Proxy sharedInstance] L4Proxy_ShakeHands_With_VPN:kVPNServer IPPort:kVPNPort username:@"" password:@""];
     }
     @catch (NSException *exception) {
         UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"提示" message:@"网络连接失败" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
         [alertView show];
         return NO;
     }
     @finally {
         if (iResult != 0) {
             NSLog(@"VPN 登录失败");
             return NO;
         }else {
             NSLog(@"VPN 登录成功");
             return YES;
         }
     }
 }
  
 // 3、获取监听端口
 - (void)refreshConnectionInfo
 {
     Constants* constants = [Constants sharedInstance];
     int webPort = [[L4Proxy sharedInstance] startLocalListen:@"BI-8080"];
     int port = [[L4Proxy sharedInstance] startLocalListen:@"BI-34952"];
     
     if(webPort > 0) {
         constants.webPort = webPort;
         constants.webServer = LOCAL_HOST;
     }
     
     if(port > 0) {
         constants.port = port;
         constants.iserver = LOCAL_HOST;
     }
 }
  
 /*
     5、6、点击登陆时调用的方法,将密码输入框的参数传过来,返回证书的主从账号信息,并会在Keychain中存储session,附应用可以通过调用相应的方法找到Keychain中存储的session直接登录成功
 */
 - (Account *)ssoLogin:(NSString*) password
 {
     int ssoLocalListenPort = [[L4Proxy sharedInstance] startLocalListen:ssoDesStr];
     if (ssoLocalListenPort > 0) {
         NSString *ssoHost = [NSString stringWithFormat:@"https://%@:%d",LOCAL_HOST, ssoLocalListenPort];
         NSString *retJson = [[SsoAuth sharedInstance] ssoAuthByCert:password withAppID:appId withServerAddress:ssoHost];
         NSLog(@"retJson = %@", retJson);
         NSError *error = nil;
         NSDictionary* result = [NSJSONSerialization JSONObjectWithData:[retJson dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingAllowFragments error:&error];
         NSLog(@"%@",result);
         if (error == nil && [@"success" isEqualToString:[result objectForKey:@"status"]]) {
             if(_account == nil) {
                 _account = [[Account alloc] init];
             }
             _account.name = [result objectForKey:@"masterAccout"];
             _account.token = [result objectForKey:@"token"];
             return _account;
         } else {
             NSString *message = [result objectForKey:@"message"];
             if(message == nil)
             {
                 message = @"请检查网络";
             }
             UIAlertView* alertView = [[UIAlertView alloc] initWithTitle:@"提示" message:message delegate:nil cancelButtonTitle:@"确认" otherButtonTitles:nil, nil];
             [alertView show];
             
             NSLog(@"单点登录JSON解析失败, error:%@", error);
             return nil;
         }
     } else {
         return nil;
     }
 }
 、
 4、动态更改app的显示模式(文件夹目录或者默认报表)
上步的第4步骤,通过VPN调用服务器接口,获取配置文件,就是为了获取XML文件,每次启动都重新获取XML文件,所以可以在后台对XML文件内容进行更改,下次启动的时候,就会显示新更改的模式。
- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions
 {
     NSString *str = [NSString stringWithFormat:@"http://127.0.0.1:%d/MicroStrategy/findDocumentId?loginname=%@",constant.webPort,certName];
     NSString *urlString = [str stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
     NSURL *url = [NSURL URLWithString:urlString];
     NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
     NSURLResponse *response = nil;
     NSError *error = nil;
     NSData *allStringdata = [NSURLConnection  sendSynchronousRequest:request returningResponse:&response error:&error];
     NSString *allString = [[NSString alloc] initWithData:allStringdata encoding:NSUTF8StringEncoding];
     //保存到沙盒目录下
     NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
     NSString *xmlPath1 = [path stringByAppendingString:@"/Preferences.xml"];
     [allString writeToFile:xmlPath1 atomically:YES encoding:NSUTF8StringEncoding error:nil];
     return YES;
 }
  
          XML文件的获取,用浏览器登录mstr的mobile server,在mobile配置的地方,进行主屏幕的配置。之后会在mstr服务器的安装路径下找到一个刚生成的XML文件,例如,D:\apache-tomcat-6.0.36\webapps\MicroStrategyMobile\WEB-INF\xml\mobile。
注意:取出的XML文件需要把服务器地址改为本地,端口号改为本地监听端口,项目地址不改。
5、服务器集群,负载均衡的使用。
         负载均衡的使用,不需要app作何更改。由于VPN的使用app中只有本地地址127.0.0.1和监听端口号的存在。同样配置文件XML文件,里面的项目地址也无须更改。
  










