内容介绍
本专题将带你使用 axum 实现一个简洁易用的博客系统应用骨架
本章我们将开始搭建本应用的骨架,包括:依赖、`Result` 和 `AppError` 以及通用数据库操作等。模板
我们的博客分为“前台”和“后台”两部分。前台用于展示博客内容,后台用于管理博客。本章我们将编写前台和后台的基础模板以及对应的路由。分类管理
本章开始,我们将对博客的具体业务进行实现。首先,我们实现博客分类的管理功能。文章管理
本章我们将实现博客的文章管理功能。鉴权与登录
本章实现后台管理的鉴权,以及管理员的登录、注销功能。涉及的知识点有:cookie及中间件等。后台管理菜单及首页模板
目前,后台管理功能基本完成,但还有两个工作没做:清理后台管理的导航菜单以及后台管理首页的模板。网站首页
后台管理完成后,我们开始进入前台功能的开发。本章我们将完成博客首页的开发。分类文章列表
本章将实现博客的分类文章列表功能。文章详情
本章将实现博客文章的详情显示功能。存档文章列表
本章将实现存档文章列表功能。注意,本章涉及较多PostgreSQL知识,如果你对相关知识不熟悉,可以先让代码跑起来,再去了解相关知识。总结与作业
恭喜你,已经完成了本专题的学习。下面我们对本专题进行简要的总结。
模板
- 547865
- 2022-03-26 12:27:22
我们的博客分为“前台”和“后台”两部分。前台用于展示博客内容,后台用于管理博客。本章我们将编写前台和后台的基础模板以及对应的路由。
目录结构
前台模板位于 templates/frontend
,后台模板位于templates/backend
。
我们的前台模板基于 Bootstrap的Blog 修改而来
我们的前台模板基于 Bootstrap的Blog 修改而来
布局
首先,我们对前台页面的布局来做一个全局总览。
注意,以下示意图链接到 github,如果你无法查看,请更换网络环境
母模板
- 对应【页面标题】的
{% block category_name %}分类名称{%endblock%}
- 对应【页面内容】的
{% block content%}页面内容{%endblock%}
虽然其它区域是固定的,但有些区域的内容还是需要从数据库中获取数据来填充。现阶段而言,我们只定义了【页面标题】对应的块,其它区域都保持硬编码的文字。
首页模板
有了母模板,我们就可以通过extends
来衍生出业务需要的特定模板了,比如现阶段我们定义的首页的模板:
<!-- templates/frontend/index.html -->
{%extends "./base.html"%}
{%block category_name%}最新博文{%endblock%}
视图类
下面,我们为前台首页定义一下视图类:
// src/view/frontend/index.rs
use askama::Template;
#[derive(Template)]
#[template(path="frontend/index.html")]
pub struct Index {}
handler
有了视图类,就可以将模板进行渲染:
// src/handler/frontend/index.rs
pub async fn index()->Result<HtmlView> {
let handler_name = "frontend/index/index";
let tmpl = Index{};
render(tmpl).map_err(log_error(handler_name))
}
在这个 handler 中,出现了几个新元素:
HtmlView
:自定义的类型,用于返回HTML视图render()
:用于通过视图类来渲染模板log_error()
:使用日志记录AppError
它们的代码实现如下:
HtmlView
// src/handler/mod.rs
type HtmlView = axum::response::Html<String>;
render()
// src/handler/mod.rs
fn render<T>(tmpl: T) ->Result<HtmlView> where T:Template {
let html = tmpl.render().map_err(AppError::from)?;
Ok(Html(html))
}
泛型T
必须满足askama::Template
约束。由于我们定义视图类的时候使用了 #[derive(Template)]
,所以我们的视图类满足这一约束。
该函数用于渲染模板,模板文件的路径通过视图类的#[template(path="frontend/index.html")]
指定。默认模板引擎会在项目根目录的templates
目录下,查找path
指定的模板文件。
log_error()
// src/handler/mod.rs
fn log_error(handler_name:&str) -> Box<dyn Fn(AppError)->AppError> {
let handler_name = handler_name.to_string();
Box::new(move |err| {
tracing::error!("操作失败:{:?}, {}", err, handler_name);
err
})
}
该函数用于记录产生的AppError
日志。
路由
我们还需要定义路由。
// src/handler/frontend/mod.rs
pub fn router()->Router {
Router::new().route("/", get(index::index))
}
后台
我们的后台模板基于 Bootstrap的Dashboard 修改而来
我们的后台模板基于 Bootstrap的Dashboard 修改而来
布局
我们来看一下后台页面的布局:
- 【页眉】显示应用名称以【退出登录】链接
- 【侧边菜单】显示可用的菜单
- 【页面标题】显示当前页面的标题
- 【工具栏】显示当前页面可用的工具栏
- 【提示信息】该区域和【页面内容】平行,示意图上没有画出。用于显示提示信息。
- 【页面内容】显示当前页面的内容
- 【分页】如果需要分页,则这里显示分页按钮
母模板
通过以上分析,我们需要定义以下块:
- 【页面标题】:
{% block title%}标题{%endblock%}
- 【工具栏】:
{% block toolbar %}{%endblock%}
- 【提示信息】
{% block msg%} {%endblock%}
- 【页面内容】
{%block content%}内容{%endblock%}
当然,现阶段而言,我们并不需要定义它们,随着项目的推进,会对其进行补全。
首页模板
根据母模板,我们来定义后台首页模板:
<!-- templates/backend/index.html -->
{% extends "./base.html" %}
视图类
// src/view/backend/index.rs
use askama::Template;
#[derive(Template)]
#[template(path="backend/index.html")]
pub struct Index {}
handler
// src/handler/backend/index.rs
pub async fn index()->Result<HtmlView>{
let handler_name="backend/index/index";
let tmpl = Index{};
render(tmpl).map_err(log_error(handler_name))
}
路由
// src/handler/backend/mod.rs
pub fn router() -> Router {
Router::new().route("/", get(index))
}
启动axum服务
// src/main.rs
use axum::Router;
use axum_rs_blog::handler::{backend, frontend};
#[tokio::main]
async fn main() {
if std::env::var_os("RUST_LOG").is_none() {
std::env::set_var("RUST_LOG", "axum_rs_blog=debug");
}
tracing_subscriber::fmt::init();
tracing::info!("服务已启动");
let frentend_routers = frontend::router();
let backend_routers = backend::router();
let app = Router::new()
.nest("/", frentend_routers)
.nest("/admin", backend_routers);
axum::Server::bind(&"127.0.0.1:9527".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
本章代码位于02/模板