简介
本专题将带你使用 axum 和 gRPC 构建一个分布式的博客系统数据结构与Protobuf
本章对我们项目的数据结构和proto进行定义实现分类服务
本章我们实现分类服务,即 `category-srv`实现文章服务
本章将带你实现文章的 gPRC 服务。实现前台web服务
本章将通过使用 axum 调用分类和文章的 gRPC 服务,来实现博客前台Web服务实现管理员服务
本章我们将实现管理员服务实现后台管理web服务
本章将使用 axum 调用 gRPC 服务来实现后台管理的 web 服务安全与鉴权
本章将讨论使用jwt进行鉴权服务扩容、注册、发现和编排
本章将讨论服务管理相关的话题配置中心服务
本章讨论配置中心的实现总结
本专题试图通过一个分布式博客的案例来探讨使用 rust 实现 gRPC 微服务架构的可行性
实现前台web服务
本章将通过使用 axum 调用分类和文章的 gRPC 服务,来实现博客前台Web服务。
创建项目将加入到 workspace 中:
添加依赖
[dependencies]
tokio = {version = "1", features = ["full"]}
prost = "0.11"
prost-types = "0.11"
tonic = "0.8"
axum = "0.5"
tera = "1"
serde = { version="1", features = ["derive"] }
chrono = "0.4"
blog-proto = {path="../blog-proto"}
blog-types = {path="../blog-types"}
由于采用了微服务,所以并没有直接的数据库操作。
blog-types
web 所需的数据结构
由于 web 需要的数据结构和 pb 生成的并不一定相同,比如需要序列化和反序列化、比如可能根据需要增加/删除某些结构体、字段等。
基于此,我们需要定义 web 所需要的数据结构,并提供 From/Into
等方法,方便和 pb 生成的数据结构进行转换。
代码相对简单,请直接在 git 上查看。
博客首页 handler::index
的实现
获取分类列表
let mut cate = state.cate.clone();
let resp = cate
.list_category(tonic::Request::new(ListCategoryRequest {
name: None,
is_del: Some(false),
}))
.await
.map_err(|err| err.to_string())?;
let reply = resp.into_inner();
let mut cate_list: Vec<blog_types::Category> = Vec::with_capacity(reply.categories.len());
for reply_cate in reply.categories {
cate_list.push(reply_cate.into());
}
我们通过调用分类服务的 list_category
方法来获取分类列表。为了共享 gRPC 客户端连接,我们将各种 gRPC 客户端连接通过 AppState 进行共享。
AppState
状态共享
在 model.rs
中,我们定义了 AppState
以实现handler间的状态共享:
pub struct AppState {
pub cate: CategoryServiceClient<tonic::transport::Channel>,
pub topic: TopicServiceClient<tonic::transport::Channel>,
pub tera: Tera,
}
impl AppState {
pub fn new(
cate: CategoryServiceClient<tonic::transport::Channel>,
topic: TopicServiceClient<tonic::transport::Channel>,
tera: Tera,
) -> Self {
Self { cate, topic, tera }
}
}
获取文章列表
文章列表需要接收多个可选参数,我们对其进行定义,并通过 axum 的 Query
进行获取:
#[derive(Deserialize, Serialize)]
pub struct QueryParams {
pub page: Option<i32>,
pub category_id: Option<i32>,
pub keyword: Option<String>,
}
为了方便在模板中组合成url参数,我们还定义了对应的结构体,并对其实现 From<QueryParams>
,方便从获取到的参数转换成模板中所需要的参数:
#[derive(Deserialize, Serialize)]
pub struct QueryParamsForUrl {
pub category_id: i32,
pub keyword: String,
pub page: i32,
}
impl From<QueryParams> for QueryParamsForUrl {
fn from(p: QueryParams) -> Self {
Self {
category_id: match p.category_id {
Some(cid) => cid,
None => 0,
},
keyword: match p.keyword {
Some(kw) => kw,
None => "".to_string(),
},
page: p.page.unwrap_or(0),
}
}
}
请点击查看 handler::index()
的完整代码。
博客文章详情 handler::detail
的实现
相对于 index()
,detail()
的实现就简单多了。
启动前台WEB服务
- 首先初始化
AppState
所需要的 gRPC 客户端连接和模板引擎 - 定义路由
本章代码位于04/实现前台服务