在这里插入图片描述

技术选型考虑

1. 为什么选择 Rust + WebAssembly?

Rust 是一种内存安全、高性能的系统编程语言,其编译器通过 借用检查器(Borrow Checker)所有权(Ownership) 机制,在编译期消除空指针、数据竞争等常见错误。
WebAssembly(Wasm) 是一种虚拟机指令集,允许代码在浏览器中以接近原生的速度运行。Rust 通过 wasm-bindgen 工具链,可以将 Rust 代码编译为 Wasm 模块,并与 JavaScript 无缝交互。

本项目设计一个 多人在线共享白板,通过 Rust + WebAssembly 实现以下目标:

  • 高性能绘图逻辑:Rust 负责图形路径计算,Wasm 提供快速执行环境;
  • 低延迟通信:WebSocket 实现实时指令同步;
  • 内存安全:Rust 的编译期检查确保无越界访问或悬垂指针;
  • 跨平台兼容:支持所有现代浏览器,无需安装插件。

在这里插入图片描述
WebAssembly 入门

2. 技术选型与核心概念

2.1 WebAssembly 与 wasm-bindgen

  • WebAssembly:一种二进制格式的虚拟机指令集,浏览器通过 Wasm 模块执行代码,性能接近原生 C/C++。
  • wasm-bindgen:Rust 的 WebAssembly 绑定生成工具,自动处理 Rust 与 JavaScript 的类型转换(如 StringstrVec<u8>ArrayBuffer)。

wasm-bindgen 是 Rust 与 WebAssembly(WASM)生态中的核心桥梁工具,由 Rust 官方WebAssembly 工作组主导开发。它的核心目标是:让 Rust 编译成的 WebAssembly 模块能无缝与 JavaScript互操作,就像调用原生 JS 函数或使用 DOM API 一样自然。

在这里插入图片描述

2.2 WebSocket 通信

  • WebSocket:双向实时通信协议,适合多人协作场景(如白板指令广播)。
  • Tokio:Rust 的异步运行时,提供非阻塞 I/O 支持,适合高并发服务器开发。

在这里插入图片描述

2.3 图形渲染方案

  • Canvas API:HTML5 的 2D 渲染上下文,适合简单绘图;
  • WebGL:基于 OpenGL ES 的 3D 图形 API,性能更高但实现复杂;
  • 选择 Canvas:兼顾实现难度与性能需求,Rust 负责路径计算,JavaScript 负责最终绘制。
    在这里插入图片描述

3. 客户端实现(Rust + WebAssembly)

3.1 核心结构体定义

#[derive(Serialize, Deserialize)]
pub enum DrawingCommand {
Line { from: (f64, f64), to: (f64, f64), color: String },
Clear,
}
  #[wasm_bindgen]
pub struct Whiteboard {
commands: Vec<DrawingCommand>,
  ws: web_sys::WebSocket,
  }
  • DrawingCommand:定义白板操作类型(如画线、清屏);
  • Whiteboard:封装 WebSocket 通信与命令管理逻辑。

3.2 鼠标事件绑定

#[wasm_bindgen]
impl Whiteboard {
pub fn new(url: &str) -> Result<Whiteboard, JsValue> {
  let ws = web_sys::WebSocket::new(url)?;
  Ok(Whiteboard {
  commands: Vec::new(),
  ws,
  })
  }
  pub fn on_mouse_move(&mut self, x: f64, y: f64) {
  // 记录路径并发送指令
  let cmd = DrawingCommand::Line {
  from: (self.last_x, self.last_y),
  to: (x, y),
  color: "#000000".to_string(),
  };
  self.send_command(cmd);
  }
  }

3.3 构建 WebAssembly 模块

wasm-pack build --target web
# 输出目录:pkg/whiteboard_client.js + whiteboard_client_bg.wasm

4. 前端集成(JavaScript + HTML)

4.1 加载 Wasm 模块

<script type="module">
  import init from './pkg/whiteboard_client.js';
  let instance;
  async function initWasm() {
  instance = await init();
  const whiteboard = new instance.Whiteboard("ws://localhost:8080");
  canvas.addEventListener('mousemove', (e) => {
  whiteboard.on_mouse_move(e.offsetX, e.offsetY);
  });
  whiteboard.add_command = (cmd) => {
  instance.whiteboard.add_command(cmd);
  redraw();
  };
  }
  function redraw() {
  // 通过 Canvas 渲染所有 commands
  // 此处省略具体绘制逻辑
  }
</script>

5. 服务器端实现(Rust + Tokio)

5.1 WebSocket 广播服务器

use tokio::net::TcpListener;
use tokio_tungstenite::{accept_async, WebSocketStream};
use futures::StreamExt;
  #[tokio::main]
async fn main() {
let listener = TcpListener::bind("127.0.0.1:8080").await.unwrap();
println!("WebSocket server running on ws://localhost:8080");
while let Ok((stream, _)) = listener.accept().await {
tokio::spawn(handle_connection(stream));
}
}
async fn handle_connection(stream: tokio::net::TcpStream) {
let ws_stream = accept_async(stream).await.unwrap();
let (mut write, mut read) = ws_stream.split();
let (tx, rx) = tokio::sync::broadcast::channel(100);
read.for_each(|msg| async {
if let Ok(msg) = msg {
if let Ok(text) = msg.to_text() {
if let Ok(cmd) = serde_json::from_str::<DrawingCommand>(text) {
  tx.send(serde_json::to_string(&cmd).unwrap()).unwrap();
  }
  }
  }
  }).await;
  let mut rx = rx.subscribe();
  while let Ok(cmd) = rx.recv().await {
  write.send(tokio_tungstenite::tungstenite::Message::Text(cmd)).await.unwrap();
  }
  }

5. 参考文献

  1. Rust 官方文档:https://doc.rust-lang.org
  2. WebAssembly 官方指南:https://webassembly.org
  3. wasm-bindgen GitHub:https://github.com/rustwasm/wasm-bindgen
  4. Tokio 异步运行时:https://tokio.rs
  5. WebSocket 协议规范:https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
  6. Canvas 与 WebGL 对比:https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API

在这里插入图片描述

设计说明

1. 项目概述

1.1 产品愿景

开发一个高性能、低延迟的多人在线共享白板应用,支持实时协作绘图,为远程教育、团队协作、在线会议等场景提供专业的绘图工具。

1.2 核心价值

  • 高性能绘图:基于Rust + WebAssembly的图形渲染引擎
  • 实时协作:毫秒级延迟的多用户同步
  • 安全可靠:内存安全的底层架构
  • 跨平台兼容:无需安装插件的Web应用

2. 功能需求

在这里插入图片描述

2.1 核心功能模块

2.1.1 绘图工具
  • 基础绘图工具

    • 铅笔/画笔工具
    • 直线/曲线工具
    • 矩形/圆形工具
    • 文字输入工具
    • 橡皮擦工具
  • 高级绘图功能

    • 图形选择与编辑
    • 图层管理
    • 撤销/重做操作
    • 图形缩放与旋转
2.1.2 多人协作
  • 实时同步机制

    • WebSocket实时通信
    • 操作指令同步
    • 冲突解决策略
    • 用户状态显示
  • 用户管理

    • 用户身份标识
    • 权限控制(创建者/参与者)
    • 用户列表显示
    • 用户光标追踪
2.1.3 白板管理
  • 白板操作

    • 新建/保存白板
    • 导入/导出功能
    • 白板模板库
    • 历史版本管理
  • 画布设置

    • 背景颜色/网格设置
    • 画布尺寸调整
    • 缩放与平移
    • 标尺与参考线

3. 项目效果

在这里插入图片描述

//websocket.rs
use actix_web::{web, HttpRequest, HttpResponse};
use actix_web_actors::ws;
use uuid::Uuid;
use chrono::Utc;
use crate::services::room::RoomService;
// 导入必要的actix模块
use actix::prelude::*;
pub struct WebSocketConnection {
pub user_id: Uuid,
pub room_id: String,
pub room_service: web::Data<RoomService>,
  }
  impl actix::Actor for WebSocketConnection {
  type Context = ws::WebsocketContext<Self>;
    fn started(&mut self, ctx: &mut Self::Context) {
    log::info!("WebSocket连接建立: 用户 {} 加入房间 {}", self.user_id, self.room_id);
    // 通知房间内其他用户有新用户加入
    let message = serde_json::json!({
    "type": "user_joined",
    "user_id": self.user_id.to_string(),
    "timestamp": Utc::now().timestamp_millis()
    });
    // 暂时直接发送回客户端(测试用)
    ctx.text(message.to_string());
    }
    fn stopped(&mut self, _ctx: &mut Self::Context) {
    log::info!("WebSocket连接关闭: 用户 {} 离开房间 {}", self.user_id, self.room_id);
    }
    }
    impl actix::StreamHandler<Result<ws::Message, ws::ProtocolError>> for WebSocketConnection {
      fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
        match msg {
        Ok(ws::Message::Ping(msg)) => ctx.pong(&msg),
        Ok(ws::Message::Text(text)) => {
        log::debug!("收到WebSocket消息: {}", text);
        // 处理绘图操作消息
        match serde_json::from_str::<serde_json::Value>(&text) {
          Ok(data) => {
          if let Some(msg_type) = data.get("type").and_then(|t| t.as_str()) {
          match msg_type {
          "drawing_operation" => {
          // 处理绘图操作
          self.handle_drawing_operation(&data, ctx);
          }
          "cursor_move" => {
          // 处理光标移动
          self.handle_cursor_move(&data, ctx);
          }
          "chat_message" => {
          // 处理聊天消息
          self.handle_chat_message(&data, ctx);
          }
          _ => {
          log::warn!("未知的消息类型: {}", msg_type);
          }
          }
          }
          }
          Err(e) => {
          log::error!("消息解析失败: {}", e);
          }
          }
          }
          Ok(ws::Message::Close(reason)) => {
          ctx.close(reason);
          ctx.stop();
          }
          _ => {}
          }
          }
          }
          impl WebSocketConnection {
          fn handle_drawing_operation(&mut self, data: &serde_json::Value, ctx: &mut ws::WebsocketContext<Self>) {
            // 广播绘图操作给房间内其他用户
            let broadcast_msg = serde_json::json!({
            "type": "drawing_operation",
            "data": data,
            "user_id": self.user_id.to_string(),
            "timestamp": Utc::now().timestamp_millis()
            });
            // 暂时直接发送回客户端(测试用)
            ctx.text(broadcast_msg.to_string());
            }
            fn handle_cursor_move(&mut self, data: &serde_json::Value, ctx: &mut ws::WebsocketContext<Self>) {
              // 广播光标移动给房间内其他用户
              let broadcast_msg = serde_json::json!({
              "type": "cursor_move",
              "data": data,
              "user_id": self.user_id.to_string(),
              "timestamp": Utc::now().timestamp_millis()
              });
              // 暂时直接发送回客户端(测试用)
              ctx.text(broadcast_msg.to_string());
              }
              fn handle_chat_message(&mut self, data: &serde_json::Value, ctx: &mut ws::WebsocketContext<Self>) {
                // 广播聊天消息给房间内其他用户
                let broadcast_msg = serde_json::json!({
                "type": "chat_message",
                "data": data,
                "user_id": self.user_id.to_string(),
                "timestamp": Utc::now().timestamp_millis()
                });
                // 暂时直接发送回客户端(测试用)
                ctx.text(broadcast_msg.to_string());
                }
                }
                pub async fn websocket_handler(
                req: HttpRequest,
                stream: web::Payload,
                room_service: web::Data<RoomService>,
                  path: web::Path<(String, String)>,
                    ) -> Result<HttpResponse, actix_web::Error> {
                      let (room_id, user_id) = path.into_inner();
                      let user_uuid = Uuid::parse_str(&user_id).map_err(|_| {
                      log::error!("无效的用户ID: {}", user_id);
                      actix_web::error::ErrorBadRequest("无效的用户ID")
                      })?;
                      // 验证用户和房间
                      if room_service.get_room(&room_id).await.is_none() {
                      return Err(actix_web::error::ErrorNotFound("房间不存在"));
                      }
                      let ws = WebSocketConnection {
                      user_id: user_uuid,
                      room_id,
                      room_service,
                      };
                      let resp = ws::start(ws, &req, stream)?;
                      Ok(resp)
                      }
                      pub fn config(cfg: &mut web::ServiceConfig) {
                      cfg.service(
                      web::scope("/ws")
                      .route("/{room_id}/{user_id}", web::get().to(websocket_handler)),
                      );
                      }
//api.rs
use actix_web::{web, HttpResponse, Result};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::services::{room::RoomService, user::UserService};
// 健康检查路由
pub async fn health_check() -> Result<HttpResponse> {
  Ok(HttpResponse::Ok().json(serde_json::json!({
  "status": "ok",
  "service": "墨契白板服务器",
  "version": "1.0.0"
  })))
  }
    #[derive(Debug, Serialize, Deserialize)]
  pub struct CreateRoomRequest {
  pub name: String,
  pub creator_id: Uuid,
  }
    #[derive(Debug, Serialize, Deserialize)]
  pub struct JoinRoomRequest {
  pub room_id: String,
  pub user_id: Uuid,
  }
    #[derive(Debug, Serialize, Deserialize)]
  pub struct UserInfo {
  pub id: Uuid,
  pub name: String,
  pub color: String,
  }
  pub async fn create_room(
  room_service: web::Data<RoomService>,
    req: web::Json<CreateRoomRequest>,
      ) -> Result<HttpResponse> {
        let room_id = room_service
        .create_room(req.name.clone(), req.creator_id)
        .await
        .map_err(|e| {
        log::error!("创建房间失败: {}", e);
        actix_web::error::ErrorInternalServerError("创建房间失败")
        })?;
        Ok(HttpResponse::Ok().json(serde_json::json!({
        "success": true,
        "room_id": room_id,
        "message": "房间创建成功"
        })))
        }
        pub async fn join_room(
        room_service: web::Data<RoomService>,
          user_service: web::Data<UserService>,
            req: web::Json<JoinRoomRequest>,
              ) -> Result<HttpResponse> {
                let user = user_service
                .get_user(req.user_id)
                .await
                .ok_or_else(|| {
                log::error!("用户不存在: {}", req.user_id);
                actix_web::error::ErrorBadRequest("用户不存在")
                })?;
                room_service
                .join_room(&req.room_id, user)
                .await
                .map_err(|e| {
                log::error!("加入房间失败: {}", e);
                actix_web::error::ErrorBadRequest("加入房间失败")
                })?;
                Ok(HttpResponse::Ok().json(serde_json::json!({
                "success": true,
                "message": "加入房间成功"
                })))
                }
                pub async fn get_room_info(
                room_service: web::Data<RoomService>,
                  path: web::Path<String>,
                    ) -> Result<HttpResponse> {
                      let room_id = path.into_inner();
                      let room = room_service
                      .get_room(&room_id)
                      .await
                      .ok_or_else(|| {
                      log::error!("房间不存在: {}", room_id);
                      actix_web::error::ErrorNotFound("房间不存在")
                      })?;
                      Ok(HttpResponse::Ok().json(room))
                      }
                      pub async fn get_room_users(
                      room_service: web::Data<RoomService>,
                        path: web::Path<String>,
                          ) -> Result<HttpResponse> {
                            let room_id = path.into_inner();
                            let users = room_service
                            .get_room_users(&room_id)
                            .await
                            .ok_or_else(|| {
                            log::error!("房间不存在: {}", room_id);
                            actix_web::error::ErrorNotFound("房间不存在")
                            })?;
                            Ok(HttpResponse::Ok().json(users))
                            }
                            pub fn config(cfg: &mut web::ServiceConfig) {
                            cfg
                            .route("/", web::get().to(health_check))
                            .service(
                            web::scope("/api")
                            .route("/rooms", web::post().to(create_room))
                            .route("/rooms/join", web::post().to(join_room))
                            .route("/rooms/{room_id}", web::get().to(get_room_info))
                            .route("/rooms/{room_id}/users", web::get().to(get_room_users)),
                            );
                            }
import { ref } from 'vue'
import type { DrawingElement, Point } from '@/types/drawing'
import type { User } from '@/types/user'
// WebSocket消息类型
export enum MessageType {
JOIN_ROOM = 'join_room',
LEAVE_ROOM = 'leave_room',
DRAW_ELEMENT = 'draw_element',
UPDATE_ELEMENT = 'update_element',
DELETE_ELEMENT = 'delete_element',
USER_CURSOR = 'user_cursor',
USER_JOINED = 'user_joined',
USER_LEFT = 'user_left'
}
// WebSocket消息接口
export interface WebSocketMessage {
type: MessageType
data: any
timestamp: number
userId: string
roomId: string
}
// WebSocket服务类
export class WebSocketService {
private ws: WebSocket | null = null
private reconnectAttempts = 0
private maxReconnectAttempts = 5
private reconnectInterval = 3000
// 事件回调
public onMessage: ((message: WebSocketMessage) => void) | null = null
public onConnect: (() => void) | null = null
public onDisconnect: (() => void) | null = null
public onError: ((error: Event) => void) | null = null
// 连接状态
public isConnected = ref(false)
// 连接到WebSocket服务器
connect(serverUrl: string, roomId: string, userId: string): Promise<void> {
  return new Promise((resolve, reject) => {
  try {
  // 确保URL格式正确
  const url = serverUrl.startsWith('ws://') || serverUrl.startsWith('wss://')
  ? serverUrl
  : `ws://${serverUrl}`
  this.ws = new WebSocket(`${url}?roomId=${roomId}&userId=${userId}`)
  this.ws.onopen = () => {
  console.log('WebSocket连接成功')
  this.isConnected.value = true
  this.reconnectAttempts = 0
  this.onConnect?.()
  resolve()
  }
  this.ws.onmessage = (event) => {
  try {
  const message: WebSocketMessage = JSON.parse(event.data)
  this.onMessage?.(message)
  } catch (error) {
  console.error('解析WebSocket消息失败:', error)
  }
  }
  this.ws.onclose = (event) => {
  console.log('WebSocket连接关闭:', event.code, event.reason)
  this.isConnected.value = false
  this.onDisconnect?.()
  // 自动重连
  if (this.reconnectAttempts < this.maxReconnectAttempts) {
  setTimeout(() => {
  this.reconnectAttempts++
  console.log(`尝试重新连接 (${this.reconnectAttempts}/${this.maxReconnectAttempts})`)
  this.connect(serverUrl, roomId, userId)
  }, this.reconnectInterval)
  }
  }
  this.ws.onerror = (error) => {
  console.error('WebSocket连接错误:', error)
  this.onError?.(error)
  reject(error)
  }
  } catch (error) {
  reject(error)
  }
  })
  }
  // 断开连接
  disconnect(): void {
  if (this.ws) {
  this.ws.close()
  this.ws = null
  this.isConnected.value = false
  }
  }
  // 发送消息
  sendMessage(message: Omit<WebSocketMessage, 'timestamp'>): void {
    if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
    console.warn('WebSocket未连接,无法发送消息')
    return
    }
    const fullMessage: WebSocketMessage = {
    ...message,
    timestamp: Date.now()
    }
    this.ws.send(JSON.stringify(fullMessage))
    }
    // 发送绘图元素
    sendDrawingElement(element: DrawingElement, roomId: string, userId: string): void {
    this.sendMessage({
    type: MessageType.DRAW_ELEMENT,
    data: element,
    userId,
    roomId
    })
    }
    // 发送元素更新
    sendElementUpdate(elementId: string, updates: Partial<DrawingElement>, roomId: string, userId: string): void {
      this.sendMessage({
      type: MessageType.UPDATE_ELEMENT,
      data: { elementId, updates },
      userId,
      roomId
      })
      }
      // 发送元素删除
      sendElementDelete(elementId: string, roomId: string, userId: string): void {
      this.sendMessage({
      type: MessageType.DELETE_ELEMENT,
      data: { elementId },
      userId,
      roomId
      })
      }
      // 发送用户光标位置
      sendUserCursor(position: Point, roomId: string, userId: string): void {
      this.sendMessage({
      type: MessageType.USER_CURSOR,
      data: { position },
      userId,
      roomId
      })
      }
      // 发送用户加入房间
      sendUserJoin(user: User, roomId: string): void {
      this.sendMessage({
      type: MessageType.USER_JOINED,
      data: { user },
      userId: user.id,
      roomId
      })
      }
      // 发送用户离开房间
      sendUserLeave(userId: string, roomId: string): void {
      this.sendMessage({
      type: MessageType.USER_LEFT,
      data: { userId },
      userId,
      roomId
      })
      }
      }
      // 创建WebSocket服务实例
      export const webSocketService = new WebSocketService()
      // 模拟WebSocket服务器(开发环境使用)
      export class MockWebSocketServer {
      private clients: Map<string, any> = new Map()
        // 模拟接收消息并广播
        handleMessage(message: WebSocketMessage, clientId: string): void {
        // 模拟服务器处理逻辑
        switch (message.type) {
        case MessageType.JOIN_ROOM:
        this.broadcastMessage({
        type: MessageType.USER_JOINED,
        data: { user: message.data },
        userId: message.userId,
        roomId: message.roomId,
        timestamp: Date.now()
        }, clientId)
        break
        case MessageType.DRAW_ELEMENT:
        case MessageType.UPDATE_ELEMENT:
        case MessageType.DELETE_ELEMENT:
        case MessageType.USER_CURSOR:
        // 广播给房间内其他用户
        this.broadcastMessage(message, clientId)
        break
        default:
        console.log('未知消息类型:', message.type)
        }
        }
        // 模拟广播消息
        private broadcastMessage(message: WebSocketMessage, excludeClientId?: string): void {
        // 在实际项目中,这里应该只广播给同一房间的用户
        this.clients.forEach((client, clientId) => {
        if (clientId !== excludeClientId && client.roomId === message.roomId) {
        // 模拟网络延迟
        setTimeout(() => {
        if (typeof client.onMessage === 'function') {
        client.onMessage({ data: JSON.stringify(message) })
        }
        }, Math.random() * 100 + 50) // 50-150ms延迟
        }
        })
        }
        // 添加客户端
        addClient(clientId: string, client: any): void {
        this.clients.set(clientId, client)
        }
        // 移除客户端
        removeClient(clientId: string): void {
        this.clients.delete(clientId)
        }
        }
        // 创建模拟服务器实例
        export const mockWebSocketServer = new MockWebSocketServer()
import type { User, Room } from '@/types/user'
// API基础配置
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8084'
// API响应接口
interface ApiResponse<T = any> {
  success: boolean
  data?: T
  message?: string
  error?: string
  }
  // 创建房间请求
  interface CreateRoomRequest {
  name: string
  creator_id: string
  }
  // 加入房间请求
  interface JoinRoomRequest {
  room_id: string
  user_id: string
  }
  // API服务类
  export class ApiService {
  // 通用请求方法
  private async request<T>(
    endpoint: string,
    options: RequestInit = {}
    ): Promise<ApiResponse<T>> {
      try {
      const url = `${API_BASE_URL}${endpoint}`
      const response = await fetch(url, {
      headers: {
      'Content-Type': 'application/json',
      ...options.headers,
      },
      ...options,
      })
      if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`)
      }
      const data = await response.json()
      return { success: true, data }
      } catch (error) {
      console.error('API请求失败:', error)
      const errorMessage = error instanceof Error ? error.message : '未知错误'
      // 在控制台输出错误信息,不在UI中显示
      console.error(`API请求失败: ${errorMessage}`)
      return { success: false, error: errorMessage }
      }
      }
      // 健康检查
      async healthCheck(): Promise<ApiResponse<{ status: string; service: string; version: string }>> {
        return this.request('/')
        }
        // 创建房间
        async createRoom(roomData: CreateRoomRequest): Promise<ApiResponse<{ room_id: string; message: string }>> {
          return this.request('/api/rooms', {
          method: 'POST',
          body: JSON.stringify(roomData),
          })
          }
          // 加入房间
          async joinRoom(joinData: JoinRoomRequest): Promise<ApiResponse<{ message: string }>> {
            return this.request('/api/rooms/join', {
            method: 'POST',
            body: JSON.stringify(joinData),
            })
            }
            // 获取房间信息
            async getRoomInfo(roomId: string): Promise<ApiResponse<Room>> {
              return this.request(`/api/rooms/${roomId}`)
              }
              // 获取房间用户列表
              async getRoomUsers(roomId: string): Promise<ApiResponse<User[]>> {
                return this.request(`/api/rooms/${roomId}/users`)
                }
                // 创建用户
                async createUser(userData: Partial<User>): Promise<ApiResponse<User>> {
                  // 这里可以扩展为调用后端用户创建API
                  // 目前使用前端生成的用户ID
                  const user: User = {
                  id: userData.id || `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
                  name: userData.name || '匿名用户',
                  color: userData.color || '#3498db',
                  isOnline: true,
                  lastActive: Date.now(),
                  }
                  return { success: true, data: user }
                  }
                  // 验证房间是否存在
                  async validateRoom(roomId: string): Promise<boolean> {
                    const response = await this.getRoomInfo(roomId)
                    return response.success && response.data !== undefined
                    }
                    }
                    // 创建API服务实例
                    export const apiService = new ApiService()
                    // API工具函数
                    export const apiUtils = {
                    // 生成房间链接
                    generateRoomLink(roomId: string): string {
                    return `${window.location.origin}/room/${roomId}`
                    },
                    // 解析房间ID
                    parseRoomIdFromUrl(url: string): string | null {
                    const match = url.match(/\/room\/([^/?]+)/)
                    return match ? match[1] : null
                    },
                    // 格式化错误消息
                    formatErrorMessage(error: any): string {
                    if (typeof error === 'string') return error
                    if (error?.message) return error.message
                    return '未知错误'
                    }
                    }

4. 小结

通过将核心绘图逻辑(如图形操作、撤销栈、指令序列化)用 Rust 编写,并借助 wasm-pack 和 wasm-bindgen 编译为 WebAssembly 模块,不仅实现了接近原生的执行效率,还显著提升了代码的可靠性与可维护性。