- 支持试读
准备工作
本专题需要事先做的准备工作。专题完结之后,你可以通过 zliu.tech、liziqi.ggff.net、zlusi.me和reqwest.me 体验我们提供的域名分发服务。 Cloudflare 常用 API
本章我们讨论本专题需要的 Cloudflare API。- 支持试读
项目初始化及配置
本章将对我们的项目进行初始化,然后编写配置。 数据及模型定义
本章我们将对数据表和对应的数据模型进行定义。数据库操作模块
本章我们将简单实现一些数据库的操作。- 支持试读
注册 Cloudflare Turnstile 并将其集成到 AXUM 应用中
本章将讨论注册 Cloudflare Turnstile 并将其集成到 AXUM 应用中。 - 支持试读
开发前端 SPA
为了更直观的进行 API 开发,我们先来编写前端 SPA。
项目初始化及配置
- 31
- 2024-12-11 13:48:21
本章将对我们的项目进行初始化,然后编写配置。
创建项目
本专题包含两个项目:用于开发后端的 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.项目初始化及配置分支。