基于 Laravel 开发博客应用系列 —— 添加评论、RSS 订阅和站点地图功能实现

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

1、评论的问题

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

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

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

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

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

2、添加 Disqus 评论框

我们使用 Disqus 实现博客评论。

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

Disqus.com 注册一个免费账号,有了账号并登录之后,点击“Add Disqus to site”链接,会跳转到如下这个页面:

添加Disqus到Laravel学院

填写好上述页面中的表单并提交之后,在跳转页面中选择“Universal Code”,然后页面会跳转到如下页面:

Disqus评论通用代码

将方式1中的代码拷贝到页面中评论显示区域即可。

传递文章 slug 字段到页面

接下来我们在控制器 BlogControllershowPost() 方法中传递 $slug 变量到显示文章详情的视图页面:

// 将如下这行代码
return view($post->layout, compact('post', 'tag'));

//修改成
return view($post->layout, compact('post', 'tag', 'slug'));

很简单吧!现在文章视图中有了一个额外的可用变量。

创建 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/{{ $slug }}';
            this.page.identifier = 'blog-{{ $slug }}';
        };
        (function() { // DON'T EDIT BELOW THIS LINE
            var d = document, s = d.createElement('script');

            s.src = '//laravel-academy.disqus.com/embed.js';

            s.setAttribute('data-timestamp', +new Date());
            (d.head || d.body).appendChild(s);
        })();
</script>
<noscript>
        Please enable JavaScript to view the 
        <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a>
</noscript>

将 Disqus 中的通用评论代码拷贝过来,并取消 disqus_config 变量的注释,然后修改 this.page.url 和 this.page.identifier 的值。

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

更新底部视图文件

最后,更新 resources/views/blog/partials 目录下的 page-footer.blade.php 文件内容如下:

@if(isset($slug) && $slug)
<hr>
<div class="container">
  <div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
    @include('blog.partials.disqus')
  </div>
</div>
@endif
<hr>
<footer>
  <div class="container">
    <div class="row">
      <div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
        <p class="copyright">Copyright © {{ config('blog.author') }}</p>
      </div>
    </div>
  </div>
</footer>

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

Laravel博客Disqus评论框

3、添加分享链接

除了评论之外,很多博客还支持分享文章或站点到社交媒体功能,比如微信、微博、QQ、豆瓣、Facebook、Twitter等。要实现这一功能,可以借助百度分享或者 JiaThis 的分享代码。将相应分享代码拷贝到到页面相应位置即可,该操作很简单,这里不做演示。

4、实现 RSS 订阅

RSS 订阅对大部分博客应用而言是必备功能。在 Laravrel 5.1 中实现 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\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' => 'http://laravelacademy.org',
    'description' => 'Laravel学院致力于提供优质Laravel中文学习资源',
    'author' => '学院君',
    'page_image' => 'home-bg.jpg',
    'posts_per_page' => 10,
    'rss_size' => 25,
    'uploads' => [
        'storage' => 'local',
        'webpath' => '/uploads/',
    ],
    'contact_email'=>env('MAIL_FROM'),
];

添加 RSS 路由、链接和方法

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

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

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

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

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

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

最后,更新 BlogController

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

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

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

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

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

@if(isset($slug) && $slug)
<hr>
<div class="container">
  <div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
    @include('blog.partials.disqus')
  </div>
</div>
@endif
<hr>
<footer>
  <div class="container">
    <div class="row">
      <div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
        <ul class="list-inline text-center">
          <li>
            <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">Copyright © {{ config('blog.author') }}</p>
      </div>
    </div>
  </div>
</footer>

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

Laravel博客RSS订阅图标

5、生成站点地图

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

实现思路和 RSS 订阅一样:

创建 SiteMap 服务

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

<?php

namespace App\Services;

use App\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')
      ->lists('updated_at', 'slug')
      ->all();
  }
}

添加路由和控制器方法

首先编辑路由文件 routes.php

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

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

然后编辑控制器 BlogController

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

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

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

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

Laravel博客站点地图截图

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

学院君

学院君 has written 548 articles

资深PHP工程师,Laravel学院院长