如何搭建个人网页(十)—— 评论系统(二)

开始

本章主要讲如何为个人网站增加评论系统。

上一章搭评论系统用的是 Waline+Leancloud数据库+Vercel服务端 的方案,今天介绍一种 服务器docker + waline + mysql 方案,还是继续用之前买的阿里云ECS服务器。

首先要说明,该方案要比上一章的方案复杂一些,主要是由于要为服务器配置SSL证书,还要求域名备案,好处在于你能对评论数据库和服务端有完全的掌控,请参考选择。

安装docker并拉取所需镜像

进入服务器命令行,下载并安装docker:

1
2
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

然后可以进行docker换源,但是总感觉服务器的dockerhub不是最新的,所以我选择了在本地机的docker拉取所需的镜像,然后通过docker save和scp上传到服务器中,再用docker load加载镜像,具体如下(如果你能确保dockerhub能拉到最新的镜像,直接在服务器里运行下面的两个docker pull命令即可):

在本地机中(确保你本地机安装了docker,并且docker服务已启动):

1
2
3
4
5
6
7
docker pull mysql:8.0  # 拉取 MySQL8.0 镜像
docker save -o mysql.tar mysql:8.0 # 将 MySQL8.0 镜像保存为tar文件
scp mysql.tar root@<你的服务器公网ip>:~ # 将 mysql.tar 文件通过 SCP 传输到服务器

docker pull lizheming/waline:latest # 拉取 Waline 镜像
docker save -o waline.tar lizheming/waline:latest
scp waline.tar root@<你的服务器公网ip>:~

然后在服务器中:

1
2
3
docker load -i mysql.tar # 从tar文件加载 MySQL 镜像
docker load -i waline.tar
docker images # 查看所有docker镜像

image-20241217224933907

别忘了把本地机和服务器里的tar文件删除,以免占用空间。

配置安全组

到阿里云ECS控制台,左侧 网络与安全/安全组,创建一个安全组,入规则方向,手动添加两个规则,协议类型选TCP,端口分别为8360和3306,授权对象为所有ipv4,如下图:

b6e858ac943134d3ad794603cb468629

然后选择刚刚创建的安全组,进入实例列表,实例加入安全组,实例选择你的服务器,确定。

299e27ec292904e1620f729f3127600d

部署MySQL数据库

首先创建一个docker卷,用以持久化数据:

1
docker volume create mysql-data

使用docker快速部署mysql数据库:

1
2
3
4
5
6
docker run -d --name waline-mysql \
-v mysql-data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=<root密码> \
-e MYSQL_DATABASE=db_waline \
-p 3306:3306 \
mysql:8.0

输入docker ps -a查看容器是否运行正常。

然后获取waline.sql,但是官网给的这个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
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
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
SET NAMES utf8mb4;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@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 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,
`sticky` boolean DEFAULT NULL,
`status` varchar(50) NOT NULL DEFAULT '',
`like` int(11) DEFAULT NULL,
`ua` text,
`url` varchar(255) DEFAULT NULL,
`createdAt` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`updatedAt` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
INDEX `idx_comment_url` (`url`),
INDEX `idx_comment_user_id` (`user_id`),
INDEX `idx_comment_status` (`status`),
INDEX `idx_comment_pid_rid` (`pid`, `rid`),
INDEX `idx_comment_created_at` (`createdAt`),
INDEX `idx_comment_updated_at` (`updatedAt`),
INDEX `idx_comment_sticky` (`sticky`)
) 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,
`reaction0` int(11) DEFAULT NULL,
`reaction1` int(11) DEFAULT NULL,
`reaction2` int(11) DEFAULT NULL,
`reaction3` int(11) DEFAULT NULL,
`reaction4` int(11) DEFAULT NULL,
`reaction5` int(11) DEFAULT NULL,
`reaction6` int(11) DEFAULT NULL,
`reaction7` int(11) DEFAULT NULL,
`reaction8` 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`),
INDEX `idx_counter_url` (`url`),
INDEX `idx_counter_time` (`time`),
INDEX `idx_counter_created_at` (`createdAt`)
) 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 '',
`label` varchar(255) DEFAULT NULL,
`url` varchar(255) DEFAULT NULL,
`avatar` varchar(255) DEFAULT NULL,
`github` varchar(255) DEFAULT NULL,
`twitter` varchar(255) DEFAULT NULL,
`facebook` varchar(255) DEFAULT NULL,
`google` varchar(255) DEFAULT NULL,
`weibo` varchar(255) DEFAULT NULL,
`qq` varchar(255) DEFAULT NULL,
`2fa` varchar(32) DEFAULT NULL,
`createdAt` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`updatedAt` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE INDEX `idx_user_email` (`email`),
INDEX `idx_user_type` (`type`),
INDEX `idx_user_created_at` (`createdAt`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;




/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

再在容器中执行该sql脚本:

1
docker exec -i waline-mysql mysql -u root -p<root密码> db_waline < waline.sql

使用DataGrip测试mysql能否连接,同时查看表是否成功创建(也可以其他方法测试):

3671a41ad2b2ec487f4aba1f3932f961

64c0cbd3514c8c1ba0d269f0556ba048

部署Waline服务

然后使用docker快速部署waline服务:

1
2
3
4
5
6
7
8
9
10
docker run -d --name waline-server \
-p 8360:8360 \
-e MYSQL_HOST="<服务器公网ip>" \
-e MYSQL_PORT="3306" \
-e MYSQL_DB="db_waline" \
-e MYSQL_USER="root" \
-e MYSQL_PASSWORD="< root密码>" \
-e MYSQL_PREFIX="wl_" \
-e MYSQL_CHARSET="utf8mb4" \
docker.io/lizheming/waline

输入docker ps -a查看容器是否运行正常。

image-20241217233935344

此时访问 <你的服务器公网ip>:8360 测试是否能访问waline服务,并输入评论测试。

a7433f7ca656ec5bc66ff4b951bf773e

此时可能会出现以下的报错:

8cb87c40994c6a4bd3d4387daaee0858

这是由于 MySQL 8.0 引入了一个新的身份验证插件 caching_sha2_password,而较旧的 MySQL 客户端(包括某些 Node.js MySQL 客户端)默认不支持这种认证方式。

解决:进入 MySQL 容器并修改 waline_user 用户的认证方式:

1
docker exec -it waline-mysql mysql -u root -p

然后在 MySQL 控制台中执行以下 SQL 命令来更改认证方式:

1
2
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '<root密码>';
FLUSH PRIVILEGES;

再次尝试,成功!

image-20241218010327336

配置Hexo next

首先,在blog/目录下安装waline:

1
npm install @waline/hexo-next --save

然后打开next主题配置文件_config.next.yml,找个地方输入以下内容并保存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Waline 评论
# For more information: https://waline.js.org, https://github.com/walinejs/waline
waline:
enable: true
serverURL: http://<你的公网ip>:8360 # Waline服务端地址
placeholder: 请文明评论呀~ # 评论框的默认文字
avatar: mm # 头像风格
meta: [nick, mail, link] # 自定义评论框上面的三个输入框的内容
pageSize: 10 # 评论数量多少时显示分页
lang: zh-cn # 语言, 可选值: en, zh-cn
# Warning: 不要同时启用 `waline.visitor` 以及 `leancloud_visitors`.
visitor: true # 文章阅读统计
comment_count: true # 如果为 false , 评论数量只会在当前评论页面显示, 主页则不显示
requiredFields: [nick] # 设置用户评论时必填的信息,[nick,mail]: [nick] | [nick, mail]
libUrl: # Set custom library cdn url

然后使用命令三件套测试,到网页进行评论:

image-20241218012550130

配置SSL证书

但此时,如果你hexo d部署后,如果尝试进行评论会报错“Failed to fetch”,查看docker logs waline-server会发现报错:

1
Mixed Content: The page at 'https:/xxxxxxx' was loaded over HTTPS, but requested an insecure resource 

这是典型的 混合内容问题(Mixed Content)。具体来说,问题出现在你的网站是通过 HTTPS 加载的,但是你请求的 Waline 服务是通过 HTTP 协议进行访问的。由于现代浏览器的安全策略,浏览器会阻止从 HTTPS 页面加载 HTTP 资源,从而导致评论功能无法正常工作。

因此我们需要为Waline 后端设置 SSL 证书,使其能够通过 HTTPS 协议提供服务。

设置子域名

由于 Let’s Encrypt 只会为域名发放证书,而不会给IP地址发放,因此我们需要为waline服务(也就是服务器)绑定一个域名。但是我们的域名已经绑定到我们的网站了,怎么办呢?

可以将服务器绑定到一个子域名,比如我们的域名是 jinqiqing.cn,那么我们将服务器IP绑定到 ecs.jinqiqing.cn 和 waline.jinqiqing.cn 两个子域名(这两个子域名都指向同一个ip,只是为了名字好看)。在域名控制台,进入 域名解析,按如下添加记录。

此外,以后还可以使用ssh root@ecs.jinqiqing.cn的方式连接服务器了,不用再记难记的ip地址。

7243b6100cff0ad396dfaff281c56f49

为域名配置SSL证书

使用免费的 Let’s Encrypt SSL 证书,配合 Certbot 工具来自动化安装和配置证书,在服务器执行命令:

1
2
apt install certbot
certbot certonly --standalone -d <你的服务器域名>

然后按照提示操作。结果我遇到了403 Forbidden报错:

c64456c40a7c3d4ee0a459fca42abb4e

结果发现:

bc121a041a97e2e58a164772ea7bf214

看来还是需要进行域名备案啊。

备案成功后,再次尝试:
7d5c66aded438104337d37340c69fb8b

成功,同时可以发现,证书有效期是90天

然后用nginx配置证书:

1
2
apt install nginx python3-certbot-nginx
certbot --nginx

然后会跳出交互,问为哪个域名配置,输入域名。然后会问是不是用已有的证书配置,输入 1(是)。

但此时,我们只是能通过https访问ecs.jinqiqing.cn,还不能访问https://ecs.jinqiqing.cn:8360;但是我们不能用nginx配置监听8360端口,因为waline服务占用了该端口。一个取巧的办法是,我们让网站访问https://ecs.jinqiqing.cn:8359端口,然后让nginx监听ssl的8359端口,同时转发到8360,这样就完成了目标。

1
vim /etc/nginx/sites-enable/default

编辑该文件,在最后加入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server {
listen 8359 ssl; # 监听8359端口
server_name 服务器域名;

ssl_certificate /etc/letsencrypt/live/服务器域名/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/服务器域名/privkey.pem;

ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:ECDHE-RSA-AES128-GCM-SHA256';

location / {
proxy_pass http://localhost:8360; # 转发请求到本地的 8360 端口
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

保存后重启nginx服务:

1
systemctl restart nginx

别忘了在服务器的安全组中加上8359端口。

把hexo-next中的配置修改为:

1
serverURL: https://服务器域名:8359 # Waline服务端地址

然后进行测试。

image-20241223115439656

成功!