Hexo Next 主题使用 Waline 评论系统

前言

版本说明

本文使用的各软件版本如下所示,教程内容虽然会持续更新,但一切内容以 Waline 官方文档为准。

软件 版本 描述
linux CentOS 7.9
docker 20.10.5
mysql 5.7.26
node 14.16.1
hexo 5.4.0
hexo-cli 4.2.0
next 8.5.0
waline-admin 0.9.0
waline-client 1.3.3

Hexo 评论系统选择

Hexo 各类评论系统的对比可看这里,之前博客一直使用的评论系统是基于 Github Issue 的 Utterances。由于 Utterances 默认没有 CDN 加速,经常造成页面加载完成后无法正常显示 Utterances 的评论区,而且需用用户登录 Github 账号才能评论,因此打算更换博客的评论系统。国外的 Disqus、Hypercomments 暂时不考虑,国内访问被墙的概率很大。Gitment、Gitalk、Gitter 和 Utterances 一样,访问速度不太稳定,且登录 Github 才能评论,暂时也不考虑。这一波排除下来,剩下的方案只有 Valine、Isso、Waline 或者 自建评论系统。考虑到 Valine 依赖 Leancloud 第三方服务,且需要在 Leancloud 额外部署 Valine Admin 才能实现邮件通知与评论管理等功能,这样一来感觉也不靠谱,万一 Leancloud 以后退出商业市场竞争呢?综合考虑下来,最终选择了 Waline 评论系统,一款从 Valine 衍生的带后端评论系统,支持多种部署方式和数据存储方式,这样就可以省去 自建评论系统 的开发成本,同时也可以尽量少依赖第三方服务,增加日后扩展和维护的自由度。

Hexo 评论系统介绍

Valine 评论系统

Valine 是一款基于 Leancloud 的快速、简洁且高效的无后端评论系统,用户无需登录即可评论,目前已有 Hexo、Jekyll、Typecho、Hugo、Ghost 等博客程序在使用。由于 Valine 自身不支持邮件通知支持,因此诞生了 Valine Admin 开源项目;一个对 Valine 评论系统的拓展应用,可增强 Valine 的邮件通知功能;基于 Leancloud 的云引擎与云函数,主要实现评论邮件通知、评论管理、自定义邮件通知模板等功能,而且还可以提供邮件 通知博主@ 通知 的功能。

Waline 评论系统

Waline 一款从 Valine 衍生的带后端评论系统,可以将 Waline 等价成 With backend Valine,采用 Client/Server 架构并基于 NodeJS 开发。Valine 支持 MarkDown 语法、邮件通知、评论管理、多种部署方式多种数据存储方式

Waline
客户端脚本 服务端部署 数据存储
@waline/client Vercel LeanCloud
MiniValine CloudBase CloudBase
Docker MongoDB
独立部署 MySQL
SQLite
PostgreSQL
Github

Waline 支持的功能:

  • 邮件通知
  • 微信通知
  • QQ 通知
  • Telegram 通知
  • Akismet 反垃圾评论
  • 文章统计
  • 多语言
  • 自定义语言支持
  • 登录支持
  • 评论管理
  • 评论删除
  • 其它数据库服务支持(已支持 LeanCloud, MySQL, MongoDB, SQLite, PostgreSQL)
  • 基于 IP 的评论发布频率限制
  • 基于关键词的评论过滤限制
  • IP 黑名单
  • 重复内容检测
  • CloudBase 腾讯云开发部署支持
  • 社交登录
  • AWS, GCP, Azure 部署支持
  • 置顶评论
  • 评论赞踩

Docker 部署 Waline Server

笔者使用 Docker 部署 Waline Server,数据存储方式选择 MySQL,容器管理工具使用 Docker-Compose。

Doker-Compose 部署 MySQL

下述的配置指定了 MySQL 容器的静态 IP 地址与系统时区,yourPassword 为数据库密码,/usr/local/docker-volumes/mysql 是 MySQL 容器的数据卷目录路径

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
version: "3.5"

services:
mysql:
image: mysql:5.7.26
container_name: waline-mysql
restart: always
privileged: false
environment:
TZ: 'Asia/Shanghai'
MYSQL_ROOT_PASSWORD: yourPassword
ports:
- 3306:3306
networks:
waline-network:
ipv4_address: 172.23.0.3
volumes:
- '/usr/local/docker-volumes/mysql/conf:/etc/mysql/conf.d'
- '/usr/local/docker-volumes/mysql/data:/var/lib/mysql'
- '/usr/local/docker-volumes/mysql/log:/var/log/mysql'
command: --default-authentication-plugin=mysql_native_password

networks:
waline-network:
name: waline-network
driver: bridge
ipam:
config:
- subnet: 172.23.0.0/24

MySQL 数据库表初始化

创建并启动 MySQL 的 Docker 容器后,导入最新的 waline.sql 脚本来创建好 Waline Server 所需的数据库表,其中相关表的结构如下:

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
CREATE DATABASE waline DEFAULT CHARACTER SET utf8mb4;

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;

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;

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;

构建 Waline Server 的 Docker 镜像

若不希望自己构建 Waline Server 的 Docker 镜像,可以直接使用 Waline 官方的 Docker 镜像

1
2
3
4
5
6
# 拉取最新的代码
$ git clone https://github.com/lizheming/waline.git

# 构建镜像
$ cd waline
$ docker build -t lizheming/waline -f packages/server/Dockerfile .

值得一提的是,官方提供的 Dockerfile 默认会使用最新的 Waline Server 代码来构建 Docker 镜像,若希望使用本地的 Waline Server 代码来构建 Docker 镜像,需要自行更改 Dockerfile 的内容(如下所示),为了统一使用东八区时区,建议更改系统默认的时区。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# https://github.com/nodejs/LTS
FROM node:lts AS build
WORKDIR /app
ENV NODE_ENV production
RUN set -eux; \
# npm config set registry https://registry.npm.taobao.org; \
npm install --production --silent @waline/vercel
# use local source code
RUN rm -rf /app/node_modules/@waline/vercel/src/*
COPY ./src/ /app/node_modules/@waline/vercel/src

FROM node:lts-buster-slim
WORKDIR /app
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
ENV TZ Asia/Shanghai
ENV NODE_ENV production
COPY --from=build /app .
EXPOSE 8360
CMD ["node", "node_modules/@waline/vercel/vanilla.js"]

Waline 采用的是 Client/Server 架构,客户端与服务端的版本必须匹配才能正常运行。由于 Waline Server 默认会使用最新版本的 Waline Client,建议更改 waline/packages/server/src/controller/index.js 源文件来指定 Client 的版本(如下所示),日后统一更新客户端与服务端的版本即可,更改后使用本地代码重新构建 Docker 镜像才会生效

1
2
3
4
5
6
7
8
9
10
<body>
<div id="waline" style="max-width: 800px;margin: 0 auto;"></div> <script src="https://cdn.jsdelivr.net/npm/@waline/client@1.3.3/dist/Waline.min.js"></script>
<script>
new Waline({
el: '#waline',
path: '/',
serverURL: location.protocol + '//' + location.host + location.pathname.replace(/\\/+$/, '')
});
</script>
</body>

若构建 Waline Server 的 Docker 镜像时,一直卡在 NPM 安装模块的过程里,可以更改对应的 Dockerfile(如下所示),使用淘宝的 NPM 源来加速 NPM 模块下载

1
2
3
4
5
$ vim packages/server/Dockerfile
...
npm config set registry https://registry.npm.taobao.org; \
npm install --production --silent @waline/vercel
...

Docker-Compose 部署 Waline Server

在上面 Docker-Compose 配置的基础上,指定 Waline Server 容器的静态 IP 地址 与 Waline Server 启动时所需的环境变量

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
version: "3.5"

services:
mysql:
image: mysql:5.7.26
container_name: waline-mysql
restart: always
privileged: false
environment:
TZ: 'Asia/Shanghai'
MYSQL_ROOT_PASSWORD: yourPassword
ports:
- 3306:3306
networks:
waline-network:
ipv4_address: 172.23.0.3
volumes:
- '/usr/local/docker-volumes/mysql/conf:/etc/mysql/conf.d'
- '/usr/local/docker-volumes/mysql/data:/var/lib/mysql'
- '/usr/local/docker-volumes/mysql/log:/var/log/mysql'
command: --default-authentication-plugin=mysql_native_password

waline:
container_name: waline
image: lizheming/waline:latest
restart: always
privileged: false
depends_on:
- mysql
ports:
- 8360:8360
networks:
waline-network:
ipv4_address: 172.23.0.4
environment:
TZ: "Asia/Shanghai"
AKISMET_KEY: "false"
DISABLE_USERAGENT: "true"
SITE_NAME: "Your site name"
SECURE_DOMAINS: "example.cn"
AUTHOR_EMAIL: "example@qq.com"
SITE_URL: "https://www.example.cn"
MYSQL_HOST: 172.23.0.3
MYSQL_PORT: 3306
MYSQL_DB: waline
MYSQL_PREFIX: wl_
MYSQL_USER: root
MYSQL_PASSWORD: yourPassword
volumes:
- /usr/local/waline/data:/app/data

networks:
waline-network:
name: waline-network
driver: bridge
ipam:
config:
- subnet: 172.89.0.0/24

Waline 服务端环境变量说明

Waline 服务端的基础环境变量如下:

环境变量名称 必填 默认值 备注
TZ 时区
SITE_URL 站点 URL
SITE_NAME 站点名称
AUTHOR_EMAIL 博主邮箱
SECURE_DOMAINS 安全域名配置,支持逗号分隔配置多个域名,配置后非该域名来源的请求会返回 403 状态码,不配置表示允许所有域名来源
IPQPS 60 基于 IP 的评论发布频率限制,单位为秒。默认为 60 秒,设置为 0 不限制
DISABLE_USERAGENT false 是否隐藏评论者的 UA,默认为否
COMMENT_AUDIT 评论发布审核开关,默认为否,配置后建议在 Placehoder 上提供文案提示
AKISMET_KEY Akismet 反垃圾评论服务的 Key(默认开启,不用请设置为 false,关闭后可以加快评论提交的速度)
AVATAR_PROXY https://avatar.75cdn.workers.dev/ 头像 CDN 代理
LOGIN 当设置为 LOGIN: 'force' 时,服务端会要求客户端必须登录才能评论;同时 Waline 客户端需要增加 login: force 的配置用于隐藏匿名输入框
  • COMMENT_AUDIT:阅读源代码发现,环境变量中不配置 COMMENT_AUDIT,则默认关闭评论发布的审核功能,只要在环境变量中添加了该属性,无论属性值是什么,都会开启评论发布的审核功能

Waline 服务端的 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 数据表的字符集

Nginx 反向代理配置

若使用 Nginx 作为 Waline Server 的反向代理,可参考以下配置内容。

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
server {
listen 80;
listen 443 ssl;
server_name www.example.com

if ($server_port !~ 443){
rewrite ^(/.*)$ https://$host$1 permanent;
}

# SSL 证书
ssl_certificate /usr/local/nginx/cert/waline.example.com.crt;
ssl_certificate_key /usr/local/nginx/cert/waline.example.com.key;

# SSL 性能调优
ssl_session_timeout 10m;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-SHA384:AES256-SHA256:RC4:HIGH:!MD5:!aNULL:!eNULL:!NULL:!DH:!EDH:!AESGCM;
add_header Strict-Transport-Security 'max-age=31536000';

location / {
# 反向代理
proxy_pass http://$server_name;
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;
proxy_set_header REMOTE-HOST $remote_addr;

# 缓存
add_header X-Cache $upstream_cache_status;
add_header Cache-Control no-cache;
expires 12h;
}
}

Nginx 配置跨域访问

默认情况下,当在 Waline 服务端的环境变量里添加了 SITE_URL 属性,那么 Nginx 的反向代理不再需要配置跨域,因为 Waline Server 会自动将 SITE_URL 添加到允许跨域的名单里。若 Waline Server 自带的跨域配置不能满足要求,可以参考以下内容自行配置 Nginx 的跨域。

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
location /comment {
# 清除Waline自带的跨域Header
proxy_hide_header Access-Control-Allow-Origin;
proxy_hide_header Access-Control-Allow-Methods;
proxy_hide_header Access-Control-Allow-Headers;
proxy_hide_header Access-Control-Allow-Credentials;

# 添加自定义的跨域Header
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Allow-Methods' 'GET,HEAD,PUT,POST,DELETE,PATCH,OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Accept,Authorization,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Origin' always;

# 反向代理
proxy_pass http://$server_name;
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;
proxy_set_header REMOTE-HOST $remote_addr;

# 缓存
add_header X-Cache $upstream_cache_status;
add_header Cache-Control no-cache;
expires 12h;
}

Waline Server 测试

分别创建并启动 MySQL 与 Waline Server 容器,然后浏览器访问 http://ip:port/ui/register,打开 Waline Server 的 Web 管理界面进行注册,第一个注册的用户会被 Waline Server 识别为系统管理员(博主),成功登录后的界面如下:

hexo-waline-ui

若 Waline Server 的 Web 管理界面无法正常访问,可以使用以下命令查看 Docker 容器的日志来定位问题

1
# docker logs -f --tail 20 waline

Next 主题安装 Waline 官方插件

安装 Waline 官方插件

Waline 官方插件的版本必须与 Next 主题的版本匹配,否则 Waline 官方插件无法正常使用,两者的兼容性说明如下:

Next 主题的版本 Waline 官方插件的版本
<= 8.3.0 <= 1.0.8
>= 8.4.0 >= 2.0.0
1
2
3
4
5
# 进入博客的根目录
$ cd /blog-root

# 安装Waline插件(最新版)
$ npm install @waline/hexo-next --save

配置 Waline 官方插件

更改 Next 主题的配置文件 themes/next/_config.yml,添加以下内容,其中 serverURL 是 Waline 服务端的访问 URL,需要自行更改该属性的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Waline
# For more information: https://waline.js.org, https://github.com/lizheming/waline
waline:
enable: true
serverURL: https://waline.example.com # Waline server address url
placeholder: 'Just go' # Comment box placeholder
avatar: mm # Gravatar style
meta: [nick, mail, link] # Custom comment header
pageSize: 10 # Pagination size
lang: zh-cn # Language, available values: en, zh-cn
visitor: false # Article reading statistic
comment_count: true # If false, comment count will only be displayed in post page, not in home page
requiredMeta: [] # Set required fields: [nick] | [nick, mail]
libUrl: # Set custom library cdn url

更改 Next 主题的样式

由于 Next 主题默认对所有图片都添加了 display: block; CSS 样式,这会导致 Waline 的表情包图片独立一行显示,需要往 Next 主题里添加以下自定义样式来解决,例如更改样式文件 themes/next/source/css/_common/scaffolding/base.styl

1
2
3
4
5
6
7
.vcontent p img {
display: inline;
}

.vpreview p img {
display: inline;
}

Hexo 构建失败的解决方法

若 Next 主题安装 Waline 官方插件后,执行 hexo g 命令抛出以下异常,这是由于 Hexo 或者 Next 的版本过低导致,此时需要升级 Hexo 或者 Next 的版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
ERROR Render HTML failed: index.html
TypeError: Cannot read property 'parent' of null
at Function.exports.update (/usr/local/hexo/node_modules/cheerio/lib/parse.js:55:26)
at module.exports (/usr/local/hexo/node_modules/cheerio/lib/parse.js:17:11)
at Function.exports.load (/usr/local/hexo/node_modules/cheerio/lib/static.js:22:14)
at Hexo.hexoMetaGeneratorInject (/usr/local/hexo/node_modules/hexo/lib/plugins/filter/meta_generator.js:8:21)
at Hexo.tryCatcher (/usr/local/hexo/node_modules/bluebird/js/release/util.js:16:23)
at Hexo.<anonymous> (/usr/local/hexo/node_modules/bluebird/js/release/method.js:15:34)
at Promise.each.filter (/usr/local/hexo/node_modules/hexo/lib/extend/filter.js:60:50)
at tryCatcher (/usr/local/hexo/node_modules/bluebird/js/release/util.js:16:23)
at Object.gotValue (/usr/local/hexo/node_modules/bluebird/js/release/reduce.js:166:18)
at Object.gotAccum (/usr/local/hexo/node_modules/bluebird/js/release/reduce.js:155:25)
at Object.tryCatcher (/usr/local/hexo/node_modules/bluebird/js/release/util.js:16:23)
at Promise._settlePromiseFromHandler (/usr/local/hexo/node_modules/bluebird/js/release/promise.js:547:31)
at Promise._settlePromise (/usr/local/hexo/node_modules/bluebird/js/release/promise.js:604:18)
at Promise._settlePromiseCtx (/usr/local/hexo/node_modules/bluebird/js/release/promise.js:641:10)
at _drainQueueStep (/usr/local/hexo/node_modules/bluebird/js/release/async.js:97:12)
at _drainQueue (/usr/local/hexo/node_modules/bluebird/js/release/async.js:86:9)
at Async._drainQueues (/usr/local/hexo/node_modules/bluebird/js/release/async.js:102:5)
at Immediate.Async.drainQueues [as _onImmediate] (/usr/local/hexo/node_modules/bluebird/js/release/async.js:15:14)
at runCallback (timers.js:705:18)
at tryOnImmediate (timers.js:676:5)
at processImmediate (timers.js:658:5)

Waline 第三方插件列表

hexo-waline-next

  • hexo-waline-next,一款更强大且适用于 Next 主题的 Waline 插件,支持自定义 Gravatar 镜像源,支持上传评论图片到七牛图床

hexo-next-darkmode

  • hexo-next-darkmode,一款适用于 Waline 与 Next 主题的暗黑模式切换插件,详细的使用说明请看这里

Waline 高级配置

客户端使用 CDN

Waline 客户端若想使用 CDN 加速,只需在 Next 主题的 _config.yml 配置文件中,更改 Waline 官方插件的配置(如下所示)即可。由于 Waline 采用 Client/Server 架构,客户端与服务端的版本必须匹配才能正常运行,因此不建议每次自动都获取最新版的客户端 JS 文件,推荐日后统一更新客户端与服务端的版本。

1
2
3
4
5
6
7
# 获取最新版本的Waline
waline:
libUrl: https://cdn.jsdelivr.net/npm/@waline/client/dist/Waline.min.js

# 获取指定版本的Waline
waline:
libUrl: https://cdn.jsdelivr.net/npm/@waline/client@1.3.3/dist/Waline.min.js

验证用户注册邮箱

用户注册和评论的邮件通知都会用到邮件服务,配置邮件服务相关变量后,用户注册流程会增加邮箱验证码确认相关的操作,用来防止恶意的注册。

环境变量名称 备注
SMTP_SERVICE SMTP 邮件发送服务提供商
SMTP_HOST SMTP 服务器地址,一般可以在邮箱的设置中找到。
SMTP_PORT SMTP 服务器端口,一般可以在邮箱的设置中找到。
SMTP_USER SMTP 邮件发送服务的用户名,一般为登录邮箱。
SMTP_PASS SMTP 邮件发送服务的密码,一般为邮箱登录密码,部分邮箱 (例如 163) 是单独的 SMTP 密码。
SENDER_NAME 自定义发送邮件的发件人
SENDER_EMAIL 自定义发送邮件的发件地址

提示:可以在这里查看支持的服务商。SMTP_SERVICE 和 (SMTP_HOSTSMTP_PORT)任选其一即可,如果没有在列表中知道对应的 SMTP_SERVICE 的话则需要配 SMTP_HOSTSMTP_PORT

客户端配置用户头像

Waline 同 Valine 一样,目前使用 Gravatar 作为评论列表头像。首先博主或者用户自行使用邮箱登录或注册 Gravatar,然后更改自己的 Gravatar 头像。当在博客评论的时候,留下在 Gravatar 注册时所使用的邮箱即可,或者使用 Waline 客户端提供的登录功能,最后 Waline 会自动根据邮箱地址去 Gravatar 获取用户的头像。目前在 Waline 的最新版本中,默认使用的 Gravatar 镜像服务由” 极客族” 提供,镜像源为 https://sdn.geekzu.org/avatar/,Waline 客户端还可以通过代码设置 avatarCDN 属性来指定 Gravatar 的镜像源。目前 Waline 非自定义头像有以下 7 种默认值可选:

waline-avatar

在 Next 主题的 _config.yml 配置文件中,可以更改 Waline 官方插件的配置(如下所示)来指定头像显示的类型

1
2
waline:
avatar: retro

若 Waline 客户端默认使用的 Gravatar 镜像源被墙,导致 Gravatar 的头像无法正常加载显示,此时建议更换 Gravatar 的镜像源,具体教程可参考:Waline 头像加载很慢

服务端配置评论通知

当博客有用户发布评论或者用户回复评论时,Waline 支持对博主和回复评论作者进行通知。博主评论通知支持邮件、微信、QQ 与 Telegram,回复评论作者仅支持邮件通知。由于篇幅有限,这里仅介绍邮箱通知的配置,其他的通知方式可参考官方文档。邮件通知需要在环境变量中配置以下属性:

  • AUTHOR_EMAIL:博主邮箱,用来区分发布的评论是否是博主本身发布的。如果是博主发布的则不进行提醒通知。
  • SMTP_SERVICE:SMTP 邮件发送服务提供商,可以在 这里 查看所有支持的运营商。如果没在列表中的可以自行配置 SMTP_HOSTSMTP_PORT
  • SMTP_HOST:SMTP 服务器地址,一般可以在邮箱的设置中找到。如果未配置 SMTP_SERVICE 的话该项必填。
  • SMTP_PORT:SMTP 服务器端口,一般可以在邮箱的设置中找到。如果未配置 SMTP_SERVICE 的话该项必填。
  • SMTP_USER:SMTP 邮件发送服务的用户名,一般为登录邮箱。
  • SMTP_PASS:SMTP 邮件发送服务的密码,一般为邮箱登录密码,部分邮箱(例如 163)是单独的 SMTP 密码。
  • SMTP_SECURE: SMTP 邮件发送加密,默认为 true,设置为 false 则不会加密请求
  • SITE_NAME:网站名称,用于在消息中显示。
  • SITE_URL:网站地址,用于在消息中显示。
  • SENDER_NAME:自定义发送邮件的发件人,选填。
  • SENDER_EMAIL:自定义发送邮件的发件地址,选填。
  • MAIL_SUBJECT:评论回复邮件标题自定义
  • MAIL_TEMPLATE:评论回复邮件内容自定义
  • MAIL_SUBJECT_ADMIN:新评论通知邮件标题自定义
  • MAIL_TEMPLATE_ADMIN:新评论通知邮件内容自定义
  1. 用户注册和评论的邮件通知都会用到邮件服务
  2. 由于国内腾讯云、阿里云默认封了 25 端口,若 Waline 的服务端是部署在云服务器上,则需要使用 465 端口,并启用 SSL 邮件加密,最后系统防火墙别忘了开放 465 端口

Waline Client 的其他特性

Github 社交登录

最新版 Waline 增加了登录评论功能,除了普通的账号登录之外,还支持使用第三方社交账号进行直接登录。目前官方支持 Github 社交账号登录,当然默认没有开启 Github 社交账号登录功能,需要做一些配置才能支持。若要增加 Github 账号登录功能,需要配置 Github OAuth 密钥。点击 《Register a new OAuth application》 进入 Github OAuth 应用申请页面,这里需要填入以下几个配置:

  • Application name:应用名称,可以随意,会在用户授权时显示,推荐使用博客名称。
  • Homepage URL:应用主页地址,可以随意,会在用户授权时显示,推荐使用博客地址。
  • Appcation description:应用描述,可以随意,会在用户授权时显示,非必填项。
  • Authorization callback URL:应用的回调地址,登录时需要使用。填入 <serverURL>/oauth/github 其中 <serverURL> 是你的 Waline 服务端地址。

填完后点击 Register application 按钮就成功创建应用了,可以在页面中看到 Client ID。点击 Client secrets 栏右边的 Generate a new client secret 按钮,可以获取到该应用的 Client secrets


最后按照如下环境变量配置,将上面获取到的密钥(Client secrets)配置进 Waline 服务端的环境变量中,然后重新部署 Waline 服务端后即可使用 Github 登录。

环境变量名称 备注
GITHUB_ID 对应 Github OAuth Application 中的 Client ID
GITHUB_SECRET 对应 Github OAuth Application 中的 Client secrets

由于 Github 的 API 调用在国内不太稳定,建议直接使用普通的账号登录

自定义样式

Waline 客户端默认提供了一些 CSS 变量,可以很轻松的通过这些变量自定义 Waline 客户端的 CSS 样式:

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
:root {
/* 字体大小 */
--waline-font-size: 16px;

/* 常规颜色 */
--waline-white: #fff;
--waline-light-grey: #999;
--waline-dark-grey: #666;

/* 主题色 */
--waline-theme-color: #27ae60;
--waline-active-color: #2ecc71;

/* 布局颜色 */
--waline-text-color: #444;
--waline-bgcolor: #fff;
--waline-bgcolor-light: #f8f8f8;
--waline-border-color: #ddd;
--waline-disable-bgcolor: #f8f8f8;
--waline-disable-color: #ddd;

/* 特殊颜色 */
--waline-bq-color: #f0f0f0;

/* 头像 */
--waline-avatar-size: 3.25rem;

/* 徽章 */
--waline-badge-color: #3498db;
--waline-badge-font-size: 0.775em;

/* 信息 */
--waline-info-bgcolor: #f8f8f8;
--waline-info-color: #999;
--waline-info-font-size: 0.625em;

/* * 使用边框 * */
--waline-border: 1px solid var(--waline-border-color);
--waline-box-shadow: none;
}

如果使用了一个大量运用阴影 (box-shadow) 的主题,可以通过修改 --waline-border--waline-box-shadow 来更改 Waline 客户端的阴影样式,如:

1
2
3
4
5
6
7
8
9
10
:root {
--waline-border: none;
--waline-box-shadow: 0 12px 40px rgb(134 151 168 / 25%);
}

@media (prefers-color-scheme: dark) {
body {
--waline-box-shadow: 0 12px 40px #0f0e0d;
}
}

多语言支持

Waline 支持多语言,默认内置了以下语言,其中 Waline 的多语言翻译以及字段都是沿用了 Valine 的配置。

  • en
  • jp
  • zh-CN
  • zh-TW

自定义语言只需两步,可以参考以下 Waline 客户端的代码:

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
// 1.定义 langMode
var langMode = {
"nick": "NickName",
"mail": "E-Mail",
"link": "Website(http://)",
"nickFail":"NickName cannot be less than 3 bytes.",
"mailFail":"Please confirm your email address.",
"sofa": "No comment yet.",
"submit": "Submit",
"reply": "Reply",
"cancelReply": "Cancel reply",
"comments": "Comments",
"cancel": "Cancel",
"confirm": "Confirm",
"continue": "Continue",
"more": "Load More...",
"preview": "Preview",
"emoji": "Emoji",
"expand": "See more....",
"seconds": "seconds ago",
"minutes": "minutes ago",
"hours": "hours ago",
"days": "days ago",
"now": "just now",
"uploading":"Uploading ...",
"uploadDone":"Upload completed!",
"busy":"Submit is busy, please wait...",
"code-98":"Waline initialization failed, please check your version of av-min.js.",
"code-99": "Waline initialization failed, Please check the `el` element in the init method.",
"code-100": "Waline initialization failed, Please check your appId and appKey.",
"code-140":"The total number of API calls today has exceeded the development version limit.",
"code-401": "Unauthorized operation, Please check your appId and appKey.",
"code-403": "Access denied by API domain white list, Please check your security domain."
};

// 2.初始化 Waline
new Waline({
el:'#waline',
path: location.pathname,
serverURL: 'YOUR_SERVER_URL',
langMode: langMode
});

必须严格按照上面的 langMode 格式书写内容

自定义表情包

启用暗黑模式

Waline 客户端默认支持暗黑模式,只需在 Waline 客户端初始化的时候,指定 dark 参数即可

  • 设置 dark: auto 会根据设备暗黑模式自适应
  • 填入 CSS 选择器,则会在对应选择器生效时启用暗黑模式

针对不同的 Hexo 主题,配置示例如下:

  • Docusaurus:它会在 <html> 上通过设置 data-theme="dark" 开启暗黑模式,那么需要将 dark 选项设置为 'html[data-theme="dark"]'
  • hexo-theme-fluid:它会在 <html> 上通过设置 data-user-color-scheme="dark" 开启暗黑模式,那么需要将 dark 选项设置为 'html[data-user-color-scheme="dark"]'
  • vuepress-theme-hope:它会在 <body> 上添加 theme-dark class 来开启暗黑模式,那么需要将 dark 选项设置为 body.theme-dark

若 Hexo 使用的是 Next 主题,且是通过插件 hexo-next-darkmode 来自动添加可切换的暗黑模式,那么可以在 Next 主题的 _config.yml 配置文件里添加以下内容来启用 Waline 客户端的暗黑模式。前提是先卸载掉 Waline 官方的 Next 插件 @waline/hexo-next,然后再安装第三方插件 hexo-waline-next

1
2
3
4
waline:
enable: true
dark: 'body.darkmode--activated'
...

暗黑模式下 Waline 客户端默认会使用以下样式,若希望自定义暗黑模式的 CSS 样式,直接覆盖以下 CSS 样式即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
darkmode-selector {
/* 常规颜色 */
--waline-white: #000;
--waline-light-grey: #666;
--waline-dark-grey: #999;

/* 布局颜色 */
--waline-text-color: #888;
--waline-bgcolor: #1e1e1e;
--waline-bgcolor-light: #272727;
--waline-border-color: #333;
--waline-disable-bgcolor: #222;
--waline-disable-color: #272727;

/* 特殊颜色 */
--waline-bq-color: #272727;

/* 其他颜色 */
--waline-info-bgcolor: #272727;
--waline-info-color: #666;
}

Next 主题使用插件 hexo-next-darkmode 来自动添加可切换的暗黑模式,详细的步骤可以参考这篇博客

上传评论图片

Waline 客户端默认使用的图床是晚风图床,而且默认是以游客的身份将图片上传到 晚风图床。在 Hexo 的 Next 主题下,若希望使用七牛图床,可以安装 Waline 的第三方插件 hexo-waline-next 来实现。

1
2
3
4
5
# 卸载Waline官方插件
$ npm uninstall @waline/hexo-next --save

# 安装Waline第三方插件(最新版)
$ npm install hexo-waline-next --save

第三方插件 hexo-waline-next 使用了七牛官方的 Qiniu-JavaScript-SDK ,为了安全,默认没有包含 upload token 的生成实现,因此 upload token 需要通过网络从服务端(自建)获取,服务端代码可以参考七牛服务端 SDK 的文档,插件的配置示例如下:

1
2
3
4
5
6
waline:
enable: true
qiniuDebug: false # print the error message of the picture uploaded by qiniu
qiniuDomain: https://qiniu.example.cn # The custom domain for qiniu, e.g https://qiniu.example.cn
qiniuTokenUrl: https://api.example.cn/qiniu/sdk/token/upload # The api to get qiniu upload token, e.g https://api.example.cn/qiniu/sdk/token/upload
...
  • qiniuDomain:七牛的外链域名
  • qiniuDebug:是否打印七牛上传图片的错误信息
  • qiniuTokenUrl:获取七牛 Upload Token 的接口地址

第三方插件 hexo-waline-next 对七牛 Upload Token 接口返回数据(JSON)的定义如下:

1
2
3
4
{
"data": "tdvdhnpSs2JFt8U9-c9hL74ddWtEj",
"msg": "success"
}
参数名称 类型 实例值 说明
status code Number 200 HTTP 响应状态码,成功返回 200,非法请求来源返回 403,接口调用太频繁返回 429,系统内部出错返回 500
data String tdvdhnpSs2JFt8U9-c9hL74ddWtEj Upload Token 的值
msg String success 消息内容

提高七牛 Upload Token 接口的安全性:

  • 全站使用 HTTPS 协议
  • 通过 HTTP Header 的 refererX-Real-IPX-Forwarded-For 等来限制请求来源的域名、IP
  • 获取 Upload Token 的接口应该内置限流功能,避免外部恶意频繁调用接口,例如限制每分钟只能调用两次接口
  • 服务端生成 Upload Token 时,应该指定上传策略,例如设置 Token 的有效时间(expiresdeadline),具体可参考七牛官方文档一七牛官方文档二,Java 版服务端的示例代码如下:
1
2
3
4
5
6
7
8
9
10
11
String bucket = "bucket name";
String accessKey = "access key";
String secretKey = "secret key";

//指定Token的有效时间为20秒
long expireSeconds = 20;

StringMap putPolicy = new StringMap();
Auth auth = Auth.create(accessKey, secretKey);
String upToken = auth.uploadToken(bucket, null, expireSeconds, putPolicy);
System.out.println(upToken);

Waline 开发指南

准备工作

  1. 使用 Git 克隆项目
1
$ git clone https://github.com/lizheming/waline.git
  1. 安装依赖
1
2
3
$ cd waline

$ npm i
  1. 保证 NPM 的版本是 7
1
$ npm i -g npm@7

本地开发

本地使用以下命令启动 @waline/client,由于 Waline 采用 Client/Server 架构,在调试 client 时,需要设置 SERVERURL 为调试服务器的地址,或同时启动下面的 server 开发服务器并使用默认的 localhost:9090

1
$ npm run client:dev

本地使用以下命令启动 @waline/server,为了使 @waline/server 能在本地正常运行,需要配置必要的本地环境变量至 example/.env,其中在 example/.env.example 文件里有可参考的配置示例

1
$ npm run server:dev

编译构建

1
2
3
$ npm run admin:build

$ npm run client:build

Waline 常见问题

防止恶意刷评论

  • Waline 服务端启用评论审核功能
  • Waline 服务端启用基于 IP 的评论发布频率限制功能
  • Waline 服务端启用客户端登录后才允许评论的功能,确保服务端的版本 >= 0.26.0,同时 Waline 客户端增加 login=force 的配置用于隐藏匿名输入框

Waline 发布评论很慢

发布评论的时候因为一些特殊原因,例如垃圾邮件检测、评论通知都是串联操作。其中垃圾邮件检测使用的是 Akismet 提供的服务,这块由于调用国外服务可能会造成访问过慢,可以通过 AKISMET_KEY=false 后端环境变量关闭垃圾评论检测功能来定位问题。除了垃圾评论检测服务之外,评论通知中的邮件通知也有可能造成超时,这块建议可以先关闭评论通知再测一下是否是因为该功能导致的过慢。

Waline 头像加载很慢

Waline 的头像加载很慢或者加载失败,最主要的原因是 Waline 客户端默认使用的 Gravatar 镜像源被墙,可以尝试更换 Gravatar 的镜像源来解决;一般情况下需要手动更改 Waline 客户端的代码,通过设置 avatarCDN 属性来指定 Gravatar 的镜像源地址,其中可用的 Gravatar 镜像源列表如下:

若 Hexo 使用了 Next 主题,建议先卸载掉 Waline 官方的 Next 插件 @waline/hexo-next,然后再安装第三方插件 hexo-waline-next

1
2
3
4
5
# 卸载Waline官方插件
$ npm uninstall @waline/hexo-next --save

# 安装Waline第三方插件(最新版)
$ npm install hexo-waline-next --save

这是因为第三方插件 hexo-waline-next 支持在 Next 主题的 _config.yml 配置文件里直接使用 avatarCDN 属性来指定 Gravatar 的镜像源地址,无需再手动更改 Waline 客户端的代码:

1
2
3
4
waline:
enable: true
avatarCDN: https://sdn.geekzu.org/avatar/ # Set custom gravatar cdn url
...

值得一提的是,若希望在 Next 主题的 _config.yml 配置文件里直接使用 avatarCDN 属性,必须保证第三方插件 hexo-waline-next 的版本 >= 2.0.6,且 Next 主题的版本 >= 8.4.0

Github 登录无法管理后台

Github 登录后无法管理后台,解决方法可参考 Github Issue:通过 Github 登录无法管理后台

Waline 版本更新流程

  • 更新 MySQL 数据库表结构

  • 更新 Hexo-Waline-Next 插件

  • 更新 Waline Server 的代码,重新构建 Docker 镜像

  • 若在 Waline 的 waline/packages/server/src/middleware/dashboard.js 源文件中,指定了 Waline Admin 的版本(如下代码),那么还需要更改 Waline Admin 的版本号,否则默认会使用最新版的 Waline Admin,更改完成后必须重新构建 Waline Server 的 Docker 镜像

1
2
3
4
5
6
7
8
<body>
<script>
window.SITE_URL = ${JSON.stringify(process.env.SITE_URL)};
window.SITE_NAME = ${JSON.stringify(process.env.SITE_NAME)};
window.ALLOW_SOCIALS = ${JSON.stringify(socials)};
</script>
<script src="https://cdn.jsdelivr.net/npm/@waline/admin@ 0.9.0/dist/admin.min.js"></script>
</body>
  • 若在 Waline 的 waline/packages/server/src/controller/index.js 源文件中,指定了 Waline Client 的版本(如下代码),那么还需要更改 Client 的版本号,否则默认会使用最新版的 Waline Client
1
2
3
4
5
6
7
8
9
10
<body>
<div id="waline" style="max-width: 800px;margin: 0 auto;"></div> <script src="https://cdn.jsdelivr.net/npm/@waline/client@1.3.3/dist/Waline.min.js"></script>
<script>
new Waline({
el: '#waline',
path: '/',
serverURL: location.protocol + '//' + location.host + location.pathname.replace(/\\/+$/, '')
});
</script>
</body>
  • 若在 Next 主题的 _config.yml 文件中,Waline 官方插件的 CDN 配置里有指定 Waline Client 的版本(如下配置),那么还需要更改 Client 的版本号,否则默认会使用最新版的 Waline Client
1
2
3
waline:
enable: true
libUrl: https://cdn.jsdelivr.net/npm/@waline/client@1.3.3/dist/Waline.min.js

特别注意: Waline Client 与 Waline Server 的版本必须匹配,否则 Waline 整体可能无法正常运行

项目源码

参考博客