SSH免密码登录

SSH

SSH(Secure Shell )是一种网络协议,用于计算机之间的加密登录。

公钥登录

公钥(Public Key)与私钥(Private Key)是通过一种算法得到的一个密钥对(即一个公钥和一个私钥),公钥是密钥对中公开的部分,私钥则是非公开的部分。
所谓”公钥登录”,原理很简单,就是用户将自己的公钥储存在远程主机上。登录的时候,远程主机会向用户发送一段随机字符串,用户用自己的私钥加密后,再发回来。远程主机用事先储存的公钥进行解密,如果成功,就证明用户是可信的,直接允许登录shell,不再要求密码。

具体步骤

1. 通过ssh-keygen生成秘钥
$ ssh-keygen -t rsa

-t指定加密类型为rsa,运行该命令以后,会在$HOME/.ssh/目录下,会新生成两个文件:id_rsa和id_rsa.pub。前者是你的私钥,后者是你的公钥。
袁克强的博客

2. 复制公钥到远程服务器

复制到/home/$USER/.ssh (.ssh目录不存在则创建一个,保证.ssh和authorized_keys都只有用户自己有写权限,否则验证无效: chmod 700 -R .ssh

$ scp id_rsa.pub root@myhost.com:~/.ssh/id_rsa.pub
3. 将公钥保存到authorized_keys文件

登录远程服务器后,将公钥保存到authorized_keys文件。

$ cd ~/.ssh 
$ cat id_rsa.pub >> authorized_keys

至此,即可免密码登录远程服务器。

iOS学习笔记之GCD

Serial Diapatch Queue 串行队列

任务相互依赖,具有明显的先后顺序的时候

1
var queue1 : dispatch_queue_t = dispatch_queue_create("com.y.queue", DISPATCH_QUEUE_SERIAL)

Concurrent Diapatch Queue 并发队列

不会存在任务间的相互依赖,并发执行

1
var queue2 : dispatch_queue_t = dispatch_queue_create("com.y.queue", DISPATCH_QUEUE_CONCURRENT)

Global Queue & Main Queue

这是系统为我们准备的2个队列:

  • Global Queue 就是系统创建的Concurrent Diapatch Queue
  • Main Queue 就是系统创建的位于主线程的Serial Diapatch Queue
1
2
var queue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
var queue2 = dispatch_get_main_queue()

常用的队列方法


dispatch_set_target_queue

dispatch_set_target_queue 可以指定2个队列的优先级:

1
dispatch_set_target_queue(queue2,queue1);

dispatch_sync

同步执行队列,会在当前线程执行队列,并且阻塞当前线程中之后运行的代码。

1
2
3
dispatch_sync(dispatch_get_main_queue()) { () -> Void in
print("我执行完了才执行后面的代码");
}

dispatch_aync

异步执行队列,不会阻塞当前线程中之后运行的代码。

1
2
3
dispatch_sync(dispatch_get_main_queue()) { () -> Void in
print("我不影响代码继续执行");
}

dispatch_after

dispatch_after 用于异步延迟执行,如在主线程中延迟10秒执行。

1
2
3
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(10 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) { () -> Void in
print("我延迟10秒执行");
}

dispatch_apply

dispatch_apply, 作用是把指定次数指定的block添加到queue中, 第一个参数是迭代次数,第二个是所在的队列,第三个是当前索引.

1
2
3
dispatch_apply(10, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { (index) -> Void in
print(index);
}

dispatchgroup*

队列组,当我们需要监听一个并发队列中,所有任务都完成了,就可以用到这个group,因为并发队列你并不知道哪一个是最后执行的,所以以单独一个任务是无法监听到这个点的,如果把这些单任务都放到同一个group,那么,我们就能通过dispatch_group_notify方法知道什么时候这些任务全部执行完成了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var queue : dispatch_queue_t = dispatch_queue_create("com.y.queue", DISPATCH_QUEUE_CONCURRENT)
var group : dispatch_group_t = dispatch_group_create();
dispatch_group_async(group, queue) { () -> Void in
print("0");
}
dispatch_group_async(group, queue) { () -> Void in
print("1");
}
dispatch_group_async(group, queue) { () -> Void in
print("2");
}
dispatch_group_notify(group, dispatch_get_main_queue()) { () -> Void in
print("组里的所有任务都执行完了");
}

dispatch_suspend & dispatch_resume

队列挂起和恢复。

1
2
3
4
5
dispatch_suspend(queue);
sleep(3)
dispatch_async(dispatch_get_main_queue()) { () -> Void in
dispatch_resume(queue);
}

其他

dispatch_barrier_async / dispatchsemaphore* / dispatch_once 等。

常用Git命令清单

[转载自阮一峰的网络日志]

我每天使用 Git ,但是很多命令记不住。
一般来说,日常使用只要记住下图6个命令,就可以了。但是熟练使用,恐怕要记住60~100个命令。

git

下面是我整理的常用 Git 命令清单。几个专用名词的译名如下。

  • Workspace:工作区
  • Index / Stage:暂存区
  • Repository:仓库区(或本地仓库)
  • Remote:远程仓库

一、新建代码库

# 在当前目录新建一个Git代码库
$ git init

# 新建一个目录,将其初始化为Git代码库
$ git init [project-name]

# 下载一个项目和它的整个代码历史
$ git clone [url]

二、配置

Git的设置文件为.gitconfig,它可以在用户主目录下(全局配置),也可以在项目目录下(项目配置)。

# 显示当前的Git配置
$ git config --list

# 编辑Git配置文件
$ git config -e [--global]

# 设置提交代码时的用户信息
$ git config [--global] user.name "[name]"
$ git config [--global] user.email "[email address]"

三、增加/删除文件

# 添加指定文件到暂存区
$ git add [file1] [file2] ...

# 添加指定目录到暂存区,包括子目录
$ git add [dir]

# 添加当前目录的所有文件到暂存区
$ git add .

# 删除工作区文件,并且将这次删除放入暂存区
$ git rm [file1] [file2] ...

# 停止追踪指定文件,但该文件会保留在工作区
$ git rm --cached [file]

# 改名文件,并且将这个改名放入暂存区
$ git mv [file-original] [file-renamed]

四、代码提交


# 提交暂存区到仓库区
$ git commit -m [message]

# 提交暂存区的指定文件到仓库区
$ git commit [file1] [file2] ... -m [message]

# 提交工作区自上次commit之后的变化,直接到仓库区
$ git commit -a

# 提交时显示所有diff信息
$ git commit -v

# 使用一次新的commit,替代上一次提交
# 如果代码没有任何新变化,则用来改写上一次commit的提交信息
$ git commit --amend -m [message]

# 重做上一次commit,并包括指定文件的新变化
$ git commit --amend [file1] [file2] ...

五、分支


# 列出所有本地分支
$ git branch

# 列出所有远程分支
$ git branch -r

# 列出所有本地分支和远程分支
$ git branch -a

# 新建一个分支,但依然停留在当前分支
$ git branch [branch-name]

# 新建一个分支,并切换到该分支
$ git checkout -b [branch]

# 新建一个分支,指向指定commit
$ git branch [branch] [commit]

# 新建一个分支,与指定的远程分支建立追踪关系
$ git branch --track [branch] [remote-branch]

# 切换到指定分支,并更新工作区
$ git checkout [branch-name]

# 建立追踪关系,在现有分支与指定的远程分支之间
$ git branch --set-upstream [branch] [remote-branch]

# 合并指定分支到当前分支
$ git merge [branch]

# 选择一个commit,合并进当前分支
$ git cherry-pick [commit]

# 删除分支
$ git branch -d [branch-name]

# 删除远程分支
$ git push origin --delete [branch-name]
$ git branch -dr [remote/branch]

六、标签


# 列出所有tag
$ git tag

# 新建一个tag在当前commit
$ git tag [tag]

# 新建一个tag在指定commit
$ git tag [tag] [commit]

# 删除本地tag
$ git tag -d [tag]

# 删除远程tag
$ git push origin :refs/tags/[tagName]

# 查看tag信息
$ git show [tag]

# 提交指定tag
$ git push [remote] [tag]

# 提交所有tag
$ git push [remote] --tags

# 新建一个分支,指向某个tag
$ git checkout -b [branch] [tag]

七、查看信息

# 显示有变更的文件
$ git status

# 显示当前分支的版本历史
$ git log

# 显示commit历史,以及每次commit发生变更的文件
$ git log --stat

# 显示某个commit之后的所有变动,每个commit占据一行
$ git log [tag] HEAD --pretty=format:%s

# 显示某个commit之后的所有变动,其"提交说明"必须符合搜索条件
$ git log [tag] HEAD --grep feature

# 显示某个文件的版本历史,包括文件改名
$ git log --follow [file]
$ git whatchanged [file]

# 显示指定文件相关的每一次diff
$ git log -p [file]

# 显示指定文件是什么人在什么时间修改过
$ git blame [file]

# 显示暂存区和工作区的差异
$ git diff

# 显示暂存区和上一个commit的差异
$ git diff --cached [file]

# 显示工作区与当前分支最新commit之间的差异
$ git diff HEAD

# 显示两次提交之间的差异
$ git diff [first-branch]...[second-branch]

# 显示某次提交的元数据和内容变化
$ git show [commit]

# 显示某次提交发生变化的文件
$ git show --name-only [commit]

# 显示某次提交时,某个文件的内容
$ git show [commit]:[filename]

# 显示当前分支的最近几次提交
$ git reflog

八、远程同步


# 下载远程仓库的所有变动
$ git fetch [remote]

# 显示所有远程仓库
$ git remote -v

# 显示某个远程仓库的信息
$ git remote show [remote]

# 增加一个新的远程仓库,并命名
$ git remote add [shortname] [url]

# 取回远程仓库的变化,并与本地分支合并
$ git pull [remote] [branch]

# 上传本地指定分支到远程仓库
$ git push [remote] [branch]

# 强行推送当前分支到远程仓库,即使有冲突
$ git push [remote] --force

# 推送所有分支到远程仓库
$ git push [remote] --all

九、撤销

# 恢复暂存区的指定文件到工作区
$ git checkout [file]

# 恢复某个commit的指定文件到工作区
$ git checkout [commit] [file]

# 恢复上一个commit的所有文件到工作区
$ git checkout .

# 重置暂存区的指定文件,与上一次commit保持一致,但工作区不变
$ git reset [file]

# 重置暂存区与工作区,与上一次commit保持一致
$ git reset --hard

# 重置当前分支的指针为指定commit,同时重置暂存区,但工作区不变
$ git reset [commit]

# 重置当前分支的HEAD为指定commit,同时重置暂存区和工作区,与指定commit一致
$ git reset --hard [commit]

# 重置当前HEAD为指定commit,但保持暂存区和工作区不变
$ git reset --keep [commit]

# 新建一个commit,用来撤销指定commit
# 后者的所有变化都将被前者抵消,并且应用到当前分支
$ git revert [commit]

十、其他

# 生成一个可供发布的压缩包
$ git archive

git

Envoy任务执行器及自动化部署项目

简介

Laravel Envoy 提供了简洁、轻量的语法用于定义在远程服务器上可执行的通用任务。通过 Blade 风格的语法,你可以很容易地设置任务从而完成部署、执行 Artisan 命令或其他更多工作。

官网:https://github.com/laravel/envoy

安装

直接通过 Composer 的 global 命令来安装 Envoy:

1
$ composer global require "laravel/envoy=~1.0"

务必将 ~/.composer/vendor/bin 目录加入到 PATH 环境变量中,这样才能在命令行中执行 envoy 命令时找到可执行文件。

1
2
3
$ vim ~/.bash_profile
$ export PATH="$PATH:~/.composer/vendor/bin"
$ source ~/.bash_profile

Envoy 基本使用

创建任务

Envoy通过执行 Envoy.blade.php文件来执行SSH任务。
在项目目录创建Envoy.blade.php,写入如下内容:

1
2
3
4
@servers(['web' => 'root@115.27.x.x'])
@task('foo', ['on' => 'web'])
ls -la
@endtask

如上所示,@servers数组定义了服务器地址,后续的任务声明中,你可以在 on 选项中直接引用。@task声明里定义了一个名称为foo的任务,用于执行ls -la。

执行任务

使用run命令来执行任务

1
$ envoy run foo

执行run命令后,控制台会要求输入服务器的密码。
输入密码后即可看到在服务器执行ls -la的结果。
sd

如有需要,你还可以通过命令行向 Envoy 文件传递参数:

1
$ envoy run deploy --path=/home/www
1
2
3
4
5
6
 @servers(['web' => '192.168.1.1'])

@task('deploy', ['on' => 'web'])
cd {{$path}}
ls
@endtask

可以在 @setup 指令中声明变量,并在 Envoy 文件中执行普通的 PHP 代码:

1
2
3
4
@setup
$now = new DateTime();
$environment = isset($env) ? $env : "testing";
@endsetup

还有多服务器并行执行:

1
2
3
@servers(['web-1' => '192.168.1.1', 'web-2' => '192.168.1.2'])
@task('deploy', ['on' => ['web-1', 'web-2'],'parallel' => true])
@endtask

其他,@macro任务宏声明让你只用一条命令就能顺序执行一组任务, @after声明在任务执行完后处理,如发一封邮件通知等。

参考https://laravel.com/docs/5.1/envoy

使用envoy部署项目

在实际项目中我们常常使用git来作版本控制,开发完成后将代码提交到远程代码库。
在产品发布新版本时,可以在服务器直接git pull下来完成更新。我们将一系列任务通过envoy来执行,如:

1
2
3
4
5
6
7
8
9
10
@servers(['web' => '115.27.x.x'])
@task('deploy', ['on' => 'web'])
cd /home/www/laravel
git pull origin {{ $branch }}
php artisan migrate
@endtask

@after
echo "\r\n新版本发布完成!\r\n";
@endafter
1
$ envoy run deploy --branch=master

基于envoy的项目:
Envoyer : https://envoyer.io/
envoy-deploy : https://github.com/papertank/envoy-deploy

细说iOS消息推送

[转载自leancloud]

APNs 的推送机制

与 Android 上我们自己实现的推送服务不一样,Apple 对设备的控制非常严格,消息推送的流程必须要经过 APNs:

swift

这里 Provider 是指某个应用的 Developer,当然如果开发者使用 LeanCloud 的服务,把发送消息的请求委托给我们,那么这里的 Provider 就是 LeanCloud 的推送服务程序了。上图可以分为三步:

  1. LeanCloud 推送服务程序把要发送的消息、目的设备的唯一标识打包,发给 APNs。
  2. APNs 在自身的已注册 Push 服务的应用列表中,查找有相应标识的设备,并把消息发送到设备。
  3. iOS 系统把发来的消息传递给相应的应用程序,并且按照设定弹出 Push 通知。

为了实现消息推送,有两点非常重要:

1、App 的推送证书
要能够完整实现一条消息推送,需要我们在 App ID 中打开 Push Notifications,需要我们准备好 Provisioning Profile 和 SSL 证书,并且一定要注意 Development 和 Distribution 环境是需要分开的。最后,把 SSL 证书导入到 LeanCloud 平台,就可以尝试远程消息推送了。具体的操作流程可以参考我们的使用指南:iOS 推送证书设置指南 。

2、设备标识 DeviceToken
知道了谁要推送,或者说要推送给哪个应用之后,APNs 还需要知道推到哪台设备上,这就是设备标识的作用。获取设备标识的流程如下:

1. 应用打开推送开关,用户要确认 TA 希望获得该应用的推送消息;  
2. 应用获得一个 DeviceToken;  
3. 应用将 DeviceToken 保存起来,这里就是通过 [AVInstallation saveInBackground] 将 DeviceToken 保存到 LeanCloud;  
4. 当某些特定事件发生,开发者委托 LeanCloud 来发送推送消息,这时候 LeanCloud 的推送服务器就会给 APNs 发送一则推送消息,APNs 最后消息送到用户设备。  

leancloud

推送相关的几个概念

消息类型

一条消息推送过来,可以有如下几种表现形式:

  • 显示一个 alert 或者 banner,展现具体内容。
  • 在应用 icon 上提示一个新到消息数。
  • 播放一段声音。

开发者可以在每次推送的时候设置,在推送达到用户设备时开发者也可以选择不同的提示方式。

本地消息通知

iOS 上有两种消息通知,一种是本地消息(Local Notification),一种是远程消息 (Push Notification,也叫 Remote Notification),设计这两种通知的目的都是为了提醒用户,现在有些什么新鲜的事情发生了,吸引用户重新打开应用。

本地消息什么时候有用呢?譬如你正在做一个 To-do 的工具类应用,对于用户加入的每一个事项,都会有一个完成的时间点,用户可以要求这个 To-do 应用在事项过期之前的某一个时间点提醒一下 TA。为了达到这一目的,应用就可以调度一个本地通知,在时间点到了之后发出一个 Alert 消息或者其他提示。

我们在处理推送消息的时候,也可以综合运用这两种方式。

代码里面如何实现推送

首先,我们要获取 DeviceToken。

应用需要每次启动的时候都去注册远程通知——通过调用 UIApplication 的 registerForRemoteNotificationTypes: 方法,传递给它你希望支持的消息类型参数即可,例如:

1
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // do some initiale working
    ...

    [application registerForRemoteNotificationTypes:UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound];
    return YES;
}

如果注册成功,APNs 会返回给你设备的 token,iOS 系统会把它传递给 app delegate 代理 application:didRegisterForRemoteNotificationsWithDeviceToken: 方法,你应该在这个方法里面把 token 保存到 LeanCloud 后台,例如:

1
- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
    NSLog(@"Receive DeviceToken: %@", deviceToken);
    AVInstallation *currentInstallation = [AVInstallation currentInstallation];
    [currentInstallation setDeviceTokenFromData:deviceToken];
    [currentInstallation saveInBackground];
}

如果注册失败,application:didFailToRegisterForRemoteNotificationsWithError: 方法会被调用,通过 NSError 参数你可以看到具体的出错信息,例如:

1
  
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
    NSLog(@"注册失败,无法获取设备 ID, 具体错误: %@", error);
}

请注意,注册流程需要在应用每次启动时调用,这并不不会带来额外的负担,因为 iOS 操作系统在第一次获得了有效的 device token 之后,会本地缓存起来,以后应用再调用 registerForRemoteNotificationTypes: 的时候会立刻返回,并不会再进行网络请求。另外,应用层面不应该对 device token 进行缓存,因为 device token 也有可能变化——如果用户重装了操作系统,那么 APNs 再次给出的 device token 就会和之前的不一样,又或者是,用户 恢复了原来的备份到新的设备上,那么原来的 device token 也会失效。

其次,我们要处理收到消息之后的回调。

我们可以设想一下消息通知的几种使用场景:

1/ 在应用没有被启动的时候,接收到了消息通知。这时候操作系统会按照默认的方式来展现一个 alert 消息,在应用的 icon 上标记一个数字,甚至播放一段声音。
2/ 用户看到消息之后,点击了一下 action 按钮或者点击了应用图标。如果 action 按钮被点击了,系统会通过调用 application:didFinishLaunchingWithOptions: 这个代理方法来启动应用,并且会把 notification 的 payload 数据传递进去。如果应用图标被点击了,系统也一样会调用 application:didFinishLaunchingWithOptions: 这个代理方法来启动应用,唯一不同的是这时候启动参数里面不会有任何 notification 的信息。示例代码如下:


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // do initializing works
    ...

    if (launchOptions) {
        // do something else
        ...

        [AVAnalytics trackAppOpenedWithLaunchOptions:launchOptions];
    }

    [application registerForRemoteNotificationTypes:UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound];

    return YES;
}

3/ 如果远程消息发送过来的时候,应用正在运行,这时候会发生什么呢?应用代理的 application:didReceiveRemoteNotification: 方法会被调用,同时远程消息中的 payload 数据会作为参数传递进去。示例代码如下:



- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
    if (application.applicationState == UIApplicationStateActive) {
        // 转换成一个本地通知,显示到通知栏,你也可以直接显示出一个 alertView,只是那样稍显 aggressive:)
        UILocalNotification *localNotification = [[UILocalNotification alloc] init];
        localNotification.userInfo = userInfo;
        localNotification.soundName = UILocalNotificationDefaultSoundName;
        localNotification.alertBody = [[userInfo objectForKey:@"aps"] objectForKey:@"alert"];
        localNotification.fireDate = [NSDate date];
        [[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
    } else {
        [AVAnalytics trackAppOpenedWithRemoteNotificationPayload:userInfo];
    }
}

swift实现UITableViewCell滑动删除

UITableViewCell滑动删除

实现Cell的滑动删除,只需重写UITableViewDelegate代理中的如下2个方法。


// 进入编辑模式,按下编辑按钮后执行  
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
    //删除数组中的数据
    self.dataArray.removeAtIndex(indexPath.row);  
    //删除单元格
    self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Top);
}

//修改文字  
func tableView(tableView: UITableView, titleForDeleteConfirmationButtonForRowAtIndexPath indexPath: NSIndexPath) -> String? {
    return "啊~不要";
}

swift

UITableview 自定义Action

上面只实现了UITableViewCell的单个编辑Action,若要自定义Action,可使用如下的代理方法。


func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
    let action1 = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "删除") { (action , index) -> Void in
        print("点击了删除");
    }
    let action2 = UITableViewRowAction(style: UITableViewRowActionStyle.Normal, title: "取消关注") { (action , index) -> Void in
        print("点击了取消关注");
    }
    return [action1,action2];
}

swift

持续集成是什么?

[转载自阮一峰的网络日志]

互联网软件的开发和发布,已经形成了一套标准流程,最重要的组成部分就是持续集成(Continuous integration,简称CI)。

本文简要介绍持续集成的概念和做法。

test

一、概念

持续集成指的是,频繁地(一天多次)将代码集成到主干。

它的好处主要有两个。

  1. 快速发现错误。每完成一点更新,就集成到主干,可以快速发现错误,定位错误也比较容易。
  2. 防止分支大幅偏离主干。如果不是经常集成,主干又在不断更新,会导致以后集成的难度变大,甚至难以集成。

持续集成的目的,就是让产品可以快速迭代,同时还能保持高质量。它的核心措施是,代码集成到主干之前,必须通过自动化测试。只要有一个测试用例失败,就不能集成。

Martin Fowler说过,”持续集成并不能消除Bug,而是让它们非常容易发现和改正。”

与持续集成相关的,还有两个概念,分别是持续交付和持续部署。

二、持续交付

持续交付(Continuous delivery)指的是,频繁地将软件的新版本,交付给质量团队或者用户,以供评审。如果评审通过,代码就进入生产阶段。

持续交付可以看作持续集成的下一步。它强调的是,不管怎么更新,软件是随时随地可以交付的。

三、持续部署

持续部署(continuous deployment)是持续交付的下一步,指的是代码通过评审以后,自动部署到生产环境。

持续部署的目标是,代码在任何时刻都是可部署的,可以进入生产阶段。

持续部署的前提是能自动化完成测试、构建、部署等步骤。它与持续交付的区别,可以参考下图。

test

四、流程

根据持续集成的设计,代码从提交到生产,整个过程有以下几步。

4.1 提交
流程的第一步,是开发者向代码仓库提交代码。所有后面的步骤都始于本地代码的一次提交(commit)。

4.2 测试(第一轮)
代码仓库对commit操作配置了钩子(hook),只要提交代码或者合并进主干,就会跑自动化测试。
测试有好几种。

  • 单元测试:针对函数或模块的测试
  • 集成测试:针对整体产品的某个功能的测试,又称功能测试
  • 端对端测试:从用户界面直达数据库的全链路测试

第一轮至少要跑单元测试。

4.3 构建
通过第一轮测试,代码就可以合并进主干,就算可以交付了。

交付后,就先进行构建(build),再进入第二轮测试。所谓构建,指的是将源码转换为可以运行的实际代码,比如安装依赖,配置各种资源(样式表、JS脚本、图片)等等。

常用的构建工具如下。

Jenkins和Strider是开源软件,Travis和Codeship对于开源项目可以免费使用。它们都会将构建和测试,在一次运行中执行完成。

4.4 测试(第二轮)
构建完成,就要进行第二轮测试。如果第一轮已经涵盖了所有测试内容,第二轮可以省略,当然,这时构建步骤也要移到第一轮测试前面。

第二轮是全面测试,单元测试和集成测试都会跑,有条件的话,也要做端对端测试。所有测试以自动化为主,少数无法自动化的测试用例,就要人工跑。

需要强调的是,新版本的每一个更新点都必须测试到。如果测试的覆盖率不高,进入后面的部署阶段后,很可能会出现严重的问题。

4.5 部署
通过了第二轮测试,当前代码就是一个可以直接部署的版本(artifact)。将这个版本的所有文件打包( tar filename.tar * )存档,发到生产服务器。

生产服务器将打包文件,解包成本地的一个目录,再将运行路径的符号链接(symlink)指向这个目录,然后重新启动应用。这方面的部署工具有Ansible,Chef,Puppet等。

4.6 回滚
一旦当前版本发生问题,就要回滚到上一个版本的构建结果。最简单的做法就是修改一下符号链接,指向上一个版本的目录。

五、参考链接

Gergely Nemeth, Continuous Deployment of Node.js Applications
Codeship, Continuous Integration Essentials

使用融云为你的App添加即时通讯功能

[使用swift语言,为项目添加一个单对单通讯功能]

简介

融云是一款第三方即时通讯SDK,通过融云平台,开发者不必搭建服务端硬件环境,就可以将即时通讯、实时网络能力快速集成至应用中。
官网:http://www.rongcloud.cn/

类似的SDK还有leancloud、环信等。

创建应用

在进行应用开发之前,需要前往融云开发者平台 创建应用。
获得App Key和App Secret 。

通过CocoaPods安装SDK

在Podfile中加入pod ‘RongCloudIMKit’
执行命令安装

$ pod install

安装完成后在swift与objective-c的桥接文件中导入SDK

#import <RongIMKit/RongIMKit.h>

初始化SDK

初始化SDK,通过token链接服务器,并设置当前的用户。

//初始化SDK
RCIM.sharedRCIM().initWithAppKey("cpj2xarljexxx")  
// 连接服务器
RCIM.sharedRCIM().connectWithToken("/3SuTgMXKxywuZcZSyVeUyKCc+4pFtAAaogrQ+3UGk7BaEgBD5YClHs6HVIYJGTtuuaS2B1/6ACXwLNzSRhJTA==", success: { (str:String!) -> Void in
    let currentUserInfo = RCUserInfo(userId: "001", name: "袁克强", portrait: nil)
    RCIMClient.sharedRCIMClient().currentUserInfo = currentUserInfo
    print("连接成功!")
}, error: {(error)-> Void in
    print(error);
}, tokenIncorrect: {()-> Void in
    print("token错误");
})

注:这里使用写死的token与用户信息,实际开发中我们需要登录后保存用户信息,并通过UserId远程获取token 参考http://www.rongcloud.cn/docs/server.html#用户服务原身份认证服务_,融云服务器不保存用户信息。

启动单聊会话界面

融云IMKit的会话界面已经高度集成,你只需要启动会话界面。
下面的例子是一个Button点击事件触发(聊天对象为自己)。

func createConversation(){
    let conVC = RCConversationViewController();
    conVC.targetId = "001"    
    conVC.userName = "袁克强"
    conVC.conversationType = RCConversationType.ConversationType_PRIVATE
    conVC.title = v.userName;
    self.navigationController?.pushViewController(conVC, animated: true);
}

效果

ios

至此,一个简单的类似微信的单对单通讯功能已经开发完毕。

使用腾讯信鸽推送iOS消息

近期App需要添加推送功能,选择了腾讯信鸽作为消息推送平台。

信鸽

信鸽(XG Push)是一款专业移动App推送平台,支持百亿级的通知/消息推送,秒级触达移动用户,现已全面支持Android和iOS两大主流平台。开发者可以方便地通过嵌入SDK,通过API调用或者Web端可视化操作,实现对特定用户推送,大幅提升用户活跃度,有效唤醒沉睡用户,并实时查看推送效果。

简单记录开发过程(使用swift语言):

1. 接入应用

登录http://xg.qq.com/ 创建应用,获得ACCESS ID及ACCESS KEY。

xinge

2. 制作证书并上传

官方提供了详细的证书制作指南 http://developer.xg.qq.com/index.php/IOS_证书设置指南

制作完成后上传即可。

xinge

3. 下载SDK及工程配置

  1. 下载信鸽SDK压缩包到本地并解压;

  2. 创建或打开Xcode iOS工程;

  3. 将XGSetting.h 和 XGPush.h 和 libXG-SDK.a添加到Xcode工程;

  4. 添加对以下libraries的引用。包括CFNetwork.framework , SystemConfiguration.framework , CoreTelephony.framework , libz.dylib , libXG-SDK.a,Security.framework
    xinge

  5. 最后由于SDK是object-c版本的,在链接文件中引入头信息以便swift调用。
    #import “XGPush.h”
    #import “XGSetting.h”

4. 使用swift调用SDK

参考技术开发文档

主要相关代码(AppDelegate.swift):


// ........

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {

 XGPush.startApp(220010xxxx,appKey:"IVPV4T39xxxx");
   XGPush.initForReregister { () -> Void in
        if(!XGPush.isUnRegisterStatus()){
            println("注册通知");
            if(UIApplication.instancesRespondToSelector(Selector( "registerUserNotificationSettings:" ))){
                application.registerUserNotificationSettings ( UIUserNotificationSettings (forTypes:  UIUserNotificationType.Sound |  UIUserNotificationType.Alert |  UIUserNotificationType.Badge, categories:  nil ))
                application.registerForRemoteNotifications();
            } else {
                application.registerForRemoteNotificationTypes(.Alert | .Sound | .Badge);
            }
        }
    }
    //推送反馈(app不在前台运行时,点击推送激活时)
    XGPush.handleLaunching(launchOptions)
}

// 远程推送注册成功,获取deviceToken
func application(application: UIApplication , didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData ) {
    var  deviceTokenStr : String = XGPush.registerDevice(deviceToken);
    println(deviceTokenStr)
}


// 远程推送注册失败
func application(application: UIApplication , didFailToRegisterForRemoteNotificationsWithError error: NSError ) {
    if error.code == 3010 {
        println ( "Push notifications are not supported in the iOS Simulator." )
    } else {
        println ( "application:didFailToRegisterForRemoteNotificationsWithError: \(error) " )
    }
}


//app在前台运行时收到远程通知
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
    println(userInfo);
    XGPush.handleReceiveNotification(userInfo);
}

//收到本地通知
func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification) {
    // ...
}

5. 进行验证测试

根据之前代码里获得的deviceToken添加一台测试设备。
xinge

在腾讯信鸽平台创建一条开发环境的推送,手机就能收到推送的消息了。

xinge