修改Ubuntu的时区

安装了Ubuntu Server之后,默认的英文环境下时区是国外的,所以使用Git等操作的时候,记录的时间会有些错乱,我们可以简单的使用命令行来修改时区。

sudo dpkg-reconfigure tzdata

image

然后我们依次选择 Asia-China-Beijing就可以了!

在Ubuntu上安装 Nodejs 以及配置 NPM

编译安装nodejs最新版本

从 nodejs 官网下载最新源代码

choose the newest Node,copy the url,and run like this: wget https://nodejs.org/dist/v7.8.0/node-v7.8.0.tar.gz.  

安装依赖以及编译

  1. sudo apt-get install python2.7 #install python2
  2. sudo ln -s /usr/bin/python2.7 /usr/bin/python #create soft link
  3. cd /home/user/
  4. tar -zxf nodejs.tar.gz
  5. cd nodejs
  6. sudo apt-get install build-essntial #install gcc compile
  7. ./configure
  8. make
  9. sudo make install

编译安装较慢,建议使用下面的方式

使用 node 管理工具安装

  1. sudo apt install nodejs-legacy //ubuntu 仓库默认安装的是低版本的的 nodejs
  2. sudo apt install npm //安装npm也是低版本的
  3. sudo npm install -g n
  4. sudo n latest  //更多文档查看https://github.com/tj/n
  5. npm config set registry https://registry.npm.taobao.org //永久设置npm registry为淘宝镜像
  6. npm config get registry //测试 npm 镜像
  7. npm install npm@latest //升级npm到最新版本

使用nvm

1
2
3
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.2/install.sh | bash
#或者
wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.33.2/install.sh | bash

添加nvm命令到.bashrc

1
2
3
4
5
cd ~/.bashrc
# or ~/.bash_profile, ~/.zshrc, ~/.profile
export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" # This loads nvm
# 验证安装
command -v nvm

安装Node

1
2
3
4
# latest version
nvm install node
# stable version
nvm install stable

使用apt包进行安装

1
2
3
4
5
6
# stable version
curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
sudo apt-get install -y nodejs
# latest version
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
sudo apt-get install -y nodejs

注意 6.0稳定版本,我安装的时候这个没有npm,命令是 nodejs 开头的。最新版本没有这个问题。

手动下载包并安装

在官网找到最新包,然后安装下面流程进行。

1
2
3
wget https://nodejs.org/dist/v9.10.1/node-v9.10.1-linux-x64.tar.gz
tar -zxvf node-v9.10.1-linux-x64.tar.gz -C /usr/local --strip-components=1 --no-same-owner
ln -s /usr/local/bin/node /usr/local/bin/nodejs

国内npm镜像设置

  1. 临时使用:npm --registry https://registry.npm.taobao.org install PACKAGE-NAME
  2. 永久设置:npm config set registry https://registry.npm.taobao.org
  3. cnpm包:npm install -g cnpm --registry=https://registry.npm.taobao.org

官方镜像

npm config set registry https://registry.npmjs.org/

npm避免系统权限

默认情况下,全局模块都安装在系统目录(比如/usr/local/lib/),普通用户没有写入权限,需要用到sudo命令。

可以在用户目录下新建配置文件.npmrc,然后创建npm目录,把PATH添加到这里即可

1
2
3
4
5
cd ~
vim .npmrc
prefix = /home/yourUsername/npm
mkdir ~/npm
export PATH=~/npm/bin:$PATH

Ubuntu中怎样添加或删除一个PPA源

PPA源

Personal Package Archives(个人软件包档案)是Ubuntu Launchpad网站提供的一项服务,允许个人用户上传软件源代码,通过Launchpad进行编译并发布为2进制软件包,作为apt/新立得源供其他用户下载和更新。在Launchpad网站上的每一个用户和团队都可以拥有一个或多个PPA。

PPA的一般形式是

1
ppa:user/ppa-name  

添加之前请确定已经安装 Python 以及必要依赖:

1
sudo apt-get install software-properties-common

添加PPA源的命令为:

1
sudo add-apt-repository ppa:user/ppa-name

也可以打开【软件中心】->【软件源】->【其他软件】,选择添加,在弹出的窗口中AT行里输入ppa:user/ppa-name 格式的内容。

例如,要添加一个用户名为 certbot 的 certbot 源中,则命令为

1
sudo add-apt-repository ppa:certbot/certbot

添加好更新一下:

1
sudo apt-get update

然后就可以运行 sudo apt install certbot 安装了。

删除命令格式则为:

1
sudo add-apt-repository -r ppa:user/ppa-name

1
sudo add-apt-repository -r ppa:eugenesan/java

然后进入 /etc/apt/sources.list.d 目录,将相应 ppa 源的保存文件删除。

最后同样更新一下

1
sudo apt-get update

安装使用Gitlab进行内部代码管理

这里选择你的系统,按照提示安装。

下面以Ubuntu 16.04 LTS版本为例,以后会补充使用docker来管理 gitlab。

安装依赖包

sudo apt-get install curl openssh-server ca-certificates postfix

添加gitlab源

curl -sS https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | sudo bash

网络不好的选项

这里 找到你的版本号并替换下面 url
sudo curl -LJO <url>
sudo dpkg -i gitlab-ce-XXX.deb

更新源以及安装

sudo apt-get update && sudo apt-get install gitlab-ce -y

配置 /etc/gitlab/gitlab.rb

sudo vim /etc/gitlab/gitlab.rb

配置访问接口

external_url = ‘http://git.example.com

配置邮件服务

1
2
3
4
5
6
7
8
9
gitlab_rails['smtp_enable'] = true  
gitlab_rails['smtp_address'] = ""
gitlab_rails['smtp_port'] = 25
gitlab_rails['smtp_user_name'] = ""
gitlab_rails['smtp_password'] = ""
gitlab_rails['smtp_domain'] = ""
gitlab_rails['smtp_authentication'] = "login"
gitlab_rails['smtp_enable_starttls_auto'] = false
gitlab_rails['smtp_tls'] = false

注意:

1
2
3
smtp_address和smtp_domain的设置,以阿里云企业邮箱为例  
gitlab_rails['smtp_address'] = "smtp.mxhichina.com"
gitlab_rails['smtp_domain'] = "mxhichina.com"

关闭 prometheus

个人经验:如果不关闭的话,会一直报错。
prometheus_monitoring[‘enable’] = false

重启GitLab

sudo gitlab-ctl reconfigure && sudo gitlab-ctl restart

一些推荐设置

首次进入设置管理员密码,设置 your-domain/admin/application_settings

  1. 建议关闭 Gravatar 头像功能
    在 Account and Limit Settings 关闭 Gravatar enabled,国内访问不友好。

  2. 关闭注册功能
    在 Sign-in Restrictions 关闭 Sign-in enabled,如果是内部使用建议关闭,然后管理员工分配注册。

升级Gitlab

在管理后台位置 overview 界面如果红色提示需要升级,那么尽快升级到最新版本。

  1. 关闭 gitlab
    sudo gitlab-ctl stop
  2. 升级 gitlab
    sudo apt-get install gitlab -y

修改为国内镜像

详情: https://mirror.tuna.tsinghua.edu.cn/help/gitlab-ce/

1
2
3
4
vim /etc/apt/sources.list.d/gitlab_gitlab-ce.list
## 添加
deb https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/ubuntu xenial main
deb-src https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/ubuntu xenial main

使用 Docker

配置 docker-compose

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
version: "3" 

services:
gitlab:
image: gitlab/gitlab-ce
ports:
- "80:80"
- "443:443"
- "2222:2222"
volumes:
- gitlab-config:/etc/gitlab
- gitlab-logs:/var/log/gitlab
- gitlab-data:/var/opt/gitlab
- $PWD/gitlab.rb:/etc/gitlab/gitlab.rb
- $PWD/ssh_config:/etc/ssh/ssh_config
- $PWD/sshd_config:/assets/sshd_config
- $PWD/ssl:/etc/gitlab/ssl
volumes:
gitlab-config:
gitlab-logs:
gitlab-data:

这里我们使用了 HTTPS,需要配置一下:

创建 ssl 目录,并导入证书和私钥。这里以 example.crt 和 example.key 为例

1
2
3
4
5
6
7
# 设置 ssh 端口为 2222
gitlab_rails['gitlab_shell_ssh_port'] = 2222

# nginx config
nginx['redirect_http_to_https'] = true
nginx['ssl_certificate'] = "/etc/gitlab/ssl/example.crt"
nginx['ssl_certificate_key'] = "/etc/gitlab/ssl/example.key"

增加 ssh_config 文件

1
2
3
4
5
6
7
Host *
# 设置 ssh 端口为 2222
Port 2222
SendEnv LANG LC_*
HashKnownHosts yes
GSSAPIAuthentication yes
GSSAPIDelegateCredentials no

增加 sshd 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 设置 ssh 端口为 2222
Port 2222
ChallengeResponseAuthentication no
HostKey /etc/gitlab/ssh_host_rsa_key
HostKey /etc/gitlab/ssh_host_ecdsa_key
HostKey /etc/gitlab/ssh_host_ed25519_key
Protocol 2
PermitRootLogin no
PasswordAuthentication no
MaxStartups 100:30:200
AllowUsers git
PrintMotd no
PrintLastLog no
PubkeyAuthentication yes
AuthorizedKeysFile %h/.ssh/authorized_keys /gitlab-data/ssh/authorized_keys

UsePAM yes
UseDNS no

Docker+Nodejs实践

依赖

  1. Docker
  2. Nodejs

步骤

1

新建一个项目,安装 express 包。

然后新建一个 index.js ,一个简单的服务器环境就搭好了。

1
2
3
4
5
6
7
8
9
10
const express = require("express");
const app = express();
const port = 3000;
app.get("/", function(req, res) {
res.status(200).send("Hello,Docker.");
});

app.listen(port);

console.log("Starting at %s", port);

我们运行 node index.js 打开浏览器 localhost:3000 就能看到 Hello,Docker 的提示,现在退出。

2

保证安装 docker 最新版本,有些命令本文需要最新版本。

下载 node 的 docker 镜像,这里我们直接用最新的就可以,如果慢可以设置国内镜像,具体参照我以前的 docker 文章。

1
2
3
4
docker pull node
# 查看node的版本
docker run --rm node node -v
# v9.2.1

这里我用的 docker run --rm 指的是运行后即删除这个容器。

好了,我们在直接进行下一步操作。

在命令行工具中定位刚才新建的项目路径,我这里用的是 windows 的 PowerShell。

image

接着运行

1
2
3
4
5
docker run -d -p 3000:3000 --name index.js \
-w /node --mount \
type=bind,src=$PWD,dst=/node \
node \
node index.js

简单说说这条命令,指定本地 3000 端口到容器的 3000 端口,并指定容器当前的工作目录是 /node(当然会自动创建这个不存在的目录)。这里 --mount 代替了以前的 -v ,官方最佳实践也是这样的。

好了我们打开浏览器访问 localhost:3000 就能看到 Hello,Docker 的提示了。

由于 node 的特性,我们在修改文件的同时,并没有实时修改网页。

这里我们需要配置一些内容,这里因为错误理解踩了几个坑。

  1. 错误理解0:发送 sighup 就可以重启 node 服务
  2. 错误理解1:使用 npm scripts 管理启动

具体说说 npm scripts 这个,也就是 npm run 这个,比如说配置 start 和 restart 命令,然后使用 docker exec 运行 npm,

1
2
3
4
{
"start": "node index",
"restart": "killall node && node index"
}

事实上,我运行之后我才反应过来,npm 是后台命令,而不是 docker 需要的前台命令,如果这样执行(看下方最后一行)容器就会运行之后就退出。

1
2
3
4
5
docker run -d -p 3000:3000 \
-w /node --mount \
type=bind,src=$PWD,dst=/node \
node \
npm start # 看这里

最后我想到了 pm2 这个神器,下载 pm2 然后只要配置 pm2 前台启动就好了,最后的配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"name": "html",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"start": "pm2 start index.js --no-daemon",
"reload": "pm2 reload all",
"stop": "pm2 stop all"
},
"dependencies": {
"express": "^4.16.2",
"pm2": "^2.8.0"
}
}

这时候我们只要运行就可以了。

1
2
3
4
5
docker run -d -p 3000:3000 --name n4 \
-w /node --mount \
type=bind,src=$PWD,dst=/node \
node \
npm start

更新操作,更简单 docker exec n4 npm restart

更进阶的内容,使用 Dockerfile + pm2 构建,放在以后再谈。

Go: 一个 JSON 切分成两个对象

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
package main

import (
"encoding/json"
"fmt"
)

// User is
type User struct {
Email string `json:"email"`
Password string `json:"password"`
}

// Blog is
type Blog struct {
Title string `json:"title"`
Content string `json:"content"`
}

func main() {

b := new(Blog)
u := new(User)

json.Unmarshal([]byte(
`{
"email":"lishude",
"password":"test",
"title": "hello",
"content": "content"
}`),
&struct {
*User
*Blog
}{u, b})

fmt.Println(b)
fmt.Println(u)
}

MySQL 默认忽略内联外键定义的坑

创建一个省份表

1
2
3
4
5
CREATE TABLE `provinces` (
`pid` SMALLINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(20) NOT NULL
);

然后 User 表中的 province 字段关联省份表的 pid 字段

1
2
3
4
CREATE TABLE `users` (
`uid` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
`province` SMALLINT NOT NULL REFERENCES `province` (`pid`)
);

这一步没有报错。

但是查看 users 表中的所有索引时

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
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| users | 0 | PRIMARY | 1 | uid | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
1 row in set (0.06 sec)
````


只有 `uid` 主键的索引定义!

这十分奇怪啊,然后我又查找了标准 SQL 语法,我这里的定义并没有错误。

```sql
CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name
(create_definition,...)
[table_options]
[partition_options]

create_definition:
col_name column_definition
| [CONSTRAINT [symbol]] PRIMARY KEY [index_type] (key_part,...)
[index_option] ...
| {INDEX|KEY} [index_name] [index_type] (key_part,...)
[index_option] ...
| [CONSTRAINT [symbol]] UNIQUE [INDEX|KEY]
[index_name] [index_type] (key_part,...)
[index_option] ...
| {FULLTEXT|SPATIAL} [INDEX|KEY] [index_name] (key_part,...)
[index_option] ...
| [CONSTRAINT [symbol]] FOREIGN KEY
[index_name] (col_name,...) reference_definition
| CHECK (expr)

column_definition:
data_type [NOT NULL | NULL] [DEFAULT {literal | (expr)} ]
[AUTO_INCREMENT] [UNIQUE [KEY]] [[PRIMARY] KEY]
[COMMENT 'string']
[COLLATE collation_name]
[COLUMN_FORMAT {FIXED|DYNAMIC|DEFAULT}]
[STORAGE {DISK|MEMORY|DEFAULT}]
[reference_definition]
| data_type
[GENERATED ALWAYS] AS (expression)
[VIRTUAL | STORED] [NOT NULL | NULL]
[UNIQUE [KEY]] [[PRIMARY] KEY]
[COMMENT 'string']

reference_definition:
REFERENCES tbl_name (key_part,...)
[MATCH FULL | MATCH PARTIAL | MATCH SIMPLE]
[ON DELETE reference_option]
[ON UPDATE reference_option]

reference_option:
RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT

之后我尝试了不使用内联的方式,然后就成功了!

1
2
3
4
5
6
7
CREATE TABLE `users` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'user id',
`province` smallint(5) unsigned NOT NULL,
`name` varchar(20) NOT NULL COMMENT 'user name',
PRIMARY KEY (`id`),
FOREIGN KEY (`province`) REFERENCES `provinces` (`pid`)
)

然后找了 MySQL 相关文档之后,才发现这是个 feature 不是个 bug,在 MySQL 内联外键会解析但是会忽略

MySQL parses but ignores “inline REFERENCES specifications” (as defined in the SQL standard) where the references are defined as part of the column specification. MySQL accepts REFERENCES clauses only when specified as part of a separate FOREIGN KEY specification.

现在是特别不明白 MySQL 这么多坑,为什么国内还会有这么多公司用。。

[翻译] Go switch 关键字

Switch

规范: https://golang.org/ref/spec#Switch_statements
Spec: https://golang.org/ref/spec#Switch_statements

Golang 的 switch 关键字十分灵活,比如说,你不需要像 C++、Java 等需要明确 break 每个分支判断。
Go’s switch statements are pretty neat. For one thing, you don’t need to break at the end of each case.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
switch c {
case '&':
esc = "&amp;"
case '\'':
esc = "&apos;"
case '<':
esc = "&lt;"
case '>':
esc = "&gt;"
case '"':
esc = "&quot;"
default:
panic("unrecognized escape character")
}

src/pkg/html/escape.go

不仅仅数值类型

Not just integers

switch 关键字可以在任何类型中使用。
Switches work on values of any type.

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
switch syscall.OS {
case "windows":
sd = &sysDir{
Getenv("SystemRoot") + `\system32\drivers\etc`,
[]string{
"hosts",
"networks",
"protocol",
"services",
},
}
case "plan9":
sd = &sysDir{
"/lib/ndb",
[]string{
"common",
"local",
},
}
default:
sd = &sysDir{
"/etc",
[]string{
"group",
"hosts",
"passwd",
},
}
}

不需要设置表达式再判断

Missing expression

事实上你根本不需要设置任何表达式后再进行判断。如果没有表达式的话就代表 switch true,这更像没有 if-else 的清晰条件判断形式,比如说下面在 Effictive Go 的例子。
In fact, you don’t need to switch on anything at all. A switch with no value means “switch true”, making it a cleaner version of an if-else chain, as in this example from Effective Go:

1
2
3
4
5
6
7
8
9
10
11
func unhex(c byte) byte {
switch {
case '0' <= c && c <= '9':
return c - '0'
case 'a' <= c && c <= 'f':
return c - 'a' + 10
case 'A' <= c && c <= 'F':
return c - 'A' + 10
}
return 0
}

Break 关键字

Break

Go 中 switch 隐式的添加了 break ,不过在某些场景仍然有用。
Go’s switch statements break implicitly, but break is still useful:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
command := ReadCommand()
argv := strings.Fields(command)
switch argv[0] {
case "echo":
fmt.Print(argv[1:]...)
case "cat":
if len(argv) <= 1 {
fmt.Println("Usage: cat <filename>")
break
}
PrintFile(argv[1])
default:
fmt.Println("Unknown command; try 'echo' or 'cat'")
}

Fallthrough 关键字

Fall through

如果想继续判断下一个分支,那么需要使用 fallthrough 关键字。
To fall through to a subsequent case, use the fallthrough keyword:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
v := 42
switch v {
case 100:
fmt.Println(100)
fallthrough
case 42:
fmt.Println(42)
fallthrough
case 1:
fmt.Println(1)
fallthrough
default:
fmt.Println("default")
}
// Output:
// 42
// 1
// default

另一个例子:
Another example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Unpack 4 bytes into uint32 to repack into base 85 5-byte.
var v uint32
switch len(src) {
default:
v |= uint32(src[3])
fallthrough
case 3:
v |= uint32(src[2]) << 8
fallthrough
case 2:
v |= uint32(src[1]) << 16
fallthrough
case 1:
v |= uint32(src[0]) << 24
}

src/pkg/encoding/ascii85/ascii85.go

fallthrough 必须要在判断表示区域最后位置,你不能像下面这样写:
The ‘fallthrough’ must be the last thing in the case; you can’t write something like

1
2
3
4
5
6
7
8
9
switch {
case f():
if g() {
fallthrough // Does not work!
}
h()
default:
error()
}

不过你可以使用标签形式的 fallthrough
However, you can work around this by using a ‘labeled’ fallthrough:

1
2
3
4
5
6
7
8
9
10
11
12
switch {
case f():
if g() {
goto nextCase // Works now!
}
h()
break
nextCase:
fallthrough
default:
error()
}

另外需要注意 fallthrough 不适用于类型分支判断情况。
Note: fallthrough does not work in type switch.

多条件判断

Multiple cases

如果你想使用多个值在同一个 case 评估表达式中,可以使用逗号分隔。
If you want to use multiple values in the same case, use a comma-separated list.

1
2
3
4
5
6
7
func letterOp(code int) bool {
switch chars[code].category {
case "Lu", "Ll", "Lt", "Lm", "Lo":
return true
}
return false
}

类型评估

Type switch

你可以对(只能)万能类型 interface{} 进行类型判断。
With a type switch you can switch on the type of an interface value (only):

1
2
3
4
5
6
7
8
9
10
func typeName(v interface{}) string {
switch v.(type) {
case int:
return "int"
case string:
return "string"
default:
return "unknown"
}
}

你也可以声明一个变量,这个变量的类型是每个判断成功分支的类型。
You can also declare a variable and it will have the type of each case:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func do(v interface{}) string {
switch u := v.(type) {
case int:
return strconv.Itoa(u*2) // u has type int
case string:
mid := len(u) / 2 // split - u has type string
return u[mid:] + u[:mid] // join
}
return "unknown"
}

do(21) == "42"
do("bitrab") == "rabbit"
do(3.142) == "unknown"

无操作表达式

Noop case

有时候 case 里是没有任何代码,这看起来很奇怪,在其它语言中会继续往下进行判断,但是在 golang 中不是这样,每一个 case 表达式都会隐式添加 break 表达式。
Sometimes it useful to have cases that require no action. This can look confusing, because it can appear that both the noop case and the subsequent case have the same action, but isn’t so.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func pluralEnding(n int) string {
ending := ""

switch n {
case 1:
default:
ending = "s"
}

return ending
}

fmt.Sprintf("foo%s\n", pluralEnding(1)) == "foo"
fmt.Sprintf("bar%s\n", pluralEnding(2)) == "bars"

给 localhost 签发 https 证书

本文介绍一个可以给 localhost 签发 https 证书的工具 mkcert,而且使用特别简单。

工具下载地址:
https://github.com/FiloSottile/mkcert/releases/latest

或者工具包管理工具:

1
2
# mac
brew install mkcert

然后两步就可以生成 localhost 证书

1
2
3
4
# 添加 CA 信任
mkcert -install
# 生成证书到当前目录
mkcert 127.0.0.1 localhost

这样就可以给 localhost127.0.0.1 生成了一个证书,如果给其它域名,可以再往后面加参数即可。

然后重命名一下为 key.pemcert.pem 测试一下效果。

1
127.0.0.1+1-key.pem 127.0.0.1+1.pem # rename -> key.pem cert.pem

使用 Nodejs 试一试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const https = require("https");
const fs = require("fs");

const options = {
key: fs.readFileSync("key.pem"),
cert: fs.readFileSync("cert.pem")
};

https
.createServer(options, (req, res) => {
res.writeHead(200);
res.end("hello world\n");
})
.listen(8000);

使用 curl 看一下结果,都正常连接了。

1
2
3
4
curl https://localhost:8000
# hello,world
curl https://127.0.0.1:8000
# hello,world

然后再换成 golang 试一试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

package main

import (
"io"
"log"
"net/http"
)

func main() {
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, "hello world\n")
})
log.Fatal(http.ListenAndServeTLS(":8000", "cert.pem", "key.pem", nil))
}

也是正常连接的。

solidity v0.5.0 主要破坏性更新示例

强制显式声明函数可见性

默认可见性声明被废弃,所有的函数和构造函数不再隐式声明 public,必须手动加上 public。而 fallback 和接口函数需要强制加上 external

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pragma solidity ^0.5.3;

interface example {
function add(uint x, uint y) external pure returns (uint);
}

contract Test is example {
constructor() public {}

function() external payable {}

function add(uint x, uint y) public pure returns (uint) {
uint z = x + y;
assert(z >= x);
return z;
}
}

强制显式指定复合型数据类型位置

结构体、数组、映射等复合型数据需要指定数据位置,包括函数参数和返回值。另外 external 函数中的需要将复合类型参数指定为 calldata.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

contract Test {
uint256[] public data;
constructor(uint[] memory _data) public {
data = _data;
}

function create(bytes memory _data) public pure returns(bytes memory) {
return _data;
}
}

interface Example {
function create(address _key, string calldata _value, bytes32 _hash) external;
}

address 类型扩展

合约不再隐式转换成 address 类型,需要手动转换。

1
2
3
4
5
6
7
contract Test {
constructor() public {}

function getBalance() public view returns (uint256) {
return address(this).balance;
}
}

address 分为 addressaddress payable 两种类型,只有后者可以调用 transfer 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pragma solidity ^0.5.3;

contract Test {
address payable owner;
constructor() public {
owner = msg.sender;
}

function() external payable {}

function transfer(address payable _to, uint _value) public {
require(msg.sender == owner);
require(address(this).balance >= _value);
_to.transfer(_value);
}

function destroy() public {
require(msg.sender == owner);
selfdestruct(owner);
}
}

函数变更

现在 .call() 合约调用函数和 keccak256() 哈希函数现在只接受一个 bytes 参数。

1
2
3
4
5
6
7
8
9
10
pragma solidity ^0.5.3;

contract Test {
constructor() public {}

function create(address _from,bytes memory _data) public view returns(bytes32) {
bytes32 hash = keccak256(abi.encodePacked(address(this), _from, _data));
return hash;
}
}

另外 .call() 等合约调用函数返回值除了调用状态外,还包括了返回值。

1
2
3
4
5
6
7
8
9
10
11
12
pragma solidity ^0.5.3;

contract Test {
constructor() public {}

function callTest() public view returns(bytes32){
address con = address(0x0);
bytes memory params = abi.encodeWithSignature("create(address,bytes)");
(bool success, bytes memory result) = con.delegatecall(params);
// ...
}
}

以下函数正式被废弃:

  • callcode 被废弃,推荐使用 delegatecall
  • suicide 被废弃,推荐使用 selfdestruct
  • sha3 被废弃,推荐使用 keccak256