Tiptap Hocuspocus协作编辑:页面刷新内容丢失及数据格式疑问
问题:Hocuspocus Database扩展刷新后无法加载已编辑内容及数据格式疑问
问题描述
- 单个用户编辑文档时,页面刷新后(并非每次出现),Hocuspocus的Database扩展无法加载已编辑内容。按预期该扩展应在页面刷新时恢复之前的编辑内容,若理解有误请指正。
- 额外疑问:是否需要手动将数据转换为Uint8Array,还是Database扩展默认会处理该转换?
原代码
import * as dotenv from "dotenv"; dotenv.config(); import { Server } from "@hocuspocus/server"; import { Database } from "@hocuspocus/extension-database"; import mysql from "mysql2"; /* 是否需要手动转换为Uint8Array,还是DB扩展默认会处理? */ //import { toUint8Array, fromUint8Array } from "js-base64"; import { Throttle } from "@hocuspocus/extension-throttle"; const pool = mysql.createPool({ connectionLimit: 100, host: process.env.DB_HOST, user: process.env.DB_USERNAME, password: process.env.DB_PASSWORD, database: process.env.DB_DATABASE, port: process.env.DB_PORT, debug: false, }); const server = Server.configure({ port: process.env.HOCUSPOCUS_PORT, extensions: [ new Throttle({ throttle: 200, banTime: 1, }), new Database({ fetch: async ({ documentName }) => { return new Promise((resolve, reject) => { console.log("Trying to fetch"); pool?.query( "SELECT data FROM ydocuments WHERE name = ? ORDER BY id DESC", [documentName], (error, row) => { if (error) { reject(error); } console.log("data: " + JSON.stringify(row)); if (row && row.data) { resolve(row.data); } else { resolve(null); } }, ); }); }, store: async ({ documentName, state }) => { pool?.query( "INSERT INTO ydocuments (name, data) VALUES (?, ?) ON DUPLICATE KEY UPDATE data = ?", [documentName, state, state], (error, result) => { if (error) { throw error; } console.log(`inserted/updated ${result.affectedRows}`); }, ); }, }), ], }); server.listen();
解决方案
一、刷新加载失败的核心问题及修复
查询结果处理错误
mysql2的query方法返回的结果是数组(即使只有一条匹配记录),原代码中row.data永远是undefined,导致每次都返回null。需要取数组的第一个元素:rows[0]?.data。异步存储未正确等待
原store方法中使用回调式的pool.query,但没有返回Promise,Hocuspocus可能在数据未写入完成时就结束流程,导致存储失败。改用mysql2的Promise版可以解决这个问题。表结构约束缺失
ON DUPLICATE KEY UPDATE需要name字段设置唯一约束,否则每次都会插入新记录,而不是更新现有记录。确保ydocuments表的name字段添加UNIQUE索引。
二、Uint8Array转换问题
不需要手动转换,Database扩展和mysql2会自动处理:
- 如果数据库
data字段类型是BLOB/LONGBLOB,mysql2会自动将Uint8Array转为Buffer存储,读取时返回Buffer(Buffer是Uint8Array的子类,Hocuspocus可以直接识别)。 - 如果用
TEXT类型存储,才需要手动将Uint8Array转成base64字符串存储,读取时再转回Uint8Array,但推荐用BLOB类型存储二进制数据,效率更高。
修复后的代码
import * as dotenv from "dotenv"; dotenv.config(); import { Server } from "@hocuspocus/server"; import { Database } from "@hocuspocus/extension-database"; import mysql from "mysql2/promise"; // 改用Promise版mysql2 import { Throttle } from "@hocuspocus/extension-throttle"; const pool = mysql.createPool({ connectionLimit: 100, host: process.env.DB_HOST, user: process.env.DB_USERNAME, password: process.env.DB_PASSWORD, database: process.env.DB_DATABASE, port: process.env.DB_PORT, debug: false, }); const server = Server.configure({ port: process.env.HOCUSPOCUS_PORT, extensions: [ new Throttle({ throttle: 200, banTime: 1, }), new Database({ fetch: async ({ documentName }) => { console.log("Trying to fetch"); // 用await处理异步查询,结果rows是数组 const [rows] = await pool.query( "SELECT data FROM ydocuments WHERE name = ? ORDER BY id DESC LIMIT 1", [documentName] ); console.log("data: " + JSON.stringify(rows)); // 返回第一条记录的data,没有则返回null return rows[0]?.data || null; }, store: async ({ documentName, state }) => { console.log("Storing document:", documentName); // 用await确保存储完成 const [result] = await pool.query( "INSERT INTO ydocuments (name, data) VALUES (?, ?) ON DUPLICATE KEY UPDATE data = ?", [documentName, state, state] ); console.log(`inserted/updated ${result.affectedRows}`); }, }), ], }); server.listen();
内容的提问来源于stack exchange,提问作者Karthik
相关产品推荐
相关产品推荐

