准备工作

793409
2021/11/26 04:37:16

搞定了短链接的核心算法,我们可以开始进行业务开发。本章将进行一系列的准备工作,包括:配置、日志、自定义错误、handler、数据库操作原型、模板等。

本章代码在02/准备工作分支。

日志

// 初始化日志
if std::env::var_os("RUST_LOG").is_none() {
    std::env::set_var("RUST_LOG", "short_url=debug");
}
tracing_subscriber::fmt::init();

配置

配置文件

WEB.ADDR=0.0.0.0:9527
PG.USER=kujmgfmv
PG.PASSWORD=dBAjIfEheeT5ua6tThQQPEkloBZL3DNq
PG.DBNAME=kujmgfmv
PG.PORT=5432
PG.HOST=john.db.elephantsql.com
PG.POOL.MAX_SIZE=4
SHORT_URL.RESERVED_WORDS=rank
SHORT_URL.DOMAIN=127.0.0.1:9527

定义结构体

/// 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>

模板及静态资源

创建模板及静态资源目录

模板文件

  • index.html:创建短链接的表单

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

函数

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

表单

#[derive(Deserialize)]
pub struct CreateUrl {
    pub url: String,
    pub email: String,
}
#[derive(Deserialize)]
pub struct UpdateUrl {
    pub id: String,
    pub url: String,
    pub email: String,
}