基于 Vue3 的 monorepo 架构设计一个数字阅读前端项目

基于 Vue3 的 monorepo 架构设计一个数字阅读前端项目,需要清晰的模块划分和合理的目录结构,以支持图书搜索、用户登录、用户中心、数字图书模板以及特色应用场景(如汉字笔画练习、中英文字词听写、普通话语音测评等)。以下是一个详细的项目结构设计,采用 monorepo 方式,使用 apps、packages 和 shared-components 文件夹来组织子模块,同时考虑 Vue3 的组件化开发和复用性。


项目设计目标

  1. 模块化:通过 monorepo 架构,将不同功能模块(如搜索、用户中心、特色场景)拆分为独立子模块,便于维护和扩展。
  2. 复用性:将通用组件、工具函数和类型定义放在 shared-components 和 packages 中,供多个 apps 复用。
  3. 可扩展性:支持多种数字图书模板和特色应用场景,确保新功能可以轻松接入。
  4. 开发效率:使用 pnpm(推荐用于 monorepo)管理依赖,结合 Vite 作为构建工具,提升开发体验。

技术栈

  • 框架:Vue3(组合式 API)
  • 构建工具:Vite
  • 包管理:pnpm(支持 monorepo 的 workspace 功能)
  • 状态管理:Pinia
  • 路由:Vue Router
  • UI 组件库:可选(如 Element Plus 或 Naive UI),或基于 shared-components 自定义
  • 类型检查:TypeScript
  • 代码规范:ESLint + Prettier
  • 测试:Vitest + Vue Test Utils
  • 部署:支持静态部署(如 Vercel)或 SSR(如 Nuxt 3,可选)

项目结构

以下是项目的目录结构,分为 apps(应用入口)、packages(工具库和业务逻辑)、shared-components(通用组件)三大模块,并包含配置文件和文档。

digital-reading-project/
├── apps/
│   ├── book-search/                    # 图书搜索应用
│   │   ├── src/
│   │   │   ├── components/           # 搜索相关组件
│   │   │   ├── views/                # 页面视图
│   │   │   ├── router/               # 路由配置
│   │   │   ├── store/                # Pinia 状态管理
│   │   │   ├── assets/               # 静态资源
│   │   │   ├── App.vue               # 应用根组件
│   │   │   └── main.ts               # 入口文件
│   │   ├── vite.config.ts            # Vite 配置文件
│   │   ├── package.json              # 应用依赖
│   │   └── tsconfig.json             # TypeScript 配置
│   ├── user-center/                    # 用户中心应用(登录、个人资料等)
│   │   ├── src/
│   │   │   ├── components/           # 用户中心相关组件
│   │   │   ├── views/                # 页面视图(如个人资料、设置)
│   │   │   ├── router/
│   │   │   ├── store/
│   │   │   ├── assets/
│   │   │   ├── App.vue
│   │   │   └── main.ts
│   │   ├── vite.config.ts
│   │   ├── package.json
│   │   └── tsconfig.json
│   ├── digital-books/                  # 数字图书模板应用
│   │   ├── src/
│   │   │   ├── templates/            # 多种图书模板
│   │   │   │   ├── novel/          # 小说阅读模板
│   │   │   │   ├── textbook/       # 教材阅读模板
│   │   │   │   └── comic/          # 漫画阅读模板
│   │   │   ├── components/
│   │   │   ├── views/
│   │   │   ├── router/
│   │   │   ├── store/
│   │   │   ├── assets/
│   │   │   ├── App.vue
│   │   │   └── main.ts
│   │   ├── vite.config.ts
│   │   ├── package.json
│   │   └── tsconfig.json
│   ├── special-scenes/                 # 特色应用场景
│   │   ├── src/
│   │   │   ├── scenes/
│   │   │   │   ├── hanzi-practice/ # 汉字笔画练习
│   │   │   │   ├── dictation/      # 中英文字词听写
│   │   │   │   └── mandarin-test/  # 普通话语音测评
│   │   │   ├── components/


│   │   │   ├── views/
│   │   │   ├── router/
│   │   │   ├── store/
│   │   │   ├── assets/
│   │   │   ├── App.vue
│   │   │   └── main.ts
│   │   ├── vite.config.ts
│   │   ├── package.json
│   │   └── tsconfig.json
├── packages/
│   ├── api-client/                     # API 请求封装
│   │   ├── src/
│   │   │   ├── index.ts              # 导出 API 方法
│   │   │   ├── book.ts               # 图书相关 API
│   │   │   ├── user.ts               # 用户相关 API
│   │   │   └── special.ts            # 特色场景 API
│   │   ├── package.json
│   │   └── tsconfig.json
│   ├── utils/                          # 通用工具函数
│   │   ├── src/
│   │   │   ├── index.ts              # 导出工具函数
│   │   │   ├── format.ts             # 格式化工具(如时间、文本)
│   │   │   ├── storage.ts            # 本地存储封装
│   │   │   └── audio.ts              # 音频处理工具(如语音测评)
│   │   ├── package.json
│   │   └── tsconfig.json
│   ├── types/                          # TypeScript 类型定义
│   │   ├── src/
│   │   │   ├── index.ts              # 导出类型
│   │   │   ├── book.ts               # 图书相关类型
│   │   │   ├── user.ts               # 用户相关类型
│   │   │   └── scene.ts              # 特色场景类型
│   │   ├── package.json
│   │   └── tsconfig.json
├── shared-components/                  # 通用组件
│   ├── src/
│   │   ├── index.ts                  # 导出所有组件
│   │   ├── Button.vue                # 通用按钮
│   │   ├── Input.vue                 # 通用输入框
│   │   ├── Modal.vue                 # 通用模态框
│   │   ├── Reader.vue                # 通用阅读器组件
│   │   ├── AudioPlayer.vue           # 音频播放器
│   │   └── StrokeCanvas.vue          # 汉字笔画画布
│   ├── package.json
│   ├── vite.config.ts
│   └── tsconfig.json
├── scripts/                            # 构建和部署脚本
│   ├── build-all.ts                  # 构建所有应用的脚本
│   └── deploy.ts                     # 部署脚本
├── docs/                               # 项目文档
│   ├── README.md                     # 项目说明
│   └── architecture.md               # 架构说明
├── .eslintrc.js                       # ESLint 配置
├── .prettierrc                        # Prettier 配置
├── pnpm-workspace.yaml                # pnpm monorepo 配置文件
├── tsconfig.json                      # 全局 TypeScript 配置
├── vite.config.ts                     # 全局 Vite 配置(可选)
└── package.json                       # 根目录依赖

目录结构说明

1. apps 目录

apps 包含独立的应用入口,每个应用是一个完整的 Vue3 项目,使用 Vite 构建,共享 packages 和 shared-components 的内容。

  • book-search:
    • 功能:图书搜索、分类浏览、搜索历史。
    • 组件:搜索框、结果列表、筛选器。
    • 路由:/search(搜索页)、/category(分类页)。
    • 状态管理:Pinia 存储搜索关键词和结果。
    • API 调用:通过 packages/api-client 获取图书数据。
  • user-center:
    • 功能:用户登录、注册、个人资料管理、阅读记录。
    • 组件:登录表单、用户头像、设置面板。
    • 路由:/login、/register、/profile。
    • 状态管理:Pinia 存储用户信息和认证状态。
    • API 调用:通过 packages/api-client 处理用户相关请求。
  • digital-books:
    • 功能:支持多种数字图书模板(如小说、教材、漫画)。
    • 子目录 templates:
      • novel:小说阅读模板,支持翻页、字体调整、夜间模式。
      • textbook:教材模板,支持目录导航、笔记功能。
      • comic:漫画模板,支持图片缩放、左右翻页。
    • 组件:阅读器(基于 shared-components/Reader.vue)、目录、书签。
    • 路由:/book/:id(阅读页面)、/book/:id/toc(目录页)。
    • 状态管理:Pinia 存储阅读进度和设置。
  • special-scenes:
    • 功能:特色应用场景,如汉字笔画练习、中英文字词听写、普通话语音测评。
    • 子目录 scenes:
      • hanzi-practice:基于 Canvas 的汉字笔画练习,使用 shared-components/StrokeCanvas.vue。
      • dictation:中英文字词听写,支持音频播放(基于 shared-components/AudioPlayer.vue)和输入验证。
      • mandarin-test:普通话语音测评,使用 Web Speech API 或第三方语音服务,结合 packages/utils/audio.ts。
    • 路由:/scene/hanzi、/scene/dictation、/scene/mandarin。
    • 状态管理:Pinia 存储练习进度和评分。

2. packages 目录

packages 包含可复用的工具库和业务逻辑模块,供多个 apps 使用。

  • api-client:
    • 封装 Axios 或 Fetch 请求,提供图书、用户、特色场景的 API 方法。
    • 示例:book.ts 包含 searchBooks、getBookDetail 方法;user.ts 包含 login、getProfile 方法。
    • 使用 TypeScript 定义请求和响应类型,引用 packages/types。
  • utils:
    • 通用工具函数,如时间格式化、本地存储封装、音频处理。
    • 示例:audio.ts 提供音频播放和录制功能,支持语音测评场景。
  • types:
    • 定义 TypeScript 类型和接口,如图书、用户、场景的数据结构。
    • 示例:book.ts 定义 Book 接口,包含 id、title、author 等字段。

3. shared-components 目录

shared-components 包含通用 Vue 组件,支持跨应用复用。

  • Button.vue:通用按钮,支持不同样式和禁用状态。
  • Input.vue:通用输入框,支持搜索、表单等场景。
  • Modal.vue:通用模态框,用于登录、提示等。
  • Reader.vue:通用阅读器组件,支持小说、教材、漫画的渲染。
  • AudioPlayer.vue:音频播放器,用于听写和语音测评。
  • StrokeCanvas.vue:基于 Canvas 的汉字笔画练习组件。

4. 根目录配置

  • pnpm-workspace.yaml:yamlpackages: - 'apps/*' - 'packages/*' - 'shared-components'定义 monorepo 的 workspace,允许 apps 和 packages 互相引用。
  • tsconfig.json: 配置路径别名(如 @shared 指向 shared-components)和 TypeScript 选项。
  • vite.config.ts: 全局 Vite 配置,定义共享的插件和优化选项,子应用可继承或覆盖。
  • scripts:
    • build-all.ts:使用 pnpm -r build 构建所有应用。
    • deploy.ts:自动化部署脚本(可选)。
  • docs:
    • README.md:项目概述、安装和运行说明。
    • architecture.md:详细描述 monorepo 架构和模块职责。

实现细节

1. Monorepo 管理

  • 使用 pnpm 管理依赖,确保 apps 和 packages 的依赖隔离。
  • 在 package.json 中配置脚本:json{ "scripts": { "dev:search": "pnpm --filter book-search dev", "dev:user": "pnpm --filter user-center dev", "build": "pnpm -r build", "test": "pnpm -r test" } }

2. 组件复用

  • shared-components 中的组件通过 index.ts 导出,供 apps 按需引入:ts// shared-components/src/index.ts export { default as Button } from './Button.vue'; export { default as Reader } from './Reader.vue';
  • 在 apps 中通过路径别名引入:tsimport { Button } from '@shared';

3. API 封装

  • packages/api-client 使用 Axios 封装请求:ts// packages/api-client/src/book.ts import axios from 'axios'; import type { Book } from '@types'; export async function searchBooks(query: string): Promise<Book[]> { const response = await axios.get('/api/books', { params: { query } }); return response.data; }

4. 特色场景实现

  • 汉字笔画练习:
    • 使用 StrokeCanvas.vue 实现 Canvas 画板,记录用户笔画并与标准笔顺比较。
    • 数据通过 packages/api-client/special.ts 获取汉字笔顺信息。
  • 中英文字词听写:
    • 使用 AudioPlayer.vue 播放单词音频,用户输入答案后通过 packages/api-client 校验。
  • 普通话语音测评:
    • 使用 Web Speech API 录制用户语音,结合 packages/utils/audio.ts 进行预处理。
    • 调用第三方语音测评 API(如科大讯飞)进行评分。

5. 路由和状态管理

  • 每个 app 使用 Vue Router 管理页面跳转,Pinia 管理状态。
  • 示例(book-search 的路由):ts// apps/book-search/src/router/index.ts import { createRouter, createWebHistory } from 'vue-router'; import SearchView from '../views/SearchView.vue'; const router = createRouter({ history: createWebHistory(), routes: [ { path: '/search', component: SearchView }, { path: '/category', component: () => import('../views/CategoryView.vue') }, ], }); export default router;

6. 测试

  • 使用 Vitest 和 Vue Test Utils 编写单元测试,覆盖组件和工具函数。
  • 示例测试(shared-components/Button.vue):tsimport { mount } from '@vue/test-utils'; import Button from '@shared/Button.vue'; describe('Button', () => { it('renders correctly', () => { const wrapper = mount(Button, { props: { label: 'Click Me' } }); expect(wrapper.text()).toContain('Click Me'); }); });

开发流程

  1. 初始化项目:
    • 初始化 monorepo:pnpm init 和 pnpm-workspace.yaml。
    • 为每个 app 和 package 创建 package.json 和 vite.config.ts。
  2. 开发通用组件:
    • 在 shared-components 中开发通用组件,先实现基础功能(如 Button、Input)。
    • 逐步添加复杂组件(如 Reader、StrokeCanvas)。
  3. 开发工具库:
    • 在 packages 中实现 API 客户端、工具函数和类型定义。
    • 使用 TypeScript 确保类型安全。
  4. 开发应用:
    • 从 book-search 开始,逐步实现其他 apps。
    • 复用 shared-components 和 packages 的内容。
  5. 测试和部署:
    • 编写单元测试,确保组件和工具函数的稳定性。
    • 使用 scripts/build-all.ts 构建所有应用,部署到 Vercel 或其他平台。

可扩展性考虑

  • 新增图书模板:在 apps/digital-books/templates 中添加新模板目录(如 magazine),复用 Reader.vue。
  • 新增特色场景:在 apps/special-scenes/scenes 中添加新场景目录,复用现有组件和工具。
  • 多语言支持:在 packages/utils 中添加 i18n 工具,使用 Vue I18n 实现多语言切换。
  • 性能优化:使用 Vite 的按需加载和 Tree Shaking,减少打包体积。

总结

这个 monorepo 架构将数字阅读项目的功能模块化,清晰划分了 apps(应用入口)、packages(工具库)和 shared-components(通用组件)。通过 pnpm 和 Vite 管理依赖和构建,结合 Vue3 和 TypeScript 实现高效开发。每个子模块(图书搜索、用户中心、数字图书、特色场景)独立维护,同时复用通用代码,确保项目可扩展和易维护。

基于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建联合索引。

既然短信签名审核那么严格,为什么不让运营商统一同一个短信验证码模板呢?

短信最主要的一个使用场景就是短信验证码了,运营商提供一个由工信部指定的短信验证码模板,一个4位,一个6位,岂不是很简单一件事情,为什么要把精力和时间浪费在签名审核上呢?

老mac设置php版本

2015的老mac,brew安装的php版本只能是7了,不能再升级,自行安装了php8.2,在.zshrc中通过设置path没有问题,但是vscode却总是提示composer所检查的版本不够。

直到在vscode中找到

Laravel: PHP Environment(此处设置为local),可忽略

Atisan:PHP:Location

设置为:/usr/local/opt/php@8.2/bin/php

以此来解决vscode默认调用/usr/bin/php的问题

在Ubuntu24上部署JupyterHub + JupyterLab + DockerSpawner

在 Ubuntu 系统上部署 JupyterHub、JupyterLab 和 DockerSpawner 的详细方案,涵盖环境准备、安装、配置和部署步骤。目标是创建一个多用户 Jupyter 环境,每个用户通过 DockerSpawner 运行独立的 JupyterLab 容器,确保隔离性和可扩展性。本方案基于官方文档和社区最佳实践,也适用于 Ubuntu 20.04 或 22.04。

总体上分两种情况,一种是直接在服务器上启动JupyterHub,另一种是在docker中启动JupyterHub,后者隔离效果更好,推荐使用,但是绑定服务器目录时需要注意。以下方案中会作出区分说明。


1. 环境准备

1.1 系统要求

  • 操作系统:Ubuntu 20.04 LTS 或 22.04 LTS(推荐服务器版)。
  • 硬件要求:
    • 最低配置:2核CPU,4GB内存,20GB磁盘空间。
    • 推荐配置:4核CPU,8GB内存,50GB+磁盘空间(根据用户数量和数据存储需求调整)。
  • 网络:确保服务器可以访问互联网以拉取 Docker 镜像和安装依赖。
  • 权限:需要 root 或 sudo 权限。

1.2 安装基本工具

更新系统并安装必要的工具:

bash

sudo apt update && sudo apt upgrade -y

下面这一步并非必要,主要用于在服务器上直接启动JupyterHub。

bash

sudo apt install -y curl git python3 python3-pip python3-venv

1.3 安装 Docker

安装 Docker 和 Docker Compose,用于运行 JupyterHub 和用户容器:

bash

# 安装 Docker
sudo apt install -y docker.io
sudo systemctl start docker
sudo systemctl enable docker

# 验证 Docker 安装
docker --version

# 安装 Docker Compose
sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

# 验证 Docker Compose 安装
docker-compose --version

# 将当前用户添加到 docker 组(避免每次使用 docker 需要 sudo)
sudo usermod -aG docker $USER
newgrp docker

上面这一步需要重新登录或者重启才能生效。其中docker-compose也可以不装,新版的docker中自带docker compose命令。

2. 安装 JupyterHub 和依赖(这是服务器直接部署JupyterHub,使用docker的话可以跳过)

2.1 创建虚拟环境

为了隔离 JupyterHub 的 Python 依赖,创建一个虚拟环境:

bash

python3 -m venv /opt/jupyterhub
source /opt/jupyterhub/bin/activate

2.2 安装 JupyterHub 和 DockerSpawner

在虚拟环境中安装 JupyterHub 和相关包:

bash

pip install --upgrade pip
pip install jupyterhub jupyterlab dockerspawner
  • jupyterhub:核心服务,用于管理多用户环境。
  • jupyterlab:为用户提供 JupyterLab 界面。
  • dockerspawner:用于在 Docker 容器中启动单用户 JupyterLab 实例。

2.3 安装 Node.js(可选,JupyterLab 依赖)

JupyterLab 需要 Node.js 来支持扩展和前端功能:

bash

sudo apt install -y nodejs npm

3. 配置 JupyterHub(这个配置文件是用于服务器直接部署,docker部署的话其中的设置有所区别)

3.1 创建 JupyterHub 配置文件

生成默认配置文件(也可以跳过,直接复制下面的配置文件内容进去,默认配置文件很长,看起来不方便):

bash

mkdir /opt/jupyterhub
jupyterhub --generate-config -f /opt/jupyterhub/jupyterhub_config.py

配置文件位于 /opt/jupyterhub/etc/jupyterhub_config.py,以下是关键配置步骤。

3.2 配置 DockerSpawner

编辑 jupyterhub_config.py,添加以下内容以启用 DockerSpawner 和 JupyterLab:

python

c = get_config()
import os
import sys
from dockerspawner import DockerSpawner

# 设置 DockerSpawner
c.JupyterHub.spawner_class = 'dockerspawner.DockerSpawner'
c.DockerSpawner.image = 'jupyter/minimal-notebook:x86_64-python-3.11.6'
c.DockerSpawner.network_name = 'jupyterhub-network'

# 设置 JupyterLab 作为默认界面
c.Spawner.default_url = '/lab'

# 持久化存储配置
notebook_dir = '/home/jovyan/work'
c.DockerSpawner.notebook_dir = notebook_dir
c.DockerSpawner.volumes = { 
    '/mnt/raid/jupyterhub_persistent/{username}': notebook_dir,
    '/mnt/raid/jupyterhub_shared': '/home/jovyan/shared'
}
# 上面路径中的/mnt/raid是我自己服务器的一个地址,可以替换。注意这个地址不管在服务器直接运行JupyterHub还是在docker中,都是设置为服务器上的地址,因为是绑定给用户容器的。

# 动态创建用户目录
def create_dir_hook(spawner):
    username = spawner.user.name
    volume_path = os.path.join('/mnt/raid/jupyterhub_persistent', username)
    if not os.path.exists(volume_path):
        os.mkdir(volume_path, 0o755)
        os.chown(volume_path, 1000, 100)  # jovyan 用户的 UID 和 GID
c.Spawner.pre_spawn_hook = create_dir_hook

# 资源限制
c.DockerSpawner.extra_host_config = {
    'mem_limit': '16g',  # 每个容器 64GB,10 个用户平分 640GB
    'cpu_period': 100000,
    'cpu_quota': 1000000,  # 每个容器 1 核心,10 个用户共 10 核心
}

# 如果本地端口有冲突可以设置,否则不设置
c.JupyterHub.bind_url = 'http://:8090'
c.JupyterHub.hub_connect_ip = '127.0.0.1'
c.JupyterHub.hub_ip = '0.0.0.0'
c.JupyterHub.hub_port = 9000

# 可选:设置认证,根据需要选择认证方式,建议用这种自定义用户比较方便,不用系统的用户
c.JupyterHub.authenticator_class = 'nativeauthenticator.NativeAuthenticator'
c.NativeAuthenticator.enable_signup = True
c.NativeAuthenticator.open_signup = True
c.Authenticator.admin_users = {'admin'}

# 可选:自动停止闲置服务器
c.JupyterHub.services = [
#    {
#        'name': 'cull_idle',
#        'admin': True,
#        'command': 'python3 /opt/jupyterhub/cull_idle_servers.py --timeout=3600'.split(),
#    },
    {
        "name": "jupyterhub-idle-culler-service",
        "command": [
            sys.executable,
            "-m", "jupyterhub_idle_culler",
            "--timeout=3600",
        ],
         "admin": True,
    }
]

3.3 创建 Docker 网络

为 JupyterHub 和用户容器创建一个专用的 Docker 网络:

bash

docker network create jupyterhub-network

3.4 配置认证

JupyterHub 默认使用 PAM 认证(基于系统用户)。若需要简单测试,可以配置 Native Authenticator 允许用户自注册:

bash

pip install jupyterhub-nativeauthenticator

在 jupyterhub_config.py 中添加:

python

c.JupyterHub.authenticator_class = 'nativeauthenticator.NativeAuthenticator'
c.Authenticator.admin_users = {'admin'}  # 设置管理员用户

若使用其他认证方式(如 OAuth、LDAP),请参考 JupyterHub 官方文档配置对应 Authenticator。

3.5 数据持久化

为确保用户数据持久化,DockerSpawner 使用 Docker 卷(如上配置)。每个用户的卷命名为 jupyterhub-user-{username},存储在 /home/jovyan/work 目录。

若需要共享数据目录,可以挂载主机路径:

python

c.DockerSpawner.volumes = {
    'jupyterhub-user-{username}': notebook_dir,
    '/path/on/host/shared': '/home/jovyan/shared'
}

确保主机路径权限正确:

bash

sudo chown :1000 /path/on/host/shared
sudo chmod g+rws /path/on/host/shared
sudo setfacl -d -m g::rwx /path/on/host/shared

4. 使用 Docker Compose 部署

为简化管理,使用 Docker Compose 部署 JupyterHub 和相关服务。

4.1 创建 docker-compose.yml

在 /opt/jupyterhub 目录下创建 docker-compose.yml:

yaml

version: '3.8'
services:
  jupyterhub:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: jupyterhub
    restart: unless-stopped
    ports:
      - "9000:8000"
    volumes:
      - /opt/jupyterhub/srv:/srv/jupyterhub # 这里单独设置了一个srv以跟docker中的srv对应,区别于服务器直接部署的配置,测试时方便区分
      - /var/run/docker.sock:/var/run/docker.sock
      - /mnt/raid/jupyterhub/jupyterhub_persistent:/jupyterhub/jupyterhub_persistent
      - /mnt/raid/jupyterhub/jupyterhub_shared:/jupyterhub/jupyterhub_shared
    environment:
      - DOCKER_HOST=unix:///var/run/docker.sock
      - DOCKER_NOTEBOOK_IMAGE=jupyter/scipy-notebook:latest
      - DOCKER_NETWORK_NAME=jupyterhub-network
    networks:
      - jupyterhub-network

networks:
  jupyterhub-network:
    external: true

用户环境定制

若需要自定义用户镜像,创建 Dockerfile:

因为启用了nativeauthenticator所以默认的镜像不行,需要自己通过Dockerfile加上。同时也指定自己的jupyterhub_config

Dockerfile

# Use the official JupyterHub base image
FROM jupyterhub/jupyterhub:latest

# Install dockerspawner and nativeauthenticator
RUN pip install --no-cache-dir \
    dockerspawner \
    jupyterhub-nativeauthenticator \
    jupyterhub-idle-culler

# Create directory for persistent storage (if needed inside the hub container)
RUN mkdir -p /jupyterhub/jupyterhub_persistent /jupyterhub/jupyterhub_shared
RUN chmod -R 755 /jupyterhub

# Copy the jupyterhub_config.py into the container
COPY jupyterhub_config.py /srv/jupyterhub/jupyterhub_config.py

# Expose the default JupyterHub port
EXPOSE 8000

# Set working directory
WORKDIR /srv/jupyterhub

# Start JupyterHub
CMD ["jupyterhub", "-f", "/srv/jupyterhub/jupyterhub_config.py"]

4.2 用docker部署并启动 JupyterHub(这是方式二)

bash

cd /opt/jupyterhub
docker-compose up -d

4.3 验证部署

  • 访问 http://<服务器IP>:8000,应看到 JupyterHub 登录页面。
  • 使用管理员账户(如 admin)登录,测试创建用户和启动 JupyterLab 容器。
  • 检查 Docker 容器:bashdocker ps应看到 jupyterhub 容器和用户容器(如 jupyter-<username>)。

4.4 日志查看

若遇到问题,查看日志:

bash

docker logs jupyterhub

5. 高级配置

5.1 HTTPS 配置(不建议,建议另外通过caddy代理并设置ssl)

为生产环境,建议配置 HTTPS:

  1. 使用 Let’s Encrypt 获取免费 SSL 证书:bashsudo apt install -y certbot python3-certbot-nginx certbot certonly --standalone -d <your-domain>
  2. 在 jupyterhub_config.py 中配置 SSL:pythonc.JupyterHub.ssl_key = '/etc/letsencrypt/live/<your-domain>/privkey.pem' c.JupyterHub.ssl_cert = '/etc/letsencrypt/live/<your-domain>/fullchain.pem' c.JupyterHub.port = 443
  3. 更新 docker-compose.yml,将端口改为 443:443。

5.2 GPU 支持

若需要 GPU 支持,确保主机安装了 NVIDIA 驱动和 nvidia-container-toolkit:

bash

sudo apt install -y nvidia-driver-<version> nvidia-container-toolkit

在 jupyterhub_config.py 中添加:

python

c.DockerSpawner.extra_host_config = {'runtime': 'nvidia'}

使用支持 GPU 的镜像,如 jupyter/tensorflow-notebook。

5.3 用户环境定制

若需要自定义用户镜像,创建 Dockerfile:

dockerfile

FROM jupyter/scipy-notebook:latest
RUN pip install numpy pandas matplotlib
CMD ["jupyterhub-singleuser"]

构建并推送到 Docker Hub 或本地仓库:

bash

docker build -t my-jupyterlab-image .
docker tag my-jupyterlab-image <your-dockerhub-username>/my-jupyterlab-image:latest
docker push <your-dockerhub-username>/my-jupyterlab-image:latest

在 jupyterhub_config.py 中更新:

python

c.DockerSpawner.image = '<your-dockerhub-username>/my-jupyterlab-image:latest'

6. 常见问题排查

  • “Spawn failed” 错误:
    • 检查 docker logs jupyterhub 是否有 KeyError 或权限问题。
    • 确保 Docker 网络 jupyterhub-network 已创建。
    • 验证 DOCKER_NOTEBOOK_IMAGE 是否可拉取。
  • 用户容器无法访问数据库:
    • 确保数据库容器在同一 Docker 网络中:bashdocker network connect jupyterhub-network <database-container>
  • 权限问题:
    • 检查卷挂载路径权限,确保用户 jovyan(UID 1000)有写权限。
  • JupyterLab 不显示:
    • 确认 c.Spawner.default_url = ‘/lab’ 已设置。
    • 确保镜像中已安装 JupyterLab:bashdocker run -it <image> pip show jupyterlab

7. 维护与备份

  • 备份用户数据:
    • 用户数据存储在 Docker 卷中,查看卷:bashdocker volume ls
    • 备份卷:bashdocker run --rm -v jupyterhub-user-<username>:/data -v /backup:/backup busybox tar cvf /backup/user-<username>.tar /data
  • 更新 JupyterHub:
    • 停止服务:docker-compose down
    • 更新镜像:docker pull jupyterhub/jupyterhub:latest
    • 重新启动:docker-compose up -d
  • 清理无用容器:bashdocker container prune

8. 参考资源


通过以上步骤,您可以在 Ubuntu 上成功部署一个支持 JupyterLab 和 DockerSpawner 的 JupyterHub 环境,适合教学、科研或团队协作场景。如需进一步定制或遇到具体问题,请提供更多细节,我可以为您提供针对性指导!

个人博客服务器从Ubuntu16升级到了Ubuntu22

因为阿里云的ECS开了性能限制,博客有几个访问就挂掉,索性整体迁移一下系统,于是把dnmp整个文件夹备份到本地,然后停机,换镜像,启动,再scp把文件放回去(这里其实应该打包下载和上传,会快很多)。然后用dnmp里面的一键启动命令恢复环境,最后docker compose up -d启动网站。

不过这中间因为周末玩耍耽误了几天,幸好这个博客也没有多少人。