博客现在用的评论是 Valine, 是一款基于 Leancloud 的快速、简洁且高效的无后端评论系统。看配置什么都挺简单的,也有不少教程,本来没想写这个,但实际在访问量统计这个功能上遇到不少问题(仍然是需要一定开发工作的),找到的教程都挺乱,干脆自己重新梳理下了。

Before. 配置好 Valine

先参照 Valine官网教程 把应用配置好,不要遗漏安全域名,至此你的评论功能应该已经好了。

如果你不需要评论功能,或者不需要 Valine 来实现评论功能,可以跳过引用 Valine,但仍然需要创建好 Leancloud 的应用。

Start.访问量统计

|前因

虽然 Valine 文档中说明从 v1.2.0 版本开始支持了文章阅读量统计

new Valine({
el:’#vcomments’,

visitor: true // 阅读量统计
})
 
如果开启了阅读量统计,Valine 会自动检测 leancloud 应用中是否存在Counter类,如果不存在会自动创建,无需手动创建~
 
Valine会自动查找页面中class值为leancloud-visitors的元素,获取其id为查询条件。并将得到的值填充到其class的值为leancloud-visitors-count的子元素里:

以上为官方说明,但配置并没有软用,因为查下源码可以看见,Valine 除了在 leancloud 中创建一个 Counter 类什么都没有干 !…

 

这里从一些其他资料看是具体逻辑是在 Hexo-theme-next 这个主题做了处理,也就是具体业务方做了存储和获取数据的逻辑,挺不合理的,还是应该 Valine 封装好才对。

 

至此,我们只能自己动手做存储和访问的逻辑了。

|动手

思路:

  1. 约定 HTML 节点用于采集渲染统计量数据
  • 带有 classleancloud_visitors_count 的表示需要渲染数据,节点属性 data-index 表示唯一索引。我这个场景是 url 为唯一索引,举例如

    1
    <span class="leancloud_visitors_count" data-index="/2019/03/12/ZRender使用记录/"></span>
  • 带有 idleancloud_visitors 的表示采集数据,节点属性data-index 表示唯一索引。举例如

    1
    <span class="leancloud_visitors_count" data-index="/2019/03/12/计算字的宽高/"></span>
  1. JS实现方法 addCount 用于上传数据(指定索引访问量+1)和 showTime 用于渲染访问量数据

  2. 要渲染的所有索引批量查询一次

  3. 减少请求,每个页面采集只允许一次,所以采集数据约定的节点为 id 而不是 class

&nbsp;

S1. Leancloud 建 Counter 类

S2. JS 代码实现

同 Hexo 的用户,把这段代码单独存文件 _partials/lean-analytics.swig,在对应主题目录下 ./layout/javascript.swing 中引用这份文件即可

以下代码根据你的实际场景做些处理,你要存储哪些字段、你的 Leancloud 的 appId 和 appKey、你的节点约定规则等。

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
// 这段代码依赖 jquery,请检查下是否有引用
// 具体的HTML节点约定你可以根据你的需要更改
<script src="https://cdn1.lncld.net/static/js/av-core-mini-0.6.1.js"></script>
<script>
function showTime(Counter) {
  var query = new AV.Query(Counter);
  var $doms = $(".leancloud_visitors_count")
  if (!$doms.length) return
  // var urls = $doms.map(function(){return $(this).attr('data-index')}) || [];
  var urls = []
  for (var i = 0; i < $doms.length; i++) {
    urls.push($($doms[i]).attr('data-index'))
  }
  query.containedIn("url", urls);
  query.find({
    success: function(rst) {
      // 初始化
      $doms.each(function(i) {
        $(this).text(0)
      })
      // 渲染数据
      for (var i in rst) {
        var item = rst[i]
        $('.leancloud_visitors_count[data-index="'+ item.get('url') +'"]').text(item.get('time'))
      }
    },
    error: function (obj, e) {
      console.warn(obj, e)
    }
  })
}
function addCount(Counter) {
  var Counter = AV.Object.extend("Counter");
  var $dom = $("#leancloud_visitors")
  if (!$dom.length) return
  url = $dom.attr('data-index').trim();
  var query = new AV.Query(Counter);
  query.equalTo("url", url); // 我的需求以存储的url为唯一索引
// Case 有这条数据则time字段+1
  query.find({
    success: function(results) {
      if (results.length > 0) {
        var counter = results[0];
        counter.fetchWhenSave(true);
        counter.increment("time");
        counter.save(null, {
          success: function(counter) {
          },
          error: function(counter, e) {
            console.warn(counter, e)
          }
        });
      }
// Case 没有这条数据则新增一行数据
else {
        var newcounter = new Counter();
        var acl = new AV.ACL();
        acl.setPublicReadAccess(true);
      // acl.setWriteAccess(AV.User.current(),true);
       acl.setWriteAccess('*', true);
        newcounter.setACL(acl)
// 这里是你想存储的数据
        newcounter.set("title", title);
        newcounter.set("url", url); // 我的需求以存储的url为唯一索引
        newcounter.set("time", 1);
        newcounter.save(null, {
          success: function(newcounter) {
          },
          error: function(newcounter, error) {
            console.log('Failed to create');
          }
        });
      }
    },
    error: function(e) {
      console.warn(e)
    }
  });
}
$(function() {
// @TODO
// appId 和 appKey 在 Leancloud 应用的设置内可以找到,
// 具体可参考上面 Valine 的官方引导吧。
AV.initialize(appId, appKey);
  var Counter = AV.Object.extend("Counter");
  addCount(Counter);
  showTime(Counter);
});
</script>

S3. HTML 节点添加

这是我在文章列表的访问量渲染节点

1
2
3
4
<span class="reading-time">
<span class="leancloud_visitors_count" data-index="{{ url_for(post.path) }}"></span>
    read
</span>

这是我在每个文章页面的采集节点

1
<div id="leancloud_visitors" data-index="{{ url_for(page.path) }}" data-title="{{ page.title }}"></div>

问题集

ACL 权限问题

出现报错 ACL 权限问题,是因为创建 Counter 类的时候选择了限制写入作为ACL的默认设置。

参考 ACL 权限管理开发指南],可以通过创建数据行时声明 ACL 权限解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 新建一个帖子对象
var Post = AV.Object.extend('Post');
var post = new Post();
post.set('title', '大家好,我是新人');

// 新建一个 ACL 实例
var acl = new AV.ACL();
acl.setPublicReadAccess(true);
acl.setWriteAccess(AV.User.current(),true);

// 将 ACL 实例赋予 Post 对象
post.setACL(acl);
post.save().then(function() {
// 保存成功
}).catch(function(error) {
console.log(error);
});

临时处理上,可以在后台直接编辑修改