salvo-flash
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSalvo Flash Messages
Salvo Flash Messages
This skill helps implement flash messages in Salvo applications for displaying one-time notifications that survive redirects.
本技能可帮助你在Salvo应用中实现Flash Messages,用于展示跨重定向的一次性通知。
What are Flash Messages?
什么是Flash Messages?
Flash messages are temporary messages stored between requests, typically used to show feedback after form submissions or actions. They're automatically deleted after being displayed once.
Common use cases:
- "Successfully logged in!"
- "Item added to cart"
- "Error: Invalid email format"
- "Profile updated successfully"
Flash Messages是存储在请求之间的临时消息,通常用于在表单提交或操作后展示反馈。它们在被展示一次后会自动删除。
常见使用场景:
- "登录成功!"
- "商品已添加至购物车"
- "错误:邮箱格式无效"
- "资料更新成功"
Setup
配置
toml
[dependencies]
salvo = { version = "0.89.0", features = ["flash"] }toml
[dependencies]
salvo = { version = "0.89.0", features = ["flash"] }Basic Flash Messages with Cookie Store
基于Cookie存储的基础Flash Messages
rust
use std::fmt::Write;
use salvo::flash::{CookieStore, FlashDepotExt};
use salvo::prelude::*;
#[handler]
async fn set_flash(depot: &mut Depot, res: &mut Response) {
// Get outgoing flash and add messages
let flash = depot.outgoing_flash_mut();
flash.info("Operation completed successfully!");
flash.debug("Debug information here");
// Redirect to show the message
res.render(Redirect::other("/show"));
}
#[handler]
async fn show_flash(depot: &mut Depot, res: &mut Response) {
let mut output = String::new();
// Read incoming flash messages
if let Some(flash) = depot.incoming_flash() {
for message in flash.iter() {
writeln!(output, "[{}] {}", message.level, message.value).unwrap();
}
}
if output.is_empty() {
output = "No flash messages".to_string();
}
res.render(Text::Plain(output));
}
#[tokio::main]
async fn main() {
let router = Router::new()
.hoop(CookieStore::new().into_handler())
.push(Router::with_path("set").get(set_flash))
.push(Router::with_path("show").get(show_flash));
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}rust
use std::fmt::Write;
use salvo::flash::{CookieStore, FlashDepotExt};
use salvo::prelude::*;
#[handler]
async fn set_flash(depot: &mut Depot, res: &mut Response) {
// Get outgoing flash and add messages
let flash = depot.outgoing_flash_mut();
flash.info("Operation completed successfully!");
flash.debug("Debug information here");
// Redirect to show the message
res.render(Redirect::other("/show"));
}
#[handler]
async fn show_flash(depot: &mut Depot, res: &mut Response) {
let mut output = String::new();
// Read incoming flash messages
if let Some(flash) = depot.incoming_flash() {
for message in flash.iter() {
writeln!(output, "[{}] {}", message.level, message.value).unwrap();
}
}
if output.is_empty() {
output = "No flash messages".to_string();
}
res.render(Text::Plain(output));
}
#[tokio::main]
async fn main() {
let router = Router::new()
.hoop(CookieStore::new().into_handler())
.push(Router::with_path("set").get(set_flash))
.push(Router::with_path("show").get(show_flash));
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}Flash Message Levels
Flash Message 级别
rust
use salvo::flash::FlashDepotExt;
#[handler]
async fn add_messages(depot: &mut Depot, res: &mut Response) {
let flash = depot.outgoing_flash_mut();
// Different message levels
flash.debug("Debug message"); // For debugging
flash.info("Info message"); // General information
flash.success("Success message"); // Success notifications
flash.warning("Warning message"); // Warnings
flash.error("Error message"); // Error notifications
res.render(Redirect::other("/"));
}rust
use salvo::flash::FlashDepotExt;
#[handler]
async fn add_messages(depot: &mut Depot, res: &mut Response) {
let flash = depot.outgoing_flash_mut();
// Different message levels
flash.debug("Debug message"); // For debugging
flash.info("Info message"); // General information
flash.success("Success message"); // Success notifications
flash.warning("Warning message"); // Warnings
flash.error("Error message"); // Error notifications
res.render(Redirect::other("/"));
}Flash with Session Store
基于Session存储的Flash Messages
For larger messages or when cookies aren't suitable:
rust
use salvo::flash::{SessionStore, FlashDepotExt};
use salvo::session::{CookieStore as SessionCookieStore, SessionHandler};
use salvo::prelude::*;
#[tokio::main]
async fn main() {
// Session handler is required for session-based flash
let session_handler = SessionHandler::builder(
SessionCookieStore::new(),
b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
)
.build()
.unwrap();
// Flash store using sessions
let flash_handler = SessionStore::new().into_handler();
let router = Router::new()
.hoop(session_handler) // Session first
.hoop(flash_handler) // Then flash
.push(Router::with_path("set").get(set_flash))
.push(Router::with_path("show").get(show_flash));
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}当消息内容较大或Cookie不适用时可使用:
rust
use salvo::flash::{SessionStore, FlashDepotExt};
use salvo::session::{CookieStore as SessionCookieStore, SessionHandler};
use salvo::prelude::*;
#[tokio::main]
async fn main() {
// Session handler is required for session-based flash
let session_handler = SessionHandler::builder(
SessionCookieStore::new(),
b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
)
.build()
.unwrap();
// Flash store using sessions
let flash_handler = SessionStore::new().into_handler();
let router = Router::new()
.hoop(session_handler) // Session first
.hoop(flash_handler) // Then flash
.push(Router::with_path("set").get(set_flash))
.push(Router::with_path("show").get(show_flash));
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}Form Submission with Flash
结合表单提交使用Flash Messages
rust
use salvo::flash::{CookieStore, FlashDepotExt};
use salvo::prelude::*;
use serde::Deserialize;
#[derive(Deserialize)]
struct ContactForm {
name: String,
email: String,
message: String,
}
#[handler]
async fn show_form(depot: &mut Depot, res: &mut Response) {
// Check for flash messages
let mut flash_html = String::new();
if let Some(flash) = depot.incoming_flash() {
for msg in flash.iter() {
let class = match msg.level.as_str() {
"success" => "alert-success",
"error" => "alert-error",
"warning" => "alert-warning",
_ => "alert-info",
};
flash_html.push_str(&format!(
r#"<div class="{}">{}</div>"#,
class, msg.value
));
}
}
res.render(Text::Html(format!(r#"
<!DOCTYPE html>
<html>
<head>
<style>
.alert-success {{ background: #d4edda; padding: 10px; margin: 10px 0; }}
.alert-error {{ background: #f8d7da; padding: 10px; margin: 10px 0; }}
.alert-warning {{ background: #fff3cd; padding: 10px; margin: 10px 0; }}
.alert-info {{ background: #d1ecf1; padding: 10px; margin: 10px 0; }}
</style>
</head>
<body>
{flash_html}
<h1>Contact Us</h1>
<form method="post" action="/contact">
<p><input type="text" name="name" placeholder="Name" required /></p>
<p><input type="email" name="email" placeholder="Email" required /></p>
<p><textarea name="message" placeholder="Message" required></textarea></p>
<button type="submit">Send</button>
</form>
</body>
</html>
"#)));
}
#[handler]
async fn handle_form(req: &mut Request, depot: &mut Depot, res: &mut Response) {
match req.parse_form::<ContactForm>().await {
Ok(form) => {
// Process the form...
println!("Received message from: {} <{}>", form.name, form.email);
// Success flash
depot.outgoing_flash_mut()
.success("Thank you! Your message has been sent.");
}
Err(e) => {
// Error flash
depot.outgoing_flash_mut()
.error(format!("Error: {}", e));
}
}
res.render(Redirect::other("/contact"));
}
#[tokio::main]
async fn main() {
let router = Router::new()
.hoop(CookieStore::new().into_handler())
.push(
Router::with_path("contact")
.get(show_form)
.post(handle_form)
);
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}rust
use salvo::flash::{CookieStore, FlashDepotExt};
use salvo::prelude::*;
use serde::Deserialize;
#[derive(Deserialize)]
struct ContactForm {
name: String,
email: String,
message: String,
}
#[handler]
async fn show_form(depot: &mut Depot, res: &mut Response) {
// Check for flash messages
let mut flash_html = String::new();
if let Some(flash) = depot.incoming_flash() {
for msg in flash.iter() {
let class = match msg.level.as_str() {
"success" => "alert-success",
"error" => "alert-error",
"warning" => "alert-warning",
_ => "alert-info",
};
flash_html.push_str(&format!(
r#"<div class="{}">{}</div>"#,
class, msg.value
));
}
}
res.render(Text::Html(format!(r#"
<!DOCTYPE html>
<html>
<head>
<style>
.alert-success {{ background: #d4edda; padding: 10px; margin: 10px 0; }}
.alert-error {{ background: #f8d7da; padding: 10px; margin: 10px 0; }}
.alert-warning {{ background: #fff3cd; padding: 10px; margin: 10px 0; }}
.alert-info {{ background: #d1ecf1; padding: 10px; margin: 10px 0; }}
</style>
</head>
<body>
{flash_html}
<h1>联系我们</h1>
<form method="post" action="/contact">
<p><input type="text" name="name" placeholder="姓名" required /></p>
<p><input type="email" name="email" placeholder="邮箱" required /></p>
<p><textarea name="message" placeholder="留言内容" required></textarea></p>
<button type="submit">发送</button>
</form>
</body>
</html>
"#)));
}
#[handler]
async fn handle_form(req: &mut Request, depot: &mut Depot, res: &mut Response) {
match req.parse_form::<ContactForm>().await {
Ok(form) => {
// Process the form...
println!("Received message from: {} <{}>", form.name, form.email);
// Success flash
depot.outgoing_flash_mut()
.success("感谢您的留言!我们已收到您的消息。");
}
Err(e) => {
// Error flash
depot.outgoing_flash_mut()
.error(format!("错误:{}", e));
}
}
res.render(Redirect::other("/contact"));
}
#[tokio::main]
async fn main() {
let router = Router::new()
.hoop(CookieStore::new().into_handler())
.push(
Router::with_path("contact")
.get(show_form)
.post(handle_form)
);
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}Multiple Flash Messages
多条Flash Messages
rust
#[handler]
async fn process_action(depot: &mut Depot, res: &mut Response) {
let flash = depot.outgoing_flash_mut();
// Add multiple messages
flash.info("Processing started...");
flash.success("Step 1 completed");
flash.success("Step 2 completed");
flash.warning("Step 3 had minor issues but continued");
flash.success("All steps completed!");
res.render(Redirect::other("/results"));
}
#[handler]
async fn show_results(depot: &mut Depot, res: &mut Response) {
let mut html = String::from("<h1>Results</h1><ul>");
if let Some(flash) = depot.incoming_flash() {
for msg in flash.iter() {
html.push_str(&format!(
"<li><strong>{}:</strong> {}</li>",
msg.level, msg.value
));
}
}
html.push_str("</ul>");
res.render(Text::Html(html));
}rust
#[handler]
async fn process_action(depot: &mut Depot, res: &mut Response) {
let flash = depot.outgoing_flash_mut();
// Add multiple messages
flash.info("Processing started...");
flash.success("Step 1 completed");
flash.success("Step 2 completed");
flash.warning("Step 3 had minor issues but continued");
flash.success("All steps completed!");
res.render(Redirect::other("/results"));
}
#[handler]
async fn show_results(depot: &mut Depot, res: &mut Response) {
let mut html = String::from("<h1>处理结果</h1><ul>");
if let Some(flash) = depot.incoming_flash() {
for msg in flash.iter() {
html.push_str(&format!(
"<li><strong>{}:</strong> {}</li>",
msg.level, msg.value
));
}
}
html.push_str("</ul>");
res.render(Text::Html(html));
}Flash with CRUD Operations
结合CRUD操作使用Flash Messages
rust
use salvo::flash::FlashDepotExt;
use salvo::prelude::*;
#[handler]
async fn create_item(depot: &mut Depot, res: &mut Response) {
// Create item logic...
let item_id = 123;
depot.outgoing_flash_mut()
.success(format!("Item #{} created successfully!", item_id));
res.render(Redirect::other("/items"));
}
#[handler]
async fn update_item(depot: &mut Depot, res: &mut Response) {
// Update item logic...
depot.outgoing_flash_mut()
.success("Item updated successfully!");
res.render(Redirect::other("/items"));
}
#[handler]
async fn delete_item(depot: &mut Depot, res: &mut Response) {
// Delete item logic...
depot.outgoing_flash_mut()
.info("Item has been deleted.");
res.render(Redirect::other("/items"));
}
#[handler]
async fn list_items(depot: &mut Depot, res: &mut Response) {
let mut flash_messages = Vec::new();
if let Some(flash) = depot.incoming_flash() {
for msg in flash.iter() {
flash_messages.push(format!("[{}] {}", msg.level, msg.value));
}
}
// Render list with flash messages...
res.render(Json(serde_json::json!({
"flash": flash_messages,
"items": []
})));
}rust
use salvo::flash::FlashDepotExt;
use salvo::prelude::*;
#[handler]
async fn create_item(depot: &mut Depot, res: &mut Response) {
// Create item logic...
let item_id = 123;
depot.outgoing_flash_mut()
.success(format!("商品 #{} 创建成功!", item_id));
res.render(Redirect::other("/items"));
}
#[handler]
async fn update_item(depot: &mut Depot, res: &mut Response) {
// Update item logic...
depot.outgoing_flash_mut()
.success("商品更新成功!");
res.render(Redirect::other("/items"));
}
#[handler]
async fn delete_item(depot: &mut Depot, res: &mut Response) {
// Delete item logic...
depot.outgoing_flash_mut()
.info("商品已删除。");
res.render(Redirect::other("/items"));
}
#[handler]
async fn list_items(depot: &mut Depot, res: &mut Response) {
let mut flash_messages = Vec::new();
if let Some(flash) = depot.incoming_flash() {
for msg in flash.iter() {
flash_messages.push(format!("[{}] {}", msg.level, msg.value));
}
}
// Render list with flash messages...
res.render(Json(serde_json::json!({
"flash": flash_messages,
"items": []
})));
}Flash with JSON API
结合JSON API使用Flash Messages
For API responses that need flash-like behavior:
rust
use salvo::flash::FlashDepotExt;
use salvo::prelude::*;
use serde::Serialize;
#[derive(Serialize)]
struct ApiResponse<T> {
success: bool,
data: Option<T>,
messages: Vec<FlashMessage>,
}
#[derive(Serialize)]
struct FlashMessage {
level: String,
text: String,
}
#[handler]
async fn api_create(depot: &mut Depot, res: &mut Response) {
// Store flash for potential redirect
depot.outgoing_flash_mut()
.success("Created successfully");
// Also return in JSON for AJAX requests
res.render(Json(ApiResponse {
success: true,
data: Some(serde_json::json!({"id": 1})),
messages: vec![
FlashMessage {
level: "success".to_string(),
text: "Created successfully".to_string(),
}
],
}));
}对于需要类Flash行为的API响应:
rust
use salvo::flash::FlashDepotExt;
use salvo::prelude::*;
use serde::Serialize;
#[derive(Serialize)]
struct ApiResponse<T> {
success: bool,
data: Option<T>,
messages: Vec<FlashMessage>,
}
#[derive(Serialize)]
struct FlashMessage {
level: String,
text: String,
}
#[handler]
async fn api_create(depot: &mut Depot, res: &mut Response) {
// Store flash for potential redirect
depot.outgoing_flash_mut()
.success("Created successfully");
// Also return in JSON for AJAX requests
res.render(Json(ApiResponse {
success: true,
data: Some(serde_json::json!({"id": 1})),
messages: vec![
FlashMessage {
level: "success".to_string(),
text: "创建成功".to_string(),
}
],
}));
}Rendering Flash Messages
渲染Flash Messages
As HTML Alerts
渲染为HTML提示框
rust
fn render_flash_html(depot: &Depot) -> String {
let mut html = String::new();
if let Some(flash) = depot.incoming_flash() {
html.push_str("<div class=\"flash-container\">");
for msg in flash.iter() {
let (class, icon) = match msg.level.as_str() {
"success" => ("flash-success", "✓"),
"error" => ("flash-error", "✗"),
"warning" => ("flash-warning", "⚠"),
"info" => ("flash-info", "ℹ"),
_ => ("flash-debug", "🔧"),
};
html.push_str(&format!(
r#"<div class="flash {}"><span>{}</span> {}</div>"#,
class, icon, msg.value
));
}
html.push_str("</div>");
}
html
}rust
fn render_flash_html(depot: &Depot) -> String {
let mut html = String::new();
if let Some(flash) = depot.incoming_flash() {
html.push_str("<div class=\"flash-container\">");
for msg in flash.iter() {
let (class, icon) = match msg.level.as_str() {
"success" => ("flash-success", "✓"),
"error" => ("flash-error", "✗"),
"warning" => ("flash-warning", "⚠"),
"info" => ("flash-info", "ℹ"),
_ => ("flash-debug", "🔧"),
};
html.push_str(&format!(
r#"<div class="flash {}"><span>{}</span> {}</div>"#,
class, icon, msg.value
));
}
html.push_str("</div>");
}
html
}As JSON
渲染为JSON
rust
fn flash_to_json(depot: &Depot) -> serde_json::Value {
let messages: Vec<_> = depot
.incoming_flash()
.map(|flash| {
flash.iter()
.map(|msg| {
serde_json::json!({
"level": msg.level,
"message": msg.value
})
})
.collect()
})
.unwrap_or_default();
serde_json::json!({ "flash": messages })
}rust
fn flash_to_json(depot: &Depot) -> serde_json::Value {
let messages: Vec<_> = depot
.incoming_flash()
.map(|flash| {
flash.iter()
.map(|msg| {
serde_json::json!({
"level": msg.level,
"message": msg.value
})
})
.collect()
})
.unwrap_or_default();
serde_json::json!({ "flash": messages })
}Best Practices
最佳实践
- Use appropriate levels: Match message importance to level (success, error, warning, info)
- Keep messages brief: Flash messages should be short and clear
- Redirect after POST: Use Post-Redirect-Get pattern with flash messages
- Style by level: Use different colors/icons for different message levels
- Cookie store for simple cases: Session store for larger/sensitive messages
- Clear after display: Flash messages auto-clear, don't display twice
- Handle empty flash: Check if flash exists before rendering
- Escape HTML: Sanitize message content if it includes user input
- 使用合适的级别:根据消息重要性匹配对应级别(成功、错误、警告、信息)
- 保持消息简洁:Flash Messages应简短清晰
- POST后重定向:结合Post-Redirect-Get模式使用Flash Messages
- 按级别设置样式:为不同级别的消息使用不同颜色/图标
- 简单场景用Cookie存储:较大/敏感消息使用Session存储
- 展示后自动清除:Flash Messages会自动清除,避免重复展示
- 处理空消息情况:渲染前先检查是否存在Flash Messages
- 转义HTML:如果消息包含用户输入,需对内容进行 sanitize 处理