You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在NestJS后端从Yjs消息中保存Lexical编辑器内容?

从Yjs提取Lexical内容并在NestJS后端保存的实现方案

针对你已实现客户端实时协作、需要将内容持久化到数据库的场景,提供两种可行方案:


方案1:客户端提取Lexical内容后发送到后端

利用Lexical自身API序列化编辑器内容,直接发送至NestJS接口,适合手动触发保存(如用户点击保存按钮)或定时自动保存。

客户端代码示例(React环境)

import { $getRoot } from 'lexical';
import { LexicalEditor } from 'lexical';

// 提取编辑器内容并发送到后端
const saveToBackend = async (editor: LexicalEditor, docId: string) => {
  let serializedContent = null;
  // Lexical状态操作必须在update回调内执行
  editor.update(() => {
    const rootNode = $getRoot();
    // 序列化为Lexical标准JSON格式
    serializedContent = rootNode.toJSON();
  });

  // 调用NestJS接口
  await fetch(`/api/documents/${docId}/save`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ content: serializedContent }),
  });
};

NestJS后端接口示例

import { Controller, Post, Param, Body } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Document } from './document.entity'; // 假设你有Document实体

@Controller('api/documents')
export class DocumentController {
  constructor(
    @InjectRepository(Document)
    private readonly docRepo: Repository<Document>,
  ) {}

  @Post(':id/save')
  async saveContent(
    @Param('id') docId: string,
    @Body() body: { content: Record<string, any> },
  ) {
    await this.docRepo.save({
      id: docId,
      content: body.content,
      updatedAt: new Date(),
    });
    return { success: true };
  }
}

方案2:服务端从Yjs文档直接提取内容(实时自动备份)

利用服务端维护的Yjs文档实例,直接提取Lexical内容并保存,适合实时自动备份场景,无需客户端额外触发。

步骤1:NestJS中维护Yjs文档实例

import { Injectable } from '@nestjs/common';
import { Doc } from 'yjs';

@Injectable()
export class YjsDocService {
  private docMap = new Map<string, Doc>();
  private saveTimeout: NodeJS.Timeout | null = null;

  constructor(private readonly docSaveService: DocumentSaveService) {}

  getDocument(docId: string): Doc {
    if (!this.docMap.has(docId)) {
      const newDoc = new Doc();
      // 监听Yjs文档变化,防抖后自动保存
      newDoc.on('update', () => {
        if (this.saveTimeout) clearTimeout(this.saveTimeout);
        this.saveTimeout = setTimeout(() => {
          this.docSaveService.saveFromYjs(docId);
        }, 1000); // 1秒防抖,避免频繁写入
      });
      this.docMap.set(docId, newDoc);
    }
    return this.docMap.get(docId)!;
  }
}

步骤2:从Yjs文档提取Lexical内容

import { Injectable } from '@nestjs/common';
import { YjsDocService } from './yjs-doc.service';
import { yjsToLexical } from '@lexical/yjs';
import { $getRoot, createEditor } from 'lexical';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Document } from './document.entity';

@Injectable()
export class DocumentSaveService {
  constructor(
    private readonly yjsDocService: YjsDocService,
    @InjectRepository(Document)
    private readonly docRepo: Repository<Document>,
  ) {}

  async saveFromYjs(docId: string) {
    const yjsDoc = this.yjsDocService.getDocument(docId);
    // 获取Yjs中绑定的Lexical内容节点
    const yLexicalFragment = yjsDoc.get('lexical', Y.XmlFragment);

    // 创建临时Lexical编辑器实例用于转换
    const tempEditor = createEditor({
      nodes: [], // 需与客户端编辑器的nodes配置完全一致
    });

    let serializedContent = null;
    await tempEditor.update(() => {
      // 将Yjs节点转换为Lexical节点
      yjsToLexical(tempEditor, yLexicalFragment);
      const rootNode = $getRoot();
      serializedContent = rootNode.toJSON();
    });

    // 保存到数据库
    await this.docRepo.save({
      id: docId,
      content: serializedContent,
      updatedAt: new Date(),
    });

    // 销毁临时编辑器
    tempEditor.destroy();
  }
}

关键注意事项

  • 节点一致性:客户端和服务端的Lexical节点配置必须完全相同,否则会出现序列化/反序列化错误。
  • 格式选择:除了保存Lexical JSON,还可以用$generateHtmlFromNodes将内容转换为HTML后保存,适合需要直接展示的场景。
  • 性能优化:服务端自动保存必须加防抖,避免Yjs频繁更新导致数据库压力过大。
  • 数据一致性:如果需要支持离线编辑,需确保Yjs的更新日志与后端保存的版本同步,可通过Yjs的encodeStateAsUpdate和applyUpdate实现版本控制。

内容的提问来源于stack exchange,提问作者Firas Latrach

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 19:46:19