基于Markdown的电子书方案:MDEpub

一个基于 Markdown 的电子书方案,类似 EPUB 标准并原生支持 Mermaid 和 Markmap 等可视化和交互功能,解决 Markdown 的轻量特性与电子书格式的结构化需求之间的平衡,同时确保对 Mermaid(流程图、时序图等)和 Markmap(思维导图)等工具的原生支持。以下是一个详细的设计方案,涵盖架构、实现方法、可视化支持和交互功能。

1. 设计目标

  • 基于 Markdown:以 Markdown 作为主要内容创作格式,保持简洁和易用。
  • 类似 EPUB 的结构:支持电子书所需的元数据、目录、样式和多文件组织,类似 EPUB 的 OPF 和 NCX 文件。
  • 原生支持 Mermaid 和 Markmap:直接解析和渲染 Mermaid 和 Markmap 代码块为可视化内容(例如 SVG 或交互式 HTML)。
  • 交互性:支持在电子书中嵌入交互式图表(如可缩放的流程图或动态思维导图)。
  • 跨平台兼容:生成的电子书可在多种阅读器上运行,同时支持 Web 浏览器查看。
  • 可扩展性:允许扩展其他可视化工具(如 PlantUML、D3.js 等)。

2. 方案架构

基于 Markdown 的电子书格式(暂称为 MDEpub,Markdown-based ePub)可以分为以下核心组件:

2.1 文件结构

类似 EPUB,MDEpub 使用文件夹结构组织内容:

mdepub-book/
├── meta.yaml           # 元数据(标题、作者、语言等)
├── toc.md              # 目录(类似 EPUB 的 NCX)
├── content/            # 内容文件夹
│   ├── chapter1.md     # 章节 1(Markdown 文件)
│   ├── chapter2.md     # 章节 2
│   └── assets/         # 静态资源(图片、CSS、JS 等)
├── styles.css          # 全局样式
└── config.json         # 构建配置(例如 Mermaid/Markmap 渲染设置)
  • meta.yaml:定义电子书元数据,类似 EPUB 的 content.opf。yamltitle: 示例电子书 author: 作者姓名 language: zh-CN publisher: 出版社 date: 2025-05-28
  • toc.md:定义目录结构,支持嵌套章节。markdown# 目录 - [第一章](content/chapter1.md) - [第二章](content/chapter2.md) - [子章节](content/chapter2.md#section1)
  • content/:存放 Markdown 章节,包含 Mermaid 和 Markmap 代码块。
  • assets/:存储图片、字体、CSS、JS(如 Mermaid 和 Markmap 的运行时库)。
  • styles.css:自定义样式,控制电子书外观。
  • config.json:配置可视化工具的参数(例如 Mermaid 主题、Markmap 样式)。

2.2 Markdown 扩展

为支持 Mermaid 和 Markmap,扩展 Markdown 语法,允许在代码块中指定渲染参数。例如:

markdown

# 第一章

## 流程图
```mermaid {theme: "dark", format: "svg"}
graph TD
    A[开始] --> B{判断}
    B -->|是| C[执行]
    B -->|否| D[结束]

思维导图

markmap {height: “300px”, zoom: true}

# 思维导图
## 分支1
### 子节点1
## 分支2
- 使用 `{}` 指定渲染参数(如主题、高度、交互性)。
- 支持标准 Markdown 语法,同时兼容 Mermaid 和 Markmap 的特定代码块。

#### **2.3 输出格式**
MDEpub 的最终输出是一个压缩包(类似 EPUB 的 ZIP 格式),包含:
- **HTML 文件**:每个 Markdown 章节转换为独立的 HTML 文件,嵌入 Mermaid 和 Markmap 的渲染结果(SVG 或交互式 JS)。
- **元数据文件**:基于 `meta.yaml` 生成的 XML 或 JSON。
- **目录文件**:基于 `toc.md` 生成的导航文件(类似 NCX 或 HTML TOC)。
- **资源文件**:CSS、JS、图片等,包含 Mermaid 和 Markmap 的运行时库。
- **容器文件**:定义文件结构的元文件,类似 EPUB 的 `mimetype` 和 `META-INF/container.xml`。

---

### 3. **实现方法**
以下是实现 MDEpub 的核心步骤和技术选型:

#### **3.1 解析 Markdown**
- **工具**:使用 [marked](https://marked.js.org/) 或 [markdown-it](https://github.com/markdown-it/markdown-it) 解析 Markdown 文件,转换为 HTML。
- **扩展插件**:
  - **Mermaid**:集成 `@mermaid-js/mermaid` 解析代码块,渲染为 SVG 或交互式 HTML。
  - **Markmap**:集成 `markmap-lib` 和 `markmap-view`,将 Markmap 代码块渲染为 SVG 或交互式思维导图。
- **自定义代码块**:扩展 Markdown 解析器,识别 `mermaid` 和 `markmap` 代码块,并调用对应渲染器。

#### **3.2 渲染可视化内容**
- **Mermaid**:
  - 使用 Mermaid CLI 或 `@mermaid-js/mermaid` 将代码块渲染为 SVG(静态)或嵌入 JS 代码(交互式)。
  - 示例(Node.js 环境):
    ```javascript
    import mermaid from 'mermaid';
    mermaid.initialize({ theme: 'dark' });
    const svg = await mermaid.render('graph', 'graph TD A-->B');
    ```
  - 将 SVG 嵌入 HTML,或包含 Mermaid JS 库以支持交互(例如点击放大)。
- **Markmap**:
  - 使用 `markmap-lib` 解析 Markmap 代码,生成 SVG 或 HTML+JS。
  - 示例:
    ```javascript
    import { Markmap } from 'markmap-view';
    const mm = Markmap.create(svgElement, { height: 300, zoom: true });
    mm.setData(markmapData);
    ```
  - 支持交互功能(如缩放、展开/折叠节点)。
- **嵌入方式**:
  - **静态**:将 SVG 直接嵌入 HTML,适合离线阅读器。
  - **动态**:嵌入 Mermaid/Markmap 的 JS 运行时,生成交互式图表,适合 Web 浏览器或支持 JS 的阅读器。

#### **3.3 生成电子书**
- **构建工具**:开发一个 CLI 工具(例如 `mdepub-cli`),基于 Node.js,执行以下步骤:
  1. 读取 `meta.yaml` 和 `toc.md`,生成元数据和导航。
  2. 解析 `content/` 下的 Markdown 文件,转换为 HTML。
  3. 处理 Mermaid 和 Markmap 代码块,渲染为 SVG 或交互式 HTML。
  4. 合并 CSS 和 JS 资源,生成最终文件结构。
  5. 打包为 ZIP 文件,添加 `mimetype`(例如 `application/mdepub+zip`)。
- **示例命令**:
  ```bash
  mdepub build mdepub-book/ -o book.mdepub

3.4 阅读器支持

  • 静态模式:生成 SVG 的 MDEpub 文件可被大多数 EPUB 阅读器(如 Calibre、iBooks)支持。
  • 交互模式:包含 JS 的 MDEpub 需要支持 HTML5 和 JavaScript 的阅读器(如 Web 浏览器、Readium、或自定义阅读器)。
  • 自定义阅读器:开发一个基于 Web 的阅读器(使用 Electron 或 PWA),加载 MDEpub 文件,运行 Mermaid 和 Markmap 的 JS 代码,实现交互。

4. 支持 Mermaid 和 Markmap 的关键技术

4.1 Mermaid

  • 渲染:将 Mermaid 代码渲染为 SVG(静态)或 HTML+JS(交互)。
  • 交互性:
    • 支持点击节点高亮、缩放等功能。
    • 需要在 MDEpub 中嵌入 mermaid.min.js(约 1.5MB,压缩后更小)。
  • 样式:通过 config.json 或代码块参数自定义主题(如 dark、forest)。
  • 兼容性:SVG 输出兼容大多数阅读器;交互模式需 JS 支持。

4.2 Markmap

  • 渲染:将 Markmap 代码渲染为 SVG 或交互式 HTML(使用 D3.js 和 markmap-view)。
  • 交互性:
    • 支持节点展开/折叠、缩放、拖拽。
    • 需要嵌入 markmap-view 和 d3 的 JS 库(约 200KB 压缩后)。
  • 样式:支持自定义颜色、字体、节点样式。
  • 兼容性:类似 Mermaid,SVG 适合静态阅读,交互模式需 JS 支持。

4.3 其他可视化工具

  • 扩展性:通过插件机制支持其他工具,如 PlantUML、Vega-Lite 等。
  • 实现:为每种工具开发独立的 Markdown 代码块解析器和渲染器,输出 SVG 或 JS。

5. 优势与挑战

优势

  • 简洁性:Markdown 作为输入格式,易于编写和维护。
  • 可视化支持:原生支持 Mermaid 和 Markmap,无需外部转换。
  • 交互性:通过 JS 提供动态图表,增强阅读体验。
  • 灵活性:可扩展支持其他可视化工具。
  • 跨平台:兼容现有 EPUB 阅读器(静态模式)和 Web 浏览器(交互模式)。

挑战

  • 阅读器兼容性:许多 EPUB 阅读器不支持 JavaScript,交互模式可能受限。
  • 文件大小:嵌入 Mermaid 和 Markmap 的 JS 库会增加文件体积。
  • 性能:复杂图表可能导致渲染时间增加,尤其在低端设备上。
  • 标准推广:MDEpub 作为新格式,需要开发专用阅读器或推广兼容性。

6. 实现工具与工作流

6.1 开发工具

  • Node.js:核心开发环境,用于解析 Markdown 和渲染图表。
  • markdown-it:Markdown 解析器,支持自定义插件。
  • Mermaid:用于流程图等渲染。
  • Markmap:用于思维导图渲染。
  • archiver:Node.js 库,用于打包 MDEpub 文件为 ZIP。
  • esbuild:压缩和打包 JS/CSS 资源。

6.2 工作流

  1. 编写:在 mdepub-book/ 中编写 Markdown 文件,包含 Mermaid 和 Markmap 代码块。
  2. 配置:设置 meta.yaml、toc.md 和 config.json。
  3. 构建:运行 mdepub build 命令,生成 MDEpub 文件。
  4. 预览:使用 Web 浏览器或自定义阅读器预览。
  5. 发布:分发 .mdepub 文件,或转换为标准 EPUB(静态模式)。

6.3 示例命令

bash

# 初始化项目
mdepub init my-book
# 构建 MDEpub
mdepub build my-book/ -o my-book.mdepub
# 预览(启动本地 Web 服务器)
mdepub preview my-book/
# 转换为标准 EPUB(静态)
mdepub export my-book/ -o my-book.epub --static

7. 与 EPUB 的对比

特性EPUBMDEpub
输入格式XHTML, XML, CSSMarkdown
Mermaid 支持需外部转换原生支持(SVG/JS)
Markmap 支持需外部转换原生支持(SVG/JS)
交互性有限(需特定阅读器)强(支持 JS 的交互式图表)
兼容性广泛(标准阅读器)需专用阅读器或浏览器
文件大小较小(静态内容)可能较大(含 JS 库)
开发复杂性高(XML 结构复杂)低(Markdown 简单)

8. 未来扩展

  • 插件系统:支持更多可视化工具(如 PlantUML、Chart.js)。
  • 云服务:提供在线编辑器和渲染服务,类似 GitBook。
  • 阅读器开发:开发跨平台的 MDEpub 阅读器(基于 Electron 或 Web)。
  • 标准推广:提交 MDEpub 作为开源标准,邀请社区贡献。

9. 快速原型实现

以下是一个简单的 Node.js 脚本,展示如何将包含 Mermaid 和 Markmap 的 Markdown 转换为 MDEpub 核心组件:

javascript

const fs = require('fs');
const markdownIt = require('markdown-it')();
const mermaid = require('mermaid');
const { Markmap } = require('markmap-view');
const archiver = require('archiver');

// 初始化 Mermaid
mermaid.initialize({ startOnLoad: false });

// 自定义 Markdown 插件
markdownIt.use((md) => {
  md.renderer.rules.fence_custom = {
    mermaid: async (tokens, idx) => {
      const code = tokens[idx].content;
      const { svg } = await mermaid.render('graph', code);
      return `<div class="mermaid">${svg}</div>`;
    },
    markmap: (tokens, idx) => {
      const code = tokens[idx].content;
      return `<div class="markmap" data-markmap="${encodeURIComponent(code)}"></div>`;
    }
  };
});

// 解析 Markdown 文件
const mdContent = fs.readFileSync('content/chapter1.md', 'utf-8');
const htmlContent = markdownIt.render(mdContent);

// 生成 HTML 文件
fs.writeFileSync('output/chapter1.html', `
<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="styles.css">
  <script src="mermaid.min.js"></script>
  <script src="d3.min.js"></script>
  <script src="markmap-view.min.js"></script>
</head>
<body>
  ${htmlContent}
  <script>
    // 初始化 Markmap
    document.querySelectorAll('.markmap').forEach(el => {
      const data = decodeURIComponent(el.dataset.markmap);
      const mm = Markmap.create(el, { height: 300 });
      mm.setData(data);
    });
  </script>
</body>
</html>
`);

// 打包为 MDEpub
const output = fs.createWriteStream('book.mdepub');
const archive = archiver('zip');
archive.pipe(output);
archive.directory('output/', false);
archive.finalize();

10. 总结

通过设计一个基于 Markdown 的电子书格式(MDEpub),可以实现类似 EPUB 的功能,同时原生支持 Mermaid 和 Markmap 的可视化和交互功能。核心是扩展 Markdown 解析器,集成 Mermaid 和 Markmap 渲染器,生成包含 SVG 或交互式 HTML 的电子书文件。MDEpub 结合 Markdown 的简洁性和可视化工具的强大功能,适合技术文档、教程和交互式电子书。下一步是开发 CLI 工具和专用阅读器,优化兼容性和性能。

mysql服务器优化过程小记

有一个查询很慢,27s,查询了一下mysql的状态:

mysql> SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool%';

+-------------------------------------------+--------------------------------------------------+
| Variable_name                             | Value                                            |
+-------------------------------------------+--------------------------------------------------+
| Innodb_buffer_pool_dump_status            | Dumping of buffer pool not started               |
| Innodb_buffer_pool_load_status            | Buffer pool(s) load completed at 241109 21:58:30 |
| Innodb_buffer_pool_resize_status          |                                                  |
| Innodb_buffer_pool_resize_status_code     | 0                                                |
| Innodb_buffer_pool_resize_status_progress | 0                                                |
| Innodb_buffer_pool_pages_data             | 7168                                             |
| Innodb_buffer_pool_bytes_data             | 117440512                                        |
| Innodb_buffer_pool_pages_dirty            | 5                                                |
| Innodb_buffer_pool_bytes_dirty            | 81920                                            |
| Innodb_buffer_pool_pages_flushed          | 152905174                                        |
| Innodb_buffer_pool_pages_free             | 1007                                             |
| Innodb_buffer_pool_pages_misc             | 17                                               |
| Innodb_buffer_pool_pages_total            | 8192                                             |
| Innodb_buffer_pool_read_ahead_rnd         | 0                                                |
| Innodb_buffer_pool_read_ahead             | 34590772215                                      |
| Innodb_buffer_pool_read_ahead_evicted     | 46737241                                         |
| Innodb_buffer_pool_read_requests          | 718733851921                                     |
| Innodb_buffer_pool_reads                  | 1798889354                                       |
| Innodb_buffer_pool_wait_free              | 6345356                                          |
| Innodb_buffer_pool_write_requests         | 506818935                                        |
+-------------------------------------------+--------------------------------------------------+
20 rows in set (0.26 sec)

问了下Grok,它说我的默认配置太低了,数据库服务器是16G内存,数据库占用的很少,大部分是被一个java程序占用了,查了一下原来是当时安装了并未使用的apache-dolphinscheduler,这个东西很占资源,于是干掉。再改以下配置:

innodb_buffer_pool_size = 8G
innodb_buffer_pool_instances = 4
innodb_buffer_pool_dump_at_shutdown = 1
innodb_buffer_pool_load_at_startup = 1

重启mysql以后:


mysql> SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool%';
+-------------------------------------------+--------------------------------------------------+
| Variable_name                             | Value                                            |
+-------------------------------------------+--------------------------------------------------+
| Innodb_buffer_pool_dump_status            | Dumping of buffer pool not started               |
| Innodb_buffer_pool_load_status            | Buffer pool(s) load completed at 250528 22:42:35 |
| Innodb_buffer_pool_resize_status          |                                                  |
| Innodb_buffer_pool_resize_status_code     | 0                                                |
| Innodb_buffer_pool_resize_status_progress | 0                                                |
| Innodb_buffer_pool_pages_data             | 231482                                           |
| Innodb_buffer_pool_bytes_data             | 3792601088                                       |
| Innodb_buffer_pool_pages_dirty            | 0                                                |
| Innodb_buffer_pool_bytes_dirty            | 0                                                |
| Innodb_buffer_pool_pages_flushed          | 1455                                             |
| Innodb_buffer_pool_pages_free             | 292740                                           |
| Innodb_buffer_pool_pages_misc             | 66                                               |
| Innodb_buffer_pool_pages_total            | 524288                                           |
| Innodb_buffer_pool_read_ahead_rnd         | 0                                                |
| Innodb_buffer_pool_read_ahead             | 226612                                           |
| Innodb_buffer_pool_read_ahead_evicted     | 0                                                |
| Innodb_buffer_pool_read_requests          | 4875046                                          |
| Innodb_buffer_pool_reads                  | 4713                                             |
| Innodb_buffer_pool_wait_free              | 0                                                |
| Innodb_buffer_pool_write_requests         | 4724                                             |
+-------------------------------------------+--------------------------------------------------+
20 rows in set (0.00 sec)

不过查询依然很慢,分析了一下请求来自laravel的一个多态关联表,4000W+行数据,两个关键字段是able_type和able_id,于是给这两个字段做了一个联合索引,idx_able,索引创建花了8分钟,索引创建过程中可能有锁,避开访问高峰时操作,完成以后查询降到了300ms以内。

总结:

laravel的多态表可以给able_type和able_id建联合索引。