- 支持试读
开启 dioxus 之旅
本章将讨论 dioxus 的安装、项目创建、运行和构建。 - 支持试读
dioxus 组件与 rsx
本章将开始第一个案例:计数器的编写。首先,我们需要学习 dioxus 组件的编写和 rsx 语法。 - 支持试读
计数器
本章我们将开始编写计数器。你将学习到:事件处理、状态管理、组件通讯、集成 Tailwind CSS 等知识。 - 支持试读
博客
本章我们将实现博客,你将学习到:dioxus 路由、获取远程数据、条件渲染、列表渲染等知识。 全局状态
本章我们开始最后一个案例:阅后即焚的开发。在真正进行开发之前,我们还需要掌握一些基础知识,本章我们将学习 dioxus 的全局状态。布局与嵌套路由
本章我们将讨论 dioxus 的布局与嵌套路由。表单处理
本章我们将学习 dioxus 的表单处理。和 React 类似,dioxus 也分为受控表单和非受控表单。阅后即焚前台UI
本章我们开始编写阅后即焚的前台UI。- 支持试读
阅后即焚API
本章我们使用 AXUM 开发阅后即焚的前台 API。 整合阅后即焚前端和API
本章将对阅后即焚前端和API进行整合。- 支持试读
编译和部署
本章我们将讨论 dioxus web 的编译和部署到 NGINX。 【加餐】dioxus 服务端渲染
本章使用前文的『博客』中的用户数据为案例,来讲解 dioxus 服务端渲染。
阅后即焚API
- 10
- 2025-04-30 21:24:36
本章我们使用 AXUM 开发阅后即焚的 API。
数据表
CREATE TABLE IF NOT EXISTS "messages" (
"id" CHAR(20) PRIMARY KEY,
"content" VARCHAR NOT NULL DEFAULT '',
"password" VARCHAR(72) NOT NULL DEFAULT '',
"dateline" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
模型
// src/model.rs
#[derive(Serialize, Deserialize, FromRow)]
pub struct Message {
pub id: String,
pub content: String,
#[serde(skip_serializing)]
pub password: String,
pub dateline: chrono::DateTime<chrono::Local>,
}
为了方便操作,我们为这个模型定义了一些方法:
// src/model.rs
impl Message {
pub fn build(content: String, password: String) -> Result<Self> {
let password = if password.trim().is_empty() {
"".to_string()
} else {
util::hash_pwd(password.trim())?
};
Ok(Self {
id: util::new_id(),
content,
password,
dateline: chrono::Local::now(),
})
}
pub fn if_has_password(&self) -> (bool, &str) {
if self.password.is_empty() {
(false, "")
} else {
(true, &self.password)
}
}
}
build
() :用于快速构建一个Message
模型的实例。如果传入的密码不是空字符,则将密码进行哈希。if_has_password()
:用于判断某个实例是否有密码。返回值为一个元组:(是否有密码, 密码)
优化建议:
if_has_password()
的返回值可以使用Option<&str>
这一个值。请读者以作业的形式对该方法进行改写
数据操作
create()
:创建一条新消息get()
:根据ID获取对应消息del()
:根据ID删除对应消息
Handler
创建新消息
// src/frontend.rs
pub async fn create_message(
State(state): State<ArcAppState>,
Json(message): Json<form::message::Create>,
) -> Result<resp::JsonResp<resp::IdResp>> {
message.validate()?;
let message = model::Message::build(message.content, message.password)?;
let id = db::message::create(&state.pool, &message).await?;
Ok(resp::id_resp(id).to_json())
}
查看消息
// src/frontend.rs
#[derive(serde::Serialize)]
pub struct MessageResp {
pub need_password: bool,
#[serde(flatten)]
pub message: Option<model::Message>,
}
pub async fn read_message(
State(state): State<ArcAppState>,
Json(frm): Json<form::message::Read>,
) -> Result<resp::JsonResp<MessageResp>> {
frm.validate()?;
let m = db::message::get(&state.pool, &frm.id).await?;
let m = match m {
Some(v) => v,
None => return Err(anyhow::anyhow!("不存在的消息").into()),
};
let (need_password, password) = m.if_has_password();
// 需要密码
if need_password {
// 未提供密码
if frm.password.is_none() {
return Ok(resp::ok(MessageResp {
need_password: true,
message: None,
})
.to_json());
}
let pwd = frm.password.unwrap();
// 密码错误
if !util::verify_pwd(&pwd, &password)? {
return Err(anyhow::anyhow!("密码错误").into());
}
// 密码正确
tokio::spawn(delete_viewed_msg(
state.pool.clone(),
Arc::new(frm.id),
state.cfg.delete_interval,
));
return Ok(resp::ok(MessageResp {
need_password: false,
message: Some(m),
})
.to_json());
}
// 不需要密码
tokio::spawn(delete_viewed_msg(
state.pool.clone(),
Arc::new(frm.id),
state.cfg.delete_interval,
));
Ok(resp::ok(MessageResp {
need_password: false,
message: Some(m),
})
.to_json())
}
MessageResp
结构体用于将指定消息响应给客户端。但它额外做了一件事:通过need_password
告诉 API 调用者,是否需要提供密码。- 对客户端提交的数据进行验证
- 调用数据操作的
get()
方法获取数据 - 调用模型的
if_has_password()
判断并获取密码 - 根据是否需要密码进行不同逻辑
- 如果需要密码
- 如果客户端未提供密码,返回需要密码的响应
- 如果客户端提供了密码
- 如果密码错误,返回密码错误
- 如果密码正确,返回数据作为响应
- 如果不需要密码,直接返回数据作为响应
- 如果需要密码
- 值得一提的是,在不需要密码和密码正确两个分支里,都通过
tokio::spawn
调用了delete_viewed_msg()
。它其实是为了实现延时删除消息。
- 休眠指定时长
- 删除指定信息
测试
// frontend-api.http
## 创建消息
POST http://127.0.0.1:9527/message
Content-Type: application/json
{
"content":"Hello, 世界",
"password":""
}
## 创建带密码的消息
POST http://127.0.0.1:9527/message
Content-Type: application/json
{
"content":"你好,World",
"password":"foobar"
}
## 访问无密码消息
POST http://127.0.0.1:9527/message/view
Content-Type: application/json
{
"id":"d08bmj4drfailna19uog"
}
## 访问带密码消息,但未提供密码
POST http://127.0.0.1:9527/message/view
Content-Type: application/json
{
"id":"d08c82sdrfatpb5ium90"
}
## 访问带密码消息,提供错误密码
POST http://127.0.0.1:9527/message/view
Content-Type: application/json
{
"id":"d08c82sdrfatpb5ium90",
"password":"barfoo"
}
## 访问带密码消息,提供正确密码
POST http://127.0.0.1:9527/message/view
Content-Type: application/json
{
"id":"d08c82sdrfatpb5ium90",
"password":"foobar"
}
## 访问不存在的消息
POST http://127.0.0.1:9527/message/view
Content-Type: application/json
{
"id":"d08bm6sdrfailna19123"
}