Markdown to DOCX 转换工具 - 开发指南
1. 开发环境设置
环境要求
- Python: 3.8+
- 操作系统: Linux/macOS/Windows
- 依赖管理: pip + virtualenv
快速开始
bash
# 1. 克隆项目
git clone https://github.com/wangqiqi/md2docx.git
cd md2docx
# 2. 创建虚拟环境
python -m venv venv
source venv/bin/activate # Linux/Mac
# 或 venv\Scripts\activate # Windows
# 3. 安装依赖
pip install -e ".[dev]"
# 4. 运行测试验证环境
pytest tests/2. 项目结构详解
核心模块说明
src/mddocx/converter/base.py
- BaseConverter类: 核心转换器,协调所有元素转换
- 错误处理: MD2DocxError、ParseError、ConvertError
- 调试支持: 可选的调试信息输出
src/mddocx/converter/elements/
- ElementConverter: 基础转换器接口
- 各元素转换器: 针对不同Markdown元素的专门处理
src/cli.py
- 命令行接口: 单文件转换支持
- 参数处理: 输入输出路径、调试选项
- 错误处理: 文件占用等异常情况处理
src/mddocx/webui/app.py (新增)
- Flask应用: Web服务器主入口
- 路由处理:
/(主页),/convert(转换),/preview(预览) - 文件处理: 上传、转换、下载临时文件管理
- 错误处理: 用户友好的错误提示
src/mddocx/webui/templates/ (新增)
- Jinja2模板: HTML页面模板
- base.html: 基础布局,包含导航和样式
- index.html: 主页,文件上传和文本输入
- preview.html: 预览页,源码和样式预览
src/mddocx/webui/static/ (新增)
- CSS样式: 现代化响应式设计
- JavaScript: 前端交互和表单验证
- 资源文件: 图标、字体等静态资源
测试结构
tests/
├── unit/ # 单元测试
│ └── test_elements/ # 各元素转换器测试
├── integration/ # 集成测试
│ └── test_*.py # 端到端转换测试
└── samples/ # 测试样例
├── basic/ # 基础语法样例
└── advanced/ # 高级语法样例3. 开发工作流
分支策略
master (主分支)
├── 稳定版本发布
└── 生产就绪代码
dev (开发分支)
├── 日常开发
├── 功能集成
└── 从master创建feature分支
feature/* (功能分支)
├── 新功能开发
├── 从dev分支创建
└── 完成后合并回dev提交规范
bash
# 格式: <type>(<scope>): <subject>
# 例如:
feat: 添加图片转换功能
fix: 修复表格对齐问题
docs: 更新开发指南
test: 添加列表转换测试
refactor: 重构错误处理逻辑代码审查流程
- 创建功能分支:
git checkout -b feature/new-feature - 编写代码: 遵循代码规范
- 编写测试: 确保测试覆盖
- 提交代码:
git commit -m "feat: 功能描述" - 推送分支:
git push origin feature/new-feature - 创建PR: 在GitHub上创建Pull Request
- 代码审查: 团队成员审查代码
- 合并代码: 审查通过后合并到dev分支
4. 代码规范
Python代码风格
python
# 使用Black进行代码格式化
# 类型注解必须完整
# 函数必须有文档字符串
def convert_element(self, element: Any) -> Any:
"""转换元素为DOCX格式
Args:
element: 要转换的元素
Returns:
转换后的DOCX元素
"""
pass命名约定
- 类名: PascalCase (如
BaseConverter,HeadingConverter) - 方法名: snake_case (如
convert_heading,process_text) - 变量名: snake_case (如
input_file,output_path) - 常量: UPPER_CASE (如
MAX_RETRY_COUNT)
错误处理
python
try:
# 可能出错的操作
result = risky_operation()
except SpecificError as e:
# 具体错误处理
logger.error(f"具体错误: {e}")
raise ConvertError(f"转换失败: {e}") from e
except Exception as e:
# 通用错误处理
logger.error(f"未知错误: {e}")
raise MD2DocxError(f"系统错误: {e}") from e5. 测试开发
单元测试编写
python
import pytest
from src.converter.elements.heading import HeadingConverter
class TestHeadingConverter:
def test_h1_conversion(self):
converter = HeadingConverter()
# 测试H1标题转换
pass
def test_h6_conversion(self):
# 测试H6标题转换
pass集成测试编写
python
def test_full_markdown_conversion():
"""测试完整Markdown文档转换"""
input_md = """
# 标题
这是一个**粗体**文本。
- 项目1
- 项目2
"""
converter = BaseConverter()
doc = converter.convert(input_md)
# 验证转换结果
assert doc is not None测试运行
bash
# 运行所有测试
pytest
# 运行单元测试
pytest tests/unit/
# 运行集成测试
pytest tests/integration/
# 生成覆盖率报告
pytest --cov=src --cov-report=html6. 调试技巧
调试模式
bash
# 启用调试输出
python -m src.cli input.md output.docx --debug日志配置
python
import logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)转换指标(ConvertMetrics)
每次 BaseConverter.convert() / convert_file() 结束后,可通过 converter.last_metrics 读取程序可读快照(与日志 convert_done 字段对齐):
| 字段 | 类型 | 说明 |
|---|---|---|
duration_ms | float | 墙钟耗时(毫秒) |
input_bytes | int | Markdown UTF-8 字节数 |
chunked | bool | 是否走分块转换路径 |
python
from mddocx.converter import BaseConverter, ConvertMetrics
c = BaseConverter()
c.convert("# Hi")
m: ConvertMetrics = c.last_metrics
print(m.duration_ms, m.input_bytes, m.chunked)转换前 last_metrics 为 None;异常路径在 finally 中仍会写入指标。
ConvertMetrics 性能基线(CI)
入库基线:tests/baselines/convert_metrics.json(小样例 basic/headings.md + large/chunked.md 强制分块)。字段含 duration_ms / input_bytes / chunked。
| 命令 | 作用 |
|---|---|
python scripts/collect_convert_metrics.py | 打印当前快照 |
python scripts/collect_convert_metrics.py --write | 更新入库基线(样例内容或预期耗时变了再跑) |
python scripts/collect_convert_metrics.py --compare | 结构字段(input_bytes/chunked/路径)须与基线一致 |
bash scripts/check_convert_metrics.sh | 结构硬失败 + 耗时相对阈值(默认 soft) |
耗时门禁:live > max(baseline × METRICS_MAX_RATIO, METRICS_FLOOR_MS) 才告警。默认 ratio=5 · floor=200ms · METRICS_SOFT=1(只 WARN、退出 0,避免 CI runner 抖动假红)。本地要硬失败:METRICS_SOFT=0 bash scripts/check_convert_metrics.sh。
失败解读:
--compareFAIL → 样例体积或分块路径变了;先确认是否故意改样例,再--write更新基线并同 PR 说明。- 耗时 WARN/FAIL → 热路径明显变慢;先本地复现,勿仅因微小波动改基线。
CI quality job 已调用 check_convert_metrics.sh。
常见调试场景
- 转换结果异常: 检查元素转换器的输出
- 样式问题: 验证DOCX样式设置
- 解析错误: 检查Markdown解析结果
- 性能问题: 使用cProfile分析性能瓶颈;回归用上表基线门禁
7. 性能优化
大文件与分块转换
- 体积上限:
MAX_MARKDOWN_BYTES = 16MB(converter/security.py,与 WebUI 上传限制一致) - 自动分块:UTF-8 体积 ≥
CHUNKED_THRESHOLD(512KB)时,按 一级标题#切分后逐段parse+TokenProcessor,追加同一 DOCX - 文件入口:
BaseConverter.convert_file(Path)流式统计字节数后再读取;CLI 已接入 - 显式控制:
convert(md, chunked=True/False)可强制开启或关闭分块
python
from pathlib import Path
from mddocx.converter.base import BaseConverter
converter = BaseConverter()
doc = converter.convert_file(Path("large.md")) # 大文件自动分块
doc = converter.convert(text, chunked=False) # 强制单次 parse代码优化
- 避免重复解析: 缓存解析结果
- 批量处理: 支持多个文件批量转换
- 内存管理: 大文件处理时的内存优化
工具使用
bash
# 性能分析
python -m cProfile -s time script.py
# 内存分析
pip install memory_profiler
python -m memory_profiler script.py8. 扩展开发
添加新元素转换器
- 创建转换器类: 继承
ElementConverter - 实现转换逻辑:
convert方法 - 注册转换器: 在
BaseConverter中注册 - 编写测试: 创建对应的单元测试
- 更新文档: 在README中添加新功能说明
示例:添加数学公式支持
python
# src/converter/elements/math.py
class MathConverter(ElementConverter):
def convert(self, math_block):
# 实现数学公式转换逻辑
pass
# 在BaseConverter中注册
self.register_converter('math', MathConverter())9. 发布准备
版本发布流程
- 更新版本号: 修改
pyproject.toml和__init__.py - 更新文档: 完善CHANGELOG和README
- 运行测试: 确保所有测试通过
- 创建标签:
git tag v1.0.0 - 打包发布: 使用PyInstaller打包
打包配置
bash
# 使用PyInstaller打包
pyinstaller --onefile --name md2docx src/cli.py
# 或使用setuptools
python setup.py sdist bdist_wheel9. Web界面开发
Web界面概述
项目提供完整的Web界面,基于Flask框架实现,支持:
- 文件上传和文本输入
- 实时预览和转换
- 响应式设计 (桌面/移动)
- RESTful API接口
启动Web界面
bash
# 方法1:使用命令行工具(推荐)
mddocx-webui
# 方法2:直接运行模块
python -m src.mddocx.webui.app
# 方法3:运行启动脚本
python src/mddocx/webui/start_webui.py
# 访问 http://localhost:5000Web界面架构
src/mddocx/webui/
├── __init__.py # 包初始化
├── app.py # Flask应用核心
├── config.py # 应用配置
├── start_webui.py # WebUI启动脚本
├── templates/ # Jinja2模板
│ ├── base.html # 基础布局
│ ├── index.html # 主页
│ └── preview.html # 预览页
├── static/ # 静态资源
│ ├── css/
│ │ └── styles.css # 样式文件
│ ├── js/
│ │ └── app.js # 前端脚本
│ └── img/ # 图片资源
├── tests/ # WebUI测试
│ ├── __init__.py
│ └── test_basic.py
└── utils/ # 工具模块添加新功能
- 后端路由: 在
webui/app.py中添加新路由 - 前端页面: 创建新的HTML模板
- 样式增强: 更新CSS文件
- 交互功能: 扩展JavaScript代码
API接口
Web界面提供了REST API接口:
POST /convert- 文档转换POST /preview- 预览功能- 详细 API 文档见 API 与部署
部署Web界面
bash
# 生产环境部署
pip install gunicorn
gunicorn -w 4 -b 0.0.0.0:8000 webui.app:app
# Docker部署
docker build -t md2docx .
docker run -p 8000:8000 md2docx10. 故障排除
常见问题
- 依赖安装失败: 检查Python版本和pip配置
- 测试失败: 运行
pytest -v查看详细错误 - 转换结果异常: 使用
--debug选项查看调试信息 - 性能问题: 使用性能分析工具定位瓶颈
获取帮助
- 问题反馈: 在GitHub Issues中提交问题
- 贡献指南: 查看CONTRIBUTING.md
- 开发讨论: 参与GitHub Discussions
此开发指南将随着项目发展持续更新。如有问题或建议,请提交Issue或PR。