banner
约 1,500 字
5 分钟

博客优化总结:从 iframe 被转义到完美支持网易云音乐播放器

2026年5月15日

摘要

在博客中插入网易云音乐播放器时,文章显示转义文本。经排查,发现数据库存储的是纯文本节点而非HTML。最终采用前端动态替换方案:利用TreeWalker遍历文本节点,识别包含data-netease-id的节点并解析ID,动态创建iframe替换原文本。此方案无需修改数据库和后端,支持歌曲与播客,且移动端适配良好。

一、问题背景

使用基于 Cloudflare Workers + D1 数据库 + React (TanStack Router) + TipTap 编辑器的博客系统(flare-stack-blog,Fuwari 主题),希望在 Markdown 文章中插入网易云音乐的外链播放器(iframe 插件)。标准的 iframe 代码在文章中编写后,前端不显示播放器,而是显示被转义后的文本(如 <iframe...)。

二、探索与排查路径

2.1 初步检查:CSP 与前端渲染

· 假设:可能是 Content Security Policy 限制了 iframe 加载。

· 验证:检查浏览器 Network 面板响应头,未发现 CSP 限制;直接写测试 HTML 能正常显示播放器,排除 CSP 问题。

2.2 定位转义发生位置

· 发现前端页面中 iframe 代码被转义为 HTML 实体(<iframe...),这意味着转义发生在数据到达浏览器之前。

· 博客文章数据存储流程:Markdown 源码 → 后端/构建时转换为 TipTap JSON → 存入 D1 数据库 → 前端 ContentRenderer 组件渲染 JSON。

· 确认转义发生在“Markdown → JSON”的转换阶段,或数据库存储时对 HTML 进行了编码。

2.3 尝试修改 Markdown 解析器(不成功)

· 找到 markdown-parser.ts,其中使用 marked 将 Markdown 转 HTML,再用 TipTap 的 DOMParser 将 HTML 转为 JSONContent。

· 尝试为 TipTap schema 添加 IframeExtension 自定义节点,使 iframe 能被解析为合法节点。

· 失败原因:该解析器仅用于后台导入/导出功能,并非前台文章发布时使用的转换逻辑。前台文章发布走的是另一条路径(可能是 Admin 面板直接保存 TipTap JSON,未经过此解析器)。

2.4 检查路由与布局组件

· 找到文章展示的 PostPage 组件(src/features/theme/themes/fuwari/pages/post/page.tsx),其中使用 <ContentRenderer content={post.contentJson} /> 渲染。

· 尝试在 PostPage 中用 useEffect 动态创建 iframe,但页面中只能看到转义后的纯文本(&lt;div data-netease-id...),querySelector 无法捕获任何 DOM 元素。

· 核心发现:数据库中 content_json 字段存储的不是 HTML 标签,而是包含转义字符串的纯文本节点。例如:

JSON
{"type":"text","text":"<div data-netease-id=\"2757727116\"></div>"}

渲染后直接在页面上显示为字符串 "<div data-netease-id=...>",而不是一个可被 JS 操作的 HTML 元素。

2.5 最终解决方案:前端文本节点替换

· 不再尝试修复数据库或转换逻辑,而是在浏览器端直接操作 DOM 文本节点。

· 使用 TreeWalker 遍历 .fuwari-custom-md 容器内的所有文本节点,找到包含 data-netease-id 的文本,解析出歌曲 ID,动态创建 <iframe> 并替换该文本节点。

· 同时处理实体编码版本(&lt;div data-netease-id=&quot;...&quot;&gt;)和原始尖括号版本。

2.6 适配与扩展

· 移动端适配:将 iframe 宽度设为 100%,最大宽度 409px,保证小屏幕自适应。

· 支持播客(type=3):增加 data-netease-type 属性解析,在 iframe 的 src 中动态设置 type 参数。

三、关键发现与反思

3.1 操作过程回顾

问题阶段

错误假设

正确结论

初期

CSP 阻止

iframe 无 CSP 限制,是内容转义问题

中期

Markdown 解析器是罪魁祸首

该解析器未被实际使用,前台文章通过另一路径存储 JSON

后期

修改布局组件插入 JS 能解决问题

数据库中已存为纯文本,无法被 DOM 选择器捕获

最终

必须修改数据库或转换逻辑

前端文本节点替换绕过了所有存储缺陷

3.2 教训

· 不要轻易假设代码的执行路径,应通过搜索调用关系确认功能归属。

· 当数据在传输/存储过程中已变形,前端动态替换是快速有效的修复手段。

· 对于已上线的博客,最小侵入性修复(不改数据库、不改后端)是首选。

四、最终方案的技术实现

TSX
// 在 PostPage 组件中

useEffect(() => {

  const container = document.querySelector('.fuwari-custom-md');

  if (!container) return;



  const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, null);

  const toReplace = [];

  while (walker.nextNode()) {

    if (walker.currentNode.textContent?.includes('data-netease-id')) {

      toReplace.push(walker.currentNode);

    }

  }



  for (const textNode of toReplace) {

    let content = textNode.textContent || '';

    const idMatch = content.match(/data-netease-id=["'](\d+)["']/) ||

                    content.match(/data-netease-id=&(?:quot|#34);(\d+)&(?:quot|#34);/);

    const typeMatch = content.match(/data-netease-type=["'](\d+)["']/) ||

                      content.match(/data-netease-type=&(?:quot|#34);(\d+)&(?:quot|#34);/);

    if (idMatch) {

      const id = idMatch[1];

      const type = typeMatch ? typeMatch[1] : '2';

      const iframe = document.createElement('iframe');

      iframe.src = `//music.163.com/outchain/player?type=${type}&id=${id}&auto=0&height=66`;

      iframe.width = '100%';

      iframe.height = '86';

      iframe.frameBorder = '0';

      iframe.style.maxWidth = '409px';

      iframe.style.display = 'block';

      textNode.parentNode?.replaceChild(iframe, textNode);

    }

  }

}, [post.id]);

文章中只需写入:

HTML
<div data-netease-id="songid"></div>          <!-- 歌曲,默认 type=2 -->

<div data-netease-id="podcastid" data-netease-type="3"></div> <!-- 播客 -->

五、优化成果

· 无需修改数据库,不影响已有数据,零风险。

· 无需修改后端转换逻辑,避免侵入性改动。

· 支持歌曲和播客,并通过属性扩展支持其他类型(如歌单 type=1)。

· 移动端完美适配,播放器自动缩放。

· 编写文章时只需简单的 HTML 占位符,比原 iframe 更简洁且不会被转义。

· 解决了根本问题:无论将来数据库如何存储,前端都能可靠转换。

六、可能的进一步优化方案

如果后续希望彻底根治,可以:

1. 修改 Admin 后台的 Markdown→JSON 转换逻辑,增加 IframeExtension 并正确存储为自定义节点。

2. 写数据迁移脚本,将现有文本节点中的 data-netease-id 占位符转换为真正的 neteasePlayer 节点。

3. 最终移除前端的文本节点替换逻辑,使数据流更干净。

但目前的前端方案已经足够稳定,且维护成本极低。

七、总结

本次优化是一次典型的前端“绕行”修复案例,展示了在无法快速修改后端存储结构的情况下,如何利用浏览器 DOM API 实现功能,同时保留了未来重构的可能性。最终博客体验得到提升,也积累了宝贵的调试经验。

END

相关文章

暂无相关文章

© 2026 破惨案/Vagabond. All Rights Reserved. / RSS / Sitemap
Powered by Tanstack Start & Flare Stack Blog