本章将实现博客文章的详情显示功能。
数据库视图
CREATE VIEW v_topic_cat_detail AS
SELECT t.id, title, html, hit, dateline,category_id,t.is_del,
c.name AS category_name
FROM
topics AS t
INNER JOIN categories AS c
ON t.category_id=c.id
WHERE c.is_del = false
;
数据模型
// src/model.rs
#[derive(PostgresMapper, Serialize)]
#[pg_mapper(table="v_topic_cat_detail")]
pub struct TopicDetail{
pub id:i64,
pub title: String,
pub category_id:i32,
pub html:String,
pub hit:i32,
pub dateline:time::SystemTime,
pub is_del:bool,
pub category_name:String,
}
impl TopicDetail {
pub fn dateline(&self) ->String {
dateline(self.dateline.clone())
}
}
fn dateline(dt:time::SystemTime) -> String {
let ts = dt.duration_since(time::UNIX_EPOCH).unwrap_or(time::Duration::from_secs(0)).as_secs() as i64;
Local.timestamp(ts, 0).format("%Y/%m/%d %H:%M:%S").to_string()
}
这里新增了 dateline()
函数,用于将时间以字符串的形式显示。为了代码重用,TopicList::dateline()
也重构为调用该函数
// src/model.rs
impl TopicList {
pub fn dateline(&self) ->String {
dateline(self.dateline.clone())
}
}
模板
视图类
文章详情的视图类位于src/view/frontend/topic.rs,请自行查看。
handler
// src/handler/frontend/topic.rs
pub async fn detail(
Extension(state): Extension<Arc<AppState>>,
Path(id): Path<i64>,
) -> Result<HtmlView> {
let handler_name = "frontend/topic/list";
let client = get_client(&state).await.map_err(log_error(handler_name))?;
let cats = category::list(&client)
.await
.map_err(log_error(handler_name))?;
let archives = topic::archive_list(&client)
.await
.map_err(log_error(handler_name))?;
let state = state.clone();
let item = topic::detail(&client, id).await.map_err(log_error(handler_name))?;
let tmpl = Detail {
cats,
archives,
item,
};
render(tmpl).map_err(log_error(handler_name))
}
topic::detail()
用于通过id获取文章详情。
数据库操作
pub async fn detail(client: &Client, id: i64) -> Result<TopicDetail> {
super::execute(client, "UPDATE topics SET hit=hit+1 WHERE id=$1", &[&id]).await ?;
let sql = "SELECT id,title,category_id,html,hit,dateline,is_del,category_name FROM v_topic_cat_detail WHERE is_del=false and id=$1 LIMIT 1";
super::query_row(client, sql, &[&id]).await
}
首先,使文章的浏览次数加1,然后返回文章的数据。
前面章节说过,Postgresql支持RETURNING
,为什么此处要写两句SQL?
- 直接
UPDATE topics...RETURNING ...
并不能满足所需要的字段,比如category_name
- 视图原则上是只读的,所以不能
UPDATE v_topic_cat_detail...RETURING...
另外,此处不需要用事务,因为如果UPDATE
出错,那么整个函数都退出了(返回值是Err(AppError)
)——除非你把SELECT
写在UPDATE
前面。
路由
本功能的路由定义在src/handler/frontend/mod.rs文件。
本章代码位于09/文章详情分支。