第一步:创建项目
bash
cargo new chat-room
cd chat-roomCargo.toml
toml
[package]
name = "chat-room"
version = "0.1.0"
edition = "2021"
[dependencies]
axum = { version = "0.7", features = ["ws"] }
tokio = { version = "1", features = ["full"] }
tower = "0.4"
tower-http = { version = "0.5", features = ["fs", "cors"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
futures = "0.3"
dashmap = "5" # 并发 HashMap
uuid = { version = "1", features = ["v4", "serde"] }
chrono = { version = "0.4", features = ["serde"] }
anyhow = "1"
tracing = "0.1"
tracing-subscriber = "0.3"第二步:消息模型
src/models/message.rs
rust
▶ Runuse chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
/// 消息类型
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum Message {
/// 用户加入
Join {
user_id: Uuid,
username: String,
room_id: String,
},
/// 用户离开
Leave {
user_id: Uuid,
username: String,
room_id: String,
},
/// 聊天消息
Chat {
id: Uuid,
user_id: Uuid,
username: String,
room_id: String,
content: String,
timestamp: DateTime<Utc>,
},
/// 系统消息
System {
content: String,
room_id: String,
},
/// 用户列表更新
UserList {
room_id: String,
users: Vec<UserInfo>,
},
/// 错误
Error {
message: String,
},
}
/// 用户信息
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserInfo {
pub id: Uuid,
pub username: String,
}
impl Message {
/// 创建聊天消息
pub fn chat(user_id: Uuid, username: String, room_id: String, content: String) -> Self {
Message::Chat {
id: Uuid::new_v4(),
user_id,
username,
room_id,
content,
timestamp: Utc::now(),
}
}
/// 创建系统消息
pub fn system(content: String, room_id: String) -> Self {
Message::System {
content,
room_id,
}
}
/// 序列化为 JSON
pub fn to_json(&self) -> String {
serde_json::to_string(self).unwrap()
}
/// 从 JSON 解析
pub fn from_json(json: &str) -> Option<Self> {
serde_json::from_str(json).ok()
}
}第三步:房间模型
src/models/room.rs
rust
▶ Runuse dashmap::DashMap;
use uuid::Uuid;
use tokio::sync::broadcast;
use crate::models::user::User;
/// 聊天房间
pub struct Room {
/// 房间 ID
pub id: String,
/// 房间名称
pub name: String,
/// 在线用户
pub users: DashMap<Uuid, User>,
/// 消息广播通道
pub broadcaster: broadcast::Sender<String>,
}
impl Room {
/// 创建新房间
pub fn new(id: String, name: String) -> Self {
let (broadcaster, _) = broadcast::channel(100);
Self {
id,
name,
users: DashMap::new(),
broadcaster,
}
}
/// 添加用户
pub fn add_user(&self, user: User) {
self.users.insert(user.id, user);
}
/// 移除用户
pub fn remove_user(&self, user_id: Uuid) {
self.users.remove(&user_id);
}
/// 获取用户列表
pub fn get_users(&self) -> Vec<crate::models::message::UserInfo> {
self.users
.iter()
.map(|entry| crate::models::message::UserInfo {
id: entry.id,
username: entry.username.clone(),
})
.collect()
}
/// 获取用户数量
pub fn user_count(&self) -> usize {
self.users.len()
}
/// 广播消息
pub fn broadcast(&self, message: &str) {
// 忽略发送错误(没有接收者时)
let _ = self.broadcaster.send(message.to_string());
}
/// 订阅消息
pub fn subscribe(&self) -> broadcast::Receiver<String> {
self.broadcaster.subscribe()
}
}第四步:用户模型
src/models/user.rs
rust
▶ Runuse uuid::Uuid;
use tokio::sync::mpsc;
/// 用户
pub struct User {
/// 用户 ID
pub id: Uuid,
/// 用户名
pub username: String,
/// 当前房间
pub current_room: Option<String>,
/// 发送消息通道
pub sender: mpsc::UnboundedSender<String>,
}
impl User {
/// 创建新用户
pub fn new(username: String, sender: mpsc::UnboundedSender<String>) -> Self {
Self {
id: Uuid::new_v4(),
username,
current_room: None,
sender,
}
}
/// 发送消息给用户
pub fn send(&self, message: &str) -> bool {
self.sender.send(message.to_string()).is_ok()
}
}第五步:应用状态
src/state.rs
rust
▶ Runuse dashmap::DashMap;
use crate::models::room::Room;
/// 应用状态
pub struct AppState {
/// 所有房间
pub rooms: DashMap<String, Room>,
}
impl AppState {
/// 创建新状态
pub fn new() -> Self {
let rooms = DashMap::new();
// 创建默认房间
let default_room = Room::new("general".to_string(), "General".to_string());
rooms.insert("general".to_string(), default_room);
Self { rooms }
}
/// 获取或创建房间
pub fn get_or_create_room(&self, room_id: &str) -> Room {
if let Some(room) = self.rooms.get(room_id) {
room.clone()
} else {
let room = Room::new(room_id.to_string(), room_id.to_string());
self.rooms.insert(room_id.to_string(), room.clone());
room
}
}
/// 获取所有房间列表
pub fn get_room_list(&self) -> Vec<(String, String, usize)> {
self.rooms
.iter()
.map(|entry| {
(entry.id.clone(), entry.name.clone(), entry.user_count())
})
.collect()
}
}