写这篇文章之前,本人也了解过其他网站的做法,大部分都是基于 Leancloud 来实现,使用谷歌 Firestore(Firebase)来做的很少。国内版 Leancloud 需要实名认证,最近还需绑定自己的域名(详情 )。一种方案就是使用国际版 来替代,此版本只需要绑定邮箱和手机号,参考:这篇文章
主要是本人比较排斥这样搜刮隐私的行为,So 经过几次尝(tou)试(ji)无果后,果断放弃!
关于 Leancloud 的相关实现,可以参考下边这篇文章,精简实用,本文也在此基础上作了参考。
hexo next 新增阅读排行页面
注意:使用 leancloud 这功能时,有以下两个前提
1、主题配置文件开启 leancloud_visitors
,并设置 app_id
和 app_key
:
1 2 3 4 leancloud_visitors: enable: true app_id: KWIxah6ChxxxxoTi-xxxHsz app_key: 3wuQxxxbjUakGxxxxxsT1P
配置文件也有 “Show number of visitors to each article” 这个说明,开启这个才能统计每篇文章的的访客数(阅读量),排行榜功能是需要用到这个数值的,所以得开启
2、leancloud 应用中创建 Class
登录leancloud控制台 创建应用(也可以用已经创建好的),在所选择应用的存储选项下创建Class按钮,新建一个class,class名一定要是Counter
,其它配置默认即可。若是不创建这个Class的话,上一步中的访客数(阅读量)就一直为空,排行榜功能就不能正常工作。
创建后正常访问:
以上两点提示,重要!!!
主题配置文件,已经提前预配了 firestore
:
是不是已经给你提示了呢?而且官方文档 也提到过,一共分为两步:
1、注册
2、配置
注册 可以直接使用 Google 帐号登录,没有的自行进行注册,此步骤略过。登录成功后,点击网页右上角:转到控制台
添加项目 输入项目名称,比如是 leafjame2019814
, 点击继续,默认配置即可,完成后还可以再修改。
创建项目完成后,点击网页左侧 setttings
按钮,如图:
可查看到自己的项目 ID
和网络 API 密钥
,这就是 next 主题配置文件中提到的 projectId
和 apiKey
创建数据库 创建完项目后,接着需要创建存储数据的地方,如下图所示:
有两种选项,自己用的话,选择以测试模式开始
,就可以。
如果选择以锁定模式开始 ,后续自己通过 js api 的方式写入时会提示没有权限
下一步设置 Cloud Firestore 位置,有以下好几种,分为多域性
和区域性
。具体区别可查看文档 ,默认选择 nam5 (us-central)
即可,点击完成,等待分配 Database。
完成后显示是这样的,现在还没有数据。
可参考Cloud Firestore 使用入门 ,文档还是很全的
配置文章阅读量 做完上边的操作后,接着就是把代码集成到自己的项目中了。
在 next/layout/_third-party/analytics/firestore.swig
中,其实已经实现了文章阅读量的功能,只需要经过本文以上的操作,然后在 next/_config.yml
文件,启用 firestore
:
里面的 apiKey
和 projectId
就是上文中创建的,collection
:集合 ID,下边会讲到。
这个功能经过配置后,可用于文章的阅读次数了,当文章未被查看时,效果是这样的:
此时 Firebase 中 Database 下还没有 数据,当浏览这篇文章后,阅读次数加 1 了,如图:
再打开 Firebase,你会发现,也有数据了:
articles
就是上边配置的 collection
的值,即:集合 ID
这控制台能对集合、文档、字段、值进行操作,比如设置阅读量啦什么的~
设置开发环境 每个页面的阅读量有了,不过我们要做的是排行榜啊,得把所有的页面汇总排序。而且刚才的 Database 里存的数据也没有 URL 等等这些信息呀,接下来要写代码了。
新增如下的页面——「热榜」,我想大家都会了吧,不细说了。
在这个 index.md 中,引入要用到的 Firebase 代码,可参照官方文档 进行。
之前我想直接在此 md 文件中引入 firestore.swig,但启动一直报错。。(应该是没配置对的缘故)
报错截图如下:
多次改路径尝试无果后,我觉定在 md 文件中再次引入依赖的 js 代码。
index.md
完整代码如下:
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 --- 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:<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 > " + '    ' + "<span' > <a href ='"+url+"' > "+title+"</a > </span > "+"</p > </h5>"; document.getElementById("top").innerHTML+=content }); }); </ script>
1、引入的 js 版本最好和 firestore.swig 中的保持一致 2、记得把 apiKey 和 projectId 换成你自己的。。 3、orderBy 这是是按 Database 中 articles 集合的 count 值降序排序 4、limit 限制返回的结果集数量 tips:Firebase 官方网站上,可全文搜索想要结果
参考文档: Firebase 读取数据 、 Firebase 对数据进行排序和限制数量 、 github 上 firestore 相关 JS 操作方法
完成以上步骤后,访问热榜 界面,就能出现以下界面了:
文章排行有了,但是点击文章要跳转的链接还没有呢。我们也看到了,Firebase 只保存了每个文章访问量 count
值,没有存文章 url 信息。这就是接下来要解决的问题。
保存文章url地址 前文说过,Next 整合了 firestore,那只能实现记录每篇文章浏览量的功能,要做热榜 ,似乎不满足需求啊,得改代码。
修改 next/layout/_third-party/analytics/firestore.swig
,主要修改的地方有三处:
修改文章页判断逻辑 以 firestore.swig 之前的代码来做排行榜功能时,发现不止文章出现在排行榜页面,连左侧点击过的链接,比如分类、标签、归档等等的链接也会显示在排行榜页面。
后来博主经过多次调试、阅读代码后,发现了问题所在,原因就出现在现有的文章页和非文章页判断逻辑上:
这个逻辑只能适用于单篇文章的阅读次数统计。。不能满足我们现在的需求了,代码修改如下:
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 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 (!isMenu) { var title = '{{ page.title }}' var doc = articles.doc(title) getCount(doc, urlFullPath, true ).then(appendCountTo($('.post-wordcount' ))) } else if (urlPath == indexPath) { var titles = []
修改前后对比如下:
注意了:
1 var indexPath = 'index.html' ;
这是我在站点配置文件中修改后的结果
之前的 permalink: :year/:month/:day/:title/
,在做 SEO 优化改成了 permalink: :title.html
,所以这里能直接用来做判断了。当然你不改的话,这里的 if 判断就得改成你现在的逻辑。
修改getCount方法 我也尝试过在 articles.doc(title)
中追加设置链接的方法,参考了 Github ,比如:
1 articles.doc(title).set({'uri' : urlPath, 'url' : urlFullPath});
不过这样操作后,在 getCount 方法中就会出现其它的错误,改动比较多,偷懒一下,我就把 url 地址直接以参数的形式传递到 getCount 方法
1 getCount(doc, urlFullPath, true )
这样改动的就相对少了。
这里用了 window.localStorage 来保存整个网站的数据,以 title 为 key,数据没有过期时间,直到手动去删除。
修改文章阅读次数样式(非必须) 最后的修改是为了改下文章阅读次数的样式
将 firestore.swig
中的 $('.post-meta')
替换成 $('.post-wordcount')
;将图标 $('<i>').addClass('fa fa-users')
替换成 $('<i>').addClass('fa fa-eye')
,效果如下:
附上 firestore.swig
完整代码:
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 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 {% 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: {% endif %} <script> (function ( ) { firebase.initializeApp({ apiKey: '{{ theme.firestore.apiKey }}' , projectId: '{{ theme.firestore.projectId }}' }) function getCount (doc, url, increaseCount ) { return doc.get().then(function (d ) { var count if (!d.exists) { if (increaseCount) { doc.set({ count: 1 , 'url' : url }) count = 1 } else { count = 0 } } else { count = d.data().count if (increaseCount) { if (!(window .localStorage && window .localStorage.getItem(title))) { doc.set({ count: count + 1 }) count++ } } } if (window .localStorage && increaseCount) { 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-eye' ) ) ).append($('<span>' ).text('{{ __("post.visitors")}} ' + count + ' 次' )) ) } } var db = firebase.firestore() var articles = db.collection('{{ theme.firestore.collection }}' ) 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 (!isMenu) { var title = '{{ page.title }}' var doc = articles.doc(title) getCount(doc, urlFullPath, true ).then(appendCountTo($('.post-wordcount' ))) } else if (urlPath == indexPath) { var titles = [] var postsstr = '{% for post in page.posts %}titles.push("{{ post.title }}");{% endfor %}' 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-wordcount' ) counts.forEach(function (val, idx ) { appendCountTo(metas[idx])(val) }) }) } })() </script> {% endif %}
结尾 至于热榜 页面的样式布局,可在其 index.md
文件中修改即可
经过博主三四天 攻坚,以参阅 Google 官方文档为主,至此,文章热榜功能已经全部完成!👏👏👏
最终效果:
基于 Firestore 做的排行榜也有个缺点,对于不能访问谷歌的用户来说,这个页面是不能正常显示的。
虽然 Firebase 具有离线访问数据 功能,不过这是针对短期不能联网的情况。
如果需要做到国内用户普遍都能访问,那好像就得依赖于 Leancloud
实现了,不知道大家有什么其他方案,欢迎留言讨论。
(本文完)