Skip to content

第一步:创建项目

bash
cargo new chat-room
cd chat-room

Cargo.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
use 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()
    }
}
▶ Run

第三步:房间模型

src/models/room.rs

rust
use 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()
    }
}
▶ Run

第四步:用户模型

src/models/user.rs

rust
use 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()
    }
}
▶ Run

第五步:应用状态

src/state.rs

rust
use 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()
    }
}
▶ Run