python使用Flask 3实现渲染指定目录下Md文件

 更新时间:2026年02月24日 09:05:29   作者:belldeep  
这篇文章主要为大家详细介绍了python如何使用Flask 3实现渲染指定目录下Md文件,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下

请用 python编程 : flask 3, mistune 2, python-multipart 实现同样功能的Web服务

以下是使用 Flask 3 + mistune 2 + python-multipart 实现与 server2.js 相同功能的 Python Web 服务,完整保留了原功能并适配 Python 生态:

# -*- coding: utf-8 -*-
import os
from pathlib import Path
import mimetypes
import mistune
from mistune.renderers import HTMLRenderer
from flask import Flask, request, jsonify, send_file, send_from_directory, make_response
from werkzeug.utils import secure_filename

# 初始化 Flask 应用
app = Flask(__name__, static_folder='static', static_url_path='/')

# ====================== 配置项 ======================
# 默认MD文件目录
DEFAULT_MD_DIR = Path(__file__).parent / 'md-files'
# 确保默认目录存在
DEFAULT_MD_DIR.mkdir(parents=True, exist_ok=True)
print(f"创建/使用默认MD文件目录: {DEFAULT_MD_DIR.absolute()}")

# 允许上传的文件类型
ALLOWED_EXTENSIONS = {'.md', '.markdown'}
# 文件大小限制 (5MB)
MAX_CONTENT_LENGTH = 5 * 1024 * 1024
app.config['MAX_CONTENT_LENGTH'] = MAX_CONTENT_LENGTH

# ====================== 安全工具函数 ======================
def validate_and_normalize_dir(dir_path: str) -> Path:
    """验证并规范化目录路径"""
    # 如果为空,返回默认目录
    if not dir_path or dir_path.strip() == '':
        return DEFAULT_MD_DIR.absolute()
    
    # 解析为绝对路径
    abs_path = Path(dir_path.strip()).absolute()
    
    # 检查目录是否存在
    if not abs_path.exists():
        raise ValueError(f"目录不存在: {abs_path}")
    
    # 检查是否是目录(不是文件)
    if not abs_path.is_dir():
        raise ValueError(f"不是有效的目录: {abs_path}")
    
    # 检查目录是否可读
    if not os.access(abs_path, os.R_OK):
        raise ValueError(f"没有目录读取权限: {abs_path}")
    
    return abs_path

def allowed_file(filename: str) -> bool:
    """检查文件扩展名是否合法"""
    return Path(filename).suffix.lower() in ALLOWED_EXTENSIONS

# ====================== 配置 mistune 渲染器(支持 mermaid) ======================
class MermaidRenderer(HTMLRenderer):
    """自定义渲染器,支持 mermaid 语法"""
    def block_code(self, code: str, info: str = '') -> str:
        if info and info.strip() == 'mermaid':
            return f'<div class="mermaid">[code]</div>'
        # 调用父类默认实现
        return super().block_code(code, info)

# 创建 mistune 实例
md_renderer = MermaidRenderer()
md_parser = mistune.create_markdown(renderer=md_renderer,
    plugins=['strikethrough', 'table', 'url'],
    escape=False       # 关键配置:禁用字符转义
    )

# ====================== 读取静态 HTML 模板(缓存) ======================
def get_index_html() -> str:
    """读取并缓存 index2.html 内容"""
    index_path = Path(__file__).parent / 'static' / 'index2.html'
    if not index_path.exists():
        raise FileNotFoundError(f"index2.html 不存在: {index_path}")
    with open(index_path, 'r', encoding='utf-8') as f:
        return f.read()

index_html = get_index_html()

# ====================== 接口:获取MD文件列表(支持自定义目录) ======================
@app.route('/get-md-files', methods=['GET'])
def get_md_files():
    try:
        # 获取并验证目录路径
        dir_path = validate_and_normalize_dir(request.args.get('dirPath', ''))
        
        # 读取目录下所有.md文件(排除隐藏文件)
        files = []
        for file in dir_path.iterdir():
            if file.is_file() and allowed_file(file.name) and not file.name.startswith('.'):
                files.append(file.name)
        
        # 按名称排序
        files.sort()
        return jsonify(files)
    
    except ValueError as e:
        app.logger.error(f"读取文件列表失败: {e}")
        return jsonify({'error': str(e)}), 400
    except Exception as e:
        app.logger.error(f"读取文件列表异常: {e}")
        return jsonify({'error': f'服务器内部错误: {str(e)}'}), 500

# ====================== 接口:加载指定MD文件并渲染(支持自定义目录) ======================
@app.route('/load-md-file', methods=['GET'])
def load_md_file():
    try:
        # 验证文件名参数
        filename = request.args.get('filename', '')
        if not filename or '..' in filename or not allowed_file(filename):
            return jsonify({'error': '无效的文件名'}), 400
        
        # 解码文件名(处理URL编码)
        filename = request.args.get('filename', '').encode('utf-8').decode('unicode_escape')
        
        # 获取并验证目录路径
        dir_path = validate_and_normalize_dir(request.args.get('dirPath', ''))
        file_path = dir_path / secure_filename(filename)
        
        # 检查文件是否存在
        if not file_path.exists() or not file_path.is_file():
            return jsonify({'error': f'文件不存在: {filename}'}), 404
        
        # 检查文件是否可读
        if not os.access(file_path, os.R_OK):
            return jsonify({'error': f'没有文件读取权限: {filename}'}), 403
        
        # 读取文件内容并渲染为HTML
        with open(file_path, 'r', encoding='utf-8') as f:
            md_content = f.read()
        rendered_html = md_parser(md_content)
        
        return rendered_html
    
    except ValueError as e:
        app.logger.error(f"加载MD文件失败: {e}")
        return jsonify({'error': str(e)}), 400
    except Exception as e:
        app.logger.error(f"加载MD文件异常: {e}")
        return jsonify({'error': f'服务器内部错误: {str(e)}'}), 500

# ====================== 原有接口 ======================
# 1. 首页:返回上传页面
@app.route('/')
def index():
    return send_file(Path(__file__).parent / 'static' / 'index2.html')

# 2. 处理 Markdown 文件上传 + 解析 + 渲染
@app.route('/upload-md', methods=['POST'])
def upload_md():
    try:
        # 检查是否有文件上传
        if 'mdFile' not in request.files:
            return '<h1>错误</h1><p>请选择要上传的 Markdown 文件!</p>', 400
        
        file = request.files['mdFile']
        # 检查文件名是否为空
        if file.filename == '':
            return '<h1>错误</h1><p>请选择要上传的 Markdown 文件!</p>', 400
        
        # 检查文件类型
        if file and allowed_file(file.filename):
            # 读取文件内容
            md_content = file.read().decode('utf-8')
            # 渲染为HTML
            rendered_html = md_parser(md_content)
            # 替换模板中的占位符
            final_html = index_html.replace('<div id="markdown-content"></div>', rendered_html)
            
            response = make_response(final_html)
            response.headers['Content-Type'] = 'text/html; charset=utf-8'
            return response
        
        # 文件类型不允许
        return '<h1>错误</h1><p>仅支持上传 .md 或 .markdown 格式的文件!</p>', 400
    
    except Exception as e:
        app.logger.error(f"处理上传文件出错: {e}")
        error_html = f"""
            <h1>出错了</h1>
            <p>处理上传的 Markdown 文件失败:{str(e)}</p>
            <a href="/" rel="external nofollow" >返回上传页面</a>
        """
        return error_html, 500

# 静态文件服务(兼容原逻辑)
@app.route('/<path:filename>')
def serve_static(filename):
    return send_from_directory(app.static_folder, filename)

# ====================== 启动服务 ======================
if __name__ == '__main__':
    # 注册 mimetypes(确保js文件正确解析)
    mimetypes.add_type('application/javascript', '.js')
    
    print(f"服务已启动:http://localhost:8000")
    print(f"默认MD文件目录:{DEFAULT_MD_DIR.absolute()}")
    print(f"访问 http://localhost:8000/index2.html 即可浏览和渲染MD文件")
    
    # 启动Flask服务(调试模式,生产环境请关闭)
    app.run(host='127.0.0.1', port=8000, debug=True)

配套依赖安装

创建 requirements.txt 文件:

Flask==3.0.3
mistune==2.0.5
python-multipart==0.0.6
Werkzeug==3.0.3

安装依赖:

pip install -r requirements.txt

关键说明(与原 Node.js 版本对齐)

功能完全对齐

  • 支持自定义目录加载 MD 文件列表
  • 加载并渲染指定 MD 文件(支持 Mermaid 图表)
  • 上传 MD 文件并实时渲染
  • 路径安全校验(防止路径遍历、权限检查)
  • 文件类型/大小限制

核心差异适配

  • Node.js 的 markdown-it → Python 的 mistune(自定义渲染器支持 Mermaid)
  • Node.js 的 multer → Flask 原生 + python-multipart 处理文件上传
  • Node.js 的 fs/path → Python 的 pathlib/os 处理文件系统
  • 错误处理、响应格式完全对齐原接口

部署注意事项

  • 需要保持原有的 static 目录结构(包含 index2.htmlmermaid.min.js
  • md-files 目录会自动创建,用于存放默认 MD 文件
  • 生产环境请关闭 debug=True,并使用 Gunicorn 等 WSGI 服务器运行

目录结构要求

TREE /F
md-mermaid
├── server.py          # Python 服务文件
├── static/            # 静态文件目录
│   ├── index2.html    # 前端页面(复用原文件)
│   └── mermaid.min.js # Mermaid 渲染脚本
├── md-files/          # 默认 MD 文件目录(自动创建)
└── requirements.txt   # 依赖文件

运行方式

python server.py

访问 http://localhost:8000 即可使用,功能与原 Node.js 版本完全一致。

以上就是python使用Flask 3实现渲染指定目录下Md文件的详细内容,更多关于python渲染Md文件的资料请关注脚本之家其它相关文章!

相关文章

  • Python数据结构之递归方法详解

    Python数据结构之递归方法详解

    这篇文章主要为大家介绍了递归的基本概念以及如何构建递归程序。通过本章的学习,大家可以理解递归的基本概念,了解递归背后蕴含的编程思想以及掌握构建递归程序的方法,需要的可以参考一下
    2022-04-04
  • Python爬虫之Requests库基本使用详解

    Python爬虫之Requests库基本使用详解

    这篇文章主要介绍了Python爬虫之Requests库基本使用详解,Requests 库是在 urllib 模块的基础上开发而来,继承了urllib.request的所有特性,与urllib.request 相比,Requests 在使用时更加简洁方便、快捷,所以 Requests 库在编写爬虫程序时使用较多,需要的朋友可以参考下
    2023-09-09
  • 利用Python中xlwt模块操作excel的示例详解

    利用Python中xlwt模块操作excel的示例详解

    在开发中,我们最常见的数据问题之一,就是对数据进行导出整理给运营人员,所以操作excel就显得重中之重,在python中操作excel可以借助xlwt模块。感兴趣的可以跟随小编一起学习一下这篇文章
    2022-01-01
  • Django 解决阿里云部署同步数据库报错的问题

    Django 解决阿里云部署同步数据库报错的问题

    这篇文章主要介绍了Django 解决阿里云部署同步数据库报错的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-05-05
  • Python中声明只包含一个元素的元组数据方法

    Python中声明只包含一个元素的元组数据方法

    这篇文章主要介绍了Python中声明只包含一个元素的元组数据方法,本文是实际经验总结而来,没有碰到这个需要可能不会注意到这个问题,需要的朋友可以参考下
    2014-08-08
  • python requests模块封装详解

    python requests模块封装详解

    requests是一个常用的HTTP请求库,可以方便地向网站发送HTTP请求,并获取响应结果,本文主要和大家介绍一下requests模块的使用与封装,需要的可以参考下
    2023-09-09
  • Python GUI之如何使用tkinter控件

    Python GUI之如何使用tkinter控件

    今天带大家学习Python GUI的相关知识,文中对如何使用tkinter控件作了非常详细的介绍及代码示例,对正在学习python的小伙伴们有很好的帮助,需要的朋友可以参考下
    2021-05-05
  • python基础之集合

    python基础之集合

    这篇文章主要介绍了python集合,实例分析了Python中返回一个返回值与多个返回值的方法,需要的朋友可以参考下
    2021-10-10
  • Pandas DataFrame.drop()删除数据的方法实例

    Pandas DataFrame.drop()删除数据的方法实例

    pandas作为数据分析强大的库,是基于numpy数组构建的,专门用来处理表格和混杂的数据,下面这篇文章主要给大家介绍了关于Pandas DataFrame.drop()删除数据的相关资料,需要的朋友可以参考下
    2022-07-07
  • Python typing_extensions介绍: NotRequired和TypedDict基本概念和使用方法

    Python typing_extensions介绍: NotRequired和TypedDict基

    文章介绍了Python的类型提示模块typing_extensions中的两个工具:NotRequired`和`TypedDict,本文结合实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2025-12-12

最新评论