动机
昨天,多说评论系统正式关闭了。
早在一个月前还在做毕设的时候,听闻如此噩耗,就以在纠结到底迁移到哪个评论系统比较好。
但可惜,除了多说之外,国内似乎找不到靠谱点的社会化评论系统。
畅言是搜狐出的评论系统,试用了一段时间后就弃坑了。主要缺点是不支持facebook,twitter,github等任何国外的平台,其次评论管理很不方便,因为后台账号不能用来评论,所以在自己网站上评论还得重新注册,好傻呀是不是。官网的用来管理评论的chrome插件还过期不维护了。
韩国人做的来必力,试过一下,支持的SNS很全,但也有问题,比如不支持评论导入。而且肉眼望过去,bug比较多。
网易云跟帖适合灌水,对于博客网站并不是很适合。
友言就不吐槽了。
所以,转了一圈,发现还是老牌的diqus最靠谱,也不用担心突然哪一天它也跪了的问题。
由于众所周知的原因,disqus在国内不能访问。但由于disqus提供了API(待会再说明这玩意怎么坑),于是可以通过API来访问和创建评论。
于是半个多月前,就做了一些准备工作。一是确认了通过API访问使用disqus的可行性,二是大概查了一下有没有现成的轮子。于是找到了fooleap用php为jekyll做的一个代理。
考虑到对php不是很熟悉,如果要改源代码并在hexo里集成对于我而言有些难度,所以最终打算自己用熟悉的Node.js重新造个轮子,用来为hexo博客做disqus的代理。
并且,我可以自定义评论的样式,来深度整合进自己写的主题aqua,感觉会很赞。
项目地址:Disqus-Proxy
配置说明:Disqus-Proxy-Config
思路
整体流程是这样的,在前端页面上测试disqus加载是否成功,如果成功则显示disqus的评论框,反之加载独立的评论框,并将请求发送给自己在国外的vps,利用vps做反向代理,接收来自客户端的请求到disqus服务器并再转发给客户端。
流程图大概长这样:
于是问题就来了:如何检测disqus是否能成功加载?
我们先来看看disqus的通用植入代码
<div id="disqus_thread"></div>
<script>
var disqus_config = function () {
this.page.url = PAGE_URL; // Replace PAGE_URL with your page's canonical URL variable
this.page.identifier = PAGE_IDENTIFIER; // Replace PAGE_IDENTIFIER with your page's unique identifier variable
};
(function() {
var d = document, s = d.createElement('script');
s.src = 'https://yourname.disqus.com/embed.js';
s.setAttribute('data-timestamp', +new Date());
(d.head || d.body).appendChild(s);
})();
</script>
可以看到disqus是通过创建script
标签,通过设置src
属性动态引入脚本的。因此,我们可以通过判断这个脚本在指定时间内加载是否完成来判断是否载入自制评论框。
这里感谢fooleap在这篇文章里提供的思路,下面是我在React.js
实现判断的代码:
componentWillMount() {
const s = document.createElement('script')
const username = window.disqusProxy.username
s.src = `https://${username}.disqus.com/embed.js`
s.async = true
s.setAttribute('data-timestamp', String(+new Date()))
s.onload = () => {
this.setState({disqusLoaded: true})
}
s.onerror = () => {
this.setState({disqusLoaded: false})
console.log('Failed to load disqus. Load disqus-proxy instead.')
}
document.body.appendChild(s)
setTimeout(async () => {
if (!this.state.disqusLoaded) {
const DisqusProxy = await import('./DisqusProxy')
this.setState({DisqusProxy: DisqusProxy.default})
}
}, 3000)
}
如果在3秒后,脚本仍然未loaded
,那么引入自制的评论框。
这里利用ES6中动态import
的命令,通过webpack
来分割代码,使得根据disqus加载状况,决定要不要加载剩余的模块,以此减少不必要的脚本载入。
评论框使用了React.js
,后端用了Node.js
的Koa
框架,负责请求转发。
配置
- 一台国外的VPS服务器
前端配置
在 Hexo 主题 Aqua 中使用 diqus-proxy
Hexo 主题 aqua 已经集成了 disqus-proxy,可以直接在主题的config.yml里配置。
# 如果disqus账号名没设置 那么disqus_proxy也不会生效
disqus_username: ciqu
disqus_proxy:
# 下面两项你需要更改为自己服务器的域名和端口
server: disqus-proxy.ycwalker.com
port: 443 #端口号需要与后端设置一致
# 头像路径设置
default_avatar: /avatars/default-avatar.png
admin_avatar: /avatars/admin-avatar.jpg
# 脚本和css路径通常不需要更改
script_path: /static/js/main.0d0338ae.js
css_path: /static/css/main.0603c539.css
所以在这里,通常只需要配置三项就可以了,分别是
- disqus 的用户名称
- 你用于代理disqus的VPS服务器地址
- 你用于代理disqus的端口
对于 aqua ,这些就是disqus-proxy全部前端配置。
在其他 Hexo 主题中使用 diqus-proxy
如果你用其他主题,那么前端配置会麻烦一些。
Hexo主题一般用的渲染引擎有pug
(原jade
),ejs
和swig
等。
如何看hexo的渲染主题是哪一个? 进入主题目录下的layout目录,看文件的格式就知道了。
下面我会一一说明配置方法。
模板引擎pug(jade)
进入hexo的主题目录,找到layout
文件夹。
通常来说,评论会单独写成一个文件,比如comment.pug
,在layout
文件夹下面或者其子目录下面。
在这个comment.pug
中,hexo
在生成(hexo g
)时,会对每一篇生成的文章调用这个文件进行渲染。
在渲染的过程中,hexo
会提供page
这个全局变量,在pug文件中,你可以在生成的script中以#{page}
调用。
将comment.pug
全部替换成如下内容(注意缩进):
div#disqus_thread
div#disqus_proxy_thread
script.
window.disqusProxy = {
username: 'ciqu',
server: 'disqus-proxy.ycwalker.com',
port: 5509,
defaultAvatar: '/avatars/default-avatar.png',
adminAvatar: '/avatars/admin-avatar.jpg',
identifier: "#{page.path}"
};
window.disqus_config = function () {
this.page.url = "#{page.permalink}"
this.page.identifier = "#{page.path}"
};
var s = document.createElement('script');
s.src = '/static/js/main.56688539.js';
s.async = true;
document.body.appendChild(s);
link(rel="stylesheet" href="/static/css/main.0603c539.css")
这个文件将会渲染出这样的结果
<div id="disqus_thread"></div>
<div id="disqus_proxy_thread"></div>
<script>
window.disqusProxy = {
username: 'ciqu',
server: 'disqus-proxy.ycwalker.com',
port: 5509,
defaultAvatar: '/avatars/default-avatar.png',
adminAvatar: '/avatars/admin-avatar.jpg',
identifier: "2017/05/25/have-a-nice-weekend/"
};
window.disqus_config = function() {
this.page.url = "http://ycwalker.com/2017/05/25/have-a-nice-weekend/"
this.page.identifier = "2017/06/01/diqus-proxy-config/"
}
;
var s = document.createElement('script');
s.src = '/static/js/main.56688539.js';
s.async = true;
document.body.appendChild(s);
</script>
<link rel="stylesheet" href="/static/css/main.0603c539.css">
其中window.disqusProxy
对象是自制的评论框参数,参数说明如下:
-
userName
是disqus账户名-
server
是你启用disqus代理的VPS的域名 -
port
是VPS服务器启用disqus代理的端口,需要和后端设置的端口一致 -
defaultAvatar
和adminAvatar
分别是默认头像和管理员头像 -
identifier
是告诉disqus该文章对应评论的标识符,一般以文章的路径做标识符。
-
其中window.disqus_config
是disqus成功加载后,disqus用到的参数,分别是文章的地址和标识符。
模板引擎swig
对于渲染引擎为swig
的hexo
主题,可以将类似comment.swig
直接改成这样
{% if true %}
<div id="disqus_proxy_thread"></div>
<div id="disqus_thread">
<script type="text/javascript">
window.disqusProxy = {
username: 'ciqu',
server: 'disqus-proxy.ycwalker.com',
port: 5509,
defaultAvatar: '/avatars/default-avatar.png',
adminAvatar: '/avatars/admin-avatar.jpg',
identifier: '{{ page.path }}'
};
window.disqus_config = function () {
this.page.url = '{{ page.permalink }}';
this.page.identifier = '{{ page.path }}';
};
var s = document.createElement('script');
s.src = '/static/js/main.0d0338ae.js';
s.async = true;
document.body.appendChild(s);
</script>
<link rel="stylesheet" href="/static/css/main.0603c539.css">
{% endif %}
比如著名的hexo
主题next就用了swig
做模板引擎,你只要将其主题下的next/layout/_partial
目录下的comments.swig
内容全部替换成上面的代码就OK了。
注意,window.disqusProxy
和window.disqus_config
的配置项请参阅前文pug
部分的说明。
模板引擎ejs
对于渲染引擎为ejs
的hexo
主题,可以将类似comment.ejs
直接改成这样:
<div id="disqus_proxy_thread"></div>
<div id="disqus_thread"></div>
<script>
window.disqusProxy = {
username: 'ciqu',
server: 'disqus-proxy.ycwalker.com',
port: 5509,
defaultAvatar: '/avatars/default-avatar.png',
adminAvatar: '/avatars/admin-avatar.jpg',
identifier: '<%= page.path %>'
};
window.disqus_config = function () {
this.page.url = '<%= page.permalink %>';
this.page.identifier = '<%= page.path %>';
};
var s = document.createElement('script');
s.src = '/static/js/main.0d0338ae.js';
s.async = true;
document.body.appendChild(s);
</script>
<link rel="stylesheet" href="/static/css/main.0603c539.css">
比如使用ejs
为模板引擎的fexo主题,可以直接将fexo/layout/_partial/component/
目录下的comments.ejs
全部改为上述文件就行了。注意,window.disqusProxy
和window.disqus_config
的配置项请参阅前文pug
部分的说明。
划重点:
下载或者克隆项目代码 disqus-proxy
复制disqus-porxy中已经build完毕的disqus-proxy/build
目录下的static
文件夹和avatars
文件夹到主题目录的source文件夹下。
将static/js/
下的以main开头js文件名替换上面代码中的s.src = '/static/js/main.56688539.js';
文件名。
将static/css/
目录下的css文件名替换link(rel="stylesheet" href="/static/css/main.0603c539.css")
中的文件名。
PS:你会发现static/js/
目录下有两个js文件,其中main开头的脚本用于检测网络状况以选择性加载disqus,另一个js文件为自制的评论框,在disqus不能加载时会被之前的js文件动态请求加载,所以不用管它,你只要确认它的路径在/static/js/
下就可以了。
至此,前端部分配置完成。
后端配置
后端用了Node.js
,由于采用了Koa
框架和async/await
语法,所以需要Node.js
版本7.6
以上,话说端午节后Node.js
最新版本都到8了耶。
在服务器上clone代码:
git clone https://github.com/ciqulover/disqus-proxy
安装依赖
只需要安装后端的依赖
npm i --production
// 或者
yarn install --production
配置server
目录下的config.js
module.exports = {
// 服务端端口,需要与disqus-proxy前端设置一致
port: 5509,
// 你的diqus secret key
api_secret: 'your secret key',
// 你的disqus名称
username:'ciqu',
// 服务端socks5代理转发,便于在本地测试,生产环境通常为null
socks5Proxy: null,
// 日志输出位置,输出到文件或控制台 'file' | 'console'
log: 'console'
}
获取api-secret
api-secret
需要你在disqus的官方网站上开启API权限,申请成功后会得到这个秘钥。
并且需要在后台的Settings => Community里开启访客评论
启动
cd server
node index.js
推荐用pm2
在生产环境启动,否则你断开ssh,node进程就终止了
npm i pm2 -g
pm2 start index.js
如果你在配置文件中选择log
类型为file
, 那么输出的日志文件将在默认为server目录下的disqus-proxy.log
Done !
关于disqus的API
虽然disqus提供了丰富的API,和界面友好的文档,但应用起来是有很多问题的。
首先,在服务端发通过API发送匿名评论是一个非官方支持的API,下面是之前咨询这个问题时官方的答复邮件:
所以,通过这个API创建匿名评论时,并不能传递IP地址的参数,因为disqus在拿到评论时会自动以请求的IP地址作为评论的IP地址。换句话说,通过后端API创建评论的IP是固定的。就是VPS的地址。
为了解决这个问题,可以拿到disqus用户的token之后调用API评论。但这很不现实。如果通过OAuth 2,需要跳转登录,但一是用户可能还没注册disqus请求匿名评论,二是既然能跳转disqus登录,还需要代理个毛线啊。
并且,由于disqus并没有提供admin账号的登录API,所以无法在服务端拿到moderator用户的cookie,所以不能直接审核通过匿名评论,而需要在匿名评论发布后,admin登录到控制台审核通过评论。
由于不能指定评论的IP,并且评论的IP地址只能是VPS主机的IP,所以导致点赞等功能是无法通过后端API来搞的。
对于https网站
由于我的网站是位于github上的静态的https网站,所以在向我的VPS服务器发送http的XHR请求时就出了问题。通常来说,VPS主机是没有证书的,所以在不购买或者不用自签名正式的情况下是不能启用https的。然而,在https网站发送请求http会被浏览器无情的block掉。
这里我的解决方案是又拍云。
又拍云提供了源站回源的功能,目的是缓存源站的静态文件。于是,我就想到了使用又拍云对前端页面https请求的目标地址进行http回源,这样相当于在前端页面与VPS再加了一层代理,前端页面的https请求通过又拍云以http协议请求到VPS服务器,得到结果后返回前端页面。这就相当于在前端页面与disqus服务器间架设了两层代理。
所以这样就相当于以https访问又拍云,再以又拍云以http协议访问VPS。只要在这里不设置缓存就行了。
这会不会导致速度减慢呢?其实,国内直连国外的VPS主机到并不一定比连接又拍云后再由又拍云连接VPS速度快。毕竟又拍云的各个主机通常位于主干网络上,两边的访问都会很快,相当于一个网游加速器了。