文章目录[隐藏]
手把手教学:为你的网站添加在线协同白板与团队创意协作功能
引言:为什么你的网站需要协同白板功能?
在当今数字化工作环境中,远程协作已成为常态。无论是产品设计、项目规划还是创意头脑风暴,团队成员往往分散在不同地点。传统的沟通方式如邮件、即时消息和视频会议虽然有效,但在创意协作方面存在明显局限——缺乏直观的视觉共享空间。
在线协同白板正是解决这一痛点的理想工具。它提供了一个虚拟的"画布",团队成员可以实时绘制图表、添加便签、上传图片、创建思维导图,并看到彼此的修改。这种视觉化协作方式能显著提高团队效率,激发创意灵感,并确保所有参与者对项目有统一的理解。
对于WordPress网站所有者来说,添加这样的功能不仅能提升用户体验,还能将你的网站从一个单向信息发布平台转变为互动协作空间。无论是企业内部协作、在线教育、咨询服务还是客户项目沟通,协同白板都能为你的网站增加巨大价值。
第一部分:准备工作与环境配置
1.1 理解WordPress二次开发基础
在开始之前,我们需要明确WordPress二次开发的基本概念。WordPress不仅是一个内容管理系统,更是一个强大的开发平台,通过其丰富的API和钩子系统,我们可以扩展其功能而不影响核心文件。
关键概念:
- 主题与插件:功能扩展主要通过子主题或自定义插件实现
- 动作钩子(Action Hooks):在特定时间点执行自定义代码
- 过滤器钩子(Filter Hooks):修改WordPress处理的数据
- 短代码(Shortcodes):在内容中嵌入动态功能
- REST API:为前端应用提供数据接口
1.2 开发环境搭建
为了安全地进行开发,我们首先需要搭建本地测试环境:
- 本地服务器环境:推荐使用XAMPP、MAMP或Local by Flywheel
- 代码编辑器:VS Code、PHPStorm或Sublime Text
- 浏览器开发者工具:Chrome DevTools或Firefox Developer Tools
- 版本控制:Git用于代码管理
1.3 创建自定义插件框架
我们将创建一个独立的插件来管理所有协同白板功能,这样可以确保功能独立于主题,便于维护和迁移。
<?php
/**
* Plugin Name: 协同白板与团队协作工具
* Plugin URI: https://yourwebsite.com/
* Description: 为WordPress网站添加在线协同白板与团队创意协作功能
* Version: 1.0.0
* Author: 你的名字
* License: GPL v2 or later
*/
// 防止直接访问
if (!defined('ABSPATH')) {
exit;
}
// 定义插件常量
define('COLLAB_WHITEBOARD_VERSION', '1.0.0');
define('COLLAB_WHITEBOARD_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('COLLAB_WHITEBOARD_PLUGIN_URL', plugin_dir_url(__FILE__));
// 初始化插件
function collab_whiteboard_init() {
// 检查依赖项
if (!class_exists('WP_List_Table')) {
require_once(ABSPATH . 'wp-admin/includes/class-wp-list-table.php');
}
// 加载必要文件
require_once COLLAB_WHITEBOARD_PLUGIN_DIR . 'includes/class-whiteboard-manager.php';
require_once COLLAB_WHITEBOARD_PLUGIN_DIR . 'includes/class-whiteboard-db.php';
require_once COLLAB_WHITEBOARD_PLUGIN_DIR . 'includes/class-whiteboard-shortcodes.php';
// 初始化组件
new Whiteboard_Manager();
new Whiteboard_Shortcodes();
}
add_action('plugins_loaded', 'collab_whiteboard_init');
// 激活插件时创建数据库表
function collab_whiteboard_activate() {
require_once COLLAB_WHITEBOARD_PLUGIN_DIR . 'includes/class-whiteboard-db.php';
Whiteboard_DB::create_tables();
}
register_activation_hook(__FILE__, 'collab_whiteboard_activate');
// 停用插件时的清理工作
function collab_whiteboard_deactivate() {
// 可选的清理代码
}
register_deactivation_hook(__FILE__, 'collab_whiteboard_deactivate');
第二部分:数据库设计与用户权限管理
2.1 设计协同白板数据库结构
协同白板需要存储白板数据、用户权限和修改历史。我们创建以下数据库表:
// 在includes/class-whiteboard-db.php中
class Whiteboard_DB {
public static function create_tables() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
$table_prefix = $wpdb->prefix . 'collab_';
// 白板主表
$whiteboards_table = $table_prefix . 'whiteboards';
$sql1 = "CREATE TABLE IF NOT EXISTS $whiteboards_table (
id mediumint(9) NOT NULL AUTO_INCREMENT,
title varchar(255) NOT NULL,
description text,
content longtext,
created_by bigint(20) NOT NULL,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
settings text,
is_public tinyint(1) DEFAULT 0,
PRIMARY KEY (id)
) $charset_collate;";
// 白板权限表
$permissions_table = $table_prefix . 'permissions';
$sql2 = "CREATE TABLE IF NOT EXISTS $permissions_table (
id mediumint(9) NOT NULL AUTO_INCREMENT,
whiteboard_id mediumint(9) NOT NULL,
user_id bigint(20) NOT NULL,
permission_level varchar(50) NOT NULL,
added_by bigint(20) NOT NULL,
added_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY unique_whiteboard_user (whiteboard_id, user_id)
) $charset_collate;";
// 白板历史记录表
$history_table = $table_prefix . 'history';
$sql3 = "CREATE TABLE IF NOT EXISTS $history_table (
id mediumint(9) NOT NULL AUTO_INCREMENT,
whiteboard_id mediumint(9) NOT NULL,
user_id bigint(20) NOT NULL,
action varchar(100) NOT NULL,
changes text,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY whiteboard_id (whiteboard_id)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql1);
dbDelta($sql2);
dbDelta($sql3);
}
}
2.2 实现用户权限系统
协同白板需要精细的权限控制,不同用户应有不同级别的访问和编辑权限:
class Whiteboard_Permissions {
const PERMISSION_VIEW = 'view';
const PERMISSION_COMMENT = 'comment';
const PERMISSION_EDIT = 'edit';
const PERMISSION_ADMIN = 'admin';
/**
* 检查用户对白板的权限
*/
public static function check_permission($whiteboard_id, $user_id, $required_permission) {
global $wpdb;
// 获取白板信息
$table_name = $wpdb->prefix . 'collab_whiteboards';
$whiteboard = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM $table_name WHERE id = %d",
$whiteboard_id
));
if (!$whiteboard) {
return false;
}
// 如果是公开白板且只需要查看权限
if ($whiteboard->is_public && $required_permission === self::PERMISSION_VIEW) {
return true;
}
// 创建者拥有所有权限
if ($whiteboard->created_by == $user_id) {
return true;
}
// 检查用户特定权限
$permissions_table = $wpdb->prefix . 'collab_permissions';
$user_permission = $wpdb->get_var($wpdb->prepare(
"SELECT permission_level FROM $permissions_table
WHERE whiteboard_id = %d AND user_id = %d",
$whiteboard_id, $user_id
));
if (!$user_permission) {
return false;
}
// 权限等级映射
$permission_hierarchy = [
self::PERMISSION_VIEW => 1,
self::PERMISSION_COMMENT => 2,
self::PERMISSION_EDIT => 3,
self::PERMISSION_ADMIN => 4
];
$required_level = $permission_hierarchy[$required_permission] ?? 0;
$user_level = $permission_hierarchy[$user_permission] ?? 0;
return $user_level >= $required_level;
}
/**
* 为用户分配白板权限
*/
public static function assign_permission($whiteboard_id, $user_id, $permission_level, $assigned_by) {
global $wpdb;
$table_name = $wpdb->prefix . 'collab_permissions';
// 检查是否已存在权限记录
$existing = $wpdb->get_var($wpdb->prepare(
"SELECT id FROM $table_name
WHERE whiteboard_id = %d AND user_id = %d",
$whiteboard_id, $user_id
));
if ($existing) {
// 更新现有权限
return $wpdb->update(
$table_name,
[
'permission_level' => $permission_level,
'added_by' => $assigned_by
],
[
'whiteboard_id' => $whiteboard_id,
'user_id' => $user_id
]
);
} else {
// 插入新权限记录
return $wpdb->insert(
$table_name,
[
'whiteboard_id' => $whiteboard_id,
'user_id' => $user_id,
'permission_level' => $permission_level,
'added_by' => $assigned_by
]
);
}
}
}
第三部分:前端白板界面与实时协作实现
3.1 选择前端绘图库
对于协同白板,我们需要一个强大的前端绘图库。这里我们选择Fabric.js,它是一个功能强大的Canvas库,支持丰富的图形操作和事件处理。
首先,在插件中注册必要的脚本和样式:
class Whiteboard_Assets {
public static function enqueue_frontend_assets() {
// Fabric.js 绘图库
wp_enqueue_script(
'fabric-js',
'https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.5.0/fabric.min.js',
[],
'4.5.0',
true
);
// Socket.io 客户端 (用于实时通信)
wp_enqueue_script(
'socket-io',
'https://cdn.socket.io/4.5.0/socket.io.min.js',
[],
'4.5.0',
true
);
// 自定义白板脚本
wp_enqueue_script(
'collab-whiteboard',
COLLAB_WHITEBOARD_PLUGIN_URL . 'assets/js/whiteboard.js',
['jquery', 'fabric-js', 'socket-io'],
COLLAB_WHITEBOARD_VERSION,
true
);
// 白板样式
wp_enqueue_style(
'collab-whiteboard-style',
COLLAB_WHITEBOARD_PLUGIN_URL . 'assets/css/whiteboard.css',
[],
COLLAB_WHITEBOARD_VERSION
);
// 本地化脚本,传递必要数据
wp_localize_script('collab-whiteboard', 'whiteboardConfig', [
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('collab_whiteboard_nonce'),
'currentUserId' => get_current_user_id(),
'socketServer' => self::get_socket_server_url()
]);
}
private static function get_socket_server_url() {
// 这里返回你的WebSocket服务器地址
// 可以是独立的Node.js服务器或通过WordPress REST API实现
return home_url('/wp-json/collab-whiteboard/v1/socket');
}
}
add_action('wp_enqueue_scripts', ['Whiteboard_Assets', 'enqueue_frontend_assets']);
3.2 创建白板画布界面
接下来,我们创建白板的主要HTML结构和JavaScript逻辑:
<!-- 在短代码输出的HTML中 -->
<div class="whiteboard-container" data-whiteboard-id="<?php echo $whiteboard_id; ?>">
<div class="whiteboard-toolbar">
<div class="tool-group">
<button class="tool-btn" data-tool="select" title="选择工具">
<i class="icon-cursor"></i>
</button>
<button class="tool-btn active" data-tool="pencil" title="铅笔">
<i class="icon-pencil"></i>
</button>
<button class="tool-btn" data-tool="line" title="直线">
<i class="icon-line"></i>
</button>
<button class="tool-btn" data-tool="rectangle" title="矩形">
<i class="icon-square"></i>
</button>
<button class="tool-btn" data-tool="circle" title="圆形">
<i class="icon-circle"></i>
</button>
<button class="tool-btn" data-tool="text" title="文本">
<i class="icon-text"></i>
</button>
</div>
<div class="tool-group">
<input type="color" class="color-picker" value="#000000" title="颜色">
<input type="range" class="brush-size" min="1" max="50" value="3" title="笔刷大小">
<button class="tool-btn" data-action="undo" title="撤销">
<i class="icon-undo"></i>
</button>
<button class="tool-btn" data-action="redo" title="重做">
<i class="icon-redo"></i>
</button>
<button class="tool-btn" data-action="clear" title="清空白板">
<i class="icon-trash"></i>
</button>
</div>
<div class="tool-group user-list">
<span class="online-users">在线用户: <span class="user-count">1</span></span>
</div>
</div>
<div class="whiteboard-canvas-container">
<canvas id="whiteboard-canvas"></canvas>
</div>
<div class="whiteboard-sidebar">
<div class="sidebar-section">
<h4>元素属性</h4>
<div class="properties-panel">
<!-- 动态属性控件将在这里显示 -->
</div>
</div>
<div class="sidebar-section">
<h4>聊天与评论</h4>
<div class="chat-container">
<div class="chat-messages"></div>
<div class="chat-input">
<input type="text" placeholder="输入消息...">
<button class="send-btn">发送</button>
</div>
</div>
</div>
</div>
</div>
3.3 实现实时协作功能
实时协作是协同白板的核心功能。我们使用WebSocket实现实时数据同步:
// assets/js/whiteboard.js
class CollaborativeWhiteboard {
constructor(containerElement) {
this.container = containerElement;
this.whiteboardId = containerElement.dataset.whiteboardId;
this.canvas = null;
this.socket = null;
this.currentTool = 'pencil';
this.isDrawing = false;
this.lastPoint = null;
this.history = [];
this.historyIndex = -1;
this.init();
}
init() {
// 初始化画布
this.canvas = new fabric.Canvas('whiteboard-canvas', {
isDrawingMode: true,
width: this.container.querySelector('.whiteboard-canvas-container').offsetWidth,
height: 600,
backgroundColor: '#ffffff'
});
// 连接WebSocket服务器
this.connectSocket();
// 绑定事件
this.bindEvents();
// 加载现有白板数据
this.loadWhiteboardData();
}
connectSocket() {
// 连接到WebSocket服务器
this.socket = io(whiteboardConfig.socketServer, {
query: {
whiteboardId: this.whiteboardId,
userId: whiteboardConfig.currentUserId
}
});
// 监听服务器消息
this.socket.on('connect', () => {
console.log('已连接到白板服务器');
});
this.socket.on('drawing', (data) => {
this.handleRemoteDrawing(data);
});
this.socket.on('object:modified', (data) => {
this.handleRemoteObjectModification(data);
});
this.socket.on('user:joined', (data) => {
this.updateOnlineUsers(data.users);
});
this.socket.on('user:left', (data) => {
this.updateOnlineUsers(data.users);
});
this.socket.on('chat:message', (data) => {
this.addChatMessage(data);
});
}
bindEvents() {
// 工具按钮点击事件
this.container.querySelectorAll('.tool-btn[data-tool]').forEach(btn => {
btn.addEventListener('click', (e) => {
this.setTool(e.target.closest('.tool-btn').dataset.tool);
});
});
// 动作按钮点击事件
this.container.querySelectorAll('.tool-btn[data-action]').forEach(btn => {
btn.addEventListener('click', (e) => {
const action = e.target.closest('.tool-btn').dataset.action;
this.handleAction(action);
});
});
// 颜色选择器
EventListener('change', (e) => {
this.setColor(e.target.value);
});
// 笔刷大小
this.container.querySelector('.brush-size').addEventListener('input', (e) => {
this.setBrushSize(parseInt(e.target.value));
});
// 画布事件
this.canvas.on('mouse:down', (options) => {
this.onMouseDown(options);
});
this.canvas.on('mouse:move', (options) => {
this.onMouseMove(options);
});
this.canvas.on('mouse:up', (options) => {
this.onMouseUp(options);
});
this.canvas.on('object:added', (options) => {
this.onObjectAdded(options);
});
this.canvas.on('object:modified', (options) => {
this.onObjectModified(options);
});
// 聊天功能
const chatInput = this.container.querySelector('.chat-input input');
const sendBtn = this.container.querySelector('.chat-input .send-btn');
sendBtn.addEventListener('click', () => {
this.sendChatMessage(chatInput.value);
chatInput.value = '';
});
chatInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
this.sendChatMessage(chatInput.value);
chatInput.value = '';
}
});
// 窗口大小调整
window.addEventListener('resize', () => {
this.resizeCanvas();
});
}
setTool(tool) {
this.currentTool = tool;
// 更新按钮状态
this.container.querySelectorAll('.tool-btn[data-tool]').forEach(btn => {
btn.classList.toggle('active', btn.dataset.tool === tool);
});
// 根据工具设置画布模式
switch(tool) {
case 'select':
this.canvas.isDrawingMode = false;
this.canvas.selection = true;
break;
case 'pencil':
this.canvas.isDrawingMode = true;
this.canvas.freeDrawingBrush = new fabric.PencilBrush(this.canvas);
this.canvas.freeDrawingBrush.width = this.brushSize || 3;
this.canvas.freeDrawingBrush.color = this.currentColor || '#000000';
break;
case 'line':
this.canvas.isDrawingMode = false;
this.canvas.selection = false;
// 实现直线绘制逻辑
break;
// 其他工具的实现...
}
}
setColor(color) {
this.currentColor = color;
if (this.canvas.isDrawingMode) {
this.canvas.freeDrawingBrush.color = color;
}
// 如果选择了对象,则更改对象颜色
const activeObject = this.canvas.getActiveObject();
if (activeObject) {
activeObject.set('fill', color);
this.canvas.renderAll();
this.sendObjectUpdate(activeObject);
}
}
setBrushSize(size) {
this.brushSize = size;
if (this.canvas.isDrawingMode) {
this.canvas.freeDrawingBrush.width = size;
}
}
onMouseDown(options) {
if (!options.target && this.currentTool === 'line') {
this.isDrawing = true;
this.lastPoint = options.pointer;
}
}
onMouseMove(options) {
if (this.isDrawing && this.currentTool === 'line') {
// 绘制直线预览
}
}
onMouseUp(options) {
if (this.isDrawing && this.currentTool === 'line' && this.lastPoint) {
const line = new fabric.Line([
this.lastPoint.x, this.lastPoint.y,
options.pointer.x, options.pointer.y
], {
stroke: this.currentColor || '#000000',
strokeWidth: this.brushSize || 3
});
this.canvas.add(line);
this.isDrawing = false;
this.lastPoint = null;
}
}
onObjectAdded(options) {
// 保存到历史记录
this.saveToHistory();
// 发送到服务器
if (options.target) {
this.sendDrawingData({
type: 'object:added',
object: options.target.toJSON(),
userId: whiteboardConfig.currentUserId
});
}
}
onObjectModified(options) {
// 发送对象更新到服务器
if (options.target) {
this.sendObjectUpdate(options.target);
}
}
sendDrawingData(data) {
if (this.socket && this.socket.connected) {
this.socket.emit('drawing', {
whiteboardId: this.whiteboardId,
...data
});
}
}
sendObjectUpdate(object) {
this.sendDrawingData({
type: 'object:modified',
object: object.toJSON(),
userId: whiteboardConfig.currentUserId
});
}
handleRemoteDrawing(data) {
// 忽略自己发送的数据
if (data.userId === whiteboardConfig.currentUserId) return;
switch(data.type) {
case 'object:added':
fabric.util.enlivenObjects([data.object], (objects) => {
objects.forEach(obj => {
this.canvas.add(obj);
});
});
break;
case 'object:modified':
const object = this.canvas.getObjects().find(obj =>
obj.data && obj.data.id === data.object.data.id
);
if (object) {
object.set(data.object);
this.canvas.renderAll();
}
break;
}
}
sendChatMessage(message) {
if (!message.trim()) return;
if (this.socket && this.socket.connected) {
this.socket.emit('chat:message', {
whiteboardId: this.whiteboardId,
userId: whiteboardConfig.currentUserId,
message: message,
timestamp: new Date().toISOString()
});
}
}
addChatMessage(data) {
const chatMessages = this.container.querySelector('.chat-messages');
const messageElement = document.createElement('div');
messageElement.className = 'chat-message';
messageElement.innerHTML = `
<div class="message-header">
<span class="user-name">用户 ${data.userId}</span>
<span class="message-time">${new Date(data.timestamp).toLocaleTimeString()}</span>
</div>
<div class="message-content">${this.escapeHtml(data.message)}</div>
`;
chatMessages.appendChild(messageElement);
chatMessages.scrollTop = chatMessages.scrollHeight;
}
updateOnlineUsers(users) {
const userCountElement = this.container.querySelector('.user-count');
if (userCountElement) {
userCountElement.textContent = users.length;
}
}
saveToHistory() {
// 保存当前画布状态到历史记录
const state = JSON.stringify(this.canvas.toJSON());
this.history = this.history.slice(0, this.historyIndex + 1);
this.history.push(state);
this.historyIndex++;
}
loadWhiteboardData() {
// 通过AJAX加载白板数据
jQuery.ajax({
url: whiteboardConfig.ajaxUrl,
method: 'POST',
data: {
action: 'load_whiteboard',
whiteboard_id: this.whiteboardId,
nonce: whiteboardConfig.nonce
},
success: (response) => {
if (response.success && response.data) {
this.canvas.loadFromJSON(response.data, () => {
this.canvas.renderAll();
});
}
}
});
}
resizeCanvas() {
const container = this.container.querySelector('.whiteboard-canvas-container');
this.canvas.setDimensions({
width: container.offsetWidth,
height: 600
});
}
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
}
// 初始化白板
document.addEventListener('DOMContentLoaded', () => {
const whiteboardContainers = document.querySelectorAll('.whiteboard-container');
whiteboardContainers.forEach(container => {
new CollaborativeWhiteboard(container);
});
});
## 第四部分:后端WebSocket服务器与数据同步
### 4.1 设置WebSocket服务器
对于实时协作,我们需要一个WebSocket服务器来处理客户端连接和数据广播。这里我们使用Node.js和Socket.io:
// server/whiteboard-server.js
const http = require('http');
const socketIo = require('socket.io');
const mysql = require('mysql2/promise');
// 创建HTTP服务器
const server = http.createServer();
const io = socketIo(server, {
cors: {
origin: process.env.WORDPRESS_URL || "http://localhost",
methods: ["GET", "POST"]
}
});
// 数据库连接池
const dbPool = mysql.createPool({
host: process.env.DB_HOST || 'localhost',
user: process.env.DB_USER || 'wordpress_user',
password: process.env.DB_PASSWORD || 'password',
database: process.env.DB_NAME || 'wordpress_db',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
// 存储在线用户
const onlineUsers = new Map(); // whiteboardId -> Set of userIds
const userSockets = new Map(); // userId -> Set of socketIds
io.on('connection', (socket) => {
const { whiteboardId, userId } = socket.handshake.query;
console.log(`用户 ${userId} 连接到白板 ${whiteboardId}`);
// 验证用户权限
validateUserPermission(whiteboardId, userId).then(hasPermission => {
if (!hasPermission) {
socket.disconnect();
return;
}
// 加入白板房间
socket.join(`whiteboard:${whiteboardId}`);
// 更新在线用户列表
if (!onlineUsers.has(whiteboardId)) {
onlineUsers.set(whiteboardId, new Set());
}
onlineUsers.get(whiteboardId).add(userId);
// 存储用户socket映射
if (!userSockets.has(userId)) {
userSockets.set(userId, new Set());
}
userSockets.get(userId).add(socket.id);
// 广播用户加入事件
io.to(`whiteboard:${whiteboardId}`).emit('user:joined', {
whiteboardId,
userId,
users: Array.from(onlineUsers.get(whiteboardId))
});
// 处理绘图事件
socket.on('drawing', (data) => {
// 验证数据
if (data.whiteboardId !== whiteboardId) return;
// 广播给同一白板的其他用户
socket.to(`whiteboard:${whiteboardId}`).emit('drawing', {
...data,
timestamp: new Date().toISOString()
});
// 保存到数据库(可选,根据需求)
saveDrawingAction(whiteboardId, userId, data);
});
// 处理聊天消息
socket.on('chat:message', (data) => {
if (data.whiteboardId !== whiteboardId) return;
// 广播聊天消息
io.to(`whiteboard:${whiteboardId}`).emit('chat:message', {
...data,
timestamp: new Date().toISOString()
});
// 保存聊天记录到数据库
saveChatMessage(whiteboardId, userId, data.message);
});
// 处理断开连接
socket.on('disconnect', () => {
console.log(`用户 ${userId} 断开连接`);
// 清理用户socket映射
if (userSockets.has(userId)) {
userSockets.get(userId).delete(socket.id);
if (userSockets.get(userId).size === 0) {
userSockets.delete(userId);
// 从在线用户中移除
if (onlineUsers.has(whiteboardId)) {
onlineUsers.get(whiteboardId).delete(userId);
// 广播用户离开事件
io.to(`whiteboard:${whiteboardId}`).emit('user:left', {
whiteboardId,
userId,
users: Array.from(onlineUsers.get(whiteboardId))
});
}
}
}
});
}).catch(error => {
console.error('权限验证失败:', error);
socket.disconnect();
});
});
// 验证用户权限
async function validateUserPermission(whiteboardId, userId) {
try {
const [rows] = await dbPool.execute(
`SELECT w.is_public, p.permission_level
FROM wp_collab_whiteboards w
LEFT JOIN wp_collab_permissions p ON w.id = p.whiteboard_id AND p.user_id = ?
WHERE w.id = ?`,
[userId, whiteboardId]
);
if (rows.length === 0) return false;
const whiteboard = rows[0];
// 检查权限
if (whiteboard.is_public) return true;
if (whiteboard.permission_level) return true;
return false;
} catch (error) {
console.error('数据库查询错误:', error);
return false;
}
}
// 保存绘图动作到历史记录
async function saveDrawingAction(whiteboardId, userId, data) {
try {
await dbPool.execute(
`INSERT INTO wp_collab_history
(whiteboard_id, user_id, action, changes)
VALUES (?, ?, ?, ?)`,
[whiteboardId, userId, data.type, JSON.stringify(data)]
);
} catch (error) {
console.error('保存历史记录失败:', error);
}
}
// 保存聊天消息
async function saveChatMessage(whiteboardId, userId, message) {
try {
await dbPool.execute(
`INSERT INTO wp_collab_chat
(whiteboard_id, user_id, message)
VALUES (?, ?, ?)`,
[whiteboardId, userId, message]
);
} catch (error) {
console.error('保存聊天消息失败:', error);
}
}
// 启动服务器
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
console.log(`白板服务器运行在端口 ${PORT}`);
});
### 4.2 WordPress REST API集成
为了让WebSocket服务器与WordPress通信,我们需要创建REST API端点:
// includes/class-whiteboard-api.php
class Whiteboard_API {
public function register_routes() {
register_rest_route('collab-whiteboard/v1', '/whiteboard/(?P<id>d+)', [
[
'methods' => 'GET',
'callback' => [$this, 'get_whiteboard'],
'permission_callback' => [$this, 'check_whiteboard_permission']
],
[
'methods' => 'POST',
'callback' => [$this, 'update_whiteboard'],
'permission_callback' => [$this, 'check_edit_permission']
]
]);
register_rest_route('collab-whiteboard/v1', '/whiteboard/(?P<id>d+)/history', [
[
'methods' => 'GET',
'callback' => [$this, 'get_whiteboard_history'],
'permission_callback' => [$this, 'check_whiteboard_permission']
]
]);
register_rest_route('collab-whiteboard/v1', '/whiteboard/(?P<id>d+)/chat', [
[
'methods' => 'GET',
'callback' => [$this, 'get_chat_messages'],
'permission_callback' => [$this, 'check_whiteboard_permission']
],
[
'methods' => 'POST',
'callback' => [$this, 'post_chat_message'],
'permission_callback' => [$this, 'check_comment_permission']
]
]);
}
public function get_whiteboard($request) {
$whiteboard_id = $request['id'];
global $wpdb;
$table_name = $wpdb->prefix . 'collab_whiteboards';
$whiteboard = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM $table_name WHERE id = %d",
$whiteboard_id
));
if (!$whiteboard) {
return new WP_Error('not_found', '白板不存在', ['status' => 404]);
}
return rest_ensure_response([
'id' => $whiteboard->id,
'title' => $whiteboard->title,
'description' => $whiteboard->description,
'content' => json_decode($whiteboard->content, true),
'settings' => json_decode($whiteboard->settings, true),
'created_by' => $whiteboard->created_by,
'created_at' => $whiteboard->created_at,
'updated_at' => $whiteboard->updated_at,
'is_public' => (bool)$whiteboard->is_public
]);
}
public function update_whiteboard($request) {
$whiteboard_id = $request['id'];
$content = $request->get_param('content');
if (empty($content)) {
return new WP_Error('invalid_data', '内容不能为空', ['status' => 400]);
}
global $wpdb;
$table_name = $wpdb->prefix . 'collab_whiteboards';
$result = $wpdb->update(
$table_name,
[
'content' => json_encode($content),
'updated_at' => current_time('mysql')
],
['id' => $whiteboard_id]
);
if ($result === false) {
return new WP_Error('update_failed', '更新失败', ['status' => 500]);
}
return rest_ensure_response([
'success' => true,
'message' => '白板已更新'
]);
}
public function check_whiteboard_permission($request) {
$whiteboard_id = $request['id'];
$user_id = get_current_user_id();
return Whiteboard_Permissions::check_permission(
$whiteboard_id,
$user_id,
Whiteboard_Permissions::PERMISSION_VIEW
