- 支持试读
开启 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 服务端渲染。【加餐】dioxus 和 axum 实现文件上传
本章将讨论使用 dioxus、reqwest 和 axum 实现文件上传。
阅后即焚API
- 76
- 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
);
字段 | 说明 |
---|---|
id | 主键 |
content | 信息内容 |
password | 密码 |
dateline | 创建时间 |
模型
// 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)
}
}
}
优化建议:
if_has_password()
的返回值可以使用Option<&str>
这一个值。请读者以作业的形式对该方法进行改写
create()
:创建一条新消息get()
:根据ID获取对应消息del()
:根据ID删除对应消息
Handler
创建新消息
- 对客户端提交的数据进行验证
- 调用
Message
模型的build()
方法,快速创建模型实例 - 调用数据操作的
create()
方法,将数据入库
查看消息
MessageResp
结构体用于将指定消息响应给客户端。但它额外做了一件事:通过need_password
告诉 API 调用者,是否需要提供密码。- 对客户端提交的数据进行验证
- 调用数据操作的
get()
方法获取数据 - 调用模型的
if_has_password()
判断并获取密码 - 根据是否需要密码进行不同逻辑
- 如果需要密码
- 如果客户端未提供密码,返回需要密码的响应
- 如果客户端提供了密码
- 如果密码错误,返回密码错误
- 如果密码正确,返回数据作为响应
- 如果不需要密码,直接返回数据作为响应
- 如果需要密码
- 值得一提的是,在不需要密码和密码正确两个分支里,都通过
tokio::spawn
调用了delete_viewed_msg()
。它其实是为了实现延时删除消息。
现在我们来看看 delete_viewed_msg()
的实现:
async fn delete_viewed_msg(p: PgPool, id: Arc<String>, delete_interval: u32) {
tokio::time::sleep(std::time::Duration::from_secs(delete_interval as u64)).await;
match db::message::del(&p, &id).await {
Ok(aff) => tracing::debug!(
"已删除消息:{},间隔时间:{},受影响的行数:{}",
id,
delete_interval,
aff
),
Err(e) => tracing::error!("删除消息 #{} 失败:{}", id, e),
};
}
- 休眠指定时长
- 删除指定信息
测试
// 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"
}