域名 AXUM.RS 将于2025年10月到期。我们无意再对其进行续费,我们希望你能够接续这个域名,让更多 AXUM 开发者继续受益。
  • 方案1️⃣AXUM.RS 域名 = 3000
  • 方案2️⃣方案1️⃣ + 本站所有专题原始 Markdown 文档 = 5000
  • 方案3️⃣方案2️⃣ + 本站原始数据库 = 5500
如果你有意接续这份 AXUM 情怀,请与我们取得联系。
说明:
  1. 如果有人购买 AXUM.RS 域名(方案1️⃣),或者该域名到期,本站将启用新的免费域名继续提供服务。
  2. 如果有人购买了 AXUM.RS 域名,且同时购买了内容和/或数据库(方案2️⃣/方案3️⃣),本站将关闭。届时我们或许会以另一种方式与你再相遇。

准备工作

搞定了短链接的核心算法,我们可以开始进行业务开发。本章将进行一系列的准备工作,包括:配置、日志、自定义错误、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:ErrorFromDisplay 等。请从代码仓库中查看完整代码。

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:模板渲染的数据类型

模板及静态资源

创建模板及静态资源目录

在项目根目录分别创建 templatesstatic

模板文件

  • 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,
}

函数

本章只定义了数据库操作的函数声明,没有实现函数体。

表单

要查看完整内容,请先登录