上一节,我们完成了文章的发布功能和图片上传功能,但是还没有将文章展示出来。这一节我们来介绍如何展示文章和增加浏览量计数问题。
文章详情页面html代码
{% include "partial/header.html" %}
<div class="layui-container index">
<div class="layui-row layui-col-space15">
<div class="layui-col-md8">
<div class="layui-card article-detail">
<div class="layui-card-body">
<h1 class="title">{{article.Title}}h1>
<div class="meta">
{% if article.Category %}<span><a href="/?category_id={{article.CategoryId}}">{{article.Category.Title}}a>span>{% endif %}
<span>{{stampToDate(article.CreatedTime, "2006-01-02")}}span>
<span>{{article.Views}} 阅读span>
{% if hasLogin %}<span><a href="/article/publish?id={{article.Id}}">编辑a>span>{% endif %}
div>
<div class="article-body">
{{article.ArticleData.Content|safe}}
div>
div>
div>
<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>
div>
<div class="layui-col-md4">
{% include "partial/author.html" %}
div>
div>
div>
{% include "partial/footer.html" %}
文章详情页面我们采用左右结构,左边显示文章标题、文章内容,上下篇文章等主要信息。右边则显示跟文章相关的最新文章、相关文章等内容。
左边显示的信息中,我们注意到显示文章分类使用的是{{article.Category.Title}}
,这是因为我们定义文章模型的时候,article.Category 它指向的是文章分类的模型,article.Category.Title 就能访问到文章分类的名称了。
同时,这里的文章发布时间,我们使用了{{stampToDate(article.CreatedTime, "2006-01-02")}}
来显示。stampToDate是我们前面自定义的模板函数,它可以将时间戳按照给定的格式格式化输出。这里我们将文章发布的时间戳按照"2006-01-02"的格式来输出显示。
文章内容的输出我们使用标签{{article.ArticleData.Content|safe}}
。这里同样地,article.ArticleData 指向的是文章内容表article_data模型,通过article.ArticleData.Content可以读取到文章的内容。这里我们使用了模板语言的|safe过滤标签,如果我们不使用safe标签的话,模板解析的时候,是为了安全,会对HTML标签和JS等语法标签进行自动转义,防止xss攻击。转义后,它就不是html了,这样不符合我们文章富文本内容输出的要求,因此这里我们需要使用safe来阻止它自动转义。
文章详情页控制器函数
文章详情页控制器我们写在controller/article.go 中。我们在article.go中添加ArticleDetail()
函数:
func ArticleDetail(ctx iris.Context) {
id := ctx.Params().GetUintDefault("id", 0)
article, err := provider.GetArticleById(id)
if err != nil {
NotFound(ctx)
return
}
_ = article.AddViews(config.DB)
ctx.ViewData("article", article)
ctx.View("article/detail.html")
}
这个函数,首先通过ctx.Params().GetUintDefault()
来获取文章的id,我们根据文章id来读取文章,如果文章不存在,则使用NotFound()
函数,输出404错误。检查完文章后,我们再使用ctx.ViewData("article", article)
将article注入到模板中,这样模板就能使用article这个变量了。然后通过ctx.View("article/detail.html")
来将控制器和文章详情模板关联起来。
这里我们使用了provider.GetArticleById(id)
,这个函数是需要访问数据库读取文章内容的,因此我们将它抽离到provider目录中。我们打开provider/article.go,在里面添加GetArticleById()
函数:
func GetArticleById(id uint) (*model.Article, error) {
var article model.Article
db := config.DB
err := db.Where("`id` = ?", id).First(&article).Error
if err != nil {
return nil, err
}
//加载内容
article.ArticleData = &model.ArticleData{}
db.Where("`id` = ?", article.Id).First(article.ArticleData)
//加载分类
article.Category = &model.Category{}
db.Where("`id` = ?", article.CategoryId).First(article.Category)
return &article, nil
}
这里我们从数据库根据文章id读取article信息的时候,先使用Preload("ArticleData")
来将文章的内容表信息也关联的读进来。
我们设置article模型的时候,category不是和文章表通过外键关联,因此我们需要单独将文章分类加载进来。
增加文章访问量
上面我们在用户访问到文章详情页的时候,使用了_ = article.AddViews(config.DB)
来增加文章的浏览量。这里为了直观,我们默认访问一次页面就当做增加一个浏览量。实际项目应用中,我们可能还需要根据用户的ip、ua来做处理,对搜索引擎的蜘蛛做区别处理,也要做访问记录等信息,而不是单纯的累加。
上面我们使用的是article.AddViews()
,说明这是一个文章模型的内置方法,因此我们在model/article.go 中,增加AddViews()
方法:
func (article *Article) AddViews(db *gorm.DB) error {
article.Views = article.Views + 1
db.Model(Article{}).Where("`id` = ?", article.Id).Update("views", article.Views)
return nil
}
这里我们需要由上层传入db对象。因此db对象是在config中的,如果我们在model中直接访问config,就会导致嵌套依赖,会导致golang项目编译不通过。而我们这里更新文章浏览量使用的是Update函数,Update函数可以直接更新对应字段,而不会在更新后再次读取表信息,可以减少一次查询操作。
配置文章详情页面路由
上面文章详情页面准备好了,我们还需要添加文章路由,才能让用户访问到文章详情页面,我们在route/base.go 中的article分组下,添加article.Get("/{id:uint}", controller.ArticleDetail)
,最终article分组的代码如下:
article := app.Party("/article", controller.Inspect)
{
article.Get("/{id:uint}", controller.ArticleDetail)
article.Get("/publish", controller.ArticlePublish)
article.Post("/publish", controller.ArticlePublishForm)
}
这里我们使用了{id:uint}
来获取文章id。这里我们定义id的类型为无符号整形数字。这么定义后,在文章详情控制器中可以通过id := ctx.Params().GetUintDefault("id", 0)
来获取。
验证结果
我们重启一下项目,我们先在浏览器中访问http://127.0.0.1:8001/article/1
来看看效果,验证下文章发布过程是否正常。如果不出意外可以看到这样的画面:
完整的项目示例代码托管在GitHub上,需要查看完整的项目代码可以到github.com/fesiong/goblog 上查看,也可以直接fork一份来在上面做修改。