内容介绍
本专题将带你使用 axum 实现一个简洁易用的博客系统应用骨架
本章我们将开始搭建本应用的骨架,包括:依赖、`Result` 和 `AppError` 以及通用数据库操作等。模板
我们的博客分为“前台”和“后台”两部分。前台用于展示博客内容,后台用于管理博客。本章我们将编写前台和后台的基础模板以及对应的路由。分类管理
本章开始,我们将对博客的具体业务进行实现。首先,我们实现博客分类的管理功能。文章管理
本章我们将实现博客的文章管理功能。鉴权与登录
本章实现后台管理的鉴权,以及管理员的登录、注销功能。涉及的知识点有:cookie及中间件等。后台管理菜单及首页模板
目前,后台管理功能基本完成,但还有两个工作没做:清理后台管理的导航菜单以及后台管理首页的模板。网站首页
后台管理完成后,我们开始进入前台功能的开发。本章我们将完成博客首页的开发。分类文章列表
本章将实现博客的分类文章列表功能。文章详情
本章将实现博客文章的详情显示功能。存档文章列表
本章将实现存档文章列表功能。注意,本章涉及较多PostgreSQL知识,如果你对相关知识不熟悉,可以先让代码跑起来,再去了解相关知识。总结与作业
恭喜你,已经完成了本专题的学习。下面我们对本专题进行简要的总结。
存档文章列表
- 543700
- 2022-03-26T16:50:25+08:00
本章将实现存档文章列表功能。注意,本章涉及较多PostgreSQL知识,如果你对相关知识不熟悉,可以先让代码跑起来,再去了解相关知识。
本功能模板文件是templates/frontend/topic_arch.html。
视图类
本功能视图类定义在src/view/frontend/topic.rs文件。
handler
// src/handler/frontend/topic.rs
pub async fn archive(
Extension(state): Extension<Arc<AppState>>,
Path(dt): Path<String>,
Query(args): Query<Args>,
) -> Result<HtmlView> {
let handler_name = "frontend/topic/archive";
let dt_str = dt.clone();
let dt = format!("{}01 00:00:00", dt);
let dt = Local.datetime_from_str(&dt, "%Y年%m月%d %H:%M:%S").map_err(AppError::from).map_err(log_error(handler_name))?;
let dt = dt.format("%Y-%m-%d %H:%M:%S").to_string();
let page = args.page();
let client = get_client(&state).await.map_err(log_error(handler_name))?;
let list = topic::list_by_arch(&client, page, dt)
.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 tmpl = ArchiveList {
list,
cats,
archives,
page,
dt:dt_str,
};
render(tmpl).map_err(log_error(handler_name))
}
- 通过
Path(dt): Path<String>
获取到的是数据是字符串类型的、类似2022年03月
这种格式的数据。 let dt = format!("{}01 00:00:00", dt);
将上述说的数据拼接为类似2022年03月01 00:00:00
的格式let dt = Local.datetime_from_str(&dt, "%Y年%m月%d %H:%M:%S").map_err(AppError::from).map_err(log_error(handler_name))?;
将上述的数据转换成chrono::DateTime<Local>
。通过这个操作,可以确保传入的参数的格式是正确的。let dt = dt.format("%Y-%m-%d %H:%M:%S").to_string();
将其转换成2022-03-01 00:00:00
的格式的字符串。
通过上面一系列的操作,我们最终得到了某年某月1日,零点零分零秒的日期时间。
topic::list_by_arch()
:通过传入的日期时间(每月1日零点,月初),使用 PostgreSQL 计算出月初和月尾的所有文章。
假设,dt
参数的值是:2022-03-01 00:00:00
,那么上述SQL语句可以写为:
SELECT
id,title,category_id,summary,hit,dateline,is_del,category_name
FROM v_topic_cat_list
WHERE
is_del=false AND
dateline BETWEEN '2022-03-01 00:00:00'::timestamp AND '2022-03-01 00:00:00'::timestamp + (INTERVAL '1' MONTH) - (INTERVAL '1' SECOND)
ORDER BY id DESC
LIMIT 30 OFFSET 0
'2022-03-01 00:00:00'::timestamp
:将字符串'2022-03-01 00:00:00'
转换为Postgresql的timestamp
类型'2022-03-01 00:00:00'::timestamp + (INTERVAL '1' MONTH)
:在2022-03-01 00:00:00
的基础上加上1个月,结果为2022-04-01 00:00:00
- (INTERVAL '1' SECOND)
:再将上一步的结果减去1秒,变成2022-03-31 23:59:59
- 代入以上结果之后,
dateline BETWEEN '2022-03-01 00:00:00' AND '2022-03-31 23:59:59'
,实现我们的目标
这里之所以要这样操作,是因为每个月的天数是不一定的,如果使用固定的值,比如30天
等,结果会不精确。当然对于博客来说,这种程度的不精确也是可以接受的。现在既然AXUM中文网给出了精确的方案,那自然应该用此方案了。
路由
本功能的路由定义在src/handler/frontend/mod.rs。
AppError
为了处理chrono可能产生的错误,对AppError
进行了补充:
/// src/error.rs
#[derive(Debug)]
pub enum AppErrorType {
// ...
Chrono,
}
impl From<chrono::ParseError> for AppError {
fn from(err: chrono::ParseError) -> Self {
Self::from_err(Box::new(err), AppErrorType::Chrono)
}
}
本章代码位于10/存档分支。