一文多发助手2.0技术文档
一文多发助手2.0技术文档
一、技术实现
1.1 整体架构
2.0版本采用"Dify workflow + Node.js客户端"的双层架构:
用户
↓
Node.js(本地)
↓ HTTP API调用
Dify Workflow(云端)
↓
各平台发布接口
架构分层
| 层级 | 职责 |
|---|---|
| Dify层 | 负责内容处理的核心逻辑,包括文档获取、图片处理、AI内容优化、平台适配、Github发布 |
| Node层 | 负责本地文件读取、用户交互、调用Dify API、调用wenyan-mcp发布公众号 |
架构优势
相比1.0版本的Coze平台:
- 工作流编排能力更强,支持更复杂的处理逻辑
- 可以发布为API供外部调用,实现本地文件操作
- 支持MCP协议,可以集成wenyan-mcp实现公众号自动发布
- 插件生态更丰富,易于调试和扩展
1.2 技术选型
| 组件 | 选择 | 原因 |
|---|---|---|
| 工作流编排 | Dify 云端(开源) | 支持复杂工作流、插件生态、易于调试,且可发布为 API 供外部调用 |
| 文档解析 | TextIn API (第三方) / 飞书文档插件 | Coze插件LinkReaderPlugin无法保留图片;TextIn可提取图片并保留超链接 |
| 图片存储 | 阿里云 OSS | 低成本、稳定,支持预签名 URL(需处理+号问题) |
| 内容增强模型 | DeepSeek v4 Flash + 部分GPT-4o | 推理速度快、中文处理能力强、成本相对较低 |
| 路径生成 (标题转图片目录) | 大模型节点 (GLM-4-flash) | 避免依赖第三方拼音API,token消耗少 (约150 tokens) |
| 发布博客 | GitHub Contents API (通过Dify工作流) | 直接提交Markdown到Hexo仓库,触发自动构建(Github Actions) |
| 发布公众号 | wenyan-mcp (本地CLI / Server模式) | 开源免费,支持主题切换,试用结果比浏览器插件wechatsync优秀 |
| 外部调用框架 | Claude Code + Node.js + undici | CC从0-1开始搭建项目,通过自定义Node.js客户端调用Dify工作流API,使用ProxyAgent解决网络代理问题 |
模型选型说明
工作流中的LLM节点使用DeepSeek v4 Flash作为主力模型,选型理由:
- 推理速度快,适合工作流中的批处理场景
- 中文内容处理能力强
- 成本相对较低,适合高频调用
注: 在1.0版本调研阶段,对豆包、DeepSeek、Kimi等模型进行了对比测试,DeepSeek在内容编排任务上表现最为稳定。
二、Dify工作流详解
2.1 总体工作流
工作流分为三条分支,最终汇聚为统一的发布流程:
2.1.1 流程总览
输入入口
├── 分支A:飞书链接
├── 分支B:有外链的Markdown文档
└── 分支C:本地Obsidian文档
↓
文档获取 / 文本提取 / 图片处理
↓
内容编排
↓
Markdown 图片链接批量替换
↓
内容适配
↓
GitHub 发布
↓
输出节点
2.1.2 三条输入路径
1、分支A:飞书链接
使用飞书文档插件get_document_content获取Markdown(仅文字+表格,图片丢失)
2、分支B:有外链的Markdown文档
用户上传单个Markdown文件 → 文档提取器 → 提取文本 → 直接进入内容编排
3、分支C:本地Obsidian文档
用户上传文件列表(markdown文档 + 图片)→ 列表操作节点分离文档和图片
文档部分:提取Markdown内容并获取标题 → 内容编排
图片部分:图片名称规范化(空格→下划线)→ 迭代节点上传OSS → 生成映射 → 在Markdown中替换图片链接
2.1.3 节点分组说明
👆入口与文档获取
1、用户输入(开始节点)
| 字段 | 说明 |
|---|---|
InputWay |
输入方式(必填) |
feishuUrl |
飞书链接(选填) |
mdWithExternalLinks |
有外链的 Markdown 文档(选填) |
mdInObsidian |
本地 Obsidian 文档(选填) |
2、获取飞书云文档的内容(插件工具)
调用飞书开放平台 API 获取文档内容,输出 Markdown 格式,目前仅支持文字和简易表格的转换。
3、文档提取器(Dify 内置节点)
解析上传的 Markdown 文件,提取其中的文本内容(图片以外链形式存在)。该节点本质上是文本提取,不保留二进制嵌入资源,因此无法解析图片。
✌️本地Obsidian文档处理
1、提取文档(列表操作节点)
当用户选择本地 Obsidian 文档输入方式时,从所有上传文件中筛选并提取 Markdown 文件。该节点支持筛选、排序和选择特定元素处理数组,适用于混合文件上传或需要在下游处理前进行数据分离的场景。
⚠️ 上传限制:文档 < 15 MB,图片 < 10 MB,总文件数不超过 10 个。
2、获取文件标题(代码节点)
通过 Python 代码从 Markdown 文件中提取文档标题。
3、提取 MD 文档(文档提取器节点)
解析上传的 Markdown 文档并提取文本内容。与前一文档提取器不同,此处图片以  形式嵌入,且图片命名有特定要求。
4、获取图片上传路径(LLM 节点)
使用 deepseek-v4-flash 模型,基于文件标题自动生成图片上传路径。转换规则如下:
1. 去掉文件后缀
2. 中文:取前 8 个汉字转拼音,每个首字母大写
3. 英文:取前 8 个单词,每个首字母大写,去除空格
4. 输出格式:`images/转换结果/`
**示例**:
`深度学习入门指南.md` → `images/ShenDuXueXiRu/`
`my first article.docx` → `images/MyFirstArticle/`
需在 user prompt 中引入上下文(文件标题)。
5、图片名称规范化(代码节点)
将图片文件名中的空格替换为下划线,避免 URL 编码问题。
6、迭代(迭代节点)
循环处理每张图片,依次执行上传至 OSS 并生成链接映射。
🤟汇聚与后处理
1、变量聚合器(聚合节点)
将「飞书链接」和「带外链的 Markdown 文档」两条分支路线收口,聚合为一个统一变量,供后续节点使用。
2、内容编排(工具节点)
调用「内容编排」自定义工作流导出的工具。
3、Markdown 图片链接批量替换(工具节点)
调用「Markdown 图片链接批量替换」工作流导出的工具。
4、内容适配(工具节点)
调用「内容适配」工作流导出的工具。
5、GitHub 发布(工具节点)
调用「Github 发布」工作流导出的工具。
6、输出节点(结束节点)
工作流终点,返回以下结果:
md_content:处理后的Markdown内容- Github相关发布结果
2.2 内容编排工作流(content_composer)
获取markdown文档中的内容后,格式优化节点负责排版、内容提取节点生成摘要和标签、随机图片节点获取封面图。
2.2.1 格式优化节点
system prompt:
# 角色
你是一个专业的技术文章编辑,负责优化Markdown文档的格式和排版。
## 优化规则
**段落优化**
- 超过200字的段落,在合适位置拆分为2-3段
- 补充段落间的过渡句,使行文更流畅
- 段落之间保持一个空行
**列表优化**
- 并列关系内容转为无序列表(- item)
- 步骤类内容转为有序列表(1. step)
- 列表项不超过1行时,删除多余标点
- 列表前后各保留一个空行
**强调优化**
- 核心术语、关键结论用 **加粗**
- 注意事项用 `> ⚠️` 引用块
- 重要提示用 `> 💡` 引用块
**代码块优化**
- 确保所有代码块标注语言类型
- 代码块前后各保留一个空行
- 行内代码用反引号包裹
**结构优化**
- 如果缺少一级标题,根据内容生成一个
- 确保标题层级正确(# → ## → ###,不跳级)
- 超过1500字的文章,在适当位置添加 `---` 分割线
- 标题与正文之间保留一个空行
**图片处理**
- 保留所有图片链接,不做任何修改
- 确保图片语法正确:``
- 图片前后各保留一个空行
**公式处理**
- 保留所有LaTeX公式的原始格式
- 块级公式使用 `$$公式$$` 格式
- 行内公式使用 `$公式$` 格式
- 不要将公式转换为代码块
## 限制
- ❌ 不修改知识内容本身
- ❌ 不改变代码块内容
- ❌ 不删除或修改任何图片、公式、表格
- ❌ 不改变公式的格式(保持 $$ 或 $ 包裹)
- ✅ 只优化格式和排版
## 输出
直接输出优化后的完整Markdown内容,不要添加任何解释或说明。
模型选型
| 模型 | 优点 | 缺点 | 是否采用 |
|---|---|---|---|
| Claude 3.5 Sonnet | 格式处理能力最强;对markdown语法理解最准确;细节把控好;中文处理优秀;保真度高 | 成本高 | 否 |
| GPT-4o | 比Claude便宜,比GPT-4快,性价比高;格式优化稳定;JSON输出可靠 | 有时会过度优化(比如把公式放置在代码块) 对中文的细腻度略逊于Claude | ✅ 采用 |
| GPT-4 | - | 将公式改成了代码块格式,未在公式符号中包裹 | 否 |
2.2.2 内容提取节点
system prompt
# 角色
你是一个专业的内容分析师,负责提取文档的关键信息和元数据。
# 任务
分析以下Markdown文档,提取元数据用于内容管理和SEO优化。
## 提取要求
**title**
- 优先使用原文一级标题(# 标题)
- 如果没有,根据内容生成一个吸引人的标题
- 不超过30字,简洁有力
**summary**
- 100字以内,突出核心观点和价值
- 让读者一眼看出文章讲什么
- 包含文章的主要结论或收获
**tags**
- 5个标签,用于分类和检索
- 覆盖:技术栈、主题、应用场景、难度等维度
- 例如:["Python", "数据分析", "入门教程", "实战", "Pandas"]
**category**
- 从以下选项中选择最合适的一个:
- 技术教程
- 工具推荐
- 项目实战
- 原理解析
- 经验总结
- 行业观察
- 其他
**word_count**
- 统计文档字数(不含代码块和图片)
- 返回整数
**reading_time**
- 按照每分钟300字估算阅读时长
- 格式:"{数字}分钟",例如 "5分钟"
标题规定:
个人博客平台修改文章需要保持文章一级标题不变,否则未检测到相同标题会被定义为增加新的文章。内容提取时若markdown有一级标题则保持标题不变,否则需要LLM节点自动生成标题。
2.2.3 设计决策: 格式优化和内容提取为什么分成两个LLM节点?
内容编排工作流将格式优化和元数据提取分开了,采用了两个LLM节点,原因:
- 两者行为性质不同,容易互相干扰
- 格式优化:关注细节(段落长度、列表格式、空行、标点等;
- 数据提取:关注模型理解全局(主题、风格、核心观点)
- 合并后的prompt很长,token消耗大,输出内容也很长,响应慢。
2.2.4 封面图获取节点
Coze中使用的是图片生成节点(付费,手绘田园风),封面图不要求一定要与文章内容对应,因此切换成unsplash插件,随机获取封面图。
2.2.5 解包内容提取结构化输出节点
没有该节点前,内容编排工作流的输出除了规定的返回之外(text、file、json),其它自定义返回结果都变成了file类型。
原因:dify发布为工具时,工具出参的类型是由系统根据变量溯源自动判断。
内容提取节点使用了LLM节点,结构化输出为:
├── text string
├── reasoning_content string
├── usage object
└── structured_output object ← 问题所在!
├── title string
├── summary string
├── keywords array[string]
├── tags array[string]
├── category enum
├── has_code boolean
├── has_images boolean
├── tone enum
├── word_count integer
└── reading_time string
但是structured_output本身就是object类型,dify目前对structured_output下的嵌套子属性,在发布为工具的出参映射中支持不完善,无法正确识别二级嵌套属性的具体类型。
因此增加该节点,python获取到structured_output中的内容再输出。
2.3 markdown图片链接替换工作流(Markdown_Image_URL_Replacer)
获取到markdown、封面图url和文章标题,markdown内容主要做上传后图片链接的替换,封面图也需要上传到图床,文章标题主要用于确定图床上传的对应位置。该工作流的作用就是将正文图片替换为OSS链接,并添加封面图到图床。
2.3.1 添加图片上传路径节点(LLM节点)
路径要求
如果有后缀名,则去掉文件后缀(.md / .docx)
若为中文名称:将每个汉字转换为拼音,每字首字母大写,拼音之间不加任何分隔符 - 示例:「深度学习入门指南」→「ShenDuXueXiRuMenZhiNan」 - 遇到多音字按最常用读音处理
若为英文名称:每个单词首字母大写,去掉空格 - 示例:「my first article」→「MyFirstArticle」
字数保留标题前5个字(中文)即可,英文保留前8个词就可以
system prompt为
将用户输入的标题转换为路径,规则如下:
1. 去掉文件后缀
2. 中文 → 取前8个汉字转拼音,每个首字母大写
3. 英文 → 取前8个单词,每个首字母大写,去空格
只输出:images/转换结果/
不要有任何解释或额外内容。
示例:
深度学习入门指南.md → images/ShenDuXueXiRu/
my first article.docx → images/MyFirstArticle/
模型:deepseek-v4-flash
选择LLM节点的原因:
dify云端沙箱中无法安装第三方依赖,代码节点将汉字转换成拼音的过程需要pypinyin库故不采用,HTTP请求i调用外部API需要代码节点负责字符串处理(包括中英文分开处理,提取相应字数等),或者自己在代码节点中配置一个映射字典,需要包含常用3500个字,但是维护成本高,准确性低,最后直接调用LLM节点,模型自带拼音能力,虽然消耗token,但是消耗量较少,性价比很高,模型消耗在200tokens以内。
2.3.2 提取图片链接节点(参数提取器)
指令:从 Markdown 内容中提取所有图片链接,包括  和 <img src="url"> 格式
提取参数:image_urls(Array[String])
2.3.3 上传替换图片节点(迭代节点)
内含两个工具节点:阿里云OSS云存储插件中的两个小工具——下载外链图片和上传到OSS。
上传完成后需要输出原图片名称与阿里云对应的链接映射。
采用循环节点还是迭代节点:
迭代节点对列表对象执行多次步骤直至输出所有结果,循环节点是循环执行一段逻辑直到满足结束条件或到达循环次数上限,这里主要是将所有图片上床到图床,选择迭代节点方便简单。
2.4 内容适配工作流(adapter)
给markdown文档添加Front-matter(标题、日期、分类、标签、封面、摘要),适配后续的个人博客和微信公众号的发布。
front_matter = f"""
---
title: {title} // 标题
date: {current_time} // 日期
tags: {tags} // 标签
categories: {category} // 分类
top_img: {top_img_url} // 顶部图
cover: {cover_url} // 封面图
description: {description} // 概括
---
"""
工作流虽然简单,但是作为后续可以扩充的点来说,值得单独做一条工作流,最后输出适配后的markdown内容即可。
2.5 github发布工作流(github_push)
将最后的markdown文件推送到github仓库,使用github actions功能进行自动部署。
该工作流可以推送已有文章进行修改,或者推送新文章,通过检查文件名是否存在判断修改还是新增。
- 文件名URL编码:主要处理中文和空格
- 文章内容编码:返回base64的文章内容
- 环境变量添加:GITHUB_TOKEN、REPO、USERNAME
- 获取文件sha值:调用github的API,判断该文章是否已经存在
- 根据情况调用不同API进行文章修改或者增加
- 将API的返回code作为最终的返回结果
三、Node.js CLI项目详解
3.1 项目概括
dify云端工作流访问需要外网,作为入口不是很方便,因此通过Claude Code助手搭建了一个小型Node项目,调用dify工作流的API,接通wenyan-mcp server,实现自动发布到个人博客和微信公众号。
项目仓库
项目结构
yangzouy-cope/
├── README.md # 安装与使用说明
├── package.json # 依赖配置
├── .env.example # 环境变量模板
└── src/
├── cli.js # CLI入口,参数解析与主流程编排
├── dify.js # Dify Workflow API客户端
├── obsidian.js # Obsidian本地文件处理
└── wenyan.js # wenyan-mcp stdio客户端(公众号发布)
3.2 核心模块说明
CLI入口(cli.js):主流程编排
- 解析用户输入的参数(输入方式、文件路径、目标平台等)
- 根据输入方式调用对应的预处理逻辑
- 调用
dify.js触发工作流 - 接收工作流结果后调用
wenyan.js发布公众号
Dify API客户端(dify.js):封装对Dify Workflow API的调用
- 支持流式响应(SSE)
- 处理API鉴权(Bearer Token)
- 支持HTTP/HTTPS代理配置(解决本地网络访问Dify的问题)
Obsidian文件上传 (obsidian.js):本地文件处理
- 读取本地Markdown文件
- 识别并收集文档中引用的本地图片
- 将图片文件准备好供Dify工作流上传
文颜(wenyan.js):公众号发布
- 将Dify工作流输出的Markdown内容转换为公众号格式
- 调用微信公众号API创建草稿
- 支持代理配
完整数据流
用户执行 CLI 命令
↓
cli.js 解析参数
↓
obsidian.js 读取本地文件(Obsidian模式)
↓
dify.js 调用 Dify Workflow API
↓
Dify 工作流执行(内容获取 → 图片处理 → AI优化 → GitHub发布)
↓
返回处理后的 Markdown 内容
↓
wenyan.js 调用 wenyan-mcp
↓
微信公众号草稿箱
需要将本地pc的公网IP添加到微信公众号的白名单中。



