Vuepress配置评论插件为Artalk
前注:本文同时收录在 artalk 官方文档的推荐当中:https://artalk.js.org/guide/extras.html#artalk-v2 (opens new window)。
# 前言
把博客平台迁移到 vuepress 以来,之前折腾过好几次评论系统,大多是依赖第三方作为后端存储,导致要么是加载速度不理想,要么是配置费劲儿,于是,我终于下决心,自建评论系统。
目前国内比较知名的有两个前端评论系统:
一开始我是走 twikoo 的配置流程进行配置,配置到最后,发现这个系统原本依赖云开发系统,对于自建非常不友好,于是立马放弃,恰逢 vdoing 群里有群友说自己折腾了 artalk 系统的插件集成,经过一番了解,准备转战 Artalk。
# 安装
安装 artalk 也非常简单,该系统提供了 go 语言版本的,作者也继承好了镜像,提供了容器部署的方案,于是,我使用 docker-compose 进行启动,配置信息如下:
version: "3.5"
services:
artalk:
container_name: artalk-go
image: artalk/artalk-go
restart: always
ports:
- 8070:23366
volumes:
- ./data:/data
2
3
4
5
6
7
8
9
10
执行如下命令启动服务:
$ docker-compose up -d
服务启动之后,可以先给 artalk 创建一个管理员账号:
docker exec -it artalk-go artalk admin
创建完管理员账号,等会儿配置了访问域名,就能登陆了。
启动完成之后,会在服务器监听 8070 端口,此时给这个服务配置一个域名,配置信息如下:
$cat /etc/nginx/vhost/comment.eryajf.net.conf
server {
listen 80;
listen 443 ssl;
server_name comment.eryajf.net;
ssl_certificate /etc/nginx/ssl/comment.eryajf.net.pem;
ssl_certificate_key /etc/nginx/ssl/comment.eryajf.net.key;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
location / {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://127.0.0.1:8070;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
然后访问 comment.eryajf.net 能够进入到管理后台,使用刚刚创建的管理员账号信息进行登陆。
# 配置服务端
artalk 的配置文件在 data 目录下,你可以通过修改配置文件根据自己的需求进行调整,也可以在管理后台的页面上进行配置,尽管两种方式都不够美好,不过花点时间了解下这些配置,就能够很快完成自己的配置了。
下边是我使用的配置信息,其中部分信息做了脱敏:
# 服务器地址
host: "0.0.0.0"
# 服务器端口
port: 23366
# 加密密钥
app_key: "eryajf"
# 调试模式
debug: false
# 时间区域
timezone: "Asia/Shanghai"
# 默认站点名
site_default: "二丫讲梵"
# 登陆有效时长 (单位:秒)
login_timeout: 259200
# 数据库
db:
# 数据库类型 ["sqlite", "mysql", "pgsql", "mssql"]
type: "sqlite"
# 数据库文件 (仅 SQLite 数据库需填写)
file: "./data/artalk-go.db"
# 数据库名称
name: "artalk"
# 数据库地址
host: "localhost"
# 数据库端口
port: 3306
# 数据库账户
user: "root"
# 数据库密码
password: ""
# 编码格式
charset: "utf8mb4"
# 表前缀 (例如:"atk_")
table_prefix: ""
# 日志
log:
# 启用日志
enabled: true
# 日志文件路径
filename: "./data/artalk-go.log"
# 缓存
cache:
# 缓存类型 ["redis", "memcache", "builtin"]
type: "builtin"
# 缓存过期时间 (单位:分钟)
expires: 30
# 缓存启动预热 (程序启动时预热缓存)
warm_up: false
# 缓存服务器地址 (例如:"localhost:6379")
server: ""
# Redis 配置
redis:
# 连接方式 ["tcp", "unix"]
network: "tcp"
# 用户名
username: ""
# 密码
password: ""
# 数据库编号 (例如使用零号数据库填写 0)
db: 0
# 可信域名
trusted_domains:
- https://wiki.eryajf.net
- http://localhost:8080
- https://eryajf.github.io
- https://www.eryajf.net
- https://eryajf.net
# SSL
ssl:
# 启用 SSL
enabled: false
# 证书文件路径
cert_path: ""
# 密钥文件路径
key_path: ""
# 管理员账户
admin_users:
- name: "eryajf"
email: "xxxxxxxxxx"
password: "xxxxxxxxxx" # 支持 bcrypt 或 md5 加密,如:"(md5)50c21190c6e4e5418c6a90d2b5031119"
badge_name: "管理员"
badge_color: "#FF6C00"
# 评论审核
moderator:
# 默认待审 (发表新评论需要后台人工审核后才能显示)
pending_default: 1
# API 请求错误时拦截 (关闭此项当请求错误时让评论放行)
api_fail_block: 1
# Akismet Key
# (Akismet 反垃圾服务,https://akismet.com)
akismet_key: ""
# 腾讯云文本内容安全
# (https://cloud.tencent.com/document/product/1124/64508)
tencent:
enabled: false
secret_id: ""
secret_key: ""
region: "ap-guangzhou"
# 阿里云内容安全
# (https://help.aliyun.com/document_detail/28417.html)
aliyun:
enabled: false
access_key_id: ""
access_key_secret: ""
region: "cn-shanghai"
# 关键词过滤 (本地离线词库)
keywords:
enabled: false
# 匹配成功设为待审状态
pending: false
# 词库文件 (支持多个词库文件)
files:
- "./data/词库_1.txt"
# 词库文件内容分割符 (例如填写 "\n" 文件中一行一个关键词)
file_sep: "\n"
# 替换字符
replac_to: "x"
# 验证码
captcha:
# 启用验证码
enabled: true
# 总是需要验证码
always: false
# 激活验证码所需操作次数
action_limit: 3
# 重置操作计数器超时 (单位:s, 设为 -1 不重置)
action_reset: 60
# Geetest 极验 (https://www.geetest.com)
geetest:
enabled: false
captcha_id: ""
captcha_key: ""
# 邮件通知
email:
# 启用邮件通知
enabled: 1
# 发送方式 ["smtp", "ali_dm", "sendmail"]
send_type: "smtp"
# 发信人昵称
send_name: "{{reply_nick}}"
# 发信人地址
send_addr: "eryajf@163.com"
# 邮件标题
mail_subject: "[{{site_name}}] 您收到了来自 @{{reply_nick}} 的回复"
# 邮件模板文件 (填入文件路径使用自定义模板)
mail_tpl: "default"
# SMTP 发送 (启用请将发送方式设为 "smtp")
smtp:
# 发件地址
host: "smtp.163.com"
# 发件端口
port: 465
# 用户名
username: "eryajf@163.com"
# 密码
password: "xxxxxxxxxxx"
# 阿里云邮件推送
# (启用请将发送方式设为 "ali_dm";参考:https://help.aliyun.com/document_detail/29444.html)
ali_dm:
access_key_id: ""
access_key_secret: ""
account_name: "noreply@example.com"
# 图片上传
img_upload:
# 启用图片上传
enabled: 0
# 图片存放路径
path: "./data/artalk-img/"
# 图片大小限制 (单位:MB)
max_size: 5
# 图片链接基础路径 (默认为 "/static/images/")
public_path: null
# Upgit 配置
# (使用 Upgit 将图片上传到 GitHub 或图床:https://github.com/pluveto/upgit)
upgit:
# 启用 Upgit
enabled: false
# 命令行参数
exec: "./upgit -c <upgit配置文件路径> -t /artalk-img"
# 上传后删除本地的图片
del_local: true
# 多元推送
admin_notify:
# 通知模版 (填入文件路径使用自定义模板)
notify_tpl: "default"
# 嘈杂模式
noise_mode: false
# 邮件通知管理员
email:
# 开启 (当使用其他推送方式时,可以关闭管理员邮件通知)
enabled: 0
# 邮件标题 (发送给管理员的邮件标题)
mail_subject: "[{{site_name}}] 您的文章「{{page_title}}」有新回复"
# Telegram
telegram:
enabled: false
api_token: ""
receivers:
- 7777777
# Bark
bark:
enabled: false
server: "http://day.app/xxxxxxx/"
# 飞书
lark:
enabled: false
webhook_url: ""
# WebHook
webhook:
enabled: false
url: ""
# 钉钉
ding_talk:
enabled: 1
token: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
secret: "wiki"
# Slack
slack:
enabled: false
oauth_token: ""
receivers:
- "CHANNEL_ID"
# LINE
line:
enabled: false
channel_secret: ""
channel_access_token: ""
receivers:
- "USER_ID_1"
- "GROUP_ID_1"
# 前端配置
frontend:
# 评论框占位文字
placeholder: "请正确填写邮箱以便接收回复通知,如需添加图片,请通过第三方图床引用图片,评论支持Markdown语法"
# 无评论显示文字
noComment: "「此时无声胜有声」"
# 发送按钮文字
sendBtn: "提交"
# 评论框旅行
editorTravel: 1
# 暗黑模式
darkMode: false
# 表情包
emoticons: "https://cdn.jsdelivr.net/gh/eryajf/emotion_generate/dist/artalk.json"
# 投票按钮
vote: true
# 反对按钮
voteDown: false
# 用户 UA 徽标
uaBadge: true
# 评论排序功能
listSort: true
# 页面 PV 绑定元素
pvEl: "#ArtalkPV"
# 评论数绑定元素
countEl: "#ArtalkCount"
# 编辑器实时预览功能
preview: true
# 平铺模式 ["auto", true, false]
flatMode: "auto"
# 最大嵌套层数
nestMax: 10
# 嵌套评论排序规则 ["DATE_ASC", "DATE_DESC", "VOTE_UP_DESC"]
nestSort: DATE_ASC
# 头像
gravatar:
# Gravatar 镜像地址
mirror: "https://cravatar.cn/avatar/"
# 默认头像
default: "mp"
# 评论分页
pagination:
# 每页评论数
pageSize: 20
# 加载更多模式 (关闭则使用分页条)
readMore: true
# 滚动加载
autoLoad: true
# 内容限高
heightLimit:
# 评论内容限高 (单位:px)
content: 300
# 子评论区域限高 (单位:px)
children: 400
# 请求超时 (单位:毫秒)
reqTimeout: 15000
# 版本检测
versionCheck: true
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
因为官方文档写的很详细,这里就不多赘述了。
补充:
配置文件中使用的表情包,来自于项目emotion_generate (opens new window),是我维护的一个拥有丰富表情包的集合,此集合永久有效(除非 github 或者 jsdelivr 崩了),你可以直接在自己的配置文件中使用。
# 配置页面端
页面端,通过如下插件来完成配置:
# 安装
npm install --save vuepress-plugin-vdoing-comment --registry=https://registry.npmmirror.com
# 配置
在自己的 vdoing 工程项目中,插件配置文件中 docs/.vuepress/config/plugins.js
添加如下配置:
module.exports = {
plugins: [
[
"vuepress-plugin-vdoing-comment",
{
choosen: "artalk",
options: {
server: "https://comment.eryajf.net", // (必填)
site: "二丫讲梵", // (必填)
// disableEmotion: false, // 是否禁用表情(可选)
// disablePicture: true, // 是否禁用图片(可选)
// disablePreview: false // 是否禁用预览(可选)
},
},
],
],
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
目前这个插件中的禁用图片参数在我这里测试没有生效,如果你想禁用评论区上传图片,可以通过下边 css 中的方案对图片按钮进行隐藏。
然后在自定义 css 文件 docs/.vuepress/styles/palette.styl
最后添加如下内容进行优化:
// artalk 评论框 适配暗黑模式
.theme-mode-dark #vuepress-plugin-vdoing-comment {
--at-color-bg: --bodyBg;
color: #ffffff;
--at-color-font: #ffffff;
--at-color-bg-transl: --bodyBg;
--at-color-bg-grey: #373a40;
}
.theme-mode-dark
#vuepress-plugin-vdoing-comment
.atk-editor-plug-emoticons
> .atk-grp-switcher
> span:hover,
.atk-editor-plug-emoticons > .atk-grp-switcher > span.active {
background: var(--at-color-bg-grey);
}
.theme-mode-dark
#vuepress-plugin-vdoing-comment
.atk-editor-plug-emoticons
> .atk-grp-switcher {
background: var(--at-color-bg-grey);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
然后可以先在本地 yarn dev
启动调试一下看看效果,没问题之后,再发布到远端即可。
看下我这里配置好之后的效果:
# 迁移
将评论系统配置为 artalk 之后,加载速度方面完全没了问题,现在需要做的一件事儿就是将原来的评论做一个迁移。
artalk 官方文档中介绍的迁移很详细,也提供了迁移工具,但我在这个过程中也踩了一些坑,这里做一下过程记录。
我原来评论系统依托于 Vssue,数据存放在 GitHub Issue,想要拿到这些数据并不难,官方文档中给到的说明如下:
Artran 格式定义:每一条评论数据 (Object) 称为 Artran,多条评数据论组成一个 Artrans (Array 类型)
{ "id": "123", "rid": "233", "content": "Hello Artalk", "ua": "ArtalkGo/6.6", "ip": "233.233.233.233", "created_at": "2021-10-28 20:50:15 +0800 +0800", "updated_at": "2021-10-28 20:50:15 +0800 +0800", "is_collapsed": "false", "is_pending": "false", "vote_up": "666", "vote_down": "0", "nick": "qwqcode", "email": "qwqcode@github.com", "link": "https://qwqaq.com", "password": "", "badge_name": "管理员", "badge_color": "#FF716D", "page_key": "https://artalk.js.org/guide/transfer.html", "page_title": "数据迁移", "page_admin_only": "false", "site_name": "Artalk", "site_urls": "http://localhost:3000/demo/,https://artalk.js.org" }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
简单来说,如上内容就是一条评论的详情,如果想要导入多条,就用一个数组包裹。
只是,这里有必要对其中几个字段做一个说明:
rid:
表示这是哪条评论的回复,如果这是第一条评论,则 rid 为 0,如果是回复 ID 为 1 的评论,则 rid 为 1。page_key:
这的确是评论所属的标识页面,但可能是哪里的配置原因,我发现我这里获取到的 page_key 不带主域名,这也是我迁移过程中踩得一个坑。
我的建议是,评论系统配置好之后,最好先在自己的博客评论一条,然后把这条数据查出来,看看详情:
INSERT INTO "main"."comments" ("id", "created_at", "updated_at", "deleted_at", "content", "page_key", "site_name", "user_id", "ua", "ip", "rid", "is_collapsed", "is_pending", "is_pinned", "vote_up", "vote_down") VALUES (1, '2022-11-24 22:32:59.597873282+08:00', '2022-11-24 22:52:19.954473963+08:00', NULL, '今天,终于决定把评论系统也改为自建,兜兜转转,不如自建,无论图床还是评论系统。以后大家可以愉快地进行评论啦🎉', '/about/', '二丫讲梵', 2, 'Mozilla/5.0 (Macintosh; Intel Mac OS X 12_6_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36', '182.119.128.2', 0, 0, 0, 0, 1, 0);
根据如上情况,我的脚本内容如下:
package main
import (
"context"
"encoding/json"
"fmt"
"strings"
"github.com/google/go-github/v47/github"
"golang.org/x/oauth2"
)
var (
client *github.Client
GtihubToken string = "ghp_7xxxxxxxxxxxxxxxxxxxxxxxxx"
)
func init() {
ctx := context.Background()
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: GtihubToken},
)
tc := oauth2.NewClient(ctx, ts)
client = github.NewClient(tc)
}
type ArtalkComment struct {
ID string `json:"id"`
Rid string `json:"rid"`
Content string `json:"content"`
Ua string `json:"ua"`
IP string `json:"ip"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
IsCollapsed string `json:"is_collapsed"`
IsPending string `json:"is_pending"`
VoteUp string `json:"vote_up"`
VoteDown string `json:"vote_down"`
Nick string `json:"nick"`
Email string `json:"email"`
Link string `json:"link"`
Password string `json:"password"`
BadgeName string `json:"badge_name"`
BadgeColor string `json:"badge_color"`
PageKey string `json:"page_key"`
PageTitle string `json:"page_title"`
PageAdminOnly string `json:"page_admin_only"`
SiteName string `json:"site_name"`
SiteUrls string `json:"site_urls"`
}
func main() {
githubUser := "eryajf"
githubRepo := "eryajf.github.io"
issues, err := GetAllIssue(githubUser, githubRepo)
if err != nil {
fmt.Println(err)
}
var artalks []ArtalkComment
for _, repo := range issues {
if *repo.Comments > 0 {
comments, err := GetAllComment(githubUser, githubRepo, repo.GetNumber())
if err != nil {
fmt.Printf("get all comments failed: %v\n", err)
}
for _, comment := range comments {
user, err := GetUser(comment.User.GetLogin())
if err != nil {
fmt.Printf("get user failed: %v\n", err)
}
var email string
if user.GetEmail() == "" {
email = "empyt@github.com"
} else {
email = user.GetEmail()
}
artalks = append(artalks, ArtalkComment{
ID: fmt.Sprintf("%v", repo.GetNumber()),
Rid: "0",
Content: comment.GetBody(),
Ua: "",
IP: "",
CreatedAt: comment.GetCreatedAt().String(),
UpdatedAt: comment.GetUpdatedAt().String(),
IsCollapsed: "false",
IsPending: "false",
VoteUp: "0", // 赞成
VoteDown: "0",
Nick: comment.User.GetLogin(), // 评论者的昵称
Email: email, // 评论者的邮箱
Link: comment.User.GetHTMLURL(), // 评论者的网站
Password: "",
BadgeName: "",
BadgeColor: "",
PageKey: strings.Split(repo.GetBody(), "https://wiki.eryajf.net")[1], // 页面的url,只取域名后的uri
PageTitle: "数据迁移",
PageAdminOnly: "false",
SiteName: "二丫讲梵",
SiteUrls: "https://comment.eryajf.net",
})
}
}
}
str, err := json.Marshal(artalks)
if err != nil {
fmt.Printf("marshal failed: %v\n", err)
}
fmt.Println(string(str))
}
// 获取用户信息
func GetUser(name string) (*github.User, error) {
ctx := context.Background()
user, _, err := client.Users.Get(ctx, name)
if err != nil {
fmt.Printf("get user info failed: %v\n", err)
}
return user, nil
}
// 获取仓库所有的issue
func GetAllIssue(owner, repoName string) ([]*github.Issue, error) {
ctx := context.Background()
opt := &github.IssueListByRepoOptions{
State: "open",
Labels: []string{"Vssue"},
ListOptions: github.ListOptions{PerPage: 10},
}
// get all pages of results
var allIssues []*github.Issue
for {
repos, resp, err := client.Issues.ListByRepo(ctx, owner, repoName, opt)
if err != nil {
return nil, err
}
allIssues = append(allIssues, repos...)
if resp.NextPage == 0 {
break
}
opt.Page = resp.NextPage
}
return allIssues, nil
}
// 获取对应issue的所有对话
func GetAllComment(owner, repoName string, number int) ([]*github.IssueComment, error) {
ctx := context.Background()
opt := &github.IssueListCommentsOptions{
ListOptions: github.ListOptions{PerPage: 10},
}
// get all pages of results
var allRepos []*github.IssueComment
for {
repos, resp, err := client.Issues.ListComments(ctx, owner, repoName, number, opt)
if err != nil {
return nil, err
}
allRepos = append(allRepos, repos...)
if resp.NextPage == 0 {
break
}
opt.Page = resp.NextPage
}
return allRepos, nil
}
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
如果你也想通过这个脚本来生成你的评论数据,可以适当调整其中的信息,如果有不了解的,可以在评论区与我交流。
当我运行完脚本之后,就获得了对应评论数据的一个大数组,将这个数组写入到一个名为 art.json.artrans
(注意:后缀必须为这个,这是官方识别的格式。)的文件中,然后来到管理后台,通过页面进行导入,在控制中心 ==> 设置 ==> 迁移中:
最后点击导入,可以看到成功导入的数据条数。
当数据导入成功,就可以到文章页面上看对应的评论信息了,此时数据还有一些小问题,需要进行修复,因为我这边数据量比较小,因此选择了手动修复。
需要修复的是 rid 这个字段,需要根据原来各条评论的对应关系来维护 rid 这个字段。
我的操作步骤是:先把数据文件下载到本地,然后使用 Navicat 打开,对数据进行修复,修复完毕之后,再把数据上传到服务器的数据目录下,然后重启 artalk-go 服务端即可。
比如我的关于页面的评论信息如下: https://github.com/eryajf/eryajf.github.io/issues/152 (opens new window) ,关于页面的 URI 为 /about/
,执行如下查询:
$ SELECT id,content,rid FROM comments WHERE "page_key"='/about/' ORDER BY created_at ASC;
然后参照 GitHub 中 issue 的内容关系,来手动修复 rid 字段的值。
经过一番艰辛的探索,总算是把原来的评论数据迁移到新的系统中了。