golang template 学习笔记

最近几天用golang写自己的博客系统,手贱选用了golang自己的template包做模版系统。发现这个模版里面好多不符合常识的地方,记录一下。(偷懒没直接用pongo2真是败笔

[toc]

首先,golang的template分在两个package里,我一开始只看 html/template 包了,一直吐槽为什么文档这么不全,后来发现大部分功能在 text/template 里,html包只负责部分html特有的功能。

Layout

先看两种常见的模版布局方式吧(都是伪代码):

1. include 方式

1
2
3
4
5
6
7
<!-- content.html -->
{% raw %}{{ include "header.html" }}{% endraw %}
<div class="content">
<!-- content -->
</div>
{% raw %}{{ include "footer.html" }}{% endraw %}
<!-- end of content.html -->
1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- header.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>
xxx
</title>
<link href="xxx.css" rel="stylesheet" media="screen">
<script src="xxx.js"></script>
</head>
<body>
<!-- end of header.html -->
1
2
3
4
<!-- footer.html -->
</body>
</html>
<!-- end end of footer.html -->

2. layout 方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- layout.html -->
{% raw %}{{ define "layout" }}{% endraw %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>
xxx
</title>
<link href="xxx.css" rel="stylesheet" media="screen">
<script src="xxx.js"></script>
{% raw %}{{ template "moreStyles" }}{% endraw %}
</head>
<body>
{% raw %}{{ template "content" }}{% endraw %}
{% raw %}{{ template "moreScripts" }}{% endraw %}
</body>
</html>
{% raw %}{{ end }}{% endraw %}
<!-- end of footer.html -->
1
2
3
4
5
6
7
{% raw %}{{ use "layout" }}{% endraw %}
{% raw %}{{ define "content" }}{% endraw %}
<!-- content -->
{% raw %}{{ end }}{% endraw %}
{% raw %}{{ define "moreScript" }}{% endraw %}
<script src="you-script.js"></script>
{% raw %}{{ end }}{% endraw %}

3. 实现

一般我习惯layout方式的模版,这样结构清晰,而且IDE友好。但是golang的template好像没有对layout格式的模版做特别的支持。只好找一种折衷的比较优雅的实现方式。

首先,在模版目录下新建一个 common 目录,用于存放各种公共的template。之后,在下面创建 layout.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{% raw %}{{ define "layout" }}{% endraw %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>
{% raw %}{{ template "title" .}}{% endraw %}
</title>
<link href="common.css" rel="stylesheet">
{% raw %}{{ template "moreStyles" .}}{% endraw %}
</head>
<body>
{% raw %}{{ template "content" .}}{% endraw %}
<script src="common.js"></script>
{% raw %}{{ template "moreScripts" .}}{% endraw %}
</body>
</html>
{% raw %}{{ end }}{% endraw %}

这个文件使用action define 定义了一个叫做 layout 的模版,这个layout模版分别引用了 titlemoreStylescontentmoreScripts 这四个模版。

之后,在模版目录下新建真正的view,比如 note.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{% raw %}{{/* 定义layout所需要的template */}}{% endraw %}
{% raw %}{{ define "title" }}{% endraw %}
{% raw %}{{ .note.Title }} - {{ .site.Name }}{% endraw %}
{% raw %}{{ end }}{% endraw %}
{% raw %}{{ define "moreHeaders" }}{% endraw %}
{% raw %}{{ end }}{% endraw %}
{% raw %}{{ define "moreStyles" }}{% endraw %}
<link href="/static/css/note.css" rel="stylesheet">
{% raw %}{{ end }}{% endraw %}
{% raw %}{{ define "moreScripts" }}{% endraw %}
<script src="/static/js/jquery-2.2.2.min.js"></script>
<script src="/static/js/note.js"></script>
<script>
// inline script
</script>
{% raw %}{{ end }}{% endraw %}
{% raw %}{{ define "content" }}{% endraw %}
<article class="note">
{% raw %}{{ .note.UnescapedContent }}{% endraw %}
</article>
{% raw %}{{ end }}{% endraw %}

{% raw %}{{/* 引用layout,传入scope */}}{% endraw %}
{% raw %}{{ template "layout" . }}{% endraw %}

这个需要文件定义所有被layout引用的模版项(为空也要定义),然后最重要的一句是 {{ template "layout" . }} 这里把 . 传给了layout,从而能使 layout 访问model。

总之golang的template只使用了两个关键字 templatedefine 完成这个工作,可以算好处也可以算坏处(不够直观)

最后,在代码中这样调用:

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
const templateDir = "template"
const commonDir = "common"

type Render struct {
templateName string
template string
data interface{}
}

func NewRender(template string, data interface{}) *Render {
return &Render{
template,
path.Join(templateDir, template),
data,
}
}

func (r *Render) Render(w http.ResponseWriter) error {
util.WriteContentType(w, []string{"text/html; charset=utf-8"})
t := template.New("")
if _, err := t.ParseGlob(path.Join(templateDir,
commonDir, "*")); err != nil {
return err
}
if _, err := t.ParseFiles(r.template); err != nil {
return err
}
return t.ExecuteTemplate(w, r.templateName, r.data)
}

另外,可以在common里定义更多通用的小组件,以便调用,比如 tagarticle 之类的。

Unescape

golang的escape做的很好,可以区分当前语境(Contexts),把变量放到不同的地方会有不同的输出:

Context {{.}} 的输出
{{.}} O'Reilly: How are &lt;i&gt;you&lt;/i&gt;?
<a title='{{.}}'> O&#39;Reilly: How are you?
<a href="/{{.}}"> O&#39;Reilly: How are %3ci%3eyou%3c/i%3e?
<a href="?q={{.}}"> O&#39;Reilly%3a%20How%20are%3ci%3e...%3f
<a onx='f("{{.}}")'> O\x27Reilly: How are \x3ci\x3eyou...?
<a onx='f({{.}})'> "O\x27Reilly: How are \x3ci\x3eyou...?"
<a onx='pattern = /{{.}}/;'> O\x27Reilly: How are \x3ci\x3eyou...\x3f

假如 {{.}}` 是 `O'Reilly: How are you?`,把 `{{.}} 放到不同的地方会有不同的输出。

但是怎么让变量输出它本身的模样呢,html/template 提供了几个type来做这件事:

1
2
3
4
5
6
7
type (
CSS string
HTML string
JS string
JSStr string
URL string
)

假如在模版中

1
<img src="{% raw %}{{ .img.url }}{% endraw %}">

是无法直接输出的。需要用到上面的几个类型。

1
2
3
data := map[string]interface{} {
url: template.URL(url),
}

其他的类似。

Pipelines

golang起的这个名字有点奇怪,当作是 expression (表达式)就好了。有三种使用方法

  • 参数
  • .method [参数 参数 参数 …]
  • 函数 [参数 参数 参数 …]

是可以直接调用方法或函数的,不过不需要加小括号。

docker 中设置时区

昨天使用 docker 时遇到一个问题,应用输出的时间永远是utc时间,一开始我检查了服务器的时区,发现没问题。然后我就以为是代码中的bug,寻找了很久bug后突然想起docker中的时区应该和宿主机不一致。修改了一下果然好了。

修改docker镜像时区的方法和修改普通Linux系统时区的方法一致。具体方法要看使用的是什么发行版。

比如我使用的alpine方法如下:

1
2
3
apk add tzdata 
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
echo "Asia/Shanghai" > /etc/timezone

写到Dockerfile里就是:

1
2
3
4
RUN apk update && apk add ca-certificates && \
apk add tzdata && \
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo "Asia/Shanghai" > /etc/timezone

update:

在ubuntu中,设置好时区之后需要执行一下

1
dpkg-reconfigure -f noninteractive tzdata

否则不会生效

色彩化你Mac下的bash

配置方法

最近发现很多同事使用的terminal还是mac自带的,灰常不人性化,比如不显示git状态也不能多tab。所以在这里给大家分享一下色彩化你的bash的小技巧。首先可以下载一个支持多tab的终端:iTerm2

iTerm2图标

打开你刚刚下载的item2,执行如下命令

1
2
3
4
5
6
7
brew install xz coreutils # 把基础命令行工具换成gnu版本
gdircolors --print-database > ~/.dir_colors # 生成ls的色彩数据库
brew install bash bash-completion # 安装bash-completion和最新版bash(mac的bash不支持conpletion)
sudo sh -c 'echo "/usr/local/bin/bash" >> /etc/shells' # 把新版bash安装到系统shell中
chsh -s /usr/local/bin/bash # 切换当前用户的shell为新安装bash
brew link git # 重新配置git,使git的自动完成生效
vim ~/.bash_profile # 打开.bash_profile,进行下一步配置

.bash_profile可以如下参考

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
# bash补全
if [ -f $(brew --prefix)/etc/bash_completion ]; then
. $(brew --prefix)/etc/bash_completion;
fi
if [ -f $HOME/.npm_completion ]; then
. $HOME/.npm_completion;
fi

# ls的颜色
if brew list | grep coreutils > /dev/null ; then
PATH="$(brew --prefix coreutils)/libexec/gnubin:$PATH"
alias ls='ls -F --show-control-chars --color=auto'
eval `gdircolors -b $HOME/.dir_colors`
fi

# ls快捷键
alias ll='ls -l'
alias la='ls -A'
alias l='ls -CF'

# 提示符的格式
export GIT_PS1_SHOWDIRTYSTATE=1
export PS1='\[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\] \[\033[01;34m\]\w\033[01;33m$(__git_ps1)\033[01;34m\n\$\[\033[00m\] '

# grep高亮
alias grep='grep --color'
alias egrep='egrep --color'
alias fgrep='fgrep --color'

最后执行一下 source ~/.bash_profile 可以立即看到效果。不行的话关了重新开。

效果图

效果图

参考

http://my.oschina.net/tsl0922/blog/178775
http://segmentfault.com/q/1010000000636402
http://linfan.info/blog/2012/02/27/colorful-terminal-in-mac/

卡尔曼滤波的实现

卡尔曼滤波在数据采集中十分有用。本文介绍一下卡尔曼滤波的理论,然后给出一个实现。

具体的理论性介绍可以在 这里 找到

具体来说对于一维的卡尔曼滤波, 如果认为控制量为零的话卡尔曼滤波公式可以简化为以下 5 条:

X(k|k-1)=X(k-1|k-1) ……… (6)
P(k|k-1)=P(k-1|k-1) +Q ……… (7)
X(k|k)= X(k|k-1)+Kg(k) (Z(k)-X(k|k-1)) ……… (8)
Kg(k)= P(k|k-1) / (P(k|k-1) + R) ……… (9)
P(k|k)=(1-Kg(k))P(k|k-1) ……… (10)

其中, X(k|k-1) 这次的估计值, X(k-1|k-1) 是上次的最优值 (也就是上次计算结果)

由于我们没有控制量, 所以假设这次的值和上次的相等, 所以令第 k 次估计值 = 第 k-1 次最优值

公式 7 中 P(k|k-1) 是这次估计值的偏差 (协方差), 令其等于上次最优值的偏差加上一个参数 (因为系统总是越运行偏差越大, 所以每次都加上一个 Q)

X(k|k)是本次的最优值 (也就是结果), Z(k) 是本次的测量值, Kg 是卡尔曼增益, 根据 (9) 计算出

由 (8), (9) 可以计算出结果

另外, 需要计算出这次最优值的偏差, 以便下次回归 (递归)

代码写出来很简单, 但是完成的功能却很强大:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
volatile uint16_t adc_values[3];		//三个前方传感器

#define Q 1e-6f
#define R 1e-1f
volatile float kalman_value[3];
volatile float kalman_p[3];

void kalman_filter()
{
int i;
float p;
float x;
float kg;

for (i = 0; i < 3; ++i)
{
x = kalman_value[i]; //(6)
p = kalman_p[i] + Q; //(7)
kg = p / (p + R); //(9)
kalman_p[i] = (1 - kg) * p; //(10)
kalman_value[i] = x + kg * (adc_values[i] - x); //(8)
}
}

docker 初心者教程

换了博客系统之后,好有写博客的欲望。先来一篇docker的。

[toc]

微服务

先谈一下我对docker的理解吧。docker这个东西和微服务是离不来的,目前各大互联网企业都实践微服务,催生了docker的出现。我现在所在的公司也不例外,现在公司的技术大方向就是把老系统一步一步拆分成微服务,同时新系统直接做成微服务。身在其中,吃的屎多了,自然感到了微服务的种种优势。

对于微服务的优点我感觉最明显的有两点:

  • 思维上解耦了
  • 团队的工作更具体明确了。

因为现在同时在做新系统(微服务)的同时还需要在老系统上加功能,对第一点感觉就十分明显。在老系统那40万行屎山一样的代码上改东西时,整个人的心智负担就会很重,生怕哪一行把整个app拖慢了。新系统因为它足够微,所以理解整体业务逻辑就比较容易,应用做了什么事情大家都心知肚明,所以写代码的时候就可以随意一些啦wwww

第二点感觉也很明显是因为几个月前公司才做了一次调整,由之前的按照技术栈分团队改为了按产品线分团队。这样一个团队中既有做web的也有iOS、android的还有懂运维的,大家组成一个团队共同来开发一个(或几个)产品,这样大家对一个产品会理解的更深刻,不会出现之前那种每部分都摸过,但是哪部分业务都不熟的情况。而且现在大家职责更分明,出了问题也能第一时间找到人来处理。

docker 解决的问题

但是,微服务也会带来很多部署上的问题。以前发布只用build、deploy一个项目,现在需要搞10+个项目。这不把运维给累死啦?最麻烦的是每个项目依赖的东西不一样,可能项目A用的库是1.5.0,但项目B用的是1.6.0。这部署到一台机器还是两台机器呢,一台出错两台费钱。出问题的更多的是两个项目的依赖的依赖的依赖有冲突(不是直接冲突,两个项目的依赖树上有冲突)。另外程序员们总喜欢用最新的库,天天催运维升级系统。这样运维老爷们就会很头疼,大喊老子不干啦!你们程序员事真多,自己搭环境自己玩吧!

因此,docker就腾空出世了。由上面可见,docker解决的最大的问题就是定制的运行环境持久化,使应用或服务无论最终在哪里运行都有同样可预测的行为,尽量减少环境导致问题的可能性

另外,docker统一了由于操作系统不同而带来的服务接口不一致,比如应用部署上线后通常配合系统的initctl或upstart之类的写一个服务脚本。但是不同的系统甚至不同版本的系统都不互相兼容。用docker的话大家都统一docker restart了。爽歪歪。

docker 使用

第一次用docker给人的感觉像用一个速度超快的虚拟机,但是比虚拟机牛逼在没有开机关机过程。docker中也有两个概念:imagecontainer。container就相当于VBox里的一个虚拟机了,而image可以认为是container的一个备份(系统快照)。

基本的helloworld咱们就不跑了,就拿今天写好的博客系统来讲吧。这个和纯的微服务不一样,并不以接口形式向外提供服务,而是使用了模版引擎提供页面服务,以及向下访问数据库,和抓取cmd markdown的数据(类似于服务间调用接口啦233)。

准备

首先你需要把你的项目编译打包,这一步一般会在持续集成里去做。但是我们就手动搞啦。我这里的项目是golang的所以是一些go的编译命令

1
2
3
# golang交叉编译
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build
tar zcvf

安装 docker

准备工作完成后,下面来安装docker。Mac下的安装过程比较蛋疼,我看了下就放弃了。手边的Linux机器不少,再不济的也能用virtual box。不过前几天据说发布了测试的原生Mac版 Docker 终于有 Windows 和 Mac 版了 喜大普奔。

Linux下安装就比较方便的,我用的ubuntu,直接用get.docker.com的安装脚本就能一键安装。

1
2
3
4
5
#!/usr/bin/env bash

apt-get update -y
apt-get install -y curl
curl -fsSL https://get.docker.com/ | sh

先拉个操作系统

装好之后现在可以做我们自己的镜像了。docker的特点是你的镜像可以基于已有的镜像构建。所以我们可以先从docker hub(类似github一样的东西)拉一个基础的操作系统镜像下来,然后在其之上来构建我们的镜像。

1
2
3
4
5
# 拉取alpine系统
docker pull alpine:3.3

# 或者习惯ubuntu的可以拉ubuntu
docker pull ubuntu:15.10

在进行docker pull命令的完整语法是 docker pull [registry]/[username]/[image]:[version] 分为四个部分,registry代表拉取的服务器地址,username是被拉取的镜像所有者的用户名,image是镜像名,version是版本tag。

其中,registry被省去代表拉取官方registry,username被省去代表拉去官方镜像,version被省去代表拉去latest镜像,也就是最新镜像。

所以上面的命令的意思是把官方的alpine3.3拉过来。docker官方的registry里维护了一些最基本的镜像和比较常用的镜像。官方维护的品质一般都是比较可以信赖的,所以制作镜像可以基于官方镜像来build。

pull好之后可以执行一下

1
2
3
4
5
6
7
8
9
10
11
12
13
zzz@ubuntu-server ~ $ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
zwh8800/xware latest 9426e8b48edc 4 days ago 176.8 MB
gogs/gogs latest 40ce39eb7c4b 9 days ago 81.17 MB
zwh8800/starbucks latest 15fbd0099e0c 12 days ago 17.22 MB
zwh8800/douyu-notify latest 48ee382f41b8 2 weeks ago 12.01 MB
dperson/samba latest a09a77b57057 2 weeks ago 250.3 MB
nginx 1.9.12 af4b3d7d5401 3 weeks ago 190.5 MB
ubuntu 15.10 79cfbe2a4950 4 weeks ago 135.9 MB
jenkins latest c2cac236cd93 4 weeks ago 708.7 MB
golang 1.6.0-alpine c40da134e949 4 weeks ago 238 MB
alpine 3.3 70c557e50ed6 4 weeks ago 4.798 MB
golang 1.6 bb6cd5033ad2 4 weeks ago 744 MB

看一下所有本地的镜像

执行一下试试

既然已经拉下来镜像了,不玩一下就工作实在是可惜。所以我们先试着运行一下官方的镜像

1
2
zzz@ubuntu-server ~ $ docker run --rm -ti ubuntu:15.10 /bin/bash
root@8b94129944d0:/#

你会发现你好像回到了命令行,但有一些不同,你的hostname好像变了。如果你像我一样配置了prompt的颜色的话,发现颜色没了。其实我们已经进入容器(container)了,但完全没想到会是这么快,就像运行一个程序一样根本不需要开机的时间。在这个bash里你可以尽情的 apt-get install 甚至 rm / -rf 完全不会影响你的宿主机。docker就像一个虚拟机一样,他有独立的文件系统,独立的网络。所以 解开安全带吧 大家一起飙车

玩开心了要退出的话直接 exit 就好了。我们在运行的时候使用了 --rm 选项所以退出 container 的时候会自动删除 container。如果我们不加这个选项的话,可以在退出之后执行一下 docker ps -a

1
2
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
2f9dd61f8a18 ubuntu:15.10 "/bin/bash" 40 seconds ago Exited (0) 35 seconds ago desperate_bartik

会看到我们的容器还存在,你可以再次 docker start 它。

在容器中运行应用

下面,我们真正运行一下应用。

1
2
3
docker run --rm -ti -p 80:3336 -v ~/go/src/github.com/zwh8800/md-blog-gen:/app ubuntu:15.10 bash
root@8b94129944d0:/# cd /app
root@8b94129944d0:/# ./md-blog-gen -log_dir log

现在在浏览器里输入 http://docker所在机器ip/ 看看是不是已经开始运行了?

在docker中,-v 是类似挂载的选项,可以把宿主机的目录挂载到容器中。可以看见,在使用docker时,大家都是很粗犷的,直接把app放到根目录下wwww。

把容器打上 tag

刚才执行过命令之后,我们可以把这个状态下的容器打一个tag,它就可以变成一个镜像(image)。

1
docker commit -m "Release md-blog-gen image" -a "zwh8800" 15b042d970c9 zwh8800/md-blog-gen:latest

现在执行一下 docker images 是不是多了一个镜像?

Dockerfile

有了个直观认识之后大家又会感觉 不给力啊老湿 着我每次都得手动执行还不如我以前呢。

![坑爹呢这是][1]

所以docker还有法宝一件Dockerfile。这个东西不仅简化而且统一了大家的部署流程。

先看看md-blog-gen的Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
FROM alpine:3.3
MAINTAINER zwh8800 <496781108@qq.com>

WORKDIR /app

RUN apk update && apk add ca-certificates

ADD ./md-blog-gen /app
ADD ./template /app/template

VOLUME /app/log
VOLUME /app/config

EXPOSE 3336

CMD ["./md-blog-gen", "-log_dir", "log", "-config", "config/md-blog-gen.gcfg"]

非常之 短小

所以逐句解释一下

  • 以alpine:3.3为基础构建镜像
  • 维护者是我
  • 当前目录设置为/app
  • 安装一下我需要的依赖
  • 把需要的文件放到容器里(ADD命令可以直接ADD一个tar.gz,可以自动解压)
  • 指明镜像的挂载点
  • 指明镜像暴漏的端口
  • 镜像的默认执行命令

把Dockerfile放到项目的目录下面,执行一下 docker build -t zwh8800/md-blog-gen:latest . 就开始构建

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
Step 1 : FROM alpine:3.3
d7a513a663c1
Step 2 : MAINTAINER zwh8800 <496781108@qq.com>
Using cache
dcf2dbbf66e2
Step 3 : WORKDIR /app
Using cache
d8aa8ad0b356
Step 4 : RUN apk update && apk add ca-certificates
Using cache
9260849655f9
Step 5 : ADD ./md-blog-gen /app
Using cache
1a21d6e13904
Step 6 : ADD ./template /app/template
Using cache
449e6a44d38b
Step 7 : VOLUME /app/log
Using cache
402f6b0dc5b3
Step 8 : VOLUME /app/config
Using cache
5d0b036a04d7
Step 9 : EXPOSE 3336
Using cache
87a65b9eff28
Step 10 : CMD ./md-blog-gen -log_dir log -config config/md-blog-gen.gcfg
Using cache
e43ef3612d6b
Successfully built e43ef3612d6b

成功了,赶紧的 docker run --name blog -d -v log:/app/log -v config:/app/config zwh8800/md-blog-gen 啊,这次我们加上了 -d 选项,这个可以让container后台运行,另外加上了一个 --name 可以给容器起个名字。现在,它真的像一个服务了。

管理容器

最常用的肯定是操作肯定是启停

1
2
3
4
5
6
# 使用容器名
docker start blog
# 使用容器id
docker stop e43ef3612d6b
# 容器id不用打全部,前几位就可以
docker restart e43e

其他要做的就是在 /etc/rc.local 里加一句 docker star blog 就好了,保证开机时服务启动。

database

另外比较关心的是,我们的服务依赖的服务应当放在哪里。首当其冲的问题就是数据库怎么办。

具体有这三种方式吧。

应用+数据库组成镜像

就是在镜像里安装上数据库,优势是部署方便,劣势就是不同应用无法共享数据库了。而且这样不很符合现在 immutable server 的概念。

应用数据库单独为镜像

这样保证了共享数据库,使用docker link或者其他工具来打通容器。

数据库独立出来

有些公司的数据库是单独部署的,或使用云服务,这样就没必要放在docker中了。

关于docker容器之间相互打通

docker默认有 --link 选项,但是现在已经不推荐了。目前有一些集群方案可供使用

具体的坑大家自己踩啦233

上次同事的分享会上谈了一些使用docker过程中的大坑,有空再谈。

linux 共享库搜索路径

linux 中, 在执行一个可执行文件时,搜索动态库路径一共有 5 种,有优先级,会从上到下依次进行搜索。当执行一个程序上发现报错 No such file or directory 可以由此顺序来查错。

  • 在 ELF 文件的动态段 DT_RPATH 所指定的路径,可以在编译本文件时通过 “-Wl,rpath” 来指定
    1
    gcc -Wl,-rpath,/usr/local/lib,-rpath,/home/zzz/opensource/lib test.c
  • 环境变量 LD_LIBRARY_PATH
  • /etc/ld.so.cache 中缓存的路径,可以通过修改配置文件 / etc/ld.so.conf 并执行 ldconf 命令来修改
  • 默认路径 /lib
  • 默认路径 /usr/lib

Hello world

我把wordpress抛弃啦,旧的数据一会搞一个归档,url应该还不会变。扔掉旧系统真是一身轻松啊!

老数据的链接:https://hzzz.lengzzz.com/blog/i

新的博客系统堪称相当轻量,花1天时间用golang写了个爬虫+简单的博客系统。主要功能就是从cmd markdown上把我公开的文章爬过来存到本地。如果cmd markdown的作者能早日开放API就好了。

另外话说cmd markdown真是宇宙最好的markdown编辑器,用着太舒服了,再次给作者点赞。

最后,这个爬虫我开源了,可能还会有希望基于markdown做博客系统的人。可以尝试一下。

1
2
3
go get -u -v github.com/zwh8800/md-blog-gen
md-blog-gen -log_dir log/ [-config <config.gcfg>]
open http://localhost:3336/

配置文件可以这么写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[dbConf]
driver="mysql"
dsn="username:password@tcp(mysql:3306)/mdblog?charset=utf8mb4&parseTime=true"

[env]
prod=true # 区分生产环境和测试环境
serverPort=3336

[spider]
startUrl="https://www.zybuluo.com/zwh8800/note/332154" # 你的随便一篇发布在cmd markdown的文章
spiderTag="blog" # 你要抓取的tag

[urlPush]
baidu="http://data.zz.baidu.com/urls?site=lengzzz.com&token=xxxxxxx" # 向百度搜索推送url收录的url

[site]
name="水能载舟 亦可赛艇" # 网站名称
baseUrl="https://lengzzz.com/" # 网站首页
noteUrl="note" # 文字的url
tagUrl="tag" # tag的url
pageUrl="page" # page的url
staticUrl="static" # 静态文件的url

记着把注释删掉哦

另外最近用docker比较多,我正在把我所有用到的东西向docker上迁。所以这个爬虫也写了Dockerfile。你用docker的话可以这样部署。

1
2
3
4
5
git clone https://github.com/zwh8800/md-blog-gen
cd md-blog-gen/
CGO_ENABLED=0 go build
docker build -t zwh8800/md-blog-gen .
docker run -d -v log:/app/log -v config:/app/config --name blog zwh8800/md-blog-gen

图省事可以从docker hub上拉

1
2
3
4
docker pull zwh8800/md-blog-gen/ 
# 国内可以拉这个 速度快一些
docker pull registry.aliyuncs.com/zwh8800/md-blog-gen
docker run -d -v log:/app/log -v config:/app/config --name blog zwh8800/md-blog-gen

服务启停使用:

1
2
3
docker start blog
docker stop blog
docker restart blog

另外,最近换上了http2和let’s encrypt的https证书。一个字,爽。
基本参照一位大神的博客搞得。另外这个博客里干货很多,值得一看。

最近做了个小玩意

lua 版的 nodejs

都听说过 nodejs 吧,最近做了个 lua 版的 nodejs。

https://github.com/zwh8800/lua-libuv

上个栗子:

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
local server = uv.createServer()
print(server)
local count = 1
server:listen('0.0.0.0', 8080,
function (socket, err)
if socket then
print(count .. ': ')
count = count + 1
print(socket)
socket:write('hello lua-uv\n')

socket:onData(
function(socket, nread, data)
if (nread >= 0) then
print('received: ' .. data)
if data == 'exit\n' then
socket:finish('bye')
end
else
print('error: ' .. socket .. ': ' .. data)
end

end
)

else
print('got nil' .. err)
end

end
)

uv.loop()

最近一直在做前端,很多想法都改变了。

关于前端的一些思考

今年年初 3 月份,自己开始找工作。虽然自己之前的研究方向一直在底层,诸如嵌入式,驱动,操作系统方面的。但不得不说互联网公司对这方面的需求算是比较少的。我个人来讲的话,比较混搭,只要是计算机沾边的的东西都感兴趣。属于兴趣广泛的,所以各个方面都有所研究,另外加上基础扎实(这点尤其重要),所以在北京找一份不错的工作还是很轻松的(虽然我才大三,哈哈)

从 3 月到现在的 7 月也算工作了 4 个月左右了,经历还算挺丰富的,不过也确实因为太忙而顾不上更新这里了(工作上倒是不忙,处理各种生活琐事太让人心累了)。最近平稳下来了,大概总结一下吧。

工作以来,大概做了三个项目。第一个是纯 web 的应用,也因为它驱动着我学习了一直有所耳闻的 MVC。项目中使用的是 Phalcon,大概用两天左右的时间搞清楚了框架的用法。确实领会到了 MVC 的一些神奇之处。但其实感觉 MVC 只是其中的一小点,很重要的整个框架的完整程度。从数据库方面,有优秀的 ORM,视图方面有易用的模板引擎。

尤其说下 ORM,真的让我耳目一新,以前自己的见识都很短浅了,以为 orm 还是得写很多配置文件,需要静态生成 ORM 模型类。现在这些东西都不需要,只需要你按照自己的想法写代码就 OK 了。我也终于明白现在的语言为什么都急着加入反射和动态特性。C# 在这方面这的尤其好。

比如:数据库中有一个表

User
username password token

你只需要这样写:

1
2
3
4
5
6
7
8
9
10
11
class User extends \Phalcon\Mvc\Model
{
public function initialize()
{
parent::initialize();
}

public $username;
public $password;
public $token;
}

然后你就可以直接使用了!什么都无需配置,ORM 框架会默认以类名在数据库中找对应的表名,字段也会自动对应成员变量这简直太棒了!
ORM 就应该这样,这些配置上的问题就应该交给框架去做,而且就算要配置,不是让程序员写 XML 这种东西,而是应该用程序语言来描述。

因此,我的想法改变了,我不再去一味地追求底层而是更喜欢去思考一种编程模式,一种让程序员更轻松的模式去编程的模式。

寒假写了个 JSON 解析器

一个 JSON 解析器

使用方法:

1
2
3
4
5
6
7
8
9
string data = File.ReadAllText(@"f:\1.Json");
dynamic obj = Json.Parse(data);

Console.WriteLine(obj.employees[0].firstName);

foreach (var it in obj.employees)
{
Console.WriteLine(it.lastName);
}

假设 json 文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"employees": [
{
"firstName": "Bill",
"lastName": "Gates"
},
{
"firstName": "George",
"lastName": "Bush"
},
{
"firstName": "Thomas",
"lastName": "Carter"
}
]
}

使用了 c#4.0 的动态特性, c# 越来越像动态语言了, 用着真舒服.
代码如下, 就是个递归下降:

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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Dynamic;

namespace Json
{
public class Json
{
private class JsonObject : DynamicObject
{
public JsonObject(Dictionary<string, dynamic> member)
{
this.member = member;
}

public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (member.ContainsKey(binder.Name))
{
result = member[binder.Name];
return true;
}
else
{
result = null;
return false;
}
}

private Dictionary<string, dynamic> member;
}

private static int CharToInt(char c)
{
switch (c)
{
case '0':
return 0;
case '1':
return 1;
case '2':
return 2;
case '3':
return 3;
case '4':
return 4;
case '5':
return 5;
case '6':
return 6;
case '7':
return 7;
case '8':
return 8;
case '9':
return 9;
}
throw new Exception("c不是一个数字字符");
}

private enum TokenType
{
LBrace, RBrace,
LBracket, RBracket,
Colon, Comma,
True, False,
Null, String,
Number
}

private struct Token
{
public TokenType type;
public dynamic value;
}

private enum NumberState
{
Start, MinusRead, ZeroRead, InInt, DotRead, InFraction, ERead, InExponent, End
}

private static Token GetToken(string json, ref int i)
{
while (i < json.Length && char.IsWhiteSpace(json[i]))
i++;

Token token = new Token();
string sub = json.Substring(i);

if (json[i] == '{')
{
token.type = TokenType.LBrace;
i = i + 1;
}
else if (json[i] == '}')
{
token.type = TokenType.RBrace;
i = i + 1;
}
else if (json[i] == '[')
{
token.type = TokenType.LBracket;
i = i + 1;
}
else if (json[i] == ']')
{
token.type = TokenType.RBracket;
i = i + 1;
}
else if (json[i] == ':')
{
token.type = TokenType.Colon;
i = i + 1;
}
else if (json[i] == ',')
{
token.type = TokenType.Comma;
i = i + 1;
}
else if (sub.StartsWith("true"))
{
token.type = TokenType.True;
i = i + 4;
}
else if (sub.StartsWith("false"))
{
token.type = TokenType.False;
i = i + 4;
}
else if (sub.StartsWith("null"))
{
token.type = TokenType.Null;
i = i + 4;
}
else if (json[i] == '"') //string
{
StringBuilder sb = new StringBuilder();
i++;

while (i < json.Length)
{
int len = 0;
while (i + len < json.Length && json[i + len] != '"' && json[i + len] != '\\')
len++;

sb.Append(json.Substring(i, len));

if (i + len >= json.Length)
{
throw new Exception("未找到匹配的引号");
}
else if (json[i + len] == '"')
{
i += len + 1;
break;
}

i += len + 1;
switch (json[i])
{
case '"':
sb.Append('"');
i++;
break;
case '\\':
sb.Append('\\');
i++;
break;
case '/':
sb.Append('/');
i++;
break;
case 'b':
sb.Append('\b');
i++;
break;
case 'f':
sb.Append('\f');
i++;
break;
case 'n':
sb.Append('\n');
i++;
break;
case 'r':
sb.Append('\r');
i++;
break;
case 't':
sb.Append('\t');
i++;
break;
case 'u':
string hex = json.Substring(i + 1, 4);
ushort code = Convert.ToUInt16(hex, 16);
char c = (char)code;
sb.Append(c);
i += 5;
break;
}
}

token.value = sb.ToString();
token.type = TokenType.String;
}
else if (json[i] == '-' || char.IsNumber(json[i])) //nubmer
{
dynamic value = 0;
bool isMinus = false;
int frac = 10;
bool isExponentNegative = false;
int exponent = 0;

NumberState state = NumberState.Start;

while (state != NumberState.End)
{
switch (state)
{
case NumberState.Start:
if (json[i] == '-')
{
isMinus = true;

state = NumberState.MinusRead;
}
else if (json[i] == '0')
{
state = NumberState.ZeroRead;
}
else if (char.IsNumber(json[i]))
{
value = CharToInt(json[i]);
state = NumberState.InInt;
}
else
{
throw new Exception("不能识别的字符‘" + json[i] + "’");
}
i++;
break;
case NumberState.MinusRead:
if (json[i] == '0')
{
state = NumberState.ZeroRead;
}
else if (char.IsNumber(json[i]))
{
value = CharToInt(json[i]);
state = NumberState.InInt;
}
else
{
throw new Exception("不能识别的字符‘" + json[i] + "’");
}
i++;
break;
case NumberState.InInt:
if (char.IsNumber(json[i]))
{
value *= 10;
value += CharToInt(json[i]);
}
else if (json[i] == '.')
{
state = NumberState.DotRead;
}
else if (json[i] == 'e' || json[i] == 'E')
{
state = NumberState.ERead;
}
else
{
i--;
state = NumberState.End;
}
i++;
break;
case NumberState.ZeroRead:
if (json[i] == '.')
{
state = NumberState.DotRead;
}
else if (json[i] == 'e' || json[i] == 'E')
{
state = NumberState.ERead;
}
else
{
i--;
state = NumberState.End;
}
i++;
break;
case NumberState.DotRead:
if (char.IsNumber(json[i]))
{
value += (double)CharToInt(json[i]) / frac;
frac *= 10;
state = NumberState.InFraction;
}
else
{
throw new Exception("不能识别的字符‘" + json[i] + "’");
}
i++;
break;
case NumberState.InFraction:
if (char.IsNumber(json[i]))
{
value += (double)CharToInt(json[i]) / frac;
frac *= 10;
}
else if (json[i] == 'e' || json[i] == 'E')
{
state = NumberState.ERead;
}
else
{
i--;
state = NumberState.End;
}
i++;
break;
case NumberState.ERead:
if (char.IsNumber(json[i]))
{
exponent = CharToInt(json[i]);
state = NumberState.InExponent;
}
else if (json[i] == '+')
{
state = NumberState.InExponent;
}
else if (json[i] == '-')
{
isExponentNegative = true;
state = NumberState.InExponent;
}
else
{
throw new Exception("不能识别的字符‘" + json[i] + "’");
}
i++;
break;
case NumberState.InExponent:
if (char.IsNumber(json[i]))
{
exponent *= 10;
exponent = CharToInt(json[i]);
}
else
{
i--;
state = NumberState.End;
}
i++;
break;
}
if (i >= json.Length)
state = NumberState.End;
}

if (exponent != 0)
{
if (isExponentNegative)
{
value = value * Math.Pow(10, -exponent);
}
else
{
value = value * Math.Pow(10, exponent);
}
}
if (isMinus)
token.value = -value;
else
token.value = value;
token.type = TokenType.Number;

}
else
throw new Exception("不能识别的字符‘" + json[i] + "’");

while (i < json.Length && char.IsWhiteSpace(json[i]))
i++;
return token;
}

private static dynamic Value(string json, ref int i)
{
Token token = GetToken(json, ref i);
if (token.type == TokenType.Null)
return null;
else if (token.type == TokenType.False)
return false;
else if (token.type == TokenType.True)
return true;
else if (token.type == TokenType.Number)
return token.value;
else if (token.type == TokenType.String)
return token.value;
else if (token.type == TokenType.LBrace)
{
Dictionary<string, dynamic> member = new Dictionary<string, dynamic>();

bool first = true;
while (true)
{
if (first)
{
int tmpi = i;
token = GetToken(json, ref i);
if (token.type == TokenType.RBrace)
break;
else
i = tmpi;

first = false;
}

token = GetToken(json, ref i);
if (token.type != TokenType.String)
{
throw new Exception("出乎意料的Token: " + token.type);
}
string name = token.value;

token = GetToken(json, ref i);
if (token.type != TokenType.Colon)
{
throw new Exception("出乎意料的Token: " + token.type);
}

dynamic val = Value(json, ref i);
member[name] = val;

token = GetToken(json, ref i);
if (token.type == TokenType.Comma)
continue;
else if (token.type == TokenType.RBrace)
break;
else
throw new Exception("出乎意料的Token: " + token.type);
}

return new JsonObject(member);
}
else if (token.type == TokenType.LBracket)
{
List<dynamic> list = new List<dynamic>();
bool first = true;
while (true)
{
if (first)
{
int tmpi = i;
token = GetToken(json, ref i);
if (token.type == TokenType.RBracket)
break;
else
i = tmpi;

first = false;
}

list.Add(Value(json, ref i));
token = GetToken(json, ref i);
if (token.type == TokenType.Comma)
continue;
else if (token.type == TokenType.RBracket)
break;
else
throw new Exception("出乎意料的Token: " + token.type);
}
return list.ToArray();
}
else
{
throw new Exception("出乎意料的Token: " + token.type);
}
}

public static dynamic Parse(string json)
{
int i = 0;
return Value(json, ref i);
}
}
}

Proudly powered by Hexo and Theme by Hacker
© 2021 wastecat