文章详情页中,我们往往会增加上一篇、下一篇的展示。为什么要增加上一篇、下一篇的展示呢?
一方面,用户在看到文章结尾后,我们一般是认为他对这篇文章比较感兴趣,为了更方便用户查看更多的关联文章,这时候我们就将与这篇文章有一定关联的上一篇、下一篇展示出来,方便用户点击直达对应的文章查看,一定程度的减少用户的跳出率。
另一方面,方便搜索引擎抓取更多的页面链接,而实现更好的内容收录。如果每一篇文章都是孤立的,蜘蛛只能靠首页或列表页来抓取内容,这样会导致一些文章的连接很难被蜘蛛发现,从而无法被搜索引擎收录。在文章详情页中增加上一篇、下一篇可以增加文章内容链接的曝光度,增加蜘蛛爬行的可能性。
上一篇、下一篇的html代码
<div class="layui-card">
<div class="layui-card-body">
<div class="article-prev-next">
{% if prev %}
<li>上一篇:<a href="/article/{{prev.Id}}">{{prev.Title}}a>li>
{% endif %}
{% if next %}
<li>下一篇:<a href="/article/{{next.Id}}">{{next.Title}}a>li>
{% endif %}
div>
div>
div>
html代码倒是挺简单的,就是判断一下有没有上一篇,有的话,就输出。下一篇也是一样。这里我们其实也可以做的更详细一点,比如判断没有上一篇或下一篇的时候则给出提示说没有。如:
<li>
上一篇:
{% if prev %}
<a href="/article/{{prev.Id}}">{{prev.Title}}a>
{% else %}
没有了
{% endif %}
li>
<li>
下一篇:
{% 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/goblog 上查看,也可以直接fork一份来在上面做修改。