- 支持试读
准备工作
本专题需要事先做的准备工作。专题完结之后,你可以通过 zliu.tech、liziqi.ggff.net、zlusi.me和reqwest.me 体验我们提供的域名分发服务。 Cloudflare 常用 API
本章我们讨论本专题需要的 Cloudflare API。- 支持试读
项目初始化及配置
本章将对我们的项目进行初始化,然后编写配置。 数据及模型定义
本章我们将对数据表和对应的数据模型进行定义。数据库操作模块
本章我们将简单实现一些数据库的操作。- 支持试读
注册 Cloudflare Turnstile 并将其集成到 AXUM 应用中
本章将讨论注册 Cloudflare Turnstile 并将其集成到 AXUM 应用中。 - 支持试读
开发前端 SPA
为了更直观的进行 API 开发,我们先来编写前端 SPA。 用户注册及邮箱激活
本章将实现用户注册功能:包括数据库操作、邮箱激活账号等。本章包含后端和前端功能。用户登录、退出登录及鉴权中间件
本章将实现用户登录和退出登录功能:包括会话管理、鉴权中间件等。本章包含后端和前端功能。用户注册域名
本章将实现用户注册域名功能:包括数据库操作、CF API 封装调用等。本章包含后端和前端功能。用户管理域名
本章将实现用户管理域名功能:包括修改、删除域名的数据库操作、CF API 封装调用等。本章包含后端和前端功能。- 支持试读
用户积分变动记录
本章将实现用户查看本账号积分变动功能。本章包含后端和前端功能。 用户修改密码
本章将实现用户修改密码功能。本章包含后端和前端功能。后台管理
本章将实现后台管理功能。本章包含后端和前端功能。由于后台大部分功能和用户面板的功能重合,所以后台管理功能将集中在本章一个大章来进行全部讲解。把 React 开发的 SPA 嵌入 AXUM 应用中
本章将讨论把 React 开发的 SPA 嵌入 AXUM 应用中,并将其部署到生产环境。
项目初始化及配置
- 91
- 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"
使用 lib.rs
创建一个空的 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()
}
}
本章最终的 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.项目初始化及配置分支。