博客功能篇:获取上一篇、下一篇文章的逻辑处理

1,214 阅读3分钟

文章详情页中,我们往往会增加上一篇、下一篇的展示。为什么要增加上一篇、下一篇的展示呢?

一方面,用户在看到文章结尾后,我们一般是认为他对这篇文章比较感兴趣,为了更方便用户查看更多的关联文章,这时候我们就将与这篇文章有一定关联的上一篇、下一篇展示出来,方便用户点击直达对应的文章查看,一定程度的减少用户的跳出率。

另一方面,方便搜索引擎抓取更多的页面链接,而实现更好的内容收录。如果每一篇文章都是孤立的,蜘蛛只能靠首页或列表页来抓取内容,这样会导致一些文章的连接很难被蜘蛛发现,从而无法被搜索引擎收录。在文章详情页中增加上一篇、下一篇可以增加文章内容链接的曝光度,增加蜘蛛爬行的可能性。

上一篇、下一篇的html代码

<div class="layui-card">
    <div class="layui-card-body">
        <div class="article-prev-next">
            {% if prev %}
            <li class="article-prev">上一篇:<a href="/article/{{prev.Id}}">{{prev.Title}}</a></li>
            {% endif %}
            {% if next %}
            <li class="article-next">下一篇:<a href="/article/{{next.Id}}">{{next.Title}}</a></li>
            {% endif %}
        </div>
    </div>
</div>

html代码倒是挺简单的,就是判断一下有没有上一篇,有的话,就输出。下一篇也是一样。这里我们其实也可以做的更详细一点,比如判断没有上一篇或下一篇的时候则给出提示说没有。如:

<li class="article-prev">
  上一篇:
  {% if prev %}
    <a href="/article/{{prev.Id}}">{{prev.Title}}</a>
  {% else %}
    没有了
  {% endif %}
</li>
<li class="article-next">
  下一篇:
  {% if next %}
    <a href="/article/{{next.Id}}">{{next.Title}}</a>
  {% else %}
    没有了
  {% endif %}
</li>

它就会在没有上一篇、下一篇的时候,显示没有了,但是上一篇、下一篇始终占着占位符,保证页面整齐度。

上一篇、下一篇的控制器代码

我们在文章详情控制器controller/article.go中,增加上一篇、下一篇的文章代码,并将它们绑定到view中:

	//获取上一篇
	prev, _ := provider.GetPrevArticleById(article.CategoryId, id)
	//获取下一篇
	next, _ := provider.GetNextArticleById(article.CategoryId, id)

我们分别使用了provider.GetPrevArticleById()provider.GetPrevArticleById()来获取上下篇。它们都需要依据文章的分类id、文章id来获取上下篇文章,具体的逻辑有不一样的地方,下面我们详细介绍。

上下篇的具体逻辑实现

我们在provider/article.go 中,增加provider.GetPrevArticleById()provider.GetPrevArticleById()两个函数:

func GetPrevArticleById(categoryId uint, id uint) (*model.Article, error) {
	var article model.Article
	db := config.DB
	if err := db.Model(model.Article{}).Where("`category_id` = ?", categoryId).Where("`id` < ?", id).Where("`status` = 1").Last(&article).Error; err != nil {
		return nil, err
	}

	return &article, nil
}

func GetNextArticleById(categoryId uint, id uint) (*model.Article, error) {
	var article model.Article
	db := config.DB
	if err := db.Model(model.Article{}).Where("`category_id` = ?", categoryId).Where("`id` > ?", id).Where("`status` = 1").First(&article).Error; err != nil {
		return nil, err
	}

	return &article, nil
}

上一篇和下一篇的获取代码,大致差不多。上下篇文章获取中,我们都限定了文章的分类,也就是说,在上下篇显示的文章中,只有是同属于当前文章的分类的文章,才会在上下篇中显示,这样做的目的是减少无关文章的展示,比如我的博客中有《技术分享》、《吃喝娱乐》的分类,它们包含的文章是完全不相关的,将他们展示在一起,对于浏览文章的用户来说,他们很可能并不会点击对应的文章链接。对于展示给用户这一方面来看就是失败的了。

获取上一篇文章除了根据文章的分类为条件判断外,我们还要判断是否存在小于当前id的文章Where("id < ?", id),并尝试获取小于当前文章id的所有文章中的最后一篇Last(&article)。它是如何保证获取的是最靠近当前id的文章呢?因为我们使用gorm的Last()方法的时候,它会自动给我们添加id desc主键排序,并返回第一条数据,因此我们就能获取到最靠近当前文章id的文章了。

由于上一篇和下一篇文章,对于文章本身来说刚好反过来,上一篇我们使用的是判断id小于当前文章id,因此下一篇我们的判断就是判断id大于当前文章id了。而读取数据的时候下一篇我们使用的是gorm的First()方法,这个方法,gorm会自动给我们添加id asc排序,并返回第一条数据,这样就保证了返回的下一篇文章是比当前文章id大,并最靠近当前文章的一篇文章了。

关于上下篇的问题,我们还可以进行更多的优化,比如,如果文章表的数据量非常大,这样根据id大于、小于来判断的话,可能会因为大于或小于当前文章的结果集太多,而导致mysql查询太慢而影响了页面性能。因此我们可能还需要根据id预测来做限制,比如:全表文章有100万条,当前id是10,那么上一篇的id最多只有1-9条,而下一篇则接近100万条,这么查询的话,结果是会有很大影响的。因此我们在这里尝试增加id限定,比如假设限制id为当前id扩展1000的id,我们可以这么写:

//上一篇
.Where("id BETWEEN ? AND ?", id-1000, id -1)
//下一篇
.Where("id BETWEEN ? AND ?", id+1, id + 1000)

这里除了使用between外,我们也可以使用大于、小于来判断,他们是等价的:

//上一篇
.Where("id < ? AND id >= ?", id, id -1000)
//下一篇
.Where("id > ? AND id <= ?", id, id + 1000)

完整的语句是:

func GetPrevArticleById(categoryId uint, id uint) (*model.Article, error) {
	var article model.Article
	db := config.DB
	if err := db.Model(model.Article{}).Where("`category_id` = ?", categoryId).Where("id BETWEEN ? AND ?", id-1000, id -1).Where("`status` = 1").Last(&article).Error; err != nil {
		return nil, err
	}

	return &article, nil
}

func GetNextArticleById(categoryId uint, id uint) (*model.Article, error) {
	var article model.Article
	db := config.DB
	if err := db.Model(model.Article{}).Where("`category_id` = ?", categoryId).Where("id BETWEEN ? AND ?", id+1, id + 1000).Where("`status` = 1").First(&article).Error; err != nil {
		return nil, err
	}

	return &article, nil
}

除了限定范围优化外,为了提高上下篇的关联程度,如果我们文章还有其他条件的话,还可以将他们都添加进来,比如他们同属于一个标签、同属于一个专题等。

完整的项目示例代码托管在GitHub上,需要查看完整的项目代码可以到github.com/fesiong/gob… 上查看,也可以直接fork一份来在上面做修改。