Hexo NexT文章阅读量排行(热榜)功能

原文 首发 北宸的小站,欢迎访问!!!

前言

想掌握网站文章欢迎度、阅读排行?那么「热榜」功能就不可或缺

在写这篇文章以前,本人也了解过其他博客的做法,无外乎是基于Leancloud实现的。很少有用谷歌Firestore(Firebase)来做的,这篇文章咱就来说说Firestore

PS:Leancloud现在需要上传手持身份证照片进行实名。。。

主要是本人比较排斥这样搜刮隐私的行为😂,So经过几次尝(tou)(ji)无果后,果断放弃。如果你也有遇到这种情况,希望此文能对你有所帮助。

关于Leancloud的相关实现,可以参考这篇文章,精简实用,本文也在此基础上作了参考。

正文

/themes/next/_config.yml文件,已经提前预配了firestore

image.png

是不是已经给你提示了呢?而且next官方文档也提到过,一共分为两步:
1、注册

image.png

2、配置

image.png

Firebase属于谷歌的,确保你能访问:https://console.firebase.google.com   (tips:github)

步骤

注册

可以直接使用Google 帐号登录,没有的自行进行注册,此步骤略过。。。

登录成功后,点击网页右上角:转到控制台

添加项目

输入项目名称,比如我用的是leafjame2019814, 点击继续,继续,用默认的配置即可,完成后还可以再修改。

image.png

创建项目完成后,点击网页左侧setttings按钮,如图:

image.png

即可查看到自己的项目ID网络 API 密钥,这就是next主题配置文件中提到的projectIdapiKey

image.png

创建数据库

创建完项目后,接着需要创建存储数据的地方,如下图所示:

image.png

有两种选项,自己用的话,选择以测试模式开始,就可以。

image.png

如果选择以锁定模式开始,则后续自己通过js api的方式写入会提示没有权限。。(可能需要配置权限什么的吧,我自己也没搞懂)

下一步设置Cloud Firestore位置,有一下好几种,分为多域性区域性。具体区别可查看文档,默认选择nam5 (us-central)即可,点击完成,等待分配Database。

image.png

完成后显示是这样的,现在还没有数据。

可参考Cloud Firestore 使用入门,文档还是很全的

配置文章阅读量

做完上边的操作后,接着就是把代码集成到自己的项目中了。

/themes/next/layout/_third-party/analytics/firestore.swig中,其实已经实现了文章阅读量的功能,只需要经过本文以上的操作,然后在/themes/next/_config.yml文件,启用firestore

image.png

apiKeyprojectId就是上文中创建的。collection:集合ID,下边会讲到。

这个功能经过配置后,可用于文章的阅读次数了,当文章未被查看时,效果是这样的:

image.png

此时的Firebase中的Database下也没有数据,当浏览这篇文章后,阅读次数加1了。
如下图:

image.png

再打开Firebase,你会发现,也有数据了:

image.png

articles就是上边配置的collection的值,即:集合ID

这控制台能对集合、文档、字段、值进行操作,比如设置阅读量啦什么的。。😜

设置开发环境

每个页面的阅读量有了,不过我们要做的是排行榜啊,得把所有的页面汇总排序。而且刚才的Database里存的数据也没有URL等等这些信息呀,接下来要写代码了。。。

新增如下的页面——「热榜」,我想大家都会了吧,不细说了。


image.png

在这个index.md文件中,引入要用到的Firebase代码,可参照官方文档进行。

之前我想直接在此md文件中引入firestore.swig,但启动一直报错。。(我也是新手😅😅😅,应该是没配置对,知道答案的麻烦请留言告知下~)

报错截图如下:

image.png

多次改路径,尝试无果后。。。我觉定在md文件中再次引入依赖的js代码。。

index.md完整代码如下:

---
title: 文章热度排行
date: 2019-08-14 15:23:11
---

<div id="top" style="margin-top:80px;">

</div>

<!-- Firebase App (the core Firebase SDK) is always required and must be listed first -->
<script src="https://www.gstatic.com/firebasejs/5.10.1/firebase-app.js"></script>

<!-- Add Firebase products that you want to use -->
<script src="https://www.gstatic.com/firebasejs/5.10.1/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/5.10.1/firebase-database.js"></script>

<script src="https://www.gstatic.com/firebasejs/5.10.1/firebase-firestore.js"></script>

<script>
  
  firebase.initializeApp({
      apiKey: 'AIzaSyDKul4ZXXXXX6l6UiHzXXXXvcDsiE', //你的apiKey
      projectId: 'aXXX5eXXX6b'  //你的projectId
   })
  
   var title= '';
   var count = 0;
   var url = '';
   const db = firebase.firestore();
   var collection =  'articles'; //主题配置文件配置的collection //{{ theme.firestore.collection }}';
   db.collection(collection).orderBy('count', 'desc').limit(10).get().then((querySnapshot) => {
       querySnapshot.forEach((doc) => {
       //  console.log(doc.id, " => ", doc.data());
           title = doc.id;
           count = doc.data().count;
           url = doc.data().url;
           var content="<h5><p>"+"<font color='#1C1C1C'>"+"【文章热度: "+count+" ℃】"+"</font>" + '&emsp;&emsp;' + "<span'><a href='"+url+"'>"+title+"</a></span>"+"</p></h5>";
           document.getElementById("top").innerHTML+=content
       });
   });

</script>

1、引入的js版本最好和firestore.swig中的保持一致
2、记得把apiKeyprojectId换成你自己的。。
3、orderBy这是是按Database中articles集合的count值降序排序
4、limit限制返回的结果集数量
tips:Firebase官方网站上,可全文搜索想要结果(都是谷歌网站的js包,必须能访问外网。。)

参考文档: Firebase读取数据Firebase对数据进行排序和限制数量github上firestore相关JS操作方法

完成以上步骤后,访问热榜界面,就能出现以下界面了:

image.png

文章排行有了,但是点击文章要跳转的链接还没有呢。我们也看到了,Firebase只保存了每个文章访问量count值,没有存文章的url信息。这就是接下来要解决的问题。。。

保存文章url地址

前文说过,Next为我们整合了firestore,那只能实现记录每篇文章浏览量的功能,要做热榜,似乎不满足需求啊,这就要我们改代码了。。🤣

修改/themes/next/layout/_third-party/analytics/firestore.swig,主要修改的地方有三处

修改文章页判断逻辑

firestore.swig之前的代码来做排行榜功能时,发现不止文章出现在排行榜页面,连左侧点击过的链接,比如分类标签归档等等的链接也会显示在排行榜页面。。。

后来博主经过多次调试、阅读代码后,发现了问题所在,原因就出现在现有的文章页非文章页判断逻辑上:

image.png

这个逻辑只能适用于单篇文章的阅读次数统计。。不能满足我们现在的需求了,代码修改如下:

      //https://hexo.io/zh-tw/docs/variables.html
      var isPost = '{{ page.title }}'.length > 0
      var isArchive = '{{ archive }}' === 'true'
      var isCategory = '{{ category }}'.length > 0
      var isTag = '{{ tag }}'.length > 0

      var urlPath = '{{ page.path }}';
      var urlFullPath = '{{ page.permalink }}';
      var indexPath = 'index.html'; //首页链接
      var isMenu = false;
      {% for name, path in theme.menu %}
         {# 判断当前链接是否是左侧菜单栏链接 #}
         var menuLink = '{{ url_for(path.split('||')[0]) | trim }}';
         if(urlPath.indexOf(menuLink) > 0 || urlPath == indexPath){
            isMenu = true;
         }
      {% endfor %}

    //  if (isPost) { //is article page
      if (!isMenu) { // 非菜单页、非主页(即在某篇文章链接里)
        var title = '{{ page.title }}'
        var doc = articles.doc(title)

      //  getCount(doc, true).then(appendCountTo($('.post-meta')))
        getCount(doc, urlFullPath, true).then(appendCountTo($('.post-wordcount')))
      }
     // else if (!isArchive && !isCategory && !isTag) { //is index page
      else if (urlPath == indexPath) { // 主页
        var titles = [] //array to titles

修改前后对比如下:


image.png

注意了:

var indexPath = 'index.html'; //首页链接

这是我在站点配置文件中修改后的结果


image.png

之前的:permalink: :year/:month/:day/:title/,在做SEO优化改成了:permalink: :title.html,所以这里能直接用来做判断了。

当然你不改的话,这里的if判断就得改成你现在的逻辑。

修改getCount方法

我也尝试过在articles.doc(title)中追加设置链接的方法,参考了Github,比如:

articles.doc(title).set({'uri': urlPath, 'url': urlFullPath});

不过这样操作后,在getCount方法中就会出现其它的错误,改动比较多。

偷懒一下,我就把url地址直接以参数的形式传递到getCount方法

getCount(doc, urlFullPath, true)

这样改动的就相对少了。。。😝

image.png

这里用了window.localStorage来保存整个网站的数据,以titlekey,保存的数据没有过期时间,直到手动去删除。

修改文章阅读次数样式(非必须)

最后的修改是为了改下文章阅读次数的样式

firestore.swig中的$('.post-meta')替换成$('.post-wordcount')
将图标$('<i>').addClass('fa fa-users')替换成$('<i>').addClass('fa fa-eye'),效果如下:

image.png

附上firestore.swig完整代码:

{% if theme.firestore.enable %}
  <script src="https://www.gstatic.com/firebasejs/4.6.0/firebase.js"></script>
  <script src="https://www.gstatic.com/firebasejs/4.6.0/firebase-firestore.js"></script>
  {% if theme.firestore.bluebird %}
    <script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.5.1/bluebird.core.min.js"></script>
  {% endif %}
  <script>
    (function () {

      firebase.initializeApp({
        apiKey: '{{ theme.firestore.apiKey }}',
        projectId: '{{ theme.firestore.projectId }}'
      })

      function getCount(doc, url, increaseCount) {
        //increaseCount will be false when not in article page

        return doc.get().then(function (d) {
          var count
          if (!d.exists) { //has no data, initialize count
            if (increaseCount) {
              doc.set({
                count: 1,
                'url': url
              })
              count = 1
            }
            else {
              count = 0
            }
          }
          else { //has data
            count = d.data().count
            if (increaseCount) {
              if (!(window.localStorage && window.localStorage.getItem(title))) { //if first view this article
                doc.set({ //increase count
                  count: count + 1
                })
                count++
              }
            }
          }
          if (window.localStorage && increaseCount) { //mark as visited
            localStorage.setItem(title, true)
          }

          return count
        })
      }

      function appendCountTo(el) {
        return function (count) {
          $(el).append(
            $('<span>').addClass('post-visitors-count').append(
              $('<span>').addClass('post-meta-divider').text('|')
            ).append(
              $('<span>').addClass('post-meta-item-icon').append(
               // $('<i>').addClass('fa fa-users')
                $('<i>').addClass('fa fa-eye')
              )
              ).append($('<span>').text('{{ __("post.visitors")}} ' + count + ' 次'))
          )
        }
      }

      var db = firebase.firestore()
      var articles = db.collection('{{ theme.firestore.collection }}')

      //https://hexo.io/zh-tw/docs/variables.html
      var isPost = '{{ page.title }}'.length > 0
      var isArchive = '{{ archive }}' === 'true'
      var isCategory = '{{ category }}'.length > 0
      var isTag = '{{ tag }}'.length > 0

      var urlPath = '{{ page.path }}';
      var urlFullPath = '{{ page.permalink }}';
      var indexPath = 'index.html'; //首页链接
      var isMenu = false;
      {% for name, path in theme.menu %}
         {# 判断当前链接是否是左侧菜单栏链接 #}
         var menuLink = '{{ url_for(path.split('||')[0]) | trim }}';
         if(urlPath.indexOf(menuLink) > 0 || urlPath == indexPath){
            isMenu = true;
         }
      {% endfor %}

    //  if (isPost) { //is article page
      if (!isMenu) { // 非菜单页、非主页(即在某篇文章链接里)
        var title = '{{ page.title }}'
        var doc = articles.doc(title)

      //  getCount(doc, true).then(appendCountTo($('.post-meta')))
        getCount(doc, urlFullPath, true).then(appendCountTo($('.post-wordcount')))
      }
     // else if (!isArchive && !isCategory && !isTag) { //is index page
      else if (urlPath == indexPath) { // 主页
        var titles = [] //array to titles

        var postsstr = '{% for post in page.posts %}titles.push("{{ post.title }}");{% endfor %}' //if you have a better way to get titles of posts, please change it
        eval(postsstr)

        var promises = titles.map(function (title) {
          return articles.doc(title)
        }).map(function (doc) {
          return getCount(doc)
        })
        Promise.all(promises).then(function (counts) {
          // var metas = $('.post-meta')
          var metas = $('.post-wordcount')
          counts.forEach(function (val, idx) {
            appendCountTo(metas[idx])(val)
          })
        })
      }
    })()
  </script>
{% endif %}

结尾

至于热榜页面的样式布局,可在其index.md文件中修改即可

经过博主三四天攻坚,以参阅Google官方文档为主,至此,文章热榜功能已经全部完成。。。👏👏👏

最终效果:

image.png


基于Firestore做的排行榜也有个缺点,那就是对于不能访问谷歌的用户来说,这个页面是不能正常显示的。。。

虽然Firebase具有离线访问数据功能,不过这是针对短期不能联网的情况。。😅😅😅

如果需要做到国内用户普遍都能访问,那好像就得依赖于Leancloud实现了,不知道大家有什么其他方案,欢迎留言讨论。

(本文完)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 143,396评论 1 301
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 61,482评论 1 258
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 94,858评论 0 213
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 41,131评论 0 179
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 48,903评论 1 256
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 38,847评论 1 177
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 30,454评论 2 273
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,206评论 0 167
  • 想象着我的养父在大火中拼命挣扎,窒息,最后皮肤化为焦炭。我心中就已经是抑制不住地欢快,这就叫做以其人之道,还治其人...
    爱写小说的胖达阅读 29,047评论 6 232
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 32,563评论 0 213
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,344评论 2 215
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 30,667评论 1 231
  • 白月光回国,霸总把我这个替身辞退。还一脸阴沉的警告我。[不要出现在思思面前, 不然我有一百种方法让你生不如死。]我...
    爱写小说的胖达阅读 24,264评论 0 32
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,163评论 2 214
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 31,546评论 3 207
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,630评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,032评论 0 166
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 33,572评论 2 231
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 33,668评论 2 232