在这里插入图片描述

技术选型考虑

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 模块,不仅实现了接近原生的执行效率,还显著提升了代码的可靠性与可维护性。


Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐