windows下基础开发环境搭建2016

工作需要在windows环境下搭建一个基础开发环境,在此记录一下:

一、注意事项

  1. 以下的安装配置方法仅支持win7/8/10之类的系统,xp可以洗洗睡了。
  2. 装了cmder-full(With git-for-windows)版或者babun就没有必要安装git-for-windows了,不过从兼容性角度出发,建议还是使用cmder-full的版本,至少纯windows的环境下也能直接使用git指令,也支持外部ide调用。
  3. babun(cygwin)是调整过的cygwin环境,cmder默认是windows环境(可以安装chocolatey做包管理)两个环境面对的场景不同,choco安装的软件基本都是port到win环境下的版本,和babun的cygwin环境下的版本配置方式不一样,看各人喜好,可以分别使用,也可以只使用一个。
  4. linux的家HOME目录用~字符来标识,windows下通常对应的就是C:\Users\Administrator目录

二、终端环境

推荐使用cmder(With git-for-windows) + babun(cygwin)预配置环境的方式,既有普通cmd终端的linux化,又支持cygwin环境安装使用你所需要的linux环境安装包

  1. cmder-full(With git-for-windows)下载后解压缩到磁盘,(推荐c:\opt\cmder)
  2. 加入系统环境变量PATH环境变量中追加c:\opt\cmder路径。
  3. 注册右键菜单:以管理员身份启动命令行提示符(cmd),然后进入c:\opt\cmder路径后执行:
1
    Cmder.exe /REGISTER ALL
  1. 提示符配置:修改cmder目录下的config\cmder.lua的第2行把λ替换为$,更符合日常linux下用户prompt的习惯。
1
2
3
    function lambda_prompt_filter()
        clink.prompt.value = string.gsub(clink.prompt.value, "{lamb}", "$")
    end
  1. 常用别名配置:修改cmder目录下的config\alias文件,在最后加入常用的alias,比如:
1
2
3
    l=ls --show-control-chars
    la=ls -aF --show-control-chars 
    ll=ls -alF --show-control-chars
  1. 中文字间距问题,启动cmder后win+alt+p呼出设置面板去掉 monospace 选项(点两下让勾选框留空),然后save settings保存设置。

  2. 可选(安装chocolatey windows包管理器),打开cmder/cmd终端,执行以下命令:

1
    @powershell -NoProfile -ExecutionPolicy Bypass -Command "iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))" && SET PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin
  1. 安装babun的环境:下载babun官网上的安装包,解压后执行其中的install.bat批处理文件
  2. 配置整合babun环境到cmder工具中:启动cmder后win+alt+p呼出设置面板(Settings),然后打开 Startup>Tasks面板点+号添加一个task,名称输入babun, Task parameters中输入:
1
    /icon "%userprofile%\.babun\cygwin\bin\mintty.exe" /dir "%userprofile%"

下面的Commands大输入文本框中输入:

1
%userprofile%\.babun\cygwin\bin\mintty.exe /bin/env CHERE_INVOKING=1 /bin/zsh.exe

save settings保存设置,然后你可以按up或down的按钮把{babun}的顺序调到{cmd}的前后,也可以勾选Default task for new console和Default Shell选项来把它作为默认console。注意,如果新开窗口想默认用{babun},需要修改Startup面板中的Specified named task不选{cmd}而是新的这个{babun}

  1. 修改babun的环境配置:cmder启动后打开{babun}的终端后编辑~/.zshrc文件:
1
vim ~/.zshrc

修改ZSH_THEME="babun"为ZSH_THEME="ys”,后保存退出,然后关闭终端后再启动{babun}终端即可。

  1. 如果你还是习惯mingw的git-bash环境也可以在cmder工具中添加配置:启动cmder后win+alt+p呼出设置面板(Settings),然后打开 Startup>Tasks面板点+号添加一个task,名称输入gitbash,Task parameters中留空下面的Commands大输入文本框中输入:
1
%ConEmuDir%\..\git-for-windows\git-cmd.exe --cd-to-home --command=usr/bin/bash.exe -l -i

save settings保存设置即可,后续的就可以启动{gitbash}终端使用了。

至此你已经完成了windows下终端环境的配置,启动cmder后,{cmd}的task为windows上下文命令行:使用choco作为包管理器 同时也支持一些基本的linux命令比如ls,curl之类的,软件包管理可以使用chocolatey的choco指令,

1
    choco install -y notepadplusplus

{babun}的task为cygwin上下文命令行:使用pact作为包管理器

1
2
    pact find php
    pact install -y php

注意{babun}的cygwin环境上下文中的安装的包不能在windows上下文环境中使用,(比如这里安装的php指令在windows环境下则会报招不到php指令),但windows上下文环境中安装的软件可以在cygwin环境下最后被搜索到则调用,注意是调用,效率比较低,而且也无法控制执行,所以通常情况下的windows指令还是推荐在{cmd}终端中执行。

  1. 命令行生成私钥公钥对:
1
    ssh-keygen -t rsa -C "you@myteam"

默认生成的文件在~/.ssh路径下的id_rsa(私钥)和id_rsa.pub(公钥),id_rsa.pub可以添加到代码托管环境的个人公钥列表中去

  1. 设置个人基础的git信息:
1
2
    git config --global user.name "你的名字或昵称"
    git config --global user.email "你的邮箱"

其他推荐软件

以下软件都可以用 chocolatey windows下的命令行包管理器 来安装,安装/卸载软件并不需要用到那些什么软件管理专家了。

  1. wox windows下类似于mac的alfred的快速启动工具,有了它记得软件、目录名字就可以了,忘了开始菜单吧。
  2. everything windows下的文件搜索工具,这个就不用多说了吧。

基于Laravel Envoy的部署脚本

如题,找了半天貌似都没有符合laravel的天朝网络环境的的部署脚本, 于是乎结合参考了多个部署脚本和工具的理念又造了个新轮子:

https://github.com/nickfan/envoy-deployscript

使用说明见:https://github.com/nickfan/envoy-deployscript/blob/master/README.zh-CN.md

欢迎大家测试吐槽。

主要特点:

支持本地构建打包部署

1
envoy run deploy_localrepo_install --branch=master --env=production

支持工作拷贝本地构建依赖模块(vendor目录)

1
envoy run deploy_mix_pack

原理/设计规划

你的 $deploybasepath 远程部署基础路径将会看起来像如下这样(如果你有多个子应用在同一个部署基路径里).

1
2
3
4
    mysite/
    mysite2/
    mysite3/

你的 $deploybasepath/$appname 远程部署应用路径将会看起来如下.

1
2
3
4
5
6
7
    releases/release_20150717032737/
    releases/release_20150717034646/
    current -> ./releases/release_20150717034646
    shared/storage/
    tmp/
    .env

正如你所见的, current 目录是软链到最近一次的部署目录的

在你的部署目录中文件列表看起来像下面这样(只列举了部分文件做例子):

1
2
3
4
5
6
7
8
9
    app/
    artisan
    boostrap/
    public/index.php
    composer.json
    .env -> ../../.env
    storage -> ../../shared/storage
    vendor/

部署目录中的.env文件和storage文件夹都软链接到了上级应用目录中的公共文件/文件夹了,这样部署本身只部署源代码和vendor等依赖环境, storage等公共数据在shared文件夹中并不随代码部署,节省了空间也保留延续了日志/应用cache等相关基础数据在应用中的使用

一个简单的php守护进程代码示例

工作中同事需要做一个简单的工作进程,需要在进程结束时不能被硬生生的掐断当前正在执行的工作流程,需要等一个处理流程跑完了再结束,所以这时候就需要用到pcntl的信号量来工作了,主要的设计思路:

  1. 捕获系统发给进程的中断的信号量
  2. 在handler代码中设置标志位变量
  3. 在一个业务循环处理完成之后判断标志位变量,如果接收到过终止请求,则跳出整个工作循环。 主要的处理逻辑:
  • 注册绑定函数:
1
2
3
4
5
6
7
    protected function regist_sig_handler()
    {
        declare(ticks = 1);
        pcntl_signal(SIGTERM, [$this, 'sig_handler']);
        pcntl_signal(SIGHUP, [$this, 'sig_handler']);
        pcntl_signal(SIGINT, [$this, 'sig_handler']);
    }
  • 在函数中设置标志位:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
protected function sig_handler($signo)
    {
        switch ($signo) {
            case SIGTERM:
            case SIGHUP:
            case SIGINT:
                self::$_MYDAEMON_SHOULD_STOP = true;
                // ...
                break;
        }
    }
  • 构建退出处理函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
 protected function shouldStop()
    {
        if (self::$_MYDAEMON_SHOULD_STOP) {
            // before exit staff code
            // ...
            return true;
        }

        return false;
    }
  • 在主循环中一次处理完成后判断是否要终止退出
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
        while (true) {
            if ($this->shouldStop()) {
                break;
            }
            try {
                $jobStatusInfo = $this->execute([]);
            } catch (Exception $ex) {
                $this->log($ex->getMessage());
            }
            usleep(10);
        }

附上完整代码(loop是示例需要长时间处理的业务逻辑,memoryusage超限也会退出,外部可以由supervisor/daemontools等进程监控程序控制)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#!/usr/bin/env php
<?php
/**
 * Description
 *
 * @project mydevkit
 * @package mydevkit
 * @author nickfan <nickfan81@gmail.com>
 * @link http://www.axiong.me
 * @version $Id$
 * @lastmodified: 2015-07-08 09:37
 *
 */
@set_time_limit(0);

class myDaemon
{
    const DEBUG = true;
    protected static $_MYDAEMON_SHOULD_STOP = false;
    public static $memLimit = 5242880;
    private $loop = 10;
    protected $logpath = '';

    public function __construct($option = [])
    {
        $option += [
            'loop' => 10,
            'memLimit' => 5242880,
            'logpath' => '/tmp/daemon_kill.log',
        ];
        $this->loop = $option['loop'];
        $this->logpath = $option['logpath'];
        self::$memLimit = $option['memLimit'];
        $this->regist_sig_handler();
    }

    public function __destruct()
    {
        $this->log('worker progress ending...');
    }

    protected function regist_sig_handler()
    {
        declare(ticks = 1);
        pcntl_signal(SIGTERM, [$this, 'sig_handler']);
        pcntl_signal(SIGHUP, [$this, 'sig_handler']);
        pcntl_signal(SIGINT, [$this, 'sig_handler']);
    }

    protected function sig_handler($signo)
    {
        switch ($signo) {
            case SIGTERM:
            case SIGHUP:
            case SIGINT:
                self::$_MYDAEMON_SHOULD_STOP = true;
                $signoStr = '';
                if ($signo == SIGTERM) {
                    $signoStr = 'SIGTERM';
                } elseif ($signo == SIGHUP) {
                    $signoStr = 'SIGHUP';
                } elseif ($signo == SIGINT) {
                    $signoStr = 'SIGINT';
                }
                $content = date('Y-m-d H:i:s') . ' ' . $signoStr . PHP_EOL;
                file_put_contents($this->logpath, $content, FILE_APPEND);
                break;
        }
    }

    protected function shouldStop()
    {
        if (self::$_MYDAEMON_SHOULD_STOP) {
            // before exit staff code
            // ...
            return true;
        }
        return false;
    }

    protected function log()
    {
        $args = func_get_args();
        $retstr = date('[Y-m-d H:i:s] ') . implode(' ', $args) . PHP_EOL;
        if (self::DEBUG == true) {
            echo $retstr;
        } else {
            return $retstr;
        }
    }

    public function execute($data = [])
    {
        $this->log('[EXECUTE] process lot of data.');
        for ($i = 0; $i < $this->loop; $i++) {
            $this->log('step:' . ($i + 1));
            sleep(1);
        }
        return rand(1, $this->loop);
    }

    public function run()
    {
        $this->log('[RUN] worker starting to run ...');
        while (true) {
            if ($this->shouldStop()) {
                break;
            }
            $this->log('processJobData Start ...');
            try {
                $jobStatusInfo = $this->execute([]);
                $this->log('processJobData end with: ' . var_export($jobStatusInfo, true));
                unset($jobStatusInfo);
            } catch (Exception $ex) {
                $this->log($ex->getMessage());
            }
            $memory = memory_get_usage();
            $this->log('memory usage:' . sprintf('%.2fMB', round($memory / 1048576, 2)));
            if ($memory > self::$memLimit) {
                $this->log('exiting run due to memory limit');
                exit;
            }
            //sleep(1);
            usleep(10);
        }
        $this->log('[RUN] worker run quit.');
    }
}

$gotMyParam = 10;
if (isset($argv[1])) {
    $gotMyParam = intval(trim(strip_tags($argv[1])));
}
$worker = new myDaemon(['loop' => $gotMyParam, 'memLimit' => 1048576 * 128,]);
$worker->run();

测试:

在class代码中的构造函数中注释掉

1
//$this->regist_sig_handler();

运行daemon代码,在执行execute的for循环时ctrl+c或者用 kill 杀死当前进程

Snip20150708_1

进程直接结束,没有执行完execute的一个完整流程就直接退出了,

把注释去掉重新执行daemon代码:

Snip20150708_2

在执行过程中按ctrl+c或者kill命令杀死前进程

观察daemon运行的结果是直到整个execute循环执行完了以后才退出程序的