编程学习日志

用技术改变世界

0%

在golang里经常需要将数据在struct对象和json串之间转换。

golang中struct和json串互相转化.png

最简单的序列化

序列化(从golang对象转换为JSON串)只需要对结构体进行标注即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 对struct进行标注
type User struct {
Name string `json:"name"`
Email string `json:"email"`
Password string `json:"password"`
}

// 序列化
if buf, err := json.Marshal(User{
Name: "seedjyh",
Email: "seedjyh@gmail.com",
Password: "123456",
}); err != nil {
fmt.Println(string(buf))
// 输出 {"name":"seedjyh","email":"seedjyh@gmail.com","password":"123456"}
}

很简单不是吗?


追加JSON键

然而,如果需要将User对象传递给服务器时,服务器需要追加一个current键值对,以存储发送时刻,那该怎么办?

方法1:直接修改原结构体

直接往User结构体里添加一个Current字段,几下敲完。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type User struct {
Name string `json:"name"`
Email string `json:"email"`
Password string `json:"password"`
Current time.Time `json:"current"` // 加了这一行
}
// 序列化
if buf, err := json.Marshal(User{
Name: "seedjyh",
Email: "seedjyh@gmail.com",
Password: "123456",
Current: time.Now(),
}); err != nil {
fmt.Println(string(buf))
// 输出 {"name":"seedjyh","email":"seedjyh@gmail.com","password":"123456","current":"2020-03-12T14:55:42.7560612+08:00"}
}

看上去不错,但是这个Current字段会影响所有用到这个User结构的地方。初始化的时候麻烦,序列化(而不需要Current字段)的地方更麻烦。

有更好的方法吗?

方法2:定义一个新的结构体

1
2
3
4
5
6
type UserWithCurrent struct {
Name string `json:"name"` // 照抄
Email string `json:"email"` // 照抄
Password string `json:"password"` // 照抄
Current time.Time `json:"current"` // 新增字段
}

这比方法1好一些。只在序列化的地方定义UserWithCurrent结构,其他代码就不受干扰了。

不过还是有两个缺点:

  1. 构造UserWithCurrent对象时,需要将User对象的每个字段逐一赋值,麻烦又易错。
  2. 如果原来的User对象增加了字段(比如Age),这里UserWithCurrent也得跟着改。

有更好的方法吗?

方法3:结构体组合

golang没有提供面向对象的继承功能,但提供了一个用组合实现继承的方法。

1
2
3
4
type UserWithCurrent struct {
*User // 不加星号也可以,加星号可以节约内存开销
Current time.Time `json:"current"` // 新增字段
}

这种结构的UserWithCurrent不但能实现添加current字段的要求,在用User对象构造UserWithCurrent对象时,可以直接将User对象赋值过去而不需对User对象的字段逐个赋值。如下所示:

1
2
3
4
userToSend := UserWithCurrent{
User: &user, // user是User结构体的对象。
Current: time.Now(),
}

隐藏JSON键

现在有个需求:将User对象(最初的那个struct)的内容按照JSON格式打印到日志里。但是由于Password是敏感字段,需要在打印时去掉。也就是原来的Name、Email、Password三个字段只打印前两个。

是去掉一个键,而不是新增。该怎么办?

方法1:定义一个新的结构体!

1
2
3
4
5
type UserWithoutPassword struct {
Name string `json:"name"` // 照抄
Email string `json:"email"` // 照抄
// Password去掉了。
}

然后在构造这个对象时逐个从user对象拷贝字段,User字段发生变化时也得连锁修改UserWithoutPassword结构。麻烦,易错。

只有这个方法吗?

并不是。

方法2:结构体组合+重名标注

用结构体组合,依然可以实现字段

1
2
3
4
5
6
7
8
9
type UserWithoutPassword struct {
*User
Abc int `json:"password,omitempty"` // 关键是标注password和omitempty
}

userWithoutPassword := UserWithoutPassword{
User: &user,
// 字段Abc留空,不赋值
}

稍微解释一下:

UserWithoutPassword里新增的一行里,字段名Abc和字段类型int可以随便写,关键是标注。

Abc的标注为password,和User对象里的Password字段的标注相同。此时,在json包对对象进行序列化时,JSON的password键的值只看外层的struct字段。

golang中json序列化时的键覆盖.png

也就是说,最后序列化的时候,password键的值由UserWithoutPassword的Abc字段决定。

然后在Abc的标注里加上omitempty,并在构造UserWithoutPassword对象时故意不对Abc赋值(让其保持默认的空值),即可在最终生成的JSON串里隐藏password这个JSON键。


重命名JSON键

还是原来那个User结构,现在要求name键改成username键。

golang中json序列化时的键重命名.png

其实也很简单,联合使用上面的“追加JSON键”和“隐藏JSON键”即可。

1
2
3
4
5
6
7
8
9
10
11
type UserRenameKey struct {
*User
Abc int `json:"name,omitempty"` // 覆盖键name
Def string `json:"username"` // 新键username
}

userRenameKey := UserRenameKey{
User: &user,
// 字段Abc留空,不赋值
Def: user.Name, // 将user对象里的Name字段赋值给Def字段,最后序列化成username
}

参考资料

本文记录了我在Windows上用hexo搭建博客,并部署到我的云服务器上的步骤。

1. hexo搭建博客的工作原理

hexo是一个基于node.js的轻量级博客框架,用于在自己的工作机里生成一个完整的网站,并提供了本地预览和部署到云服务器的工具。

大多数操作都在自己的电脑上进行,只有最后几步发布到云服务器。

1.1. 在自己的电脑上建站

  • 安装hexo。
  • 用hexo生成一个hexo工作目录
  • 用markdown写文章,将md文件保存到hexo工作目录下的source子目录中。
  • 用hexo根据hexo工作目录生成一个完整的博客网站(包括html、css、js等),保存到hexo工作目录下的public子目录中。

1.2. 部署到云服务器上

  • 将上一步生成的public子目录里的所有文件(包括html、css、js等)拷贝到云服务器的某个目录里。
  • 启动nginx,将网页请求重定向到上一步的目录里,从而实现公网访问。

2. 准备工作

全部工作都在自己的PC机进行。

2.1. 安装node.js

node.js官网下载页面下载node.js并安装。会一并安装npm。

用下面的指令检查安装是否成功。

1
2
3
4
5
C:\Users\jyh>node -v
v12.16.1

C:\Users\jyh>npm -v
6.14.1

2.2. 安装hexo

将npm的源设置为淘宝镜像,并下载安装hexo。

下面的命令可以在任何目录下进行。

1
2
npm config set registry https://registry.npm.taobao.org
npm install -g hexo-cli

用下面的指令检查安装是否成功。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
C:\Users\jyh>hexo -v
hexo-cli: 3.1.0
os: Windows_NT 10.0.15063 win32 x64
node: 12.16.1
v8: 7.8.279.23-node.31
uv: 1.34.0
zlib: 1.2.11
brotli: 1.0.7
ares: 1.15.0
modules: 72
nghttp2: 1.40.0
napi: 5
llhttp: 2.0.4
http_parser: 2.9.3
openssl: 1.1.1d
cldr: 35.1
icu: 64.2
tz: 2019c
unicode: 12.1

3. 本地生成博客页面

随便找个hexo的工作目录,比如D:\abc\myblog>。用命令行进入该目录,用hexo初始化该目录。

1
2
3
4
5
6
7
D:\abc>cd myblog
D:\abc\myblog>dir
:: 初始化hexo工作目录
D:\abc\myblog>hexo init
D:\abc\myblog>npm install
:: 生成网页
D:\abc\myblog>hexo g

4. 将博客部署到云服务器

虽然可以将public目录里的所有内容都打包拷贝到云服务器再解包,但hexo提供了更方便的方法,可以在本地用一条命令就将网站文件推送到云服务器。

4.1. 准备git仓库

云服务器需要安装git。

1
[root@VM_0_9_centos ~]# sudo apt-get install git

在云服务器建立一个名叫git的用户,并切换到git用户下。

1
2
[root@VM_0_9_centos ~]# useradd git
[root@VM_0_9_centos ~]# su - git

在git用户根目录下建立一个裸仓库。

1
[git@VM_0_9_centos ~]# git init --bare myblog.git

进入该裸仓库,创建一个钩子,让这个仓库收到新提交时,将文件checkout到网站根目录里。
创建钩子的方法就是在裸仓库hooks目录里加一个脚本文件。

1
[git@VM_0_9_centos ~]# vim ~/blog.git/hooks/post-receive

写入以下内容并保存。其中/path/to/www是云服务器上的网站目录。

1
2
#!/bin/sh
git --work-tree=/path/to/www --git-dir=~/myblog.git checkout -f

这样,每当该仓库接到提交,就会将提交的仓库内容checkout到目录/path/to/www

4.2. 用ssh密钥实现免密登录

在云服务器上用ssh-keygen生成公钥-私钥对。

将公钥保存到/home/git/.ssh/authorized_keys这个文本文件中。

将私钥保存到本地工作机。

4.3. 配置hexo工作目录,实现一条指令完成发布

修改hexo工作目录下的_config.yml

找到deploy项,默认是这样的:

1
2
deploy:
type: ''

修改成这样:

1
2
3
4
deploy:
type: git
repo: git@your-host.com/home/git/your-bare-git-repo.git
branch: master

然后再度生成本地网页并部署。

1
D:\abc\myblog>hexo clean && hexo g --deploy

5. 让云服务器上的博客能被网页浏览器访问

配置nginx,将网页请求重定向到第4步的/path/to/www目录中即可。

6. 参考资料

【腾讯云的1001种玩法】Hello Hexo之静态博客搭建+自动部署