内容介绍
本专题将带你使用 axum 构建一个短链接服务。短链接,又称短网址,是指将一个可能比较长的链接变成固定长度的短链接,便于在社交媒体、短信等渠道发布。另外,它也可以隐藏原链接里的一些敏感信息,比如带邀请码的推广链接。短链接的核心算法
本章将对短链接的原理及实现算法进行讲解。准备工作
搞定了短链接的核心算法,我们可以开始进行业务开发。本章将进行一系列的准备工作,包括:配置、日志、自定义错误、handler、数据库操作原型、模板等。创建短链接
本章将实现创建短链接功能。短链接跳转到原始链接
我们继续实现功能,本章要实现的是通过短链接跳转到原始链接的功能。注意,跳转之前,我们需要将它的访问量加一。排行榜
本章继续完善短链接服务:显示排行。总结与代码清理
目前为止,我们的短链接服务基本已经完成了。但有一些地方不完善,同时有些警告没有处理掉。
准备工作
搞定了短链接的核心算法,我们可以开始进行业务开发。本章将进行一系列的准备工作,包括:配置、日志、自定义错误、handler、数据库操作原型、模板等。
本章代码在02/准备工作分支。
本章代码在02/准备工作分支。
日志
// 初始化日志
if std::env::var_os("RUST_LOG").is_none() {
std::env::set_var("RUST_LOG", "short_url=debug");
}
tracing_subscriber::fmt::init();
配置
配置文件
定义结构体
/// Web 配置
#[derive(Deserialize)]
pub struct WebConfig {
pub addr: String,
}
#[derive(Deserialize)]
pub struct ShortUrlConfig {
pub reserved_words:String,
pub domain:String,
}
impl ShortUrlConfig {
pub fn reserved_words(&self) -> Vec<&str> {
self.reserved_words.split(',').collect()
}
pub fn in_reserved_words(&self, word:&str) -> bool {
for w in self.reserved_words() {
if w == word {
return true;
}
}
false
}
}
/// 应用配置
#[derive(Deserialize)]
pub struct Config {
pub web: WebConfig,
pub pg: deadpool_postgres::Config,
pub short_url:ShortUrlConfig,
}
impl Config {
/// 从环境变量中初始化配置
pub fn from_env() -> Result<Self, config::ConfigError> {
let mut cfg = config::Config::new();
cfg.merge(config::Environment::new())?;
cfg.try_into()
}
}
实例化
// 解析 .env 文件
dotenv().ok();
let cfg = config::Config::from_env().expect("初始化配置失败");
共享状态
定义
/// 应用状态共享
#[derive(Clone)]
pub struct AppState {
/// PostgreSQL 连接池
pub pool: deadpool_postgres::Pool,
}
添加到共享
let pool = cfg
.pg
.create_pool(tokio_postgres::NoTls)
.expect("初始化数据库连接池失败");
let app = Router::new()
// ...
.layer(AddExtensionLayer::new(AppState { pool }));
自定义错误
定义
#[derive(Debug)]
pub enum AppErrorType {
DbError,
NotFound,
TemplateError,
}
#[derive(Debug)]
pub struct AppError {
pub message: Option<String>,
pub cause: Option<String>,
pub error_type: AppErrorType,
}
实现 IntoResponse
impl IntoResponse for AppError {
type Body = Full<Bytes>;
type BodyError = Infallible;
fn into_response(self) -> axum::http::Response<Self::Body> {
let msg = self.message.unwrap_or("有错误发生".to_string());
let tmpl = MsgTemplate {
is_ok: false,
msg: msg.clone(),
target_url: None,
};
let html = tmpl.render().unwrap_or(msg);
Html(html).into_response()
}
}
注意,这里使用了模板。具体请看下面的小节
实现其它 trait
AppError
还实现了其它 trait:Error
、From
、Display
等。请从代码仓库中查看完整代码。
Result
本项目定义了几个Result
:
pub type Result<T> = std::result::Result<T, crate::AppError>;
pub type HandlerResult<T> = self::Result<T>;
pub type RedirectResponse = (StatusCode, HeaderMap, ());
pub type HandlerRedirectResult = self::HandlerResult<RedirectResponse>;
pub type HtmlResponse = Html<String>;
pub type HandlerHtmlResult = HandlerResult<HtmlResponse>;
-
Result<T>
:简化标准库中的Result
调用 -
HandlerResult<T>
:Result<T>
的别名 -
RedirectResponse
:用于跳转的数据类型 -
HandlerRedirectResult
:简化HandlerResult<RedirectResponse>
的调用 -
HtmlResponse
:模板渲染的数据类型 -
HandlerHtmlResult
:简化HandlerResult<HtmlResponse>
HandlerResult<T>
:Result<T>
的别名
RedirectResponse
:用于跳转的数据类型
HandlerRedirectResult
:简化 HandlerResult<RedirectResponse>
的调用
HtmlResponse
:模板渲染的数据类型
模板及静态资源
创建模板及静态资源目录
在项目根目录分别创建 templates
和 static
模板文件
-
base.html
:父模板,其它模板都继承自它 -
index.html
:创建短链接的表单 -
msg.html
:显示提示信息 -
rank.html
:排行
base.html
:父模板,其它模板都继承自它
msg.html
:显示提示信息
rank.html
:排行
模板结构体
为模板文件创建对应的模板结构体(base.html
)除外:
#[derive(Template)]
#[template(path = "index.html")]
pub struct IndexTemplate {}
#[derive(Template)]
#[template(path = "rank.html")]
pub struct RankTemplate {
pub urls: Vec<Url>,
}
#[derive(Template)]
#[template(path = "msg.html")]
pub struct MsgTemplate {
pub is_ok: bool,
pub msg: String,
pub target_url: Option<String>,
}
MsgTemplate
还实现了一些方法,完整代码请从 git 查看。
handler
与之前专题的知识点一致,请通过 git 查看代码
数据库操作
模型
#[derive(PostgresMapper, Serialize)]
#[pg_mapper(table = "url")]
pub struct Url {
pub id: String,
pub url: String,
pub email: String,
pub visit: i32,
pub is_del: bool,
}
#[derive(PostgresMapper, Serialize)]
#[pg_mapper(table = "url")]
pub struct UrlID {
pub id: String,
}
#[derive(PostgresMapper, Serialize)]
#[pg_mapper(table = "url")]
pub struct UrlTarget {
pub url: String,
}
函数
本章只定义了数据库操作的函数声明,没有实现函数体。