使用Waline给Hexo静态博客添加评论系统

1. Waline简介

Waline - 一款从 Valine 衍生的带后端评论系统。可以将 Waline 等价成 With backend Valine。—— Waline 官网的介绍

Waline同时具有Valine的轻量和后端管理的方便两大特性,并且提供了简单方便的Serverless部署方案,遵循轻量化、集约化设计思路的指导,可以方便地部署在各种FaaS平台上,包括著名的Vercel、腾讯云CloudBase以及使用Docker独立部署的方案,提供LeanCloud、MongoDB、MySQL、PostgreSQL和SQLite的数据库支持,支持邮件通知、Telegram通知、QQ通知及微信通知接口的接入,提供大量可自定义参数接口。强大的扩展组合能力让Waline成为了集几乎所有优点于一身的评论系统组合,只要配置适当,相信能成为静态站点的一大优秀评论服务合作伙伴。

启用 Waline 即可为诸如Hexo、Hugo、Gitbook这样的静态网站提供评论与浏览量服务。

image-20210617021625524

2. 搭建前的环境准备

以下我将采用宝塔面板与Docker混合部署的方式(宝塔面板安装Nginx环境并开启HTTPS,Docker安装MySQL和Waline),VPS系统用的是Debian 10 x86_64。

VPS的购买及配置、域名解析、宝塔面板的搭建及使用、Docker的概念及使用…这些基本的就不再赘述了,如果不会的话见我的另一篇博客:VPS基本部署环境的搭建与配置

Waline支持的部署方式非常多,即便没有服务器也可以白嫖服务进行部署,具体参见 官方文档,这里就不赘述了。

2.1 Docker环境搭建

2.1.1 卸载旧版本Docker

旧版本的 Docker 称为 docker 或者 docker-engine,使用以下命令卸载旧版本:

1
2
3
4
$ apt-get install -y sudo
$ sudo apt-get remove docker \
docker-engine \
docker.io

2.1.2 安装HTTPS传输包及CA证书

由于 apt 源使用 HTTPS 以确保软件下载过程中不被篡改。因此,我们需要添加使用 HTTPS 传输的软件包以及 CA 证书。

Step1:更新apt包索引

1
$ sudo apt-get update

Step2:安装包以允许apt通过HTTPS使用存储库

1
2
3
4
5
6
$ sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg2 \
software-properties-common

Step3:添加Docker的官方GPG密钥

1
$ curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -

Step4:验证您现在拥有带指纹的密钥

1
$ sudo apt-key fingerprint 0EBFCD88

Step5:添加源

1
2
3
4
$ sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/debian \
$(lsb_release -cs) \
stable"

2.1.3 安装DOCKER CE

1
2
$ sudo apt-get update
$ sudo apt-get install docker-ce docker-ce-cli containerd.io

2.1.4 启动Docker服务并查看版本

1
2
$ sudo systemctl start docker
$ sudo docker --version

注:如果是apt安装的docker,开机自启脚本已经自动放置在/etc/init.d/目录下了,无需配置。

2.2 Docker-MySQL环境搭建

2.2.1 搜索MySQL镜像

1
$ docker search mysql

docker-mysql

通常选择OFFICIAL里Star数最多的那个镜像,即mysql。

2.2.2 拉取MySQL镜像

1
$ docker pull mysql:5.7

2.2.3 创建实例容器并运行

1
2
3
4
5
6
7
$ docker run -p 3306:3306 --name mysql \
-e TZ=Asia/Shanghai \
-v /mydocker/mysql/conf:/etc/mysql/conf.d \
-v /mydocker/mysql/logs:/var/log/mysql \
-v /mydocker/mysql/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=[password] \
-d mysql:5.7 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci

命令解释说明:

1
2
3
4
5
6
7
-p 3306:3306:将主机的3306端口映射到docker容器的3306端口。
--name mysql:运行服务名字
-e TZ=Asia/Shanghai:时区是使用了世界标准时间(UTC)。因为在中国使用,所以需要把时区改成东八区的。
-e MYSQL_ROOT_PASSWORD=[password]:初始化 root 用户的密码。
-d mysql:5.7 : 后台程序运行mysql5.7
--character-set-server=utf8mb4 :设置字符集
--collation-server=utf8mb4_unicode_ci:设置校对集

设置开机自启

1
$ docker update mysql --restart=always

2.2.4 创建数据库及用户

在本地使用Navicat工具使用root用户连接上该数据库(国外服务器建议配置代理),使用如下四条命令创建数据库及用户。

1
2
3
4
5
6
7
8
9
10
11
--创建新的数据库,并设置数据库编码
$ CREATE DATABASE 你的数据库名 DEFAULT CHARSET=utf8 DEFAULT COLLATE utf8_unicode_ci;

--创建新的用户
$ CREATE USER '你的用户名'@'你的服务器IP' IDENTIFIED BY '你的密码';

--把数据库的管理权限给予刚刚创建的MySQL用户
$ GRANT ALL PRIVILEGES ON *.* TO '你的用户名'@'%';

--刷新权限,使用设置生效
$ FLUSH PRIVILEGES;

注:如果连接数据库时出现Access denied for user '用户名'@'某IP' (using password: YES)问题,则是第三句授权出了问题,你的本地外网IP被拦截了,那个’%’代表的是访问IP不受限制。

另注:注意设置数据库的时区,默认时区使用了世界标准时间(UTC),比国内晚8个小时,会导致评论时间显示有误。如果没设置的话可以通过以下方式修改:

1
2
3
4
5
$ docker exec -it mysql bash                 # 进入mysql容器内部
$ date -R # 查看当前时区
$ cp /usr/share/zoneinfo/PRC /etc/localtime # 修改为当地时区
$ exit # 退出mysql容器
$ docker restart mysql # 重启mysql容器

3. 搭建Waline服务端

3.1 初始化Waline的表结构

前面我们已经创建好了MySQL数据库并使用Navicat工具连接,下面我们将执行对该数据库执行 waline.sql 文件初始化表结构。

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
/*!40101 SET @[email protected]@CHARACTER_SET_CLIENT */;
/*!40101 SET @[email protected]@CHARACTER_SET_RESULTS */;
/*!40101 SET @[email protected]@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
SET NAMES utf8mb4;
/*!40014 SET @[email protected]@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @[email protected]@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @[email protected]@SQL_NOTES, SQL_NOTES=0 */;

# Dump of table wl_Comment
# ------------------------------------------------------------

CREATE TABLE `wl_Comment` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL,
`comment` text,
`insertedAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`ip` varchar(100) DEFAULT '',
`link` varchar(255) DEFAULT NULL,
`mail` varchar(255) DEFAULT NULL,
`nick` varchar(255) DEFAULT NULL,
`pid` int(11) DEFAULT NULL,
`rid` int(11) DEFAULT NULL,
`status` varchar(50) NOT NULL DEFAULT '',
`ua` text,
`url` varchar(255) DEFAULT NULL,
`createdAt` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`updatedAt` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

# Dump of table wl_Counter
# ------------------------------------------------------------

CREATE TABLE `wl_Counter` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`time` int(11) DEFAULT NULL,
`url` varchar(255) NOT NULL DEFAULT '',
`createdAt` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`updatedAt` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

# Dump of table wl_Users
# ------------------------------------------------------------

CREATE TABLE `wl_Users` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`display_name` varchar(255) NOT NULL DEFAULT '',
`email` varchar(255) NOT NULL DEFAULT '',
`password` varchar(255) NOT NULL DEFAULT '',
`type` varchar(50) NOT NULL DEFAULT '',
`url` varchar(255) DEFAULT NULL,
`avatar` varchar(255) DEFAULT NULL,
`github` varchar(255) DEFAULT NULL,
`createdAt` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`updatedAt` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

/*!40111 SET [email protected]_SQL_NOTES */;
/*!40101 SET [email protected]_SQL_MODE */;
/*!40014 SET [email protected]_FOREIGN_KEY_CHECKS */;
/*!40101 SET [email protected]_CHARACTER_SET_CLIENT */;
/*!40101 SET [email protected]_CHARACTER_SET_RESULTS */;
/*!40101 SET [email protected]_COLLATION_CONNECTION */;

3.2 拉取并部署Waline的Docker镜像

Step1:先使用docker search命令搜索镜像,发现有原作者的官方docker镜像lizheming/waline,使用docker pull命令拉取下来。

1
2
$ docker search waline
$ docker pull lizheming/waline:latest

Step2:使用docker run命令创建实例容器并运行,与MySQL相关的参数如下表所示:

环境变量名称 必填 默认值 备注
MYSQL_HOST 127.0.0.1 MySQL 服务的地址
MYSQL_PORT 3306 MySQL 服务的端口
MYSQL_DB MySQL 数据库库名
MYSQL_USER MySQL 数据库的用户名
MYSQL_PASSWORD MySQL 数据库的密码
MYSQL_PREFIX wl_ MySQL 数据表的表前缀
MYSQL_CHARSET utf8mb4 MySQL 数据表的字符集

我配置的启动参数如下,其余启动参数详见官方文档。

1
2
3
4
5
6
7
8
9
$ docker run -d --name waline -p 8360:8360 \
-v /mydocker/waline/data:/app/data \
-e TZ="Asia/Shanghai" \
-e MYSQL_HOST="172.17.0.1" \
-e MYSQL_DB="数据库名" \
-e MYSQL_USER="用户名" \
-e MYSQL_PASSWORD="密码" \
--restart always \
lizheming/waline:latest

3.3 打开管理界面注册账号

这时用Chrome浏览器访问http://IP:8360/ui/register地址,便能看到注册界面。

Waline注册

如果能够成功注册账号并登录进下图所示的管理界面,就说明Waline搭建成功了。前端的访问地址为http://IP:8360

3RAkQFLwWc

常见问题:

1)如果无法打开该注册页面,说明docker容器的启动参数配置的有问题,使用如下命令查看日志排查:

1
$ docker logs -f --tail 20 waline

2)如果打开了该注册页面,但是无法注册用户,说明docker容器启动参数里的mysql配置有问题,比如MYSQL_HOST填的不对等问题。

3.4 设置反向代理用域名访问

3.4.1 新建网站

宝塔面板——网站——添加站点——填写域名即可(其他的都不需要,我们只是用它来做反向代理和配置HTTPS)

3.4.1 设置反向代理

宝塔面板——网站——设置——反向代理——添加反向代理——填写代理名称和目标URL(http://127.0.0.1:8360

3.5 申请泛域名SSL证书并开启HTTPS

SSL证书是一种数字证书,用于加密从用户的浏览器发送到Web服务器的数据。 通过这种方式,发送的数据对于使用Wireshark等数据包嗅探器来拦截和窃听您的通信的黑客来说是安全的。

Chrome一直在推动https,所有的http协议网站被标记为不安全,如果再不对网站进行https改造的话,那么可能会对信任度造成一定的影响,所以说对一个面向用户的网站来说,开启https是非常有必要的。

下面将使用acme.sh开源项目申请免费的 Let’s Encrypt 泛域名SSL证书。

注:如果懒得折腾泛域名证书,也可在宝塔面板直接傻瓜式申请Let’s Encrypt的单域名SSL证书(宝塔面板——网站——设置——SSL——Let’s Encrypt——勾选域名,点文件验证——申请出来后设置强制HTTPS)。

3.5.1 安装 acme.sh

1
$ curl  https://get.acme.sh | sh

acme.sh

普通用户和 root 用户都可以安装使用,安装过程进行了以下几步:

  • [1] 把 acme.sh 安装到你的 root 目录下,并创建 一个 bash 的 alias, 方便你的使用。

  • [2] 自动为你创建 cronjob, 每天 0:00 点自动检测所有的证书, 如果快过期了, 需要更新, 则会自动更新证书。

注:安装过程不会污染已有的系统任何功能和文件,所有的修改都限制在安装目录中。那个socat未安装的问题不用管,那是http验证无Web Server时才需要的。

3.5.2 生成SSL泛域名证书

acme.sh 实现了 acme 协议支持的所有验证协议,一般有两种方式验证:http 和 dns 验证。

  • http 验证:http 方式需要在你的网站根目录下放置一个文件,来验证你的域名所有权。
  • dns 验证:dns 方式,在域名上添加一条 txt 解析记录,验证域名所有权。

dns 方式的可以使用域名解析商提供的 API 自动添加 txt 记录完成验证,下面我们将采用这种方法申请Namesilo的泛域名证书。

Step1:打开 https://www.namesilo.com/account/api-manager 去申请 NameSilo API,勾选第2个复选框,点击Generate,即可生成。

申请NameSilo API

注:务必不要勾选上Generate key for read-only access的哪个复选框,否则会导致Unable to add the DNS record. Error add txt for domain的问题。另外,生成的API只出现一次,如果没记下来只能重置。

Step2:在服务器输入以下命令,实现自动dns验证生成泛域名证书。

1
2
3
$ cd /root/.acme.sh
$ export Namesilo_Key="xxxxxxxxxxxxxxxxxxxxxxxx"
$ ./acme.sh --issue --dns dns_namesilo --dnssleep 1800 -d example.com -d *.example.com

等待1800s即可看到申请下来的SSL证书(NameSilo的验证比较慢,官方文档上写的900s有时不足以验证完)

申请下来的SSL证书

生成文件都放在/root/.acme.sh/example.com/目录下,其中:example.com.key是密钥文件,fullchain.cer是证书文件。

注:如果你的域名不是NameSilo的,上述操作有所不同,具体请参考: https://github.com/acmesh-official/acme.sh/wiki/dnsapi

3.5.3 给网站开启HTTPS

打开宝塔面板——网站——设置——SSL——其他证书,把example.com.key密钥文件、fullchain.cer证书文件复制上去,强制https。

开启HTTPS

此时,我们的Waline的访问地址变为了:

1
2
管理面板:https://域名/ui/
前端界面:https://域名/

4. 开启评论通知

当网站有用户发布评论或者用户回复评论时,Waline 支持对博主和回复评论作者进行通知。博主通知支持多种方式,包括 QQ、微信、邮件、Telegram Bot 等,回复评论作者仅支持邮件通知。

以下仅介绍我所使用的 Gmail 通知和 Telegram bot 通知的相关配置(支持的配置项没有全用),其他配置详见:评论通知官方文档。如果你不想配置评论通知可直接跳过本节。

4.1 Telegram Bot通知

4.1.1 申请Telegram bot

找 BotFather 官方机器人申请自己的 Telegram Bot,需要记录下:BotName、TOKEN、CHATID等信息。

  • Step1:在Telegram中添加BotFather这个账号,然后依次发送/start,/newbot,按照提示即可创建一个新的机器人。记下来给你生成的token。(可使用/setuserpic命令更换机器人的头像)
  • Step2:搜索刚刚创建的机器人的名字,并给它发送一条消息。(注意:需要先与机器人之间创建会话,机器人才能下发消息,否则机器人无法主动发送消息)
  • Step3:在Telegram中搜索userinfobot,并给它发送一条消息,它会返回给你chatid,将它也记下来。

注:如果还不熟悉Telegram bot的使用,可以去看下我的另一篇博客:Telegram及Telegram-bot使用指南

4.1.2 Telegram Bot通知相关配置

Telegram 通知通过 Telegram bot 机器人实现,需要配置以下几个环境变量:

  • TG_BOT_TOKEN: Telegram 机器人用于访问 HTTP API 的 token,必填。
  • TG_CHAT_ID: 接收消息对象的 chat_id,可以是单一用户、频道、群组,必填。
  • AUTHOR_EMAIL: 博主邮箱,用来区分发布的评论是否是博主本身发布的。如果是博主发布的则不进行提醒通知。
  • SITE_NAME: 网站名称,用于在消息中显示。
  • SITE_URL: 网站地址,用于在消息中显示。

4.2 Gmail通知

注意这里有个坑,一开始我是用的邮箱账号和邮箱密码配的,但无法实现消息通知,查看Waline日志里却提示登录失败,可账号密码都是正确的。

Gmail登录失败的Waline日志

查阅资料后得知,需要先对Gmail进行“开启IMAP”和“单独应用密码”设置,然后用单独应用密码登录。

注:出于安全性考虑,你的Gmail邮箱可能配了二级验证等安全性设置,在这里用邮箱密码可能登录不上。

4.2.1 Gmail开启IMAP

登录Gmail——点击设置按钮(齿轮样式)——查看所有设置——转发和POP/IMAP——启用IAMP

IMAP设置

4.2.2 创建Gmail的单独应用密码

登录你的 Google账号管理,选择”安全性“,”登录Google“模块里有个”应用专用密码设置“,点开之后选择应用、选择设备,生成一个16位的单独应用密码。

4.2.3 Gmail通知相关配置

Gmail邮件通知需要配置以下环境变量:

  • SMTP_SERVICE: SMTP 邮件发送服务提供商,必填。(这里填”Gmail”即可)
  • SMTP_USER: SMTP 邮件发送服务的用户名,必填。(这里填”Gmail邮箱账号”即可)
  • SMTP_PASS: SMTP 邮件发送服务的密码,必填。(这里填“Gmail的单独应用密码”即可)
  • SITE_NAME: 网站名称,用于在消息中显示。
  • SITE_URL: 网站地址,用于在消息中显示。

4.3 重新创建容器以实现评论通知

Step1:先停止并删除原来的Docker容器(MySQL数据库不要删)

1
2
$ docker stop waline
$ docker rm -f waline

Step2:重新创建容器应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ docker run -d --name waline -p 8360:8360 \
-v /mydocker/waline/data:/app/data \
-e TZ="Asia/Shanghai" \
-e MYSQL_HOST="172.17.0.1" \
-e MYSQL_DB="数据库名" \
-e MYSQL_USER="用户名" \
-e MYSQL_PASSWORD="密码" \
-e TG_BOT_TOKEN="Telegram-Bot的Token" \
-e TG_CHAT_ID="Telegram-Bot的CHAT-ID" \
-e AUTHOR_EMAIL="博主邮箱账号" \
-e SITE_NAME="网站名称" \
-e SITE_URL="网站地址" \
-e SMTP_SERVICE="Gmail" \
-e SMTP_USER="Gmail邮箱账号" \
-e SMTP_PASS="Gmail邮箱单独应用密码" \
--restart always \
lizheming/waline:latest

这时应该就可以实现 Telegram Bot 和 Gmail 的评论通知了。

5. 引入Waline客户端

5.1 Waline前端配置

接下来就是往项目里引入Waline的前端了,具体情况具体分析,配置参数详见官方文档 Waline前端配置,摘录部分配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- serverURL:Waline 的服务端地址。【必填】
- el:Waline 的初始化挂载器。必须是一个有效的 CSS 选择器 或 HTMLELement 对象。【不必填】
- path:当前文章页路径,用于区分不同的文章页,以保证正确读取该文章页下的评论列表。默认值:window.location.pathname【不必填】
- lang:多语言支持,默认值: zh-CN (可选值:'zh'、'zh-CN'、'zh-TW'、'en'、'en-US'、'jp'、'jp-JP')【不必填】
- visitor:文章访问量统计。默认值: false【不必填】
- emoji:表情设置。默认值: ['https://cdn.jsdelivr.net/gh/walinejs/emojis/weibo']【不必填】
- dark:暗黑模式适配,设置为auto会根据设备暗黑模式自适应【不必填】
- meta:评论者相关属性。默认值: ['nick', 'mail', 'link']【不必填】
- requiredMeta:设置评论者属性必填项。默认值:[](即匿名)【不必填】
- login:登录模式状态,默认值:enable('enable': 启用登录、'disable': 禁用登录,用户只能填写信息评论、'force': 强制登录,用户必须注册并登录才可发布评论)【不必填】
- avatar:Gravatar头像展示方式。默认值:'mp'【不必填】
- wordLimit:评论字数限制,填入单个数字时为最大字数限制。默认值:0(即不限制)【不必填】
- pageSize:评论列表分页,每页条数。默认值:10【不必填】
- avatarCDN:设置Gravatar头像CDN地址。默认值: https://sdn.geekzu.org/avatar/【不必填】
- avatarForce:每次访问是否强制拉取最新的评论列表头像。默认值:false【不必填】
- uploadImage:自定义图片上传方法,方便更好的存储图片,方法执行时会将图片对象传入。【不必填】
- highlight:代码高亮显示。默认值:true【不必填】
- mathTagSupport:是否注入核外样式以兼容 <math> 显示,默认值:false【不必填】
- copyright:是否显示页脚版权信息。默认值true【不必填】
- placeholder:评论框占位提示符。默认值:'欢迎评论'【不必填】--即将废弃
- emojiCDN:设置表情包 CDN。默认值:https://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/【不必填】--即将废弃

5.2 将Waline整合到kratos-rebirth主题

kratos-rebirth主题是一款二次元的Hexo主题,详见我的另一篇博客:搭建Hexo静态博客并使用Git部署到VPS

作者已经将Waline整合进去了,我们只需要修改该主题下_config.yml文件里的相关配置即可,下面是我的配置仅供参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Waline [Waline](https://waline.js.org) 评论相关
## el 和 path 会在页面自动生成,不必加入
waline:
serverURL: https://域名/ # 后端URL(必填)
visitor: true # 文章访问量统计
lang: zh-CN # 语言,默认zh-CN
dark: auto # 黑暗模式适配
login: enable # 登录模式状态,默认值enable
wordLimit: 0 # 评论字数限制,0为不限制,默认值为0
pageSize: 10 # 评论列表分页,数字为条数,默认值10
highlight: true # 代码高亮,默认true
meta: ['nick', 'mail'] # 评论者相关属性,默认['nick', 'mail', 'link']
requiredMeta: [] #设置评论者属性必填项,默认[](即匿名)
placeholder: '随便说点什么喵~o(=•ェ•=)m \n没有登录也是完全没问题的哦~' # 评论框占位提示符,默认'欢迎评论'
copyright: false # 是否显示页脚版权信息

修改完配置后,执行hexo cleanhexo s -g重新部署即可看到效果了,使用效果如下:

E1Z6gmhRSn

至此,Waline的搭建及整合已经完成啦。可以去测试一下代码块、图片上传、表情、评论嵌套、评论通知等功能是否可以正常使用,并检查后台管理功能。

6. 参考资料

[1] Waline快速上手 from Waline官方手册

[2] Waline介绍 from Waline官方手册

[3] Waline前端配置 from Waline官方手册

[4] 一款基于 Valine 衍生的简洁、安全的评论系统 from Github

[5] 从Disqus迁移到Waline from 糖菓·部落

[6] Kratos-Rebirth食用说明 from 糖菓·部落

[7] Waline 评论系统部署日志 from cc的部落格

[8] Hexo Next 主题使用 Waline 评论系统 from Clay的技术博客

[9] docker中的MySQL修改时区 from itpub

[10] Gmail SMTP问题解决汇总 from CSDN

[11] Gmail使用应用专用密码登录 from Google官方帮助