KeiferGu的博客

探索、学习、思考


  • 首页

  • 分类

  • 归档

  • 标签

  • 关于
KeiferGu的博客

20分钟体验使用Docker自动构建、部署Web应用

发表于 2017-01-13 | 分类于 Docker | | 阅读次数

前言

本文旨在带领读者们花费20分钟左右的时间,体验使用 Docker 以及 PaaS 服务自动化构建、发布 Web 应用,让大家了解到 Docker 这个工具的好处和便利。

首先我会假定读者拥有一个 Github帐号 ,能进行基本的 git 操作。没有也没关系,两分钟就可以注册一个,而且我们这次只会使用最简单的几条 git 命令。

我使用的是 vue-cli 创建的初始项目来作为样例,所以如果你的项目也是使用 vue-cli 创建的,那么只要将我提供的代码仓库中的 Dockerfile、Dockerfile.sec、daocloud.yml 三个文件拷贝到你的仓库中,然后经过下面的几步配置就可以使用了。

读完本文后你会了解到使用 Docker 进行前端应用的持续集成、自动化构建以及部署的工作流程。

准备工作

  • 本次我们使用 Daocloud 作为Docker服务提供商,建议使用 Github 帐号注册,因为我们后面要使用 Github 作为代码源
  • Fork 我为本文准备的样例仓库 docker-vue-sample 到你自己的 Github 仓库中

创建项目

首先我们需要使用代码构建出一个 Docker 镜像,作为最终的交付件。

  • 登录并进入 DaoCloud控制面板,选择 项目 - 创建新项目
  • 项目名称随意,我这里填的是 “docker”
  • 选择代码源,这里请选择 docker-vue-sample (请确定你已经完成了准备工作,并且绑定了 Github 帐号,并将我提供的代码仓库 Fork 到了你自己的帐号中。如果仓库没有显示,点击“设置代码源”右边的刷新按钮)
    creat
  • 其余选项均为无关项,默认值即可
  • 创建成功后可以看到构建流程已经定义好了
    details
  • 现在我们需要手动构建一次,在右上角选择 master 分支,然后手动构建
    build
  • 等待大约5分钟,构建完成,可以开始部署

创建应用并部署

  • 进入 DaoCloud应用管理 - 创建新应用,选择刚刚构建成功的镜像
    createapp
  • 所有选项默认值即可,然后 立即部署
    appconfig
  • 此时镜像已经开始了部署,等待大约1分钟,镜像部署成功
    app
  • 点击访问地址即可访问应用
    vue

自动化流程

上面所提到的构建、部署流程均可以自动完成。当我们向 Github 仓库提交代码后,根据事先定义的规则触发自动构建、部署

  • 进入 控制台 - 项目,选择我们刚刚创建的项目,设置构建触发规则,由于我们是为了体验流程,所以添加规则为提交代码到任意分支即触发构建流程
  • 点击触发规则 - 添加规则,默认设置(提交代码到分支)即可,点击确定
    rule
  • 进入 流水线,设置部署规则
    pipline
  • 添加应用,选择我们刚刚创建的应用,打开 自动发布,选择触发规则为 提交代码到分支
    piprule
  • 现在我们的自动构建、发布流程就定义好了
    pip

修改并提交代码

在我们的工作流程中,当我们新版本的代码提交到了代码仓库中,我们就需要其触发自动构建流程并且发布到线上环境。这也是我们定义触发流程规则的意思所在,正常情况下,当我们的应用完成了新的版本,打上tag,意味着这是我们需要发布的版本,然后提交到仓库,此时自动构建并部署。

  • 克隆你自己帐号中的样例仓库
    git clone git@github.com:<你的github账户名>/docker-vue-sample.git
  • 修改其中的一部分代码,我在下面添加了 “Docker” 字样
    code
  • 提交代码

    1
    2
    3
    git add ./
    git commit -m "change"
    git push
  • 此时回到控制台,可以发现已经触发了自动构建的流程
    autobuild

  • 这次构建会使用以前的缓存,所以会快的多,大概1分钟后,构建完成,自动部署到应用
  • 打开应用的访问地址,可以看到最新版本已经发布上去了
    changeapp

结语

至此,我们已经配置好了一个使用 Docker 自动构建并部署应用的工作流程。当我们向代码仓库 push 代码时,DaoCloud 会自动的拉取代码并构建,然后发布应用。在实际的工作流程中,我们还需要添加测试相关的配置,应用会先部署到开发环境,当测试通过后再部署到生产环境。这样的工作流程无疑大大的降低了运维的压力,减少了重复的工作,让开发人员能够更专注与代码当中。

感觉有点兴趣了?查看更加详细的 Docker 文档:

  • Docker - 从入门到实践: https://yeasy.gitbooks.io/docker_practice/content/introduction/what.html
  • Docker 官方文档(英文): https://docs.docker.com/
  • DaoCloud 文档:http://guide.daocloud.io/
KeiferGu的博客

ES6箭头函数(arrow_function)的this值探究

发表于 2016-12-06 | 分类于 前端 | | 阅读次数

前言

对于ES6新增的箭头函数,相关的介绍可以查看MDN - Arrow_functions以及ECMAScript 6 入门。

引入

首先我们先看下面一段代码:

1
2
3
4
5
6
7
8
9
'use strict'
var obj = {
i: 1,
arrow: () => console.log(this.i),
func: function(){ console.log(this.i); },
};
obj.arrow() //undefined
obj.func() // 1

这里为什么使用箭头函数定义的方法,不能访问到对象内的属性呢?

Function定义的方法

这里我们先说为什么使用function定义的方法能够按照我们的预计正确读取到i的值。

首先,我们要明白,使用function定义的方法MDN - Functions:

在函数执行时,this 关键字并不会指向正在运行的函数本身,而是指向调用该函数的对象。

那么,也就是说,我们使用function定义的函数,在定义时,它并不知道这个this会指向哪里,只有在运行的时候才能明确this的值。

在这里,我们是使用obj.func,调用函数func的对象是obj,所以函数func的this被指向调用它的对象obj。所以此时func内的this是能够通过this.i访问到obj.i的。

那么如果我们像下面这样调用:

1
2
3
4
5
6
7
8
9
10
'use strict'
var obj = {
i: 1,
arrow: () => console.log(this.i),
func: function(){ console.log(this.i); },
};
var f = obj.func;
f(); // undefined 此时 this 指向 window

此时调用函数func的是window对象,所以函数func的this被指向了window,而window不具有i属性,显示undefined。

箭头函数定义的方法

接下来再来说说为什么使用箭头函数定义的方法不能访问到obj里的值。

首先,我们还是要明白MDN - Arrow_functions:

箭头函数拥有词法作用域的this值(即不会新产生自己作用域下的this, arguments, super 和 new.target 等对象)

箭头函数本身不具有this,它会直接绑定到它的词法作用域内的this,也就是定义它时的作用域内的this值。所以试图使用apply,call等方法修改箭头函数的this是不会成功的,因为箭头函数自身没有this。所以我们来看下面一段代码:

1
2
3
4
5
6
var func = () => {
console.log(this);
}
func(); // Window
func.apply({}); // Window

可以看出,箭头函数是直接使用的作用域内的this,apply等方法是无效的。为了加深理解,我们再来看下面一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function func() {
console.log(this)
return () => {console.log(this)}
};
func()()
// Window
// Window
func.apply({a:1})()
// Object {a:1}
// Object {a:1}
func.apply({a:2})()
// Object {a:2}
// Object {a:2}

通过这段代码,我们应该可以明确的看出来,箭头函数是直接使用的它词法作用域内的this,也就是定义它时的作用域内的this。当我们修改它的作用域内的this值,也就是func的this值时,在箭头函数内也可以反映出来。用作对比,我们看下使用function定义的函数:

1
2
3
4
5
6
7
8
9
10
11
12
function func() {
console.log(this)
return function(){
console.log(this)
}
};
func()()
func.apply({a:1})()
func.apply({a:2})()

想一下,这段函数的输出应该是什么,回顾下我们前面提到的:

在函数执行时,this 关键字并不会指向正在运行的函数本身,而是指向调用该函数的对象。

那么在这里,不管函数func内的this究竟是指向哪里,但是调用func返回的匿名函数的对象是window。这里需要明白,函数也是值,func只是返回了一个值,最终调用这个值的对象实际是window。所以,这里的控制台的打印结果应该是:

1
2
3
4
5
6
7
8
Window
Window
Object {a: 1}
Window
Object {a: 2}
Window

即匿名函数的this值是指向的window。跟上面的箭头函数的结果做对比,是不是对箭头函数拥有词法作用域的this值,理解更深了呢?

小结

此时再来看看我们的引入里面的问题,为什么Object内使用箭头函数定义的方法,在使用Object调用时,箭头函数内的this无法访问到Object对象?原因还是我们那句话:使用词法作用域内的this。Object对象是不会新建自己的this值的,所以箭头函数会一直使用词法作用域内的this,也就是window。

后记

箭头函数的出现使的在 JavaScript 中的this值更加符合我们的预期,避免了一些意料之外的问题出现。但是某些时候,例如说在Object里定义的函数,使用方法调用时不会使用调用它的对象作为this值。所以,我们应该理解箭头函数和普通定义的函数的区别,正确的使用他们。

KeiferGu的博客

Pip 使用国内镜像

发表于 2016-11-24 | | 阅读次数

官方文档地址:User Guide — pip 9.0.1 documentation

新建或编辑配置配置文件

  • Unix和macOS
    $HOME/.pip/pip.conf
  • Windows
    %HOME%\pip\pip.ini

    添加配置信息

    1
    2
    3
    [global]
    trutsed-host=pypi.doubanio.com
    index-url=http://pypi.doubanio.com/simple

这里我使用的是豆瓣的镜像地址

KeiferGu的博客

Tensorflow 学习笔记

发表于 2016-11-22 | 分类于 机器学习 | | 阅读次数

安装

注意: 中文文档中所安装的版本已经过期,请使用官方安装指南安装!
文档地址(仅做内容参考,其中的下载地址均已过期或老旧):Tensorflow中文文档
首先安装pip:

1
sudo apt-get install python-pip python-dev

由于国内下载pip上的软件包缓慢,所以使用国内源手动安装一部分:

1
sudo pip install --upgrade numpy -i http://pypi.doubanio.com/simple --trusted-host pypi.doubanio.com

然后使用以下命令安装tensorflow:

1
sudo pip install --upgrade https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-0.11.0-cp27-none-linux_x86_64.whl

安装完毕后测试是否可以正常使用,打开一个python终端:

1
2
3
4
5
6
7
8
9
10
11
12
$ python
>>> import tensorflow as tf
>>> hello = tf.constant('Hello, TensorFlow!')
>>> sess = tf.Session()
>>> print sess.run(hello)
Hello, TensorFlow!
>>> a = tf.constant(10)
>>> b = tf.constant(32)
>>> print sess.run(a+b)
42
>>>

至此,软件安装完毕,有任何问题可以查看官方安装指南,看是否有解决办法。

MNIST训练

下载数据

Tensorflow的代码仓库已经从 GoogleSource 迁移到了 Github,所以指南上提供的链接已经下载不了了。

我建议直接下载整个仓库,因为后面教程需要的所有代码也在里面,后面就不用再下了。

克隆 TensorFlow 仓库:
git clone git@github.com:tensorflow/tensorflow.git

下载完毕后进入tensorflow/examples/tutorials/mnist/目录,这里存放着MNIST的所有代码。

在当前目录创建mymnist.py文件,内容为:

1
2
import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

然后运行该文件,即会自动下载并解压数据。

模型训练

准备工作

阅读这段文字时,我会假设你已经完整的看了一遍
MNIST入门
,并将文章中的代码运行了一遍。以下是完整的’mymnist.py’代码:

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
import input_data
import tensorflow as tf
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
x = tf.placeholder("float", [None, 784])
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))
y = tf.nn.softmax(tf.matmul(x, W) + b)
y_ = tf.placeholder("float", [None, 10])
cross_entropy = -tf.reduce_sum(y_ * tf.log(y))
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)
init = tf.initialize_all_variables()
sess = tf.Session()
sess.run(init)
for i in range(1000):
batch_xs, batch_ys = mnist.train.next_batch(100)
sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})
correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
print sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels})

运行后会得到一个0.91左右的输出。

数据集

训练集里面的数据包括图片和标签,例如手写的图片1,和对应的标签1。训练集的图片是一个28x28的图片,这里我们将其转换为一维的向量数据。我默认认为数据是按照列的方式展开,即从上到下,从左到右展开。

训练集的标签是一个10维向量,用来表示数字[0-9],例如数字 2 ,就是([0,0,1,0,0,0,0,0,0,0])。

Softmax回归

为了得到一张给定图片属于某个特定数字类的证据(evidence),我们对图片像素值进行加权求和。如果这个像素具有很强的证据说明这张图片不属于该类,那么相应的权值为负数,相反如果这个像素拥有有利的证据支持这张图片属于这个类,那么权值是正数。

例如说1,中间有连续向下的像素则说明很可能不是0,不是9等。在实际的数据中则是:
图像-矩阵
在图像二维矩阵中的第[8-10]行或者是其它任意行有连续的1,在我们展开后的训练集中则是有大量的连续的1。这个就是一个很强的证据说明这个图片是数字1。

问题

  1. input_data.py 无法使用
    当尝试运行input_data.py时提示错误信息:
    1
    from tensorflow.contrib.learn.python.learn.datasets.mnist import read_data_sets ImportError: No module named contrib.learn.python.learn.datasets.mnist

原因是因为你安装的 Tensorflow 版本过于老旧,请下载最新版本。

使用 pip show tensorflow 可以查看当前版本。具体安装方法请查看官方安装指南或着本文章的安装部分,以官方为准。请先卸载旧版本(sudo pip uninstall tensorflow)再安装新版本。

KeiferGu的博客

关于 Github Page 的 Vendors 文件夹无法访问的问题

发表于 2016-11-07 | | 阅读次数

主要原因是 Github Page 升级到了Jekyll 3.3,可以看官方更新文章,而新版本的 Jekyll 会忽略/vendor和/node_modules文件夹,所以导致了我们的博客访问不正常。

官方回复

解决办法是在网站根目录下面新建一个空白.nojekyll文件。

KeiferGu的博客

微型游戏引擎开发阶段总结

发表于 2016-11-07 | | 阅读次数

前言

写这个小型游戏引擎的目的,是为了深入学习 JavaScript ,现在功能已经有了一定的雏形,所以作一个阶段性的总结。

架构

系统采用分层架构,主要分为 底层工具层 、 基础类层 、 功能层。

底层工具层

  1. 碰撞检测模块
    第一次重构,这时的碰撞检测模块是使用Object.assign()追加到Shape.prototype里面,然后分开为两个文件。包括求多边形的所有边的向量等函数,均是使用该种方法追加到各自的图形类中。这样,虽然在文件上是分开的,但是在逻辑上,两个模块是杂揉在一起的。

    第二次重构时,我的想法是使用#函数式编程#,将各个具体功能(例如求多边形的所有边的法向量)写成单独的函数,而实例对象作为参数传进去,返回计算结果,然后在主函数里面顺序调用。这样的写法能够满足对外只有#单一接口#。但是在后面我对图形类进行重构时,这里遇到的很大的问题。

    第三次重构,因为对图形类进行重构时,将数据接口进行了更改,导致了碰撞检测模块中的大量方法需要更改,这时我的想法是将碰撞检测模块重构为一个可复用的模块,对外提供单一接口并确定参数格式内容。这时,我将除Vector模块之外的依赖删除,在函数内部创建图形类,当接收到外部参数时,实例化图形类,然后进行计算。同时,为了便于以后对接口进行重构,添加一个数据转换函数,将外部的参数格式转换为内部使用的格式,同时也起着数据格式校验的功能,在后面的处理大量switch/case时,也起着重要的作用。

    第四次重构,此时数据格式,外部接口已经固定,但是有一个问题,碰撞检测需要对不同的图形进行不同的检测算法,那么需要使用switch/case或者if/else对图形的类型进行判断。但是当图形类别较多时,这里的代码会非常繁杂,及其不便于修改和扩展。这时,我想到了使用#查找表#来彻底解决这个问题。具体的做法是根据传入的图形类名构造函数名,然后动态的调用函数。可以看这个代码样例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    var collisionObject = {
    polygon_circle: function(polygon, circle){
    // 碰撞检测的主程序代码
    },
    }
    collison(s1, s2){
    /**
    s1{
    type: "polygon",
    data: [Object Array]
    }
    s2{
    type: "circle",
    data: [Object Array]
    }
    **/
    // 动态的构造参数,来确定调用的函数
    return collisionObject[s1.type + '_' + s2.type](s1, s2);
    }
  2. 图形绘制模块
    初始版本,此时的图形绘制函数是直接写在图形类里面的,直接调用canvas的接口进行绘制

    第一次重构, 直接将图形绘制函数拆分为单一模块,对外提供统一接口。这样不管外部图形类如何改变,只需要将其转换为标准接口即可继续正常使用图形绘制模块。第二个好处是,将数据和表现分开,便于移植,也就是说如果我不在canvas上绘图,那么我可以直接修改图形绘制函数,使用目标平台的绘制API来实现以前的功能,而且对上层的代码没有任何影响。

基础类层

  1. 图形类
    图形类也经历过多次的重构,但大多数是更改数据接口。现在基本的图形基类抽象为四种,均继承与基类Shape:

    • 点(Point)
    • 线段(Line)
    • 多边形(Polygon)
    • 圆(Circle)

    • 矩形(Rect),继承于Polygon

后记

架构以及接口

编写这个项目的过程中,有一大部分的时间都是在设计程序架构,设计接口。不断的推翻以前的设计,不断的重构。这个时候,真正的体会到,程序设计是一件需要创造力和想象力的事情,并不是简单的堆叠代码。

整个项目的开发流程大概类似“原型模式”,首先使用 JavaScript 完成了一个能够运行的原型,然后拆分功能,将图形、算法、视图分开,然后尝试扩展,抽象出高级的类。再然后尝试将某些功能独立出来,使其成为一个可复用的模块。虽然最后的展现形式没有改变,但是对于日后的维护和扩展,却有着不同的难度。

接口的设计,我认为这是最难的一部分。

设计模式

我认为,设计模式的使用就是为了便于对功能进行增删改。那么,当你在进行开发时,一直在想着如果这个需求后面改了怎么办,如果函数参数要改怎么办,如果接口要改怎么办。当你在这么想时,并且尽可能让你的代码能够从容应对这些改动需求时,你就在应用设计模式了。

接口模式的使用

KeiferGu的博客

在Javascript中获取class的类型名的方法

发表于 2016-10-20 | 分类于 前端 | | 阅读次数

在Javascript中获取class的类型名主要有以下几种方法:

  1. Function.name
    在JS中class的类型名是由constructor的函数名决定的,那么我们可以直接调用constructor.name获取到类型名,这个方法不管是对通过new新建的实例还是class对象都能适用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Polygon {
    constructor(){
    this.x = 1;
    }
    say() {
    console.log(this.constructor.name); //Polygon
    }
    }
    let p = new Polygon();
    console.log(p.constructor.name); //Polygon
  2. class.name
    由于class本质只是函数的一层包装,也就是跟我们以前使用构造函数写类本质是一样的,所以我们可以直接使用class.name获取到类型名,但是对使用new新建的实例没用:

    1
    2
    3
    4
    5
    class Polygon {
    constructor(){
    }
    }
    console.log(Polygon.name); //Polygon
  3. toString与正则表达式
    因为现在Function.name还不是正式的标准,MDN不推荐在生产环境使用,详见Function.name,所以在较低的版本最好的办法是使用Function.toString(),然后使用正则表达式匹配在function后面的字符。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function Polygon() {
    this.x = 1;
    }
    class Circle {
    constructor() {
    }
    }
    var nameFromToStringRegex = /^function\s?([^\s(]*)/;
    var funName = Polygon.toString().match(nameFromToStringRegex)[1];
    console.log(funName); //Polygon
    var className = Circle.constructor.toString().match(nameFromToStringRegex)[1];
    console.log(className);
  4. 通用的方法
    适用与function与class的类名,函数名的获取。
    摘自stackoverflow:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    /**
    * Gets the classname of an object or function if it can. Otherwise returns the provided default.
    *
    * Getting the name of a function is not a standard feature, so while this will work in many
    * cases, it should not be relied upon except for informational messages (e.g. logging and Error
    * messages).
    *
    * @private
    */
    function className(object, defaultName) {
    var nameFromToStringRegex = /^function\s?([^\s(]*)/;
    var result = "";
    if (typeof object === 'function') {
    result = object.name || object.toString().match(nameFromToStringRegex)[1];
    } else if (typeof object.constructor === 'function') {
    result = className(object.constructor, defaultName);
    }
    return result || defaultName;
    }
Keifer Gu

Keifer Gu

7 日志
3 分类
11 标签
Github
© 2016 - 2017 Keifer Gu
由 Hexo 强力驱动
主题 - NexT.Pisces