use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::Arc;
use axum::extract::{Path, State};
use axum::routing::{get, post};
use axum::Router;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use sz_rust_core::controller::{BaseController, SzController};
use sz_rust_core::event::ClosureListener;
use sz_rust_core::{cache::Cache, cache::MemoryCacheDriver};
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Post {
id: i64,
title: String,
content: String,
author: String,
}
struct AppState {
posts: std::sync::Mutex<Vec<Post>>,
next_id: AtomicI64,
cache: Cache,
cache_hits: AtomicI64,
post_count: Arc<AtomicI64>,
}
impl AppState {
fn new() -> Self {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
let state = Self {
posts: std::sync::Mutex::new(Vec::new()),
next_id: AtomicI64::new(1),
cache,
cache_hits: AtomicI64::new(0),
post_count: Arc::new(AtomicI64::new(0)),
};
let counter = state.post_count.clone();
sz_rust_core::event::facade::dispatcher().listen(
"PostCreated",
Arc::new(ClosureListener::new(move |params: &Value| {
let id = params["id"].as_i64().unwrap_or(0);
counter.fetch_add(1, Ordering::SeqCst);
tracing::info!("事件驱动:文章 {id} 已发布");
Ok(Value::Null)
})),
false,
);
state
}
fn list(&self) -> Vec<Post> {
self.posts.lock().unwrap().clone()
}
fn find(&self, id: i64) -> Option<Post> {
self.posts
.lock()
.unwrap()
.iter()
.find(|p| p.id == id)
.cloned()
}
fn create(&self, title: &str, content: &str, author: &str) -> Post {
let id = self.next_id.fetch_add(1, Ordering::SeqCst);
let post = Post {
id,
title: title.to_string(),
content: content.to_string(),
author: author.to_string(),
};
self.posts.lock().unwrap().push(post.clone());
self.cache.delete("posts:list").ok();
let _ = sz_rust_core::event::facade::dispatcher().trigger(
"PostCreated",
&json!({"id": id, "title": title}),
false,
);
post
}
fn delete(&self, id: i64) -> bool {
let mut posts = self.posts.lock().unwrap();
let before = posts.len();
posts.retain(|p| p.id != id);
let removed = posts.len() != before;
if removed {
self.cache.delete("posts:list").ok();
}
removed
}
}
#[allow(dead_code)] struct PostController;
impl SzController for PostController {}
impl BaseController for PostController {}
async fn list_posts(State(state): State<Arc<AppState>>) -> axum::response::Response {
if let Ok(Some(cached)) = state.cache.get::<Vec<Post>>("posts:list") {
state.cache_hits.fetch_add(1, Ordering::SeqCst);
return sz_rust_core::response::render_success(json!(cached), "缓存命中");
}
let posts = state.list();
state
.cache
.set(
"posts:list",
posts.clone(),
Some(std::time::Duration::from_secs(60)),
)
.ok();
sz_rust_core::response::render_success(json!(posts), "ok")
}
async fn detail_post(
State(state): State<Arc<AppState>>,
Path(id): Path<i64>,
) -> axum::response::Response {
match state.find(id) {
Some(post) => sz_rust_core::response::render_success(json!(post), "ok"),
None => sz_rust_core::response::render_error("文章不存在"),
}
}
async fn create_post(
State(state): State<Arc<AppState>>,
axum::extract::Json(payload): axum::extract::Json<Value>,
) -> axum::response::Response {
let title = payload["title"].as_str().unwrap_or_default().to_string();
let content = payload["content"].as_str().unwrap_or_default().to_string();
let author = payload["author"]
.as_str()
.unwrap_or("anonymous")
.to_string();
if title.is_empty() {
return sz_rust_core::response::render_error("标题不能为空");
}
let post = state.create(&title, &content, &author);
sz_rust_core::response::render_success(json!(post), "发布成功")
}
async fn delete_post(
State(state): State<Arc<AppState>>,
Path(id): Path<i64>,
) -> axum::response::Response {
if state.delete(id) {
sz_rust_core::response::render_success(json!({"id": id}), "删除成功")
} else {
sz_rust_core::response::render_error("文章不存在")
}
}
async fn stats(State(state): State<Arc<AppState>>) -> axum::response::Response {
sz_rust_core::response::render_success(
json!({
"post_count": state.post_count.load(Ordering::SeqCst),
"cache_hits": state.cache_hits.load(Ordering::SeqCst),
"total_posts": state.list().len(),
}),
"ok",
)
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.init();
let state = Arc::new(AppState::new());
state.create(
"Rust 异步运行时入门",
"async/await 与 tokio 详解",
"sz-team",
);
state.create("facade 拆包实战", "从 57K 单体到 11 个 facade", "sz-team");
state.create("ThinkPHP 迁移指南", "PHP 开发者视角的 Rust 框架", "sz-team");
let app = Router::new()
.route("/post/list", get(list_posts))
.route("/post/detail/{id}", get(detail_post))
.route("/post/create", post(create_post))
.route("/post/delete/{id}", post(delete_post))
.route("/post/stats", get(stats))
.with_state(state);
let addr = "127.0.0.1:8081";
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
tracing::info!("博客示例运行于 http://{addr} (/post/list /post/create /post/stats)");
axum::serve(listener, app).await.unwrap();
}