状态共享 AppState
增加管理员服务
pub struct AppState {
pub tera: Tera,
pub cate: CategoryServiceClient<tonic::transport::Channel>,
pub topic: TopicServiceClient<tonic::transport::Channel>,
pub admin: AdminServiceClient<tonic::transport::Channel>,
}
后台管理的 AppState
增加了管理员服务的连接。相应地,main()
函数也需要增加对其的初始化
main()
初始化管理员服务客户端
#[tokio::main]
async fn main() {
let addr = "0.0.0.0:59527";
let cate = CategoryServiceClient::connect("http://127.0.0.1:19527")
.await
.unwrap();
let topic = TopicServiceClient::connect("http://127.0.0.1:29527")
.await
.unwrap();
let admin = AdminServiceClient::connect("http://127.0.0.1:49527")
.await
.unwrap();
let tera = Tera::new("blog-backend/templates/**/*.html").unwrap();
let m_router = Router::new().route("/cate", get(handler::list_cate)).route(
"/cate/add",
get(handler::add_cate_ui).post(handler::add_cate),
);
let app = Router::new()
.nest("/m", m_router)
.route("/", get(handler::index))
.route("/login", get(handler::login_ui).post(handler::login))
.route("/logout", get(handler::logout))
.layer(Extension(Arc::new(model::AppState {
tera,
admin,
cate,
topic,
})));
axum::Server::bind(&addr.parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
登录状态和Cookie
为了维护登录状态,我们使用 Cookie
handler::login
登录
pub async fn login(
Extension(state): Extension<Arc<AppState>>,
Form(frm): Form<form::Login>,
) -> Result<(StatusCode, HeaderMap), String> {
let condition = blog_proto::get_admin_request::Condition::ByAuth(ByAuth {
email: frm.email,
password: frm.password,
});
let mut admin = state.admin.clone();
let resp = admin
.get_admin(tonic::Request::new(blog_proto::GetAdminRequest {
condition: Some(condition),
}))
.await
.map_err(|err| err.to_string())?;
let repl = resp.into_inner();
let logined_admin = match repl.admin {
Some(la) => la,
None => return Err("登录失败".to_string()),
};
let cookie = format!("axum_rs_token={}", &logined_admin.email);
Ok(redirect_with_cookie("/m/cate", Some(&cookie)))
}
handler::logout
注销登录
pub async fn logout() -> Result<(StatusCode, HeaderMap), String> {
Ok(redirect_with_cookie("/login", Some("axum_rs_token=")))
}
重定向
handler::list_cate
分类列表
pub async fn list_cate(
Extension(state): Extension<Arc<AppState>>,
Query(params): Query<form::CateListFilter>,
) -> Result<Html<String>, String> {
let mut ctx = Context::new();
let msg = params.msg.clone();
if let Some(msg) = msg {
ctx.insert("msg", &msg);
}
let mut cate = state.cate.clone();
let resp = cate.list_category(tonic::Request::new(params.into())).await;
let reply = match resp {
Ok(r) => r.into_inner(),
Err(err) => {
if err.code() == tonic::Code::NotFound {
ListCategoryReply { categories: vec![] }
} else {
return Err(err.to_string());
}
}
};
let mut cate_list = Vec::with_capacity(reply.categories.len());
for c in reply.categories {
let tc: blog_types::Category = c.into();
cate_list.push(tc);
}
ctx.insert("cate_list", &cate_list);
let out = state
.tera
.render("cate/index.html", &ctx)
.map_err(|err| err.to_string())?;
Ok(Html(out))
}
handler::add_cate
添加分类
pub async fn add_cate(
Extension(state): Extension<Arc<AppState>>,
Form(frm): Form<form::AddCatetory>,
) -> Result<(StatusCode, HeaderMap), String> {
let mut cate = state.cate.clone();
let resp = cate
.create_category(tonic::Request::new(blog_proto::CreateCategoryRequest {
name: frm.name,
}))
.await
.map_err(|err| err.to_string())?;
let repl = resp.into_inner();
let url = format!("/m/cate?msg=分类(ID为{})添加成功", repl.id);
Ok(redirect(&url))
}
作业
本章的代码只实现登录、注册登录、分类列表和添加分类。请结合之前章节和源码里的导航菜单,将其余功能实现完整。
本章代码位于06/后台管理分支。