Hexo NexT文章閱讀量排行(熱榜)功能

原文 首發(fā) 北宸的小站,歡迎訪問!!!

前言

想掌握網(wǎng)站文章歡迎度、閱讀排行?那么「熱榜」功能就不可或缺

在寫這篇文章以前,本人也了解過其他博客的做法,無外乎是基于Leancloud實(shí)現(xiàn)的。很少有用谷歌Firestore(Firebase)來做的,這篇文章咱就來說說Firestore

PS:Leancloud現(xiàn)在需要上傳手持身份證照片進(jìn)行實(shí)名。。。

主要是本人比較排斥這樣搜刮隱私的行為??,So經(jīng)過幾次嘗(tou)(ji)無果后,果斷放棄。如果你也有遇到這種情況,希望此文能對你有所幫助。

關(guān)于Leancloud的相關(guān)實(shí)現(xiàn),可以參考這篇文章,精簡實(shí)用,本文也在此基礎(chǔ)上作了參考。

正文

/themes/next/_config.yml文件,已經(jīng)提前預(yù)配了firestore

image.png

是不是已經(jīng)給你提示了呢?而且next官方文檔也提到過,一共分為兩步:
1、注冊

image.png

2、配置

image.png

Firebase屬于谷歌的,確保你能訪問:https://console.firebase.google.com ? (tips:github)

步驟

注冊

可以直接使用Google 帳號登錄,沒有的自行進(jìn)行注冊,此步驟略過。。。

登錄成功后,點(diǎn)擊網(wǎng)頁右上角:轉(zhuǎn)到控制臺

添加項目

輸入項目名稱,比如我用的是leafjame2019814, 點(diǎn)擊繼續(xù),繼續(xù),用默認(rèn)的配置即可,完成后還可以再修改。

image.png

創(chuàng)建項目完成后,點(diǎn)擊網(wǎng)頁左側(cè)setttings按鈕,如圖:

image.png

即可查看到自己的項目ID網(wǎng)絡(luò) API 密鑰,這就是next主題配置文件中提到的projectIdapiKey

image.png

創(chuàng)建數(shù)據(jù)庫

創(chuàng)建完項目后,接著需要創(chuàng)建存儲數(shù)據(jù)的地方,如下圖所示:

image.png

有兩種選項,自己用的話,選擇以測試模式開始,就可以。

image.png

如果選擇以鎖定模式開始,則后續(xù)自己通過js api的方式寫入會提示沒有權(quán)限。。(可能需要配置權(quán)限什么的吧,我自己也沒搞懂)

下一步設(shè)置Cloud Firestore位置,有一下好幾種,分為多域性區(qū)域性。具體區(qū)別可查看文檔,默認(rèn)選擇nam5 (us-central)即可,點(diǎn)擊完成,等待分配Database。

image.png

完成后顯示是這樣的,現(xiàn)在還沒有數(shù)據(jù)。

可參考Cloud Firestore 使用入門,文檔還是很全的

配置文章閱讀量

做完上邊的操作后,接著就是把代碼集成到自己的項目中了。

/themes/next/layout/_third-party/analytics/firestore.swig中,其實(shí)已經(jīng)實(shí)現(xiàn)了文章閱讀量的功能,只需要經(jīng)過本文以上的操作,然后在/themes/next/_config.yml文件,啟用firestore

image.png

apiKeyprojectId就是上文中創(chuàng)建的。collection:集合ID,下邊會講到。

這個功能經(jīng)過配置后,可用于文章的閱讀次數(shù)了,當(dāng)文章未被查看時,效果是這樣的:

image.png

此時的Firebase中的Database下也沒有數(shù)據(jù),當(dāng)瀏覽這篇文章后,閱讀次數(shù)加1了。
如下圖:

image.png

再打開Firebase,你會發(fā)現(xiàn),也有數(shù)據(jù)了:

image.png

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

這控制臺能對集合、文檔、字段、值進(jìn)行操作,比如設(shè)置閱讀量啦什么的。。??

設(shè)置開發(fā)環(huán)境

每個頁面的閱讀量有了,不過我們要做的是排行榜啊,得把所有的頁面匯總排序。而且剛才的Database里存的數(shù)據(jù)也沒有URL等等這些信息呀,接下來要寫代碼了。。。

新增如下的頁面——「熱榜」,我想大家都會了吧,不細(xì)說了。


image.png

在這個index.md文件中,引入要用到的Firebase代碼,可參照官方文檔進(jìn)行。

之前我想直接在此md文件中引入firestore.swig,但啟動一直報錯。。(我也是新手??????,應(yīng)該是沒配置對,知道答案的麻煩請留言告知下~)

報錯截圖如下:

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限制返回的結(jié)果集數(shù)量
tips:Firebase官方網(wǎng)站上,可全文搜索想要結(jié)果(都是谷歌網(wǎng)站的js包,必須能訪問外網(wǎng)。。)

參考文檔: Firebase讀取數(shù)據(jù)Firebase對數(shù)據(jù)進(jìn)行排序和限制數(shù)量github上firestore相關(guān)JS操作方法

完成以上步驟后,訪問熱榜界面,就能出現(xiàn)以下界面了:

image.png

文章排行有了,但是點(diǎn)擊文章要跳轉(zhuǎn)的鏈接還沒有呢。我們也看到了,F(xiàn)irebase只保存了每個文章訪問量count值,沒有存文章的url信息。這就是接下來要解決的問題。。。

保存文章url地址

前文說過,Next為我們整合了firestore,那只能實(shí)現(xiàn)記錄每篇文章瀏覽量的功能,要做熱榜,似乎不滿足需求啊,這就要我們改代碼了。。??

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

修改文章頁判斷邏輯

firestore.swig之前的代碼來做排行榜功能時,發(fā)現(xiàn)不止文章出現(xiàn)在排行榜頁面,連左側(cè)點(diǎn)擊過的鏈接,比如分類標(biāo)簽歸檔等等的鏈接也會顯示在排行榜頁面。。。

后來博主經(jīng)過多次調(diào)試、閱讀代碼后,發(fā)現(xiàn)了問題所在,原因就出現(xiàn)在現(xiàn)有的文章頁非文章頁判斷邏輯上:

image.png

這個邏輯只能適用于單篇文章的閱讀次數(shù)統(tǒng)計。。不能滿足我們現(xiàn)在的需求了,代碼修改如下:

      //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 %}
         {# 判斷當(dāng)前鏈接是否是左側(cè)菜單欄鏈接 #}
         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'; //首頁鏈接

這是我在站點(diǎn)配置文件中修改后的結(jié)果


image.png

之前的:permalink: :year/:month/:day/:title/,在做SEO優(yōu)化改成了:permalink: :title.html,所以這里能直接用來做判斷了。

當(dāng)然你不改的話,這里的if判斷就得改成你現(xiàn)在的邏輯。

修改getCount方法

我也嘗試過在articles.doc(title)中追加設(shè)置鏈接的方法,參考了Github,比如:

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

不過這樣操作后,在getCount方法中就會出現(xiàn)其它的錯誤,改動比較多。

偷懶一下,我就把url地址直接以參數(shù)的形式傳遞到getCount方法

getCount(doc, urlFullPath, true)

這樣改動的就相對少了。。。??

image.png

這里用了window.localStorage來保存整個網(wǎng)站的數(shù)據(jù),以titlekey,保存的數(shù)據(jù)沒有過期時間,直到手動去刪除。

修改文章閱讀次數(shù)樣式(非必須)

最后的修改是為了改下文章閱讀次數(shù)的樣式

firestore.swig中的$('.post-meta')替換成$('.post-wordcount')
將圖標(biāo)$('<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 %}
         {# 判斷當(dāng)前鏈接是否是左側(cè)菜單欄鏈接 #}
         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 %}

結(jié)尾

至于熱榜頁面的樣式布局,可在其index.md文件中修改即可

經(jīng)過博主三四天攻堅,以參閱Google官方文檔為主,至此,文章熱榜功能已經(jīng)全部完成。。。??????

最終效果:

image.png


基于Firestore做的排行榜也有個缺點(diǎn),那就是對于不能訪問谷歌的用戶來說,這個頁面是不能正常顯示的。。。

雖然Firebase具有離線訪問數(shù)據(jù)功能,不過這是針對短期不能聯(lián)網(wǎng)的情況。。??????

如果需要做到國內(nèi)用戶普遍都能訪問,那好像就得依賴于Leancloud實(shí)現(xiàn)了,不知道大家有什么其他方案,歡迎留言討論。

(本文完)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。