js 的世界很大,并不是只有对象
这段时间在复习前端基础知识,遇到了下面这道面试题:1
2
3
4
5
6
7var F = function(){}
var f = new F()
Object.prototype.a = function(){}
Function.prototype.b = function(){}
console.log(f.a)
console.log(f.b)
请问这段代码会输出什么?
打开浏览器控制台,将上面这段代码复制进去,回车,我们可以得到它的答案:1
2ƒ (){}
undefined
细看我们可以发现这道题并不难,对于f.a
,JavaScript 解释器会按照原型链的规则去查找 f 的 a 属性:
f.__proto__
是否有 a 属性,而f.__proto__ === F.prototype
,显然也没有;f.__proto__.__proto__
,即F.prototype.__proto__
,而F.prototype.__proto__ === Object.prototype
,可以发现Object.prototype
中有 a 属性。对于f.b
,解释器会进行和上面一样的操作,但并不会在原型链中找到 b 属性, 因为f.__proto__.__proto__.__proto__ === Object.prototype.__proto__ === null
,结束查找
那么代码中的Function.prototype.b = function(){}
能有什么作用呢?不难发现:1
2
3
4console.log(F.b) // ƒ (){}
// 甚至还有
console.log(Object.b) // ƒ (){}
console.log(Object.b === F.b) // true
看起来 F 和 Object 都是 Function 的实例?的确是这样的,所有的函数都是由 Function 的原型构造出来的,这么说 Function 是 JavaScript 的一个很基础的工具咯?但 JavaScript 不是万物皆对象吗,那 Object 和 Function 到底是什么关系呢?
我们可以做以下测试:1
2
3
4
5F instanceof Object // true
F instanceof Function // true
Object instanceof Function // true
Function instanceof Object // true
Object == Function // false
完全懵了…
为什么会出现这么怪异的结果,Object 和 Function 谁更底层一些?它们为什么会”互为实例”?
通过对 JavaScript 各个概念出现先后顺序的分析,我得到了如下的一张图:
毫无疑问 JavaScript 是我学过的最糟糕的语言…
首先解释一下这张图:
结合该图和 instanceof 的判断规则,我们可以解释上面的结果:
先附上 instanceof 判断规则的伪代码:1
2
3
4
5
6
7
8
9
10
11
12function instance_of(L, R) { // L 表示左表达式,R 表示右表达式
if (isBasicType(L)) return false; // 如果是基础类型直接返回 false
var O = R.prototype; // 取 R 的显示原型
L = L.__proto__; // 取 L 的隐式原型
while (true) {
if (L === null)
return false;
if (O === L)
return true;
L = L.__proto__;
}
}
结果解释:1
2
3
4F instanceof Object // true, F.__proto__.__proto__ === '函数原型'.__proto__ === '对象原型' === Object.prototype
F instanceof Function // true, F.__proto__ === '函数原型' === Function.prototype
Object instanceof Function // true, Object.__proto__ === '函数原型' === Function.prototype
Function instanceof Object // true, Function.__proto__.__proto__ === '函数原型'.__proto__ === '对象原型' === Object.prototype
我们还可以从上图发现其它一些有趣的信息:1
2Function.prototype === Function.__proto__ // true
Object.prototype === Object.__proto__.__proto__ // true
好了到此为止,我不该入门前端的…
2019.05.08 更新
引用自 高能!typeof Function.prototype 引发的先有 Function 还是先有 Object 的探讨,看来函数原型并不是由对象原型构造的,下面是浏览器或 Node 在初始化 JavaScript 环境的详细过程:
]]>
- 用 C/C++ 构造内部数据结构创建一个 OP 即(Object.prototype)以及初始化其内部属性但不包括行为。
- 用 C/C++ 构造内部数据结构创建一个 FP 即(Function.prototype)以及初始化其内部属性但不包括行为。
- 将 FP 的[[Prototype]]指向 OP。
- 用 C/C++ 构造内部数据结构创建各种内置引用类型。
- 将各内置引用类型的[[Prototype]]指向 FP。
- 将 Function 的 prototype 指向 FP。
- 将 Object 的 prototype 指向 OP。
- 用 Function 实例化出 OP,FP,以及 Object 的行为并挂载。
- 用 Object 实例化出除 Object 以及 Function 的其他内置引用类型的 prototype 属性对象。
- 用 Function 实例化出除Object 以及 Function 的其他内置引用类型的 prototype 属性对象的行为并挂载。
- 实例化内置对象 Math 以及 Grobal。至此,所有内置类型构建完成。
OAuth (开放授权 Open Authorization) 是一个开放标准,允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方应用或分享他们数据的所有内容。
但 GitHub OAuth 对桌面端的认证并没有直接的支持,本文提供了一种利用浏览器的缓存功能实现的针对桌面端 (移动端也适用) 的 GitHub OAuth 第三方登录方式,并提供了一个实例 (通过 Express.js 和 socket-io.js 搭建)。
GitHub 提供了针对网站的第三方登录策略,主要步骤如下:
https://github.com/login/oauth/authorize?client_id={Client_ID}&scope=user:email
(将 Client_ID 替换为上一步得到的 Client ID,scope 参数是你希望得到的用户信息的内容,具体请查看 understanding-scopes-for-oauth-apps)https://github.com/login/oauth/access_token?client_id=${Client_ID}&client_secret=${Client_Secret}&code=${code}
便可得到一个 access_token因为获取 access_token 需要 code 参数,而 code 参数是重定向链接的内容之一,只能在浏览器中获得。
在网页应用中,后台通过 code 参数后可以得到 access_token,然后将 access_token 放到渲染好的网页中,发送到浏览器,浏览器将 access_token 保存到本地缓存,之后每次都访问网页资源都把本地缓存的 access_token 发送到后台,后台便能获得需要的用户信息,通过网页发送到浏览器。
然而对于桌面端软件,软件可以自行打开浏览器访问认证链接,让用户确认是否授权,但得想个方法将后台获得的 access_token 发送到桌面端软件。其中最重要的是让后台知道不同的浏览器对应的桌面端软件是哪一个,解决这个问题之后,才能把用户在不同浏览器中确认授权后得到的 access_token 发送到对应的桌面端,桌面端把它存到软件的配置文件中。第二个问题是如何把 access_token 发送到桌面端。
针对这两个问题,我分析了一下 GitKraken 的登录策略,找到了他们利用 GitHub OAuth 登录的方法,这也是本篇文章介绍的重点内容。
对于后台如何主动向客户端发送 access_token 的问题,解决的办法比较简单,让后台和桌面端软件建立 Socket 连接实现双向通信,每个 Socket 连接都有一个唯一标识,即 Socket id,当后台得到 access_token 后,利用 Socket id 将 access_token 发送到指定的桌面端即可。
当然也可以采取轮询或 long pull 实现双向通信,但因为我们这里的后台相当于一个桥梁,用于连接客户端和 GitHub 的后台,所以对于每个客户端,都必须有一个唯一标识让后台知道每个 access_token 应该发给谁,Socket 连接本身提供了这个标识,无需我们额外产生,而这两种方法都需要自己产生一个唯一标识,相应的,后台也需要做一些多余的操作来储存这些标识,本文不采用这两种办法。
解决了第二个问题,第一个问题中区分每个客户端的问题也就解决了,接下来要做的是实现把浏览器和客户端一一对应起来,现在浏览器的缓存技术(缓存客户端和服务器连接的 Socket id)终于可以派上用场了,步骤如下:
https://github.com/login/oauth/authorize?client_id={Client_ID}&scope=user:email
)https://github.com/login/oauth/access_token?client_id=${Client_ID}&client_secret=${Client_Secret}&code=${code}
)我用 Express.js 和 Socket-io.js 搭建了一个后台服务示例,GitHub 仓库地址:whu-library-seat-ghauth。
桌面端仓库:whu-library-seat,移动端仓库:whu-library-seat-mobile。
这里不再赘述代码上的细节,我将之前写的使用方法和截图记录在这里,供大家对比和参考。
点击软件下方的钥匙进入软件授权页面(第一次打开软件会默认进入本页面)
点击GitHub Star 永久授权
按钮,软件会打开系统浏览器访问认证页面
点击确定通过 GitHub 账号登录
,此时 GitHub 会让你确认是否授权(如果没有登录 GitHub,此时会进入登录页面)
点击Authorize CS-Tao
即可成功登录
登录成功后返回软件,如果出现下面的弹窗,说明您还未对本仓库点星,请进行下一步
如果您还未给本仓库点星,请到指定仓库点星以供管理员了解软件使用情况。桌面端进入:whu-library-seat,移动端进入:whu-library-seat-mobile
桌面端点击右上角的Star
按钮,按钮如下图所示:
移动端需要登录才会显示Star
按钮,登录状态下直接点击即可,按钮如下图所示:
点星后回到本软件,点击确定即可
对于 Vue 项目,官方建议的网络请求工具是 Axios,但在 Vue 项目和 cordova 集成的时候,我发现想要更改网络请求的 header 并不是一件很简单的事(比如修改 User-Agent),cordova 对 Axios 发出的请求似乎还添加了一层封装。为了修改 User-Agent,不得已最后还是使用了 cordova 自己的 http 插件:cordova-plugin-advanced-http.
不难发现 Axios 是使用的 Promise 实现异步网络请求,而 cordova-plugin-advanced-http 使用的却是函数回调。所以实现这个转换最关键问题其实是如何将函数回调替换为 Promise.
当然,能实现这个转换的前提是您在项目中是通过
axios.create()
创建了全局能访问到的实例,并且所有的网络请求都是调用这个实例实现的。也就是说本文记录的是如何将原来使用axios.create()
创建的实例转换为 cordova-plugin-advanced-http 的网络请求函数。
具体步骤如下:
1 | cordova plugin add cordova-plugin-advanced-http |
在 main.js 中添加下面的代码:1
Vue.cordova = Vue.prototype.$cordova = window.cordova
原文件内容:
1 | import axios from 'axios' |
修改之后的文件内容:
1 | import Vue from 'vue' |
这样的修改可以使 request 的调用方法不变,即:
1 | // import request from 'file/path' |
修改之后的文件只能在 cordava 的 deviceready
事件触发之后调用(否则 Vue.$cordova 为 undefined
),所以最好像按下面这样加载 Vue 实例(如果提前调用 import App from './App'
,上面的代码会被提前加载)
1 | // 加载 Vue 实例 |
前些时间心血来潮,开发了一款针对武汉大学图书馆预约系统的桌面端抢座软件(使用 Electron + Vue 搭建),发布之后,有很多同学向我反映说想要 Android 版本的。
虽然利用 Cordova/PhoneGap 将网页打包为 Android 安装包并不是一件很复杂的事,但在笔记本上安装 Android 的开发环境实在让人崩溃。
但出于对用户需求的尊重,我还是开始了将网页打包为 Apk 的挖坑之旅,项目链接:https://github.com/CS-Tao/whu-library-seat-mobile。
本文用于记录在本项目中利用 Travis CI 持续集成和部署的配置代码。
本方法的构建和部署耗时 580s 左右,构建日志:https://travis-ci.com/CS-Tao/whu-library-seat-mobile/builds/88561677
1 | sudo: required |
源文件链接:.travis.yml
本方法的构建和部署耗时 450s 左右,构建日志:https://travis-ci.com/CS-Tao/whu-library-seat-mobile/builds/89426551
1 | sudo: required |
源文件链接:.travis.yml
请在 Travis CI 的网站中设置
$GH_TOKEN
环境变量并设置为不可见(默认便是不可见),该环境变量是 GitHub 的Personal access token
。
代码中的
yarn build
命令用于将生产版本的网页放入 cordova 项目的www
文件夹中。代码中的${STORE_PASSWORD}
和${PASSWORD}
环境变量为下面这条命令需要输入的两个密码,也应该添加到 Travis CI 的网站中,并设置为不可见。
1 | # 在 cordova 项目的根目录中生成 release-key.keystore 文件 |
可视化的变量包括机试成绩
、面试成绩
、背景评分
、综合分数
优秀营员共 120 名(161 位同学参营)。机试成绩占 40%,面试成绩占 30%,背景评分占 30 %
从散点图矩阵中我们可以发现一些有趣的信息:
所以准备参加遥感院夏令营的同学们,认真准备机试吧
祝所有看到这篇文章的同学都能拿到优秀营员
]]>Git workflow 分为五个分支,包括 master
、develop
、release
、hotfix
和 feature
,如下图:
在代码的中央仓库一直存在两个主要分支:master
和 develop
。
其他仓库(非中央仓库)的这两个分支应当始终和中央仓库保持一致,在每次向中央仓库的对应分支合并时,应当先确认中央仓库的对应分支(下面简称中央分支)没有新的提交,如果有新的提交,应当先把本分支的基
设置为中央分支的最新提交,即使用 rebase
将中央分支与本分支合并,再将本分支合并(merge)到中央分支。
master
用于管理发布版本,每次 commit (其他分支向它合并形成的 merge commit)应当对应一个 Tag,也就是形成一个发布版。develop
用于管理开发版本,所有的开发都会汇总到这个分支。短期分支可以同时存在多个(当然命名不能重复),每个分支使用完应当被删除掉,包括release
、hotfix
和 feature
。
release
用于在正式发布之前的预发布版本,在这个版本中的提交都应当是修复 Bug,不能在本分支上开发新的功能。本分支应当从 develop
检出,Bug 修复之后合并(merge)到 develop
和 master
。feature
用于新功能的开发,可以有多个。本分支应当从 develop
分支检出,功能开发完成后合并(merge)到 develop
。
在
release
和feature
两个分支的开发过程中,如果develop
分支有更新,可以选择不合并develop
,如果一定要合并。应当使用git rebase
进行合并,即将feature
的基
和develop
的最新提交保持一致。
hotfix
用于在版本发布之后的紧急 Bug 修复。本分支应当从 master
分支检出,在 Bug 修复之后直接合并(merge)到 master
和 develop
。
合并命令分为 merge 命令和 rebase 命令,在没有特别说明的情况下的合并命令一般指 merge 命令。
Merge 命令可以让两个分支合并,但可能产生合并提交(merge commit),在项目中一般都会使用 merge 命令进行分支的合并,但如果在某些情况下不想产生合并提交,则不应该使用这个命令。以将 feature-1
合并到 develop
为例:1
2
3
4
5
6
7
8# 切换到 develop 分支
git checkout develop
# 策略合并 feature 分支
git merge --no-ff feature-1
# 删除原分支
git branch -d feature-1
# 推送 develop 分支到远程仓库
git push origin develop
Rebase 命令和它的字面意思一样,会改变该分支的基
,它会将该分支的基
变为另一个分支的最新的提交,基
是一个分支在另一个分支中分叉后的的第一个提交。rebase 命令不会像 merge 命令那样产生合并提交,它会通过移动一个分支在另一个分支上分叉后的所有提交,形成一个完美的线性历史。例如,在 feature-1
的开发过程中需要将 develop
合并,但不希望合并提交的产生,便可以使用 rebase 命令:1
2
3
4# 如果不在 feature-1 分支,切换到 feature-1 分支
git checkout feature-1
# 合并(rebase) develop 分支
git rebase develop
如果 feature
分支的提交太乱(比如有很多 Fix bug),可以使用交互式 rebase 命令对 feature
分支的提交进行重构:1
2
3
4# 如果不在 feature-1 分支,切换到 feature-1 分支
git checkout feature-1
# 交互式合并(rebase) develop 分支
git rebase -i develop
使用 -i
参数可以启动交互式的 rebase,它会打开一个文本编辑器,显示所有被移动的提交:1
2
3pick 34b6aca 这是 feature 分支的第一次提交
pick 2bb57ac 修复第一次提交的 Bug
pick 233dc11 添加一个新功能
我们可以对这段代码进行编辑:1
2
3pick 34b6aca 这是 feature 分支的第一次提交
fixup 2bb57ac 修复第一次提交的 Bug
pick 233dc11 添加一个新功能
这样在最终形成的 feature
分支中便不会显示 2bb57ac
这次提交了(和之前的提交合并为一个新的提交)。当我们打开交互式 rebase 的时候,在注释里还可以看到其它功能的说明,利用这些功能我们可以随意地更改提交历史
拉取并合并远程分支时使用 rebase 命令可以避免可能产生的合并提交:1
2# 采用 rebase 命令拉取并合并远程分支
git pull origin develop --rebase
因为 git pull
命令是 git fetch
命令和 git merge
命令的语法糖,加上 --rebase
参数会使合并过程采用 rebase 命令合并。
合并提交(merge commit)可以将一个分支上的多个提交整合为一个,然后合并到另一个分支。如果两个分支没有出现分叉,这两个分支的合并是不会产生合并提交的。如果出现了分叉,它们的的合并(merge)一定会产生合并提交。
从上文我们可以看出,git workflow 中的五个分支是有一定服务关系的,其服务关系如下:
feature
-> develop
release
-> develop
& master
develop
-> master
hotfix
-> develop
& master
在团队协作时,会有一定的服务关系,一般是非中心仓库的分支为中心仓库的分支服务。
这里提到的不能反向合并即不能把被服务分支合并(merge)到服务分支(例如不能将 develop
合并到 feature
)。当然,如果在开发过程中一定要反向合并,应当使用 rebase 合并。
在Merge 命令中我们使用了 --no-ff
参数,这会让 git 的合并(merge)操作不采用 Fast-Forward
的合并方式,而是采用策略合并,这样的合并可以保留分支间的合并历史,如下图:
GitHub 提供的 Pull Request (简称“PR”)为我们提供了很好的代码合并的工具,开发者可以通过 PR 向自己的仓库或其他协助者的仓库发起合并请求。而且在这个合并请求中,我们可以对每次提交的具体内容和文件的更改情况进行 Review。例如我们可以在 GitHub 上执行 release
分支向 master
和 develop
分支的合并,并且在合并完成后添加发布版本到 GitHub 上。
1 | // 添加 Vue 静态方法, 在全局通过调用 Vue.triggerResize() 触发 |
原本是准备寒假在家学习 Docker 的,但很无奈的是,回到家的我完全不知道如何写下新年的第一段代码。
说来也巧,回到学校后,由于某些不可抗力,搁置已久的消防项目又得开始了,原来负责服务器端 Docker 部署的某大神学长外出实习,重新部署 Docker 服务的任务就落在了我的头上(无奈.jpg)。
我原本是不准备记录下这篇文章的,但是当我写完了 Dockerfile,向服务器部署的时候发现,这次部署可能需要 —— 三个小时?一个人在图书馆坐着,像看剧一样地看着命令终端不断输出完成进度,实在无聊,就想着把最近两天学到的知识记录一下吧。
Docker 是 DotCloud 公司开发的基于 LXC(Linux Container,一种内核虚拟化技术)的高级容器引擎,go 语言编写,使用 Apache 2.0 开源协议,源代码托管在 Github 上。
我们可以发现近几年云计算非常流行,那么云计算是什么东西呢?
云计算是指通过互联网提供动态易扩展且经常是虚拟化的资源,被划分为IaaS(Infrastructure as a Service)、PaaS(Platform as a Service)、SaaS(Software as a Service)。
但其中 PaaS 既不如 IaaS 那样灵活而自由,也不如 SaaS 那样可以直接推向消费者。有人说 PaaS 是未来的云计算,但是近几年 IaaS 和 SaaS 各自发展,反而是 PaaS 几乎裹足不前,虽然各种应用引擎层出不穷,但是没有什么人专门为 PaaS 开发应用。
这个时候 Docker 的出现完美地解决了这个问题,Docker 很好地实现了 PaaS,如果把云计算比作货轮的话,Docker 就是放在货轮上的集装箱了。
最开始我以为 Docker 和虚拟机差不多,因为都实现了虚拟化,而且都是可移植的。但是之后在各大技术交流平台上探索,我发现,这两个技术在本质上是有区别的,虚拟机直接面向了硬件层,而 Docker 则是面向操作系统层,它的出现和发展得益于 Linux 系统的 Namespace 机制和 CGroup 机制,这两个机制保证了 Docker 容器和外部环境的隔离,保障了 PaaS 的安全性。
Namespace(命名空间)机制是 Linux 为我们提供的用于分离进程树、网络接口、挂载点以及进程间通信等资源的方法。在日常使用 Linux 或者 macOS 时,我们并没有运行多个完全分离的服务器的需要,但是如果我们在服务器上启动了多个服务,这些服务其实会相互影响的,每一个服务都能看到其他服务的进程,也可以访问宿主机器上的任意文件,这是很多时候我们都不愿意看到的,我们更希望运行在同一台机器上的不同服务能做到完全隔离,就像运行在多台不同的机器上一样。Docker 便是通过 Linux 的 Namespaces 对不同的容器实现了这样的隔离。
我们通过 Linux 的命名空间为新创建的进程隔离了文件系统、网络并与宿主机器之间的进程相互隔离,但是命名空间并不能够为我们提供物理资源上的隔离,比如 CPU 或者内存,如果在同一台机器上运行了多个对彼此以及宿主机器一无所知的『容器』,这些容器却共同占用了宿主机器的物理资源。
如果其中的某一个容器正在执行 CPU 密集型的任务,那么就会影响其他容器中任务的性能与执行效率,导致多个容器相互影响并且抢占资源。如何对多个容器的资源使用进行限制就成了解决进程虚拟资源隔离之后的主要问题,而 Control Groups(简称 CGroups)就是能够隔离宿主机器上的物理资源,例如 CPU、内存、磁盘 I/O 和网络带宽。
在 CGroup 中,所有的任务就是一个系统的一个进程,而 CGroup 就是一组按照某种标准划分的进程,在 CGroup 这种机制中,所有的资源控制都是以 CGroup 作为单位实现的,每一个进程都可以随时加入一个 CGroup 也可以随时退出一个 CGroup。
关于不同操作系统 Docker 的安装方法,Docker 的官方文档写得很详细,因为 Docker 官方推荐使用 Ubuntu 部署 Docker(Ubuntu 系统本身就实现了Docker容器运行需要的 AUFS 机制),所以这里只记录一下 Ubuntu 16.04 系统下 Docker CE 的安装。
更新apt包索引
1 | $ sudo apt-get update |
安装软件包,用于允许 apt 通过 HTTPS 访问 Docker 安装源
1 | $ sudo apt-get install \ |
添加 Docker 的官方 GPG 密钥:
1 | $ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - |
确认一下您已经获取到了指纹:
1 | $ sudo apt-key fingerprint 0EBFCD88 |
添加储存库:
1 | $ sudo add-apt-repository \ |
更新 apt 包索引
1 | $ sudo apt-get update |
安装Docker CE
1 | $ sudo apt-get install docker-ce |
因为 Docker 的运行绑定了/var/run/docker.sock
文件,用于 docker 的守护进程(Docker daemon)和容器进程之间通信,而该文件的访问权限为 660,必须使用 sudo 命令才有该文件的执行权限。
前两天看了本书《Docker 全攻略》,里面提到 Docker 的出现统一了三界(开发、测试、生产)。Dockerfile 面向开发,Docker 镜像可以直接交付给我们的甲方,Docker容器面向部署和运维。Daemon 进程是 Docker 运行的主进程,负责和容器间的通信。
我们使用 Docker,最后会得到一个运行状态的容器,简单来说,使用Docker大概的步骤是:编写 Dockerfile 生成镜像,启动镜像,然后可以得到容器。
也就是说,Dockerfile 是生成镜像和容器的原材料,它会被 Docker 解释器解释,生成指定的镜像,镜像是静态的文件,启动镜像便能得到运行的容器,镜像和容器之间的关系可以理解为一个已经关机的电脑和一个开机状态的电脑的关系,顺便我们可以把 Dockerfile 理解为给电脑装系统时的用到的 ISO 镜像。
接下来,本文会重点介绍一下镜像和容器之间的关系,参考自我前两天在网上一篇文章,文章的链接已附在文末。
容器和镜像的大概关系可以参考下图:
如图,镜像是分层的文件,每一层都是只读的,其分层的方式类似于 git 的版本控制方式,除了最底层,每一层都会有它的父层,每一层都记录了和上一层的差异(diff),而容器则是在顶层运行的一个可读写层。
现在,我们可以通过特定的命令来了解一下它们之间的关系。
sudo docker create <image-id>
docker create
命令为指定的镜像(image)添加了一个可读写层,构成了一个新的容器,但这个容器并没有运行。
sudo docker start <container-id>
Docker start
命令为容器文件系统创建了一个进程隔离空间。注意,每一个容器只能够有一个进程隔离空间。
sudo docker run <image-id>
我们可以看到docker start
命令和docker run
命令都生成了容器,它们有什么区别呢?
从图片可以看出,docker run
命令先是利用镜像创建了一个容器,然后运行这个容器。
sudo docker ps
docker ps
命令会列出所有运行中的容器。这隐藏了非运行态容器的存在,如果想要找出这些容器,我们需要使用下面这个命令。
sudo docker ps –a
docker ps –a
命令会列出所有的容器,不管是运行的,还是停止的。
sudo docker images
docker images
命令会列出了所有顶层(top-level)镜像。
sudo docker images –a
docker images –a
命令列出了所有的镜像,也可以说是列出了所有的可读层。如果你想要查看某一个 image-id 下的所有层,可以使用docker history
来查看。
sudo docker stop <container-id>
docker stop
命令会向运行中的容器发送一个 SIGTERM 的信号,然后停止所有的进程。
sudo docker kill <container-id>
docker kill
命令向所有运行在容器中的进程发送了一个不友好的 SIGKILL 信号。
sudo docker pause <container-id>
docker stop
和docker kill
命令会发送 UNIX 的信号给运行中的进程,docker pause
命令则不一样,它利用了 cgroups 的特性将运行中的进程空间暂停。但是这种方式的不足之处在于发送一个 SIGTSTP 信号对于进程来说不够简单易懂,以至于不能够让所有进程暂停。
sudo docker rm <container-id>
docker rm
命令会移除构成容器的可读写层。注意,这个命令只能对非运行态容器执行。
sudo docker rmi <image-id>
docker rmi
命令会移除构成镜像的一个只读层。你只能够使用docker rmi
来移除最顶层(top level layer)(也可以说是镜像),你也可以使用 -f 参数来强制删除中间的只读层。
sudo docker commit <container-id>
docker commit
命令将容器的可读写层转换为一个只读层,这样就把一个容器转换成了不可变的镜像。
sudo docker build
docker build
命令它会反复的执行多个命令,如下图docker build
命令根据 Dockerfile 文件中的 FROM 指令获取到镜像,然后重复地 1) run(create和start)
、2) 修改
、3) commit
。在循环中的每一步都会生成一个新的层,因此许多新的层会被创建。
sudo docker exec <running-container-id>
docker exec
命令会在运行中的容器执行一个新进程。
sudo docker inspect <container-id> or <image-id>
docker inspect
命令会提取出容器或者镜像最顶层的元数据。
sudo docker save <image-id>
docker save
命令会创建一个镜像的压缩文件,这个文件能够在另外一个主机的 Docker 上使用。和 export 命令不同,这个命令为每一个层都保存了它们的元数据。这个命令只能对镜像生效。
sudo docker export <container-id>
docker export
命令创建一个tar文件,并且移除了元数据和不必要的层,将多个层整合成了一个层,只保存了当前统一视角看到的内容
sudo docker history <image-id>
docker history
命令递归地输出指定镜像的历史镜像。
本文主要介绍如何建立远程 git 仓库以及如何在 gitweb 页面中显示仓库的描述信息,以 Ubuntu 16.04 LTS 操作系统为例。
为了能在 gitweb 上查看仓库的信息,建议在 gitweb 的仓库根目录下新建文件夹(仓库根目录在 gitweb 的配置文件”/etc/gitweb.conf”中由”$projectroot”变量指定)
1 | mkdir Test.git |
git 远程库目录建议带上 .git 后缀。
因为我们用于连接私有 git 仓库的方法是 ssh 远程连接,我们以有远程 ssh 登录权限的用户 ‘CSTao’ 为例。
1 | chown -R CSTao:CSTao Test.git |
注意:本步骤和第一步的顺序可以交换,那么便可以不使用 chown 命令更改文件夹权限,只切换用户即可。这样做的原因是我新建的文件夹所在目录的权限不属于用户 CSTao,以 CSTao 用户新建文件夹会出现权限不足的警告。
1 | cd Test.git |
和建立本地仓库的命令不一样的是,建立远程仓库其实建立了一个裸仓库,也就是不含文件信息,只有 git 的提交记录。
1 | vim description |
写入描述信息即可
1 | vim config |
在原有内容后添加
1
2
3[gitweb]
owner = CSTao <whucstao@qq.com>
URL = ssh://CSTao@39.108.171.209:22/home/git/repositories/Test.git
通过owner指定gitweb中owner的显示内容,通过URL指定gitweb中URL的显示内容,基本格式为”ssh://[ssh登录的用户名]@[host:ssh端口][远程主机中的仓库目录]”
在本地计算机的特定文件夹中执行:
1
git clone ssh://CSTao@39.108.171.209:22/home/git/repositories/Test.git
这部分内容为 git 的基本操作,不再赘述
]]>1 | sudo apt-get install gitweb apache2 |
1 | vim /etc/gitweb.conf |
内容如下:
1 | $projectroot = "/home/git/repositories"; |
保存退出
1 | vim /etc/apache2/conf-available/gitweb.conf |
内容如下:
1 | Alias /gitweb /usr/share/gitweb |
保存退出
注意:
“AuthUserFile”是认证文件位置,用如下命令生成认证文件并添加一个访问用户:
1 | htpasswd -c 认证文件位置 用户名 |
然后根据提示输入密码即可。
1 | sudo a2enmod cgi |
如果搭建在本地,访问http://localhost/gitweb并登录就可看到 gitweb 设置的 git 库根目录下的所有项目信息。
但此时访问http://localhost/cgi-bin/gitweb.cgi不用登录也能访问到项目信息。我直接删除了”/usr/lib/cgi-bin/“文件夹中与 gitweb 相关的文件。其他人便不能通过该路径访问。
可以使用别人已经写好的布局和图标,详见github
需要将”/usr/share/gitweb”文件夹下的文件和文件夹设置正确的权限,<其他用户>必须有读取文件权限和执行文件权限。缺少读文件的权限服务器会返回”Internal Server Error(500)”错误,缺少执行文件的权限服务器会返回”Forbidden(403)”错误。读取文件权限为4,执行文件权限为1,也就是说<其他用户>的权限至少为’5’。如下,这里设置的’755’权限的最后一个’5’对应<其他用户>的权限。
1 | cd /usr/share/gitweb/ |
旧版本 Apache 的 /etc/apache2/conf.d/gitweb 和新版本的 /etc/apache2/conf-available/gitweb 的是同一个目录。
1 | <IfDefine ENABLE_GITWEB> |
本文记录了在 C# 环境下,如何利用 SMTP 邮箱服务发送邮件,以 QQ 邮箱为例
System.Configuration
程序集的引用1 |
|
1 | using System; |
若使用 QQ 邮箱,输入密码为许可码,需要在 QQ 邮箱中打开 SMTP 服务
在 JavaScript 中利用百度地图API对地理坐标系和投影坐标系(墨卡托)进行互转
在html文件中添加
1 | <script src="http://api.map.baidu.com/api?v=1.2"></script> |
1 | var projection = new BMap.MercatorProjection(); |
1 | var projection = new BMap.MercatorProjection(); |
本文记录了利用 GMap.net for WPF 绘制点线面的方式和在项目中遇到的对未指定长宽的要素与其他要素间相对定位的方式
GMap.net是一个强大、免费、跨平台、开源的 .NET 控件,它在 WinForm 和 WPF 环境中能够通过 Google, Yahoo!, Bing, OpenStreetMap, ArcGIS, Pergo, SigPac 等实现寻找路径、地理编码以及地图展示功能,并支持缓存和运行在 Mobile 环境中。
GMap.NET 是一个开源的GEO地图定位和跟踪程序。就像谷歌地图、雅虎地图一样,可以自动计算两地的距离,定位经纬度,与 Google 地图不同的是,该项目是建立在 WinForm 框架或 WPF 框架基础上的。可以对地图放大缩小,进行城市标记等。
不同于 Winform 版本,WPF 版本没有图层的概念,但用于显示要素的对象 GMapMarker 提供了 Zindex 属性,该属性值大的会遮盖属性值小的。所以大家可以利用 Zindex 对地理要素建立逻辑上的图层关联。
WinForm版本绘图可以直接在显示对象上设置图形的属性,如:
1 | GMapPolygon polygon = new GMapPolygon(pointList, "Polygon") |
对于wpf版本的点对象,可以直接指定显示用户控件,如:
1 | GMapMarker marker = new GMapMarker(pointLatLng); |
其中 MyUserControl 可以重载自 UserControl,并自定义显示内容。LayerIndex 为自定义的枚举类型。mapControl 重载自GMapControl。
但是wpf版本的线的属性设置需要重载 GMapControl 的 CreateRoutePath 方法,面的属性设置需要重载 CreatePolygonPath 方法。为了不影响原函数的内容,我们可以参考 GMapControl 的源代码GMapControl.cs文件。重载 CreateRoutePath 方法和 CreatePolygonPath 后的内容如下,只做了少量修改:
1 | /// <summary> |
注意:
代码中 lineBrush、lineWidth、lineOpacity 为重载 GMapControl 时新添的公共字段。
1 | /// <summary> |
注意:
代码中 polygonStrokeBrush、polygonThickness、polygonFillBush、polygonOpacity 为重载 GMapControl 时新添的公共字段。
wpf 版本只能绘制 Point、PolyLine、Polygon 三种图形,绘制圆则需要借助多边形的绘制。示例如下:
1 | public void DrawCircle(PointLatLng center, double R) |
效果如下:
因为投影问题,说好的圆变为了椭圆,如果想生成正圆,可以在程序中使用一些 WebAPI 服务替换 GMap 的投影服务,我们项目使用的是搭建在自己服务器上的的 GeoServer 服务。效果如下:
在使用 GMap 添加要素的时候,遇到需要对要素添加 Tooltip,但不能指定 Tooltip 的长宽,且该要素与 Tooltip 需要水平中心对其,试过很多办法都不能成功,因为 wpf 控件的 ActualWidth 和 ActualHeight 属性必须加载过一次才能有正确的属性值,也就是说如果根据长宽计算 GMapMarker 的偏移量,Tolltip 在第一次显示的时候无法正确定位,经过探索,最终利用 wpf 控件的 SizeChanged 响应函数实现了该效果。效果如下:
1 | public void AddIconWithTooltip(PointLatLng pll, Uri iconUri, string tooltip) |
注意:
代码中id的作用是用于 GMapMarker 间的逻辑关联,方便同时从 MapControl 中移除。
关键代码:
1 | TooltipForMap content = new TooltipForMap(tooltip, tooltipViewer); |
其中 TooltipForMap 类的 SizeChanged 函数如下:
1 | private void TooltipForMap_SizeChanged(object sender, SizeChangedEventArgs e) |
注意:
_TooltipViewer 和传入构造函数的 tooltipViewer 为同一实例。