承上一篇: nodejs: 能在线编辑 Markdown 文档的 Web 服务程序,更多扩展功能
模仿 app2.js, 请用 python编程 : flask 3, mistune 2, 或者其它模块 实现同样功能的Web服务
以下是使用 Flask 3 + Mistune 2 实现与 app2.js 相同功能的 Python Web 服务,包含 Markdown 解析(支持代码高亮、Mermaid、表格、公式)、文档保存/加载/列表查询等核心功能:
1. 先安装依赖
pip install flask==3.0.3 mistune==2.0.5
pip install pygments python-multipart python-dotenv
2. 完整代码(app.py)
python
# -*- coding: utf-8 -*-
""" Markdown 在线编辑器 (支持表格/公式/Mermaid) """
import os
import json
from pathlib import Path
import mistune
from mistune.renderers import HTMLRenderer
from mistune.plugins import table, task_lists, footnotes
from pygments import highlight
from pygments.lexers import get_lexer_by_name, guess_lexer
from pygments.formatters import HtmlFormatter
from flask import Flask, render_template, request, jsonify, send_from_directory
# 初始化 Flask 应用
app = Flask(__name__, static_url_path='/',
static_folder='public', # 静态文件目录(对应前端资源)
template_folder='views') # 模板目录(对应editor.html)
# 配置
app.config['JSON_AS_ASCII'] = False # 支持中文
DOCS_DIR = Path(__file__).parent / 'docs' # 文档保存目录
DOCS_DIR.mkdir(exist_ok=True) # 确保目录存在
# ---- Markdown 解析配置 ----
# 自定义代码高亮渲染器(支持 Mermaid/代码高亮)
class CustomRenderer(HTMLRenderer):
def block_code(self, code, info=None):
# 处理 Mermaid 代码块
if info and info.strip() == 'mermaid':
return f'<div class="mermaid">{mistune.escape(code)}</div>'
# 处理普通代码块高亮
try:
# 尝试获取指定语言的 lexer
lexer = get_lexer_by_name(info.strip()) if info else guess_lexer(code)
except:
# 自动检测语言
lexer = guess_lexer(code)
# 使用 Pygments 高亮代码
formatter = HtmlFormatter(
noclasses=False, # 生成带类名的 HTML(配合 highlight.js 样式)
cssclass='hljs', # 兼容 highlight.js 样式类
linenos=False # 不显示行号(可根据需求开启)
)
highlighted = highlight(code, lexer, formatter)
return f'<pre>{highlighted}</pre>'
# 初始化 Mistune 解析器(启用所有扩展)
markdown_parser = mistune.create_markdown(
renderer=CustomRenderer(),
plugins=[
'table', # 表格支持
'task_lists', # 任务列表支持
'footnotes' # 脚注支持
],
escape=False # 关键配置:禁用字符转义
)
# ---- 路由配置 ----
# 首页 - 编辑器界面
@app.route('/')
def index():
return render_template('editor.html', title='Markdown 在线编辑器 (支持表格/公式/Mermaid)')
# 解析 Markdown 为 HTML (API)
@app.route('/api/parse', methods=['POST'])
def parse_markdown():
try:
data = request.get_json()
markdown = data.get('markdown', '')
if not markdown:
return jsonify({'error': 'Markdown 内容不能为空'}), 400
# 解析 Markdown 为 HTML
html = markdown_parser(markdown)
return jsonify({'html': html})
except Exception as e:
return jsonify({'error': f'解析 Markdown 失败: {str(e)}'}), 500
# 保存文档 (API)
@app.route('/api/save', methods=['POST'])
def save_document():
try:
data = request.get_json()
filename = data.get('filename', '').strip()
content = data.get('content', '').strip()
if not filename or not content:
return jsonify({'error': '文件名和内容不能为空'}), 400
# 拼接文件路径
file_path = DOCS_DIR / f'{filename}.md'
# 写入文件(UTF-8 编码)
with open(file_path, 'w', encoding='utf-8') as f:
f.write(content)
return jsonify({
'success': True,
'message': '文件保存成功',
'filePath': str(file_path)
})
except Exception as e:
return jsonify({'error': f'保存文件失败: {str(e)}'}), 500
# 加载文档 (API)
@app.route('/api/load/<filename>', methods=['GET'])
def load_document(filename):
try:
file_path = DOCS_DIR / f'{filename}.md'
# 检查文件是否存在
if not file_path.exists():
return jsonify({'error': '文件不存在'}), 404
# 读取文件内容
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
return jsonify({'success': True, 'content': content})
except Exception as e:
return jsonify({'error': f'加载文件失败: {str(e)}'}), 500
# 获取文档列表 (API)
@app.route('/api/docs', methods=['GET'])
def list_docs():
try:
# 读取目录下所有 .md 文件
docs = []
for file in DOCS_DIR.glob('*.md'):
docs.append({
'name': file.stem, # 不带扩展名的文件名
'path': str(file)
})
return jsonify({'success': True, 'docs': docs})
except Exception as e:
return jsonify({'error': f'获取文档列表失败: {str(e)}'}), 500
# 静态文件服务(兼容前端资源加载)
@app.route('/public/<path:path>')
def serve_static(path):
return send_from_directory('public', path)
# ---- 启动服务器 ----
if __name__ == '__main__':
# 启动 Flask 开发服务器(生产环境建议用 Gunicorn)
app.run(
host='127.0.0.1', # 不允许外部访问
port=8000, # 与原 Node.js 端口保持一致
debug=True # 开发模式(生产环境关闭)
)
3. 目录结构说明
需要和原 Node.js 版本保持一致的目录结构,确保前端页面能正常加载:
TREE /F md-editor
md-editor
├── app.py # Python 服务端代码
├── docs/ # 文档保存目录(自动创建)
│ └── demo.md
├── public/ # 静态资源目录(前端 JS/CSS)
│ ├── js/
│ │ ├── mermaid.min.js
│ │ └── highlight.min.js
│ └── css/
│ └── github-dark.min.css
└── views/ # 模板目录
└── editor.html # 原 Node.js 版本的 editor.ejs(无需修改)
4. 核心功能说明
-
Markdown 解析:
- 使用
mistune 2作为核心解析器,兼容 GFM 规范 - 自定义渲染器支持 Mermaid 代码块(输出
<div class="mermaid">) - 使用
Pygments实现代码高亮(兼容 highlight.js 样式) - 启用表格、任务列表、脚注等扩展
- 使用
-
API 接口:
/api/parse:POST 请求,解析 Markdown 为 HTML/api/save:POST 请求,保存 Markdown 文档/api/load/<filename>:GET 请求,加载指定文档/api/docs:GET 请求,获取所有文档列表
-
前端兼容:
- 静态资源路径、接口地址与原 Node.js 版本完全一致
- 复用原
editor.html前端页面,只需修改一行
cd public\css
rename editor.ejs editor.html
修改第6行:<title>{``{ title }}</title>
5. 运行方式
直接运行
python app.py
生产环境建议用 Gunicorn
gunicorn -w 4 -b 0.0.0.0:8000 app:app
6. 关键注意事项
- 静态资源 :需要将原 Node.js 版本中
public目录下的js/mermaid.min.js、js/highlight.min.js、css/github-dark.min.css复制到 Python 项目的public目录中。 - 公式渲染:数学公式渲染仍由前端 KaTeX 处理,服务端仅负责 Markdown 解析,与原逻辑一致。
- 编码 :所有文件读写均使用
UTF-8编码,确保中文正常显示。 - 兼容性:接口返回格式与原 Node.js 版本完全一致,前端无需任何修改即可对接。
该实现完整复刻了原 Node.js 版本的所有核心功能,同时符合 Python/Flask 的开发规范,可直接替换原服务端使用。