使用 Socket.IO 和 MongoDB 构建实时协作记事本
发布: (2026年1月3日 GMT+8 01:50)
4 min read
原文: Dev.to
Source: Dev.to
(抱歉,您只提供了来源链接,没有提供需要翻译的正文内容。请粘贴您想要翻译的文本,我会为您翻译成简体中文,并保留原始的 Markdown 格式、代码块和 URL。)
介绍
是否曾经需要快速与他人共享笔记,却被注册表单和复杂的权限设置弄得烦恼?Collaborative Notepad 是一个免费、开源的实时记事本,您可以创建笔记、分享链接,立即开始协作——无需账户。
功能
- 无需注册
- 通过 Socket.IO 实时协作
- 自定义 URL(例如
/meeting-notes) - 暗/亮模式切换
- 每 3 秒自动保存
- 实时用户计数显示
实时演示
仓库
GitHub – collaborative‑notepad – MIT 许可证
Backend Stack
- Node.js + Express
- Socket.IO 用于实时通信
- MongoDB 用于持久化
- EJS 作为模板引擎
- 安全性:Helmet、速率限制、CSP
服务器设置 (Express + Socket.IO)
// server.js
const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const mongoose = require('mongoose');
const app = express();
const server = http.createServer(app);
const io = socketIo(server);
// MongoDB connection
mongoose.connect(process.env.MONGODB_URI);
// Socket.IO connection handling
io.on('connection', (socket) => {
console.log('User connected');
socket.on('join-note', async (noteId) => {
socket.join(noteId);
// Broadcast user count
io.to(noteId).emit('user-count',
io.sockets.adapter.rooms.get(noteId)?.size || 0
);
});
socket.on('note-update', async ({ noteId, content }) => {
// Save to database
await Note.findOneAndUpdate(
{ noteId },
{ content, lastModified: Date.now() }
);
// Broadcast to all users in room
socket.to(noteId).emit('note-change', content);
});
});
MongoDB 模式
// models/Note.js
const noteSchema = new mongoose.Schema({
noteId: { type: String, unique: true, required: true },
content: { type: String, default: '' },
createdAt: { type: Date, default: Date.now },
lastModified: { type: Date, default: Date.now },
viewCount: { type: Number, default: 0 }
});
const Note = mongoose.model('Note', noteSchema);
客户端实时同步
// public/js/app.js
const socket = io();
const noteId = window.location.pathname.substring(1) || 'home';
const textarea = document.getElementById('note');
const userCountElement = document.getElementById('user-count');
// Join note room
socket.emit('join-note', noteId);
// Send updates to server (debounced)
let timeout;
textarea.addEventListener('input', () => {
clearTimeout(timeout);
timeout = setTimeout(() => {
socket.emit('note-update', {
noteId,
content: textarea.value
});
}, 300);
});
// Receive updates from other users
socket.on('note-change', (content) => {
if (document.activeElement !== textarea) {
textarea.value = content;
}
});
// Update user count
socket.on('user-count', (count) => {
userCountElement.textContent = count;
});
限流
// middleware/rateLimit.js
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per window
});
app.use('/api/', limiter);
内容安全策略
// security/csp.js
const helmet = require('helmet');
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'"]
}
}));
XSS 防护
const xss = require('xss');
// Example sanitization
const sanitizedContent = xss(userInput);
Express 路由自定义 URL
app.get('/:noteId', async (req, res) => {
const noteId = req.params.noteId;
const note = await Note.findOne({ noteId }) ||
await Note.create({ noteId });
res.render('index', { note });
});
自动保存逻辑
let saveTimeout;
const AUTO_SAVE_DELAY = 3000; // 3 seconds
textarea.addEventListener('input', () => {
clearTimeout(saveTimeout);
saveTimeout = setTimeout(saveToDatabase, AUTO_SAVE_DELAY);
});
主题(CSS 自定义属性)
:root {
--bg-color: #fefae0;
--text-color: #1e3a1f;
}
[data-theme="dark"] {
--bg-color: #1e1e1e;
--text-color: #e0e0e0;
}
性能与可靠性优化
- Debouncing socket 事件,以降低服务器负载
- 用于快速笔记查找的 MongoDB indexing
- 用于高效数据库使用的 Connection pooling
- 用于更快资源交付的 Gzip compression 中间件
- 用于将每个笔记的协作隔离的 Socket.IO rooms
未来改进(路线图)
- Markdown 支持
- 代码块语法高亮
- 密码保护的笔记
- 导出为 PDF/TXT
- 协作光标位置
- 语音笔记
- 移动应用
技术栈概览
- Node.js
- Express
- Socket.IO
- MongoDB
- EJS
开发时间
≈ 20 小时
许可证
MIT(开源)
如果您觉得这有帮助,请点个 ❤️ 并给 GitHub 仓库加星!