项目初始化及配置

本章将对我们的项目进行初始化,然后编写配置。

创建项目

本专题包含两个项目:用于开发后端的 Rust 项目和用于开发前端的 React 项目。

# 创建本专题需要的目录
mkdir domain-distribution
# 进入该目录
cd domain-distribution
# 创建后端项目
cargo new domain-distribution

# 创建前端项目
yarn create vite domain-distribution-ui  --template react-ts

最终的目录结构是:

domain-distribution
├── domain-distribution
└── domain-distribution-ui

初始化后端项目

抛开前端不看,我们先初始话后端项目。

项目依赖

[dependencies]
tokio = { version = "1", features = ["full"] }
axum = { version = "0.7" }
serde = { version = "1", features = ["derive"] }
chrono = { version = "0.4", features = ["serde"] }
sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "chrono"] }
bcrypt = "0.16"
config = { version = "0.14", features = ["toml"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
reqwest = { version = "0.12", features = ["json"] }
serde_json = "1"
validator = { version = "0.19", features = ["derive"] }
tower-http = { version = "0.6", features = ["cors"] }
xid = "1"
anyhow = "1"
rand = "0.8"
lettre = { version = "0.11", features = ["tokio1-native-tls"] }
utf8_slice = "1"

创建一个空的 src/lib.rs 文件。我们还是使用 lib 方式来组织代码。

创建 src/config.rs 文件,并在 src/lib.rs 加上 pub mod config;

该文件的内容如下:

use rand::Rng;
use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize)]
pub struct Config {
    pub web: Web,
    pub database: Database,
    pub user: User,
    pub emails: Vec<Email>,
    pub cloudflare: Cloudflare,
    pub domail: Domail,
    pub turnstile: Turnstile,
    pub session: Session,
}

#[derive(Deserialize, Serialize)]
pub struct Web {
    /// 监听地址
    pub addr: String,
    /// URL 前缀
    pub prefix: String,
}

#[derive(Deserialize, Serialize)]
pub struct Database {
    /// 数据源
    pub dsn: String,
    /// 最大连接数
    pub max_conns: u16,
}

#[derive(Deserialize, Serialize)]
pub struct User {
    /// 是否禁用邮箱验证
    pub disabled_verify: bool,
    /// 初始积分
    pub init_pointers: i32,
    /// 允许注册的 Email 后缀
    pub allowed_email_domains: Vec<String>,
}

#[derive(Deserialize, Serialize)]
pub struct Email {
    /// 发信服务器
    pub smtp: String,
    /// 发信人用户名
    pub user: String,
    /// 发信人密码
    pub password: String,
}

#[derive(Deserialize, Serialize)]
pub struct Cloudflare {
    /// ZONE ID
    pub zone_id: String,
    /// TOKEN
    pub token: String,
    /// API 超时秒数
    pub api_timeout: u8,
}

#[derive(Deserialize, Serialize)]
pub struct Domail {
    /// 域名列表
    pub domails: Vec<String>,
    /// 每天记录消耗的积分
    pub per_pointers: i32,
    /// 允许的记录类型
    pub allowed_dns_type: Vec<String>,
    /// 域名最小长度
    pub min_len: u16,
    /// 域名最大长度
    pub max_len: u16,
    /// 黑名单/保留字
    pub keywords: Vec<String>,
}

#[derive(Deserialize, Serialize)]
pub struct Turnstile {
    /// 安全密钥
    pub secret_key: String,
    /// 验证地址
    pub validation_url: String,
    /// 超时秒数
    pub timeout: u8,
}
#[derive(Deserialize, Serialize)]
pub struct Session {
    /// 安全密钥
    pub secret_key: String,
    /// 超时分钟数
    pub timeout: u16,
}

impl Config {
    pub fn from_toml() -> Result<Self, config::ConfigError> {
        config::Config::builder()
            .add_source(config::File::with_name("config"))
            .build()?
            .try_deserialize()
    }

    pub fn get_mail(&self) -> crate::Result<&Email> {
        let idx = rand::thread_rng().gen_range(0..self.emails.len());
        let m = match self.emails.get(idx) {
            Some(m) => m,
            None => return Err(crate::Error::new("获取发信数据失败")),
        };
        Ok(m)
    }
}

注释很详细了,不再进行特别说明了。

封装错误和项目专属Result<T>

创建 src/err.rs,并将其添加到 src/lib.rs 中。

use axum::response::IntoResponse;

#[derive(Debug)]
pub struct Error(anyhow::Error);

impl Error {
    pub fn new(msg: &str) -> Self {
        Self(anyhow::anyhow!("{}", msg))
    }
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{:?}", self.0)
    }
}

impl<E> From<E> for Error
where
    E: Into<anyhow::Error>,
{
    fn from(e: E) -> Self {
        Self(e.into())
    }
}

impl IntoResponse for Error {
    fn into_response(self) -> axum::response::Response {
        self.0.to_string().into_response()
    }
}

我们使用 anyhow 来简化错误处理。

本章最终的 lib.rs

回顾一下本章最终的 src/lib.rs 的内容:

pub mod config;
mod err;

pub use err::Error;

pub type Result<T> = std::result::Result<T, crate::Error>;

说明:本章代码只是当前状态,后续章节可能会对配置、数据库定义、模型定义、结构体定义、函数定义等进行修改,请留意。

本章代码位于01.项目初始化及配置分支。

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