添加评论、RSS 订阅和站点地图功能实现


其实通过之前的步骤我们已经完成了博客的基本功能,这一篇也是功能部分的最后一篇,我们来给博客加点料,让博客功能更加完善。

1、评论的问题

现在这个博客的主要缺憾就是用户不能对文章进行评论,不幸的是,博客评论有很多问题要处理。

首先是稳定、令人满意且通用的评论管理,当然,基于 Laravel 我们可以添加这个功能到后台管理系统,并且允许用户注册、登录、对文章进行评论等等。创建这些功能都很简单,没什么复杂性可言。

但是真正的问题在于垃圾评论。

你将如何有效防止垃圾评论?使用验证码?黑名单/白名单?还是创建类似 Maksim Surguy 这样的 SPAM Honeypot ?或者通过集成 Akismet

坦白说,我不想处理这些令人头疼的事情,只想安安心心写博客。我们还可以使用第三方评论系统分分钟搞定博客评论。

注:学院采用的策略是注册用户邮箱验证,验证后的用户才能登录进行评论,这虽然一定程度上提升了用户操作的复杂度,但是同时有效拒绝了垃圾用户,整体而言对于有效用户来说,是提升了用户体验的,不然就会到处看到令人糟心的垃圾评论。

2、添加 Disqus 评论框

国内的第三方评论系统基本上都歇菜了,我们可以使用墙外的 Disqus 实现博客评论。

注册 Disqus 账户并获取评论代码

首先到 Disqus.com 注册一个免费账号,有了账号并登录之后,点击「GET STARTED」链接,选择「I want to install Disqus on my site」,会跳转到一个表单页面,按照自己的博客站点填写表单信息如下:

添加Disqus到Laravel学院

然后点击「Create Site」提交表单,在跳转到的订阅计划里选择一个免费的计划:

接下来在博客平台选择页面选择最下面这个:

最终跳转到的页面中包含了评论区域渲染代码:

我们将上述方框中的代码拷贝到博客页面中评论显示区域即可。

创建 Disqus 评论局部视图

resources/views/blog/partials 目录下创建一个新的局部视图文件 disqus.blade.php,编辑该文件内容如下:

<div id="disqus_thread"></div>
<script>
    var disqus_config = function () {
        this.page.url = 'http://blog.app/blog/{{ $post->slug }}';
        this.page.identifier = 'blog-{{ $post->slug }}';
    };
    (function () { // DON'T EDIT BELOW THIS LINE
        var d = document, s = d.createElement('script');
        s.src = 'https://laravelxue-yuan.disqus.com/embed.js';
        s.setAttribute('data-timestamp', +new Date());
        (d.head || d.body).appendChild(s);
    })();
</script>
<noscript>
    请启用 JavaScript 查看 <a href="https://disqus.com/?ref_noscript">Disqus 驱动的评论框</a>。
</noscript>

其实就是将上个步骤中 Disqus 的通用评论代码拷贝过来,并取消 disqus_config 变量的注释,然后修改 this.page.urlthis.page.identifier 的值。

我们将传递到视图的 $post->slug 变量作为 Disqus 的标识符以便于聚合该文章下的所有评论。

更新博客详情页视图文件

最后,在布局视图 resources/views/blog/layouts/master.blade.php@yield('content') 这行代码之后新增如下这行代码:

@yield('comments')

然后在 resources/views/blog/layouts/post.blade.php 视图文件底部添加如下代码:

@section('comments')
    <hr>
    <div class="container">
        <div class="row">
            <div class="col-lg-8 col-md-10 mx-auto">
                @include('blog.partials.disqus')
            </div>
        </div>
    </div>
@stop

好了,现在你的博客具备评论功能了,在文章详情页底部现在可以看到 Disqus 评论框如下:

Laravel博客Disqus评论框

这样,我们就可以通过评论框发送代码了。对于未登录用户,可以通过社交媒体登录,或者注册 Disqus 账号进行评论:

Laravel博客评论功能

3、添加分享链接

除了评论之外,很多博客还支持分享文章或站点到社交媒体功能,比如微信、微博、QQ、豆瓣、Facebook、Twitter等。要实现这一功能,可以借助百度分享或者 JiaThis 之类的第三方分享代码工具,还可以通过 overture 维护的 Share.js 来实现。对应的中文文档比较丰富,操作也很简单,这里就不做演示了。

4、实现 RSS 订阅

RSS 订阅对大部分博客应用而言是必备功能。在 Laravrel 中实现 RSS 订阅非常方便。

安装 Composer 依赖包

我们使用 suin/php-rss-writer 扩展包来生成 RSS 文件。

首先通过 Composer 安装该扩展包:

composer require suin/php-rss-writer

创建 RSS 订阅服务

接下来我们来创建一个服务类用于创建和返回 RSS 订阅。在 app/Services 目录下创建一个 RssFeed.php 文件,并编辑其代码如下:

<?php

namespace App\Services;

use App\Models\Post;
use Carbon\Carbon;
use Illuminate\Support\Facades\Cache;
use Suin\RSSWriter\Channel;
use Suin\RSSWriter\Feed;
use Suin\RSSWriter\Item;

class RssFeed
{
    /**
     * Return the content of the RSS feed
     */
    public function getRSS()
    {
        if (Cache::has('rss-feed')) {
            return Cache::get('rss-feed');
        }

        $rss = $this->buildRssData();
        Cache::add('rss-feed', $rss, 120);

        return $rss;
    }

    /**
     * Return a string with the feed data
     *
     * @return string
     */
    protected function buildRssData()
    {
        $now = Carbon::now();
        $feed = new Feed();
        $channel = new Channel();
        $channel
            ->title(config('blog.title'))
            ->description(config('blog.description'))
            ->url(url('/'))
            ->language('en')
            ->copyright('Copyright (c) ' . config('blog.author'))
            ->lastBuildDate($now->timestamp)
            ->appendTo($feed);

        $posts = Post::where('published_at', '<=', $now)
            ->where('is_draft', 0)
            ->orderBy('published_at', 'desc')
            ->take(config('blog.rss_size'))
            ->get();
        foreach ($posts as $post) {
            $item = new Item();
            $item
                ->title($post->title)
                ->description($post->subtitle)
                ->url($post->url())
                ->pubDate($post->published_at->timestamp)
                ->guid($post->url(), true)
                ->appendTo($channel);
        }

        $feed = (string)$feed;

        // Replace a couple items to make the feed more compliant
        $feed = str_replace(
            '<rss version="2.0">',
            '<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">',
            $feed
        );
        $feed = str_replace(
            '<channel>',
            '<channel>' . "\n" . '    <atom:link href="' . url('/rss') .
            '" rel="self" type="application/rss+xml" />',
            $feed
        );

        return $feed;
    }
}

更新博客配置

添加 rss_size 配置项到配置文件 config/blog.php,我们会在 RSSFeed 服务类中用这个配置值判断在 RSS 显示多少篇文章:

<?php
return [
    'name' => "Laravel 学院",
    'title' => "Laravel 学院",
    'subtitle' => 'https://laravel.geekai.co',
    'description' => 'Laravel学院致力于提供优质Laravel中文学习资源',
    'author' => '学院君',
    'page_image' => 'home-bg.jpg',
    'posts_per_page' => 5,
    'rss_size' => 25,
    'uploads' => [
        'storage' => 'public',
        'webpath' => '/storage/uploads/',
    ],
    'contact_email'=>env('MAIL_FROM'),
];

添加 RSS 路由、链接和方法

要实现 RSS 订阅功能还有三件事情要做。首先是添加路由到 routes/web.php

// 在下面这个路由后面
Route::post('contact', 'ContactController@sendContactInfo');

// 添加新的路由
Route::get('rss', 'BlogController@rss');

接下来更新 blog.layouts.master 视图文件 :

// 将如下这行代码
<title>{{ $title ?? config('blog.title') }}</title>

// 替换为
<title>{{ $title ?? config('blog.title') }}</title>
<link rel="alternate" type="application/rss+xml" href="{{ url('rss') }}"
        title="RSS Feed {{ config('blog.title') }}">

最后,更新 BlogController 控制器,新增 rss 方法:

// 在控制器顶部添加如下这个use语句
use App\Services\RssFeed;

// 同时在控制器中添加如下这个方法
public function rss(RssFeed $feed)
{
    $rss = $feed->getRSS();

    return response($rss)
      ->header('Content-type', 'application/rss+xml');
}

好了,现在去浏览器中访问 http://blog57.test/rss 你将会看到想要看到的东西。

如果想要在页面中显示 RSS 订阅链接,编辑 page-footer.blade.php 视图文件内容如下:

<hr>
<footer>
    <div class="container">
        <div class="row">
            <div class="col-lg-8 col-md-10 mx-auto">
                <ul class="list-inline text-center">
                    <li class="list-inline-item">
                        <a href="#">
                          <span class="fa-stack fa-lg">
                            <i class="fas fa-circle fa-stack-2x"></i>
                            <i class="fab fa-twitter fa-stack-1x fa-inverse"></i>
                          </span>
                        </a>
                    </li>
                    <li class="list-inline-item">
                        <a href="#">
                          <span class="fa-stack fa-lg">
                            <i class="fas fa-circle fa-stack-2x"></i>
                            <i class="fab fa-facebook-f fa-stack-1x fa-inverse"></i>
                          </span>
                        </a>
                    </li>
                    <li class="list-inline-item">
                        <a href="#">
                          <span class="fa-stack fa-lg">
                            <i class="fas fa-circle fa-stack-2x"></i>
                            <i class="fab fa-github fa-stack-1x fa-inverse"></i>
                          </span>
                        </a>
                    </li>
                    <li class="list-inline-item">
                        <a href="{{ url('rss') }}" data-toggle="tooltip" title="RSS feed">
                          <span class="fa-stack fa-lg">
                            <i class="fa fa-circle fa-stack-2x"></i>
                            <i class="fa fa-rss fa-stack-1x fa-inverse"></i>
                          </span>
                        </a>
                    </li>
                </ul>
                <p class="copyright text-muted">Copyright © {{ config('blog.author') }} 2018</p>
            </div>
        </div>
    </div>
</footer>

这样在博客底部会显示如下图标:

Laravel博客RSS订阅

5、生成站点地图

最后,我们为博客生成站点地图以利于SEO。

实现思路和 RSS 订阅一样:

创建 SiteMap 服务

app/Services 目录下新建一个 SiteMap.php,编辑其内容如下:

<?php

namespace App\Services;

use App\Models\Post;
use Carbon\Carbon;
use Illuminate\Support\Facades\Cache;

class SiteMap
{
    /**
     * Return the content of the Site Map
     */
    public function getSiteMap()
    {
        if (Cache::has('site-map')) {
            return Cache::get('site-map');
        }

        $siteMap = $this->buildSiteMap();
        Cache::add('site-map', $siteMap, 120);
        return $siteMap;
    }

    /**
     * Build the Site Map
     */
    protected function buildSiteMap()
    {
        $postsInfo = $this->getPostsInfo();
        $dates = array_values($postsInfo);
        sort($dates);
        $lastmod = last($dates);
        $url = trim(url('/'), '/') . '/';

        $xml = [];
        $xml[] = '<?xml version="1.0" encoding="UTF-8"?' . '>';
        $xml[] = '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';
        $xml[] = '  <url>';
        $xml[] = "    <loc>$url</loc>";
        $xml[] = "    <lastmod>$lastmod</lastmod>";
        $xml[] = '    <changefreq>daily</changefreq>';
        $xml[] = '    <priority>0.8</priority>';
        $xml[] = '  </url>';

        foreach ($postsInfo as $slug => $lastmod) {
            $xml[] = '  <url>';
            $xml[] = "    <loc>{$url}blog/$slug</loc>";
            $xml[] = "    <lastmod>$lastmod</lastmod>";
            $xml[] = "  </url>";
        }

        $xml[] = '</urlset>';

        return join("\n", $xml);
    }

    /**
     * Return all the posts as $url => $date
     */
    protected function getPostsInfo()
    {
        return Post::where('published_at', '<=', Carbon::now())
            ->where('is_draft', 0)
            ->orderBy('published_at', 'desc')
            ->pluck('updated_at', 'slug')
            ->all();
    }
}

添加路由和控制器方法

首先在路由文件 routes/web.php 中新增一个路由:

// 在如下这行之后
Route::get('rss', 'BlogController@rss');

// 添加新的路由
Route::get('sitemap.xml', 'BlogController@siteMap');

然后编辑控制器 BlogController,新增 siteMap 方法:

// 在控制器顶部添加如下use语句
use App\Services\SiteMap;

// 同时在控制器中新增这个方法
public function siteMap(SiteMap $siteMap)
{
    $map = $siteMap->getSiteMap();

    return response($map)
      ->header('Content-type', 'text/xml');
}

到浏览器访问 http://blog57.test/sitemap.xml,页面显示如下(以下是部分截图):

Laravel博客站点地图

至此,我们的博客系列告一段落,你已经构建起一个完整的博客应用,并且在此过程中相信你也学到了很多 Laravel 技能,但愿这个项目能作为大家学习 Laravel 框架的入门项目,并在此基础上,初步掌握 Laravel 框架,也希望大家在以后的进阶之路上越走越远,越走越好!


Vote Vote Cancel Collect Collect Cancel

<< 上一篇: 博客前台联系我们 & 邮件发送功能实现

>> 下一篇: 将博客应用自动部署到线上服务器完整流程详解