Переглянути джерело

Merge remote-tracking branch 'origin/master'

# Conflicts:
#	main.py
zrd 3 місяців тому
батько
коміт
aa86faf0ce
4 змінених файлів з 543 додано та 3 видалено
  1. 122 3
      main.py
  2. 256 0
      utils/markd_pdf/markdown_pdf.py
  3. 105 0
      utils/markd_pdf/minio_upload.py
  4. 60 0
      utils/markd_pdf/save_markdown.py

+ 122 - 3
main.py

@@ -7,6 +7,11 @@ from utils import dmQuery
 from utils.shellUtil import execute_command_on_linux
 from utils.text_to_html import save_html_to_file
 
+from utils.markd_pdf.markdown_pdf import *
+from utils.markd_pdf.minio_upload import *
+from utils.markd_pdf.save_markdown import *
+
+
 app = FastAPI() # 创建API实例
 
 @app.get("/")
@@ -14,9 +19,6 @@ async def root():
     return {"message": "Hello World"}
 
 
-
-
-
 # 将数据模型定义为继承BaseModel的类
 class Item(BaseModel):
     sql: str
@@ -32,6 +34,18 @@ class Saved2text(BaseModel):
     htmlContent: str
     fileName: str
 
+class MarkdownRequest(BaseModel):
+    content: str    #Markdown文本内容
+    output_dir: Optional[str] = os.getcwd()  # 输出文件保存目录(可选),默认保存在当前目录
+    keep_docx: Optional[bool] = False   #是否保留中间生成的DOCX文件(可选),仅PDF转换时有效
+
+class MinIORequest(BaseModel):
+    file_path: str  #本地文件路径,例如:D:/output/test.pdf
+    object_name: Optional[str] = None   #MinIO中存储的文件名(可选),默认使用本地文件名
+
+class MinIODownloadRequest(BaseModel):
+    object_name: str    # MinIO中要下载的文件名
+    local_file_path: Optional[str] = None   # 本地保存路径(可选),默认保存在当前目录
 
 @app.put("/exsql")
 async def create_item( item: Item, q: str = None):
@@ -75,5 +89,110 @@ async def saved2text(htmlContent: str = Form(...), fileName: str = Form(...)):
     return result
 
 
+@app.get("/test")
+async def test_api():
+    """
+    测试接口
+    :return: 包含测试信息的JSON响应
+    """
+    return {
+        "status": "success",
+        "message": "服务运行正常",
+        "timestamp": os.path.getmtime(__file__),
+        "version": "1.0.0"
+    }
+
+
+@app.post("/convert/pdf",
+          summary="Markdown转PDF",
+          description="将Markdown文本内容转换为PDF文件,支持中文和代码高亮")
+async def convert_to_pdf(request: MarkdownRequest):
+    try:
+        # 创建临时文件保存Markdown内容
+        temp_md = "temp.md"
+        with open(temp_md, "w", encoding="utf-8") as f:
+            f.write(request.content)
+
+        # 转换PDF
+        pdf_path = markdown_to_pdf(temp_md, request.output_dir)
+
+        # 清理临时文件
+        os.remove(temp_md)
+
+        return {"status": "success", "pdf_path": pdf_path}
+    except Exception as e:
+        raise HTTPException(status_code=500, detail=str(e))
+
+
+@app.post("/convert/docx",
+          summary="Markdown转DOCX",
+          description="将Markdown文本内容转换为DOCX文件,保留原始格式")
+async def convert_to_docx(request: MarkdownRequest):
+    try:
+        # 创建临时文件保存Markdown内容
+        temp_md = "temp.md"
+        with open(temp_md, "w", encoding="utf-8") as f:
+            f.write(request.content)
+
+        # 转换DOCX
+        docx_path = markdown_to_docx(temp_md, request.output_dir)
+
+        # 清理临时文件
+        os.remove(temp_md)
+
+        return {"status": "success", "docx_path": docx_path}
+    except Exception as e:
+        raise HTTPException(status_code=500, detail=str(e))
+
+
+@app.post("/save/markdown",
+          summary="保存Markdown文件",
+          description="将Markdown内容保存为本地文件")
+async def save_markdown_file(request: MarkdownRequest):
+    try:
+        # 使用当前时间戳作为默认文件名
+        import time
+        filename = f"markdown_{int(time.time())}"
+
+        # 保存文件
+        filepath = save_markdown(request.content, filename, request.output_dir)
+
+        return {
+            "status": "success",
+            "message": "Markdown文件保存成功",
+            "file_path": filepath
+        }
+    except Exception as e:
+        raise HTTPException(status_code=500, detail=str(e))
+
+
+@app.post("/minio/upload",
+          summary="上传文件到MinIO",
+          description="将本地文件上传到MinIO存储服务")
+async def upload_file(request: MinIORequest):
+    try:
+        success = upload_to_minio(request.file_path, request.object_name)
+        if success:
+            return {"status": "success", "message": "文件上传成功"}
+        else:
+            raise HTTPException(status_code=500, detail="文件上传失败")
+    except Exception as e:
+        raise HTTPException(status_code=500, detail=str(e))
+
+
+@app.post("/minio/download",
+          summary="从MinIO下载文件",
+          description="从MinIO存储服务下载文件到本地")
+async def download_file(request: MinIODownloadRequest):
+    try:
+        success = download_from_minio(request.object_name, request.local_file_path)
+        if success:
+            return {"status": "success", "message": "文件下载成功"}
+        else:
+            raise HTTPException(status_code=500, detail="文件下载失败")
+    except Exception as e:
+        raise HTTPException(status_code=500, detail=str(e))
+
+
 if __name__ == "__main__":
     uvicorn.run(app, host="0.0.0.0", port=29015, timeout_keep_alive=600)

+ 256 - 0
utils/markd_pdf/markdown_pdf.py

@@ -0,0 +1,256 @@
+import os
+import subprocess
+from pathlib import Path
+import tempfile
+import shutil
+
+# 设置pandoc路径
+PANDOC_PATH = r"D:\work\pandoc-3.6.4-windows-x86_64\pandoc-3.6.4\pandoc.exe"
+
+
+def check_dependencies():
+    """检查必要的依赖是否已安装"""
+    # 检查 pandoc
+    if not os.path.exists(PANDOC_PATH):
+        raise Exception(f"未找到 pandoc,请检查路径是否正确: {PANDOC_PATH}")
+    print(f"找到 pandoc: {PANDOC_PATH}")
+
+    # 测试 pandoc 命令
+    try:
+        result = subprocess.run([PANDOC_PATH, '--version'], capture_output=True, text=True, encoding='utf-8')
+        print(f"Pandoc 版本信息:\n{result.stdout}")
+    except Exception as e:
+        print(f"测试 pandoc 命令时出错: {str(e)}")
+        raise
+
+    # 检查 xelatex
+    if shutil.which('xelatex') is None:
+        raise Exception("未找到 xelatex,请先安装 MiKTeX:https://miktex.org/download")
+    print("找到 xelatex")
+
+    # 检查 pdflatex
+    if shutil.which('pdflatex') is None:
+        print("警告:未找到 pdflatex")
+    else:
+        print("找到 pdflatex")
+
+
+def create_latex_template():
+    """创建支持中文的LaTeX模板"""
+    template = r"""\documentclass{article}
+\usepackage[UTF8]{ctex}
+\usepackage{listings}
+\usepackage{color}
+\usepackage{enumitem}
+\usepackage{booktabs}
+\usepackage{longtable}
+\usepackage{hyperref}
+\usepackage{amsmath}
+
+% 定义tightlist命令
+\newcommand{\tightlist}{%
+  \setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}
+
+\lstset{
+    basicstyle=\ttfamily,
+    breaklines=true,
+    frame=single,
+    numbers=left,
+    numberstyle=\tiny,
+    keywordstyle=\color{blue},
+    commentstyle=\color{green!60!black},
+    stringstyle=\color{red}
+}
+
+\begin{document}
+$body$
+\end{document}"""
+
+    # 使用临时文件
+    with tempfile.NamedTemporaryFile(suffix='.tex', delete=False, mode='w', encoding='utf-8') as f:
+        f.write(template)
+        return f.name
+
+
+def markdown_to_docx(markdown_file: str, output_docx: str = None) -> str:
+    """
+    将Markdown文件转换为DOCX文件
+
+    Args:
+        markdown_file (str): Markdown文件的路径
+        output_docx (str, optional): 输出的DOCX文件路径。如果为None,则使用与输入文件相同的名称
+
+    Returns:
+        str: 生成的DOCX文件路径
+    """
+    # 获取绝对路径
+    markdown_file = os.path.abspath(markdown_file)
+    print(f"输入文件绝对路径: {markdown_file}")
+
+    # 检查输入文件是否存在
+    if not os.path.exists(markdown_file):
+        raise FileNotFoundError(f"Markdown文件不存在: {markdown_file}")
+    print(f"输入文件存在: {markdown_file}")
+
+    # 如果没有指定输出文件,则使用与输入文件相同的名称
+    if output_docx is None:
+        output_docx = os.path.splitext(markdown_file)[0] + '.docx'
+    else:
+        output_docx = os.path.abspath(output_docx)
+    print(f"输出DOCX文件路径: {output_docx}")
+
+    # 确保输出目录存在
+    output_dir = os.path.dirname(output_docx)
+    if not os.path.exists(output_dir):
+        os.makedirs(output_dir)
+        print(f"创建输出目录: {output_dir}")
+
+    try:
+        # 使用pandoc将markdown转换为docx
+        cmd = [
+            PANDOC_PATH,
+            markdown_file,
+            '-o',
+            output_docx,
+            '--standalone',  # 生成独立的文档
+            '--wrap=preserve',  # 保持原始换行
+            '--highlight-style=tango',  # 代码高亮样式
+            '--metadata', 'lang=zh-CN'  # 设置文档语言
+        ]
+
+        print(f"\n执行转换命令: {' '.join(cmd)}")
+
+        # 使用utf-8编码执行命令
+        result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8')
+        print(f"命令输出: {result.stdout}")
+        if result.stderr:
+            print(f"命令警告: {result.stderr}")
+
+        # 检查DOCX是否生成
+        if os.path.exists(output_docx):
+            print(f"DOCX文件已成功生成: {output_docx}")
+            return output_docx
+        else:
+            raise Exception(f"DOCX文件未生成: {output_docx}")
+
+    except subprocess.CalledProcessError as e:
+        print(f"命令执行错误: {e}")
+        print(f"错误输出: {e.output if hasattr(e, 'output') else '无输出'}")
+        raise Exception(f"转换过程中出错: {str(e)}")
+    except Exception as e:
+        print(f"发生错误: {str(e)}")
+        raise Exception(f"发生未知错误: {str(e)}")
+
+
+def markdown_to_pdf(markdown_file: str, output_pdf: str = None) -> str:
+    """
+    将Markdown文件转换为PDF文件
+
+    Args:
+        markdown_file (str): Markdown文件的路径
+        output_pdf (str, optional): 输出的PDF文件路径。如果为None,则使用与输入文件相同的名称
+
+    Returns:
+        str: 生成的PDF文件路径
+    """
+    # 检查依赖
+    check_dependencies()
+
+    # 获取绝对路径
+    markdown_file = os.path.abspath(markdown_file)
+    print(f"输入文件绝对路径: {markdown_file}")
+
+    # 检查输入文件是否存在
+    if not os.path.exists(markdown_file):
+        raise FileNotFoundError(f"Markdown文件不存在: {markdown_file}")
+    print(f"输入文件存在: {markdown_file}")
+
+    # 如果没有指定输出文件,则使用与输入文件相同的名称
+    if output_pdf is None:
+        output_pdf = os.path.splitext(markdown_file)[0] + '.pdf'
+    else:
+        output_pdf = os.path.abspath(output_pdf)
+    print(f"输出PDF文件路径: {output_pdf}")
+
+    # 确保输出目录存在
+    output_dir = os.path.dirname(output_pdf)
+    if not os.path.exists(output_dir):
+        os.makedirs(output_dir)
+        print(f"创建输出目录: {output_dir}")
+
+    try:
+        # 创建支持中文的LaTeX模板
+        latex_template = create_latex_template()
+        print(f"LaTeX模板文件: {latex_template}")
+
+        # 使用pandoc直接将markdown转换为pdf
+        cmd = [
+            PANDOC_PATH,
+            markdown_file,
+            '-o',
+            output_pdf,
+            '--pdf-engine=xelatex',
+            f'--template={latex_template}',
+            '--listings',
+            '--highlight-style=tango',
+            '--verbose'
+        ]
+
+        print(f"\n执行转换命令: {' '.join(cmd)}")
+
+        # 使用utf-8编码执行命令
+        result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8')
+        print(f"命令输出: {result.stdout}")
+        if result.stderr:
+            print(f"命令警告: {result.stderr}")
+
+        # 检查PDF是否生成
+        if os.path.exists(output_pdf):
+            print(f"PDF文件已成功生成: {output_pdf}")
+            return output_pdf
+        else:
+            raise Exception(f"PDF文件未生成: {output_pdf}")
+
+    except subprocess.CalledProcessError as e:
+        print(f"命令执行错误: {e}")
+        print(f"错误输出: {e.output if hasattr(e, 'output') else '无输出'}")
+        raise Exception(f"转换过程中出错: {str(e)}")
+    except Exception as e:
+        print(f"发生错误: {str(e)}")
+        raise Exception(f"发生未知错误: {str(e)}")
+    finally:
+        # 清理临时文件
+        if os.path.exists(latex_template):
+            try:
+                os.remove(latex_template)
+                print(f"已删除临时模板文件: {latex_template}")
+            except Exception as e:
+                print(f"删除临时文件时出错: {str(e)}")
+
+
+if __name__ == "__main__":
+    # 使用示例
+    try:
+        # 获取当前脚本所在目录
+        current_dir = os.path.dirname(os.path.abspath(__file__))
+        print(f"当前工作目录: {current_dir}")
+
+        # 示例:转换当前目录下的test.md文件
+        input_file = os.path.join(current_dir, "test.md")
+
+        # 转换为DOCX
+        docx_file = os.path.join(current_dir, "output.docx")
+        print(f"\n开始转换为DOCX...")
+        docx_path = markdown_to_docx(input_file, docx_file)
+        print(f"DOCX转换成功!")
+        print(f"DOCX文件已保存至: {docx_path}")
+
+        # 转换为PDF
+        pdf_file = os.path.join(current_dir, "output.pdf")
+        print(f"\n开始转换为PDF...")
+        pdf_path = markdown_to_pdf(input_file, pdf_file)
+        print(f"PDF转换成功!")
+        print(f"PDF文件已保存至: {pdf_path}")
+
+    except Exception as e:
+        print(f"转换失败: {str(e)}")

+ 105 - 0
utils/markd_pdf/minio_upload.py

@@ -0,0 +1,105 @@
+from minio import Minio
+from minio.error import S3Error
+import os
+
+# MinIO 配置
+MINIO_SERVER = "192.168.0.25:9000"  # 服务器地址(无http前缀)
+ACCESS_KEY = "minioadmin"  # 默认Access Key
+SECRET_KEY = "minioadmin"  # 默认Secret Key
+BUCKET_NAME = "szxc"  # 你的Bucket名称
+
+
+def upload_to_minio(local_file_path, object_name=None):
+    """
+    上传文件到MinIO
+    :param local_file_path: 本地文件路径(如 "C:\\data\\test.txt")
+    :param object_name: 存储在MinIO中的名称(可选,默认使用本地文件名)
+    """
+    # 初始化客户端
+    client = Minio(
+        MINIO_SERVER,
+        access_key=ACCESS_KEY,
+        secret_key=SECRET_KEY,
+        secure=False  # HTTP模式(如果是HTTPS则设为True)
+    )
+
+    try:
+        # 自动创建Bucket(如果不存在)
+        if not client.bucket_exists(BUCKET_NAME):
+            client.make_bucket(BUCKET_NAME)
+            print(f"[INFO] Bucket '{BUCKET_NAME}' 创建成功")
+
+        # 如果未指定object_name,则使用本地文件名
+        if object_name is None:
+            object_name = os.path.basename(local_file_path)
+
+        # 上传文件
+        client.fput_object(
+            BUCKET_NAME,
+            object_name,
+            local_file_path
+        )
+        print(f"[SUCCESS] 文件 '{local_file_path}' 已上传到 '{BUCKET_NAME}/{object_name}'")
+        return True
+
+    except S3Error as e:
+        print(f"[ERROR] MinIO操作失败: {e}")
+    except Exception as e:
+        print(f"[ERROR] 其他错误: {e}")
+    return False
+
+
+def download_from_minio(object_name, local_file_path=None):
+    """
+    从MinIO下载文件
+    :param object_name: MinIO中的文件名称
+    :param local_file_path: 本地保存路径(可选,默认使用当前目录下的同名文件)
+    :return: 下载成功返回True,失败返回False
+    """
+    # 初始化客户端
+    client = Minio(
+        MINIO_SERVER,
+        access_key=ACCESS_KEY,
+        secret_key=SECRET_KEY,
+        secure=False
+    )
+
+    try:
+        # 检查Bucket是否存在
+        if not client.bucket_exists(BUCKET_NAME):
+            print(f"[ERROR] Bucket '{BUCKET_NAME}' 不存在")
+            return False
+
+        # 如果未指定本地保存路径,则使用当前目录下的同名文件
+        if local_file_path is None:
+            local_file_path = os.path.join(os.getcwd(), object_name)
+
+        # 确保目标目录存在
+        os.makedirs(os.path.dirname(local_file_path), exist_ok=True)
+
+        # 下载文件
+        client.fget_object(
+            BUCKET_NAME,
+            object_name,
+            local_file_path
+        )
+        print(f"[SUCCESS] 文件 '{BUCKET_NAME}/{object_name}' 已下载到 '{local_file_path}'")
+        return True
+
+    except S3Error as e:
+        print(f"[ERROR] MinIO操作失败: {e}")
+    except Exception as e:
+        print(f"[ERROR] 其他错误: {e}")
+    return False
+
+
+# 示例用法
+if __name__ == "__main__":
+    # # 上传文件示例
+    # local_file = r"D:\work\codemyself\dify\output.pdf"  # 使用原始字符串避免转义问题
+    # upload_to_minio(local_file)  # 使用默认object_name
+
+    # 下载文件示例
+    object_name = "output.pdf"  # MinIO中的文件名
+    download_from_minio(object_name)  # 下载到当前目录
+    # 或指定保存路径:download_from_minio(object_name, "path/to/save/file.pdf")

+ 60 - 0
utils/markd_pdf/save_markdown.py

@@ -0,0 +1,60 @@
+import os
+from typing import Optional
+
+
+def save_markdown(content: str, filename: str, directory: Optional[str] = None) -> str:
+    """
+    将字符串内容保存为Markdown文件
+
+    :param content: 要保存的Markdown内容
+    :param filename: 文件名(不需要.md后缀)
+    :param directory: 保存目录(可选,默认为当前目录)
+    :return: 保存的文件完整路径
+    """
+    try:
+        # 确保文件名以.md结尾
+        if not filename.endswith('.md'):
+            filename += '.md'
+
+        # 确定保存路径
+        if directory:
+            # 确保目录存在
+            os.makedirs(directory, exist_ok=True)
+            filepath = os.path.join(directory, filename)
+        else:
+            filepath = filename
+
+        # 写入文件
+        with open(filepath, 'w', encoding='utf-8') as f:
+            f.write(content)
+
+        print(f"[SUCCESS] Markdown文件已保存到: {filepath}")
+        return filepath
+
+    except Exception as e:
+        print(f"[ERROR] 保存文件时出错: {str(e)}")
+        raise
+
+
+# 使用示例
+if __name__ == "__main__":
+    # 示例内容
+    markdown_content = """# 测试标题
+
+这是一个测试的Markdown文件。
+
+## 二级标题
+
+- 列表项1
+- 列表项2
+
+```python
+print("Hello, World!")
+```
+"""
+    print(markdown_content)
+    # 保存到当前目录
+    save_markdown(markdown_content, "test")
+
+    # 保存到指定目录
+    # save_markdown(markdown_content, "test", "D:/output")