493 lines
15 KiB
Markdown
493 lines
15 KiB
Markdown
# N8N 内容转化流水线工作流设计(v6 — 与实际部署对齐)
|
||
|
||
> 用于:AI 英文文章 → 中文公众号/X/视频 内容转化
|
||
> 工作流 ID:`SjGKBtSnQ0u87mlK`
|
||
> 触发方式:OpenClaw 通过 Webhook 调用
|
||
> 管理平台:Mac mini 上的 n8n (`https://n8n.ishenwei.online`)
|
||
|
||
---
|
||
|
||
## ⚠️ 路径映射说明(重要!)
|
||
|
||
n8n 容器通过 Docker volume mount 访问宿主机文件系统:
|
||
|
||
| 容器内路径 | 宿主机路径(OpenClaw 用这个) |
|
||
|-----------|--------------------------|
|
||
| `/home/node/.n8n-files/` | `/Users/weishen/docker/n8n/n8n_data/files/` |
|
||
|
||
> **所有 `scp` / `ssh cp` / `ssh rm` 命令都必须使用宿主机路径。**
|
||
> n8n 节点内部配置的是容器路径,两者是同一个目录。
|
||
|
||
---
|
||
|
||
## 核心需求
|
||
|
||
### 输出要求
|
||
|
||
**单一输出:中文 Markdown 文件**
|
||
|
||
| 输出文件 | 宿主机路径 | 格式 |
|
||
|---------|-----------|------|
|
||
| **Markdown 成品** | `content-output/{output_name}_out.md` | 中文 Markdown,可在 Obsidian 中编辑后发布公众号 |
|
||
|
||
### Obsidian 目录结构
|
||
|
||
```
|
||
~/Workspace/nexus/openclaw/
|
||
├── content-queue/ # 原始英文文章(输入)
|
||
│ └── 3 Essential Tools for OpenClaw.md
|
||
│
|
||
└── content-output/ # 成品(输出)
|
||
└── 3 Essential Tools for OpenClaw_out.md # 中文 Markdown ✅
|
||
```
|
||
|
||
---
|
||
|
||
## 节点详细设计(v6 实际版本)
|
||
|
||
### 节点 1️⃣:Webhook Trigger
|
||
|
||
**类型:** Webhook
|
||
**名称:** `Webhook Trigger`
|
||
**Path:** `/content-translation-v6`
|
||
**Method:** POST
|
||
|
||
**接收数据格式(重要更新):**
|
||
|
||
```json
|
||
{
|
||
"note_name": "3 Essential Tools for OpenClaw.md",
|
||
"output_name": "3 Essential Tools for OpenClaw"
|
||
}
|
||
```
|
||
|
||
> ⚠️ `source_path` 字段可选(v6 实际 webhook body 不依赖此字段)。
|
||
|
||
---
|
||
|
||
### 节点 2️⃣:Read Binary File
|
||
|
||
**类型:** Read Binary File
|
||
**名称:** `Read Obsidian Note`
|
||
|
||
**配置(容器内路径):**
|
||
- File Path: `=/home/node/.n8n-files/{{ $json.body.note_name }}`
|
||
|
||
---
|
||
|
||
### 节点 3️⃣:Extract from File
|
||
|
||
**类型:** Extract from File
|
||
**名称:** `Extract from File`
|
||
**Operation:** `text`
|
||
|
||
---
|
||
|
||
### 节点 4️⃣:Structured Output Parser
|
||
|
||
**类型:** Structured Output Parser
|
||
**名称:** `Structured Output Parser`
|
||
**说明:** n8n 内置的结构化输出解析器,负责将 AI 输出的文本解析为 JSON 供后续节点使用。
|
||
|
||
**JSON Schema:**
|
||
```json
|
||
{
|
||
"wechat_title": "中文标题",
|
||
"wechat_excerpt": "公众号摘要(100字内)",
|
||
"wechat_content": "完整公众号文章(Markdown格式)",
|
||
"twitter_copy": "X/Twitter文案(280字内,带emoji)",
|
||
"video_title": "视频标题",
|
||
"video_script": "口播脚本(含时间戳和字幕)",
|
||
"cover_keywords": ["关键词1", "关键词2", "关键词3"]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 节点 5️⃣:AI Agent
|
||
|
||
**类型:** LangChain Agent
|
||
**名称:** `AI Agent`
|
||
**Version:** 3.1
|
||
**Model:** DeepSeek Chat Model
|
||
**Output Parser:** `Structured Output Parser`(已连接)
|
||
|
||
**Prompt 模板(与 v6 文档一致,但 JSON 字段名注意小写):**
|
||
```
|
||
你是专业的中文内容编辑,擅长将英文文章转化为适合中国读者的高质量内容。
|
||
|
||
## 你的任务
|
||
|
||
将以下英文原文转化为:
|
||
1. 公众号风格的深度文章(2000-3000字)
|
||
2. X/Twitter 风格的短文案(280字内,带emoji钩子)
|
||
3. 视频口播脚本(3-5分钟,适合抖音/YouTube)
|
||
|
||
## 内容要求
|
||
|
||
- 语言:地道中文,无翻译腔
|
||
- 风格:专业、有干货、适合中国读者
|
||
- 调性:公众号大V风格,有观点有案例
|
||
- 商业化:可自然植入 AI Agent / 知识管理相关内容(软性,不硬广)
|
||
|
||
## 输出格式(严格按此 JSON 返回)
|
||
|
||
{
|
||
"wechat_title": "中文标题",
|
||
"wechat_excerpt": "公众号摘要(100字内)",
|
||
"wechat_content": "完整公众号文章(Markdown格式)",
|
||
"twitter_copy": "X/Twitter文案(280字内,带emoji)",
|
||
"video_title": "视频标题",
|
||
"video_script": "口播脚本(含时间戳和字幕)",
|
||
"cover_keywords": ["关键词1", "关键词2", "关键词3"]
|
||
}
|
||
|
||
## 原文内容
|
||
{{ $json.data }}
|
||
```
|
||
|
||
> ⚠️ 注意:Build Markdown 节点访问 AI 结果用的是 `$input.first().json.output`,不是 `message`。
|
||
|
||
---
|
||
|
||
### 节点 6️⃣:Build Markdown
|
||
|
||
**类型:** Code
|
||
**名称:** `Build Markdown`
|
||
|
||
**功能:** 将 AI 输出的 JSON 组装成 Markdown 文件
|
||
|
||
```javascript
|
||
const aiResponse = $input.first().json.output;
|
||
const aiResult = typeof aiResponse === 'string' ? JSON.parse(aiResponse) : aiResponse;
|
||
|
||
const noteName = $('Webhook Trigger').first().json.body.note_name;
|
||
const outputName = $('Webhook Trigger').first().json.body.output_name;
|
||
const sourcePath = $('Webhook Trigger').first().json.body.source_path || '';
|
||
|
||
const markdown = '# ' + aiResult.wechat_title + '\n\n'
|
||
+ aiResult.wechat_excerpt + '\n\n'
|
||
+ aiResult.wechat_content + '\n\n'
|
||
+ '---\n\n'
|
||
+ '## X/Twitter 文案\n\n'
|
||
+ aiResult.twitter_copy + '\n\n'
|
||
+ '---\n\n'
|
||
+ '## 视频信息\n\n'
|
||
+ '**标题:** ' + aiResult.video_title + '\n\n'
|
||
+ '**口播脚本:\n\n'
|
||
+ aiResult.video_script + '\n\n'
|
||
+ '---\n\n'
|
||
+ '*封面图关键词:' + (aiResult.cover_keywords ? aiResult.cover_keywords.join(' | ') : '') + '*\n\n'
|
||
+ '*原文路径:' + sourcePath + '*/';
|
||
|
||
return {
|
||
json: {
|
||
output_name: outputName,
|
||
markdown_content: markdown,
|
||
ai_result: aiResult
|
||
}
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
### 节点 7️⃣:Convert to File
|
||
|
||
**类型:** Convert to File
|
||
**名称:** `Convert to File`
|
||
**Operation:** `toText`
|
||
**Source Property:** `markdown_content`
|
||
|
||
> 将 Build Markdown 输出的文本(`markdown_content` 字段)转换为二进制文件,供 Write 节点写入。
|
||
|
||
---
|
||
|
||
### 节点 8️⃣:Write Markdown File
|
||
|
||
**类型:** Read/Write File
|
||
**名称:** `Write Markdown File`
|
||
**Operation:** `write`
|
||
|
||
**配置:**
|
||
- File Name: `=/home/node/.n8n-files/{{ $('Build Markdown').item.json.output_name }}_out.md`
|
||
- Data Property Name: `=data`
|
||
|
||
> ⚠️ 输出文件名格式:`{output_name}_out.md`(不是 `_output.md`!)
|
||
|
||
---
|
||
|
||
### 节点 9️⃣:Send Telegram Message
|
||
|
||
**类型:** Telegram
|
||
**名称:** `Send Telegram Message`
|
||
|
||
**配置:**
|
||
- Chat ID: `5038825565`
|
||
- Text:
|
||
```
|
||
=✅ 文章转换完成!
|
||
|
||
📝 标题:{{ $('Build Markdown').item.json.ai_result.wechat_title }}
|
||
```
|
||
- Parse Mode: `Markdown`
|
||
|
||
> ⚠️ v6 实际通知只含标题,不含文件路径。OpenClaw 根据约定的文件命名规则自行定位输出文件。
|
||
|
||
---
|
||
|
||
## 节点连接图
|
||
|
||
```
|
||
Webhook Trigger
|
||
│
|
||
▼
|
||
Read Binary File
|
||
│
|
||
▼
|
||
Extract from File
|
||
│
|
||
▼
|
||
┌──────────────────────────────────────┐
|
||
│ Structured Output Parser ←────────┤(AI Output Parser 连接)
|
||
│ AI Agent ─────────────────────────→│(prompt + outputSchema)
|
||
│ DeepSeek Chat Model │
|
||
└──────────────────────────────────────┘
|
||
│
|
||
▼
|
||
Build Markdown
|
||
│
|
||
▼
|
||
Convert to File
|
||
│
|
||
▼
|
||
Write Markdown File
|
||
│
|
||
▼
|
||
Send Telegram Message
|
||
```
|
||
|
||
---
|
||
|
||
## 调用方式(OpenClaw 侧 — 实际测试命令)
|
||
|
||
### 宿主机 n8n-files 目录
|
||
```
|
||
/Users/weishen/docker/n8n/n8n_data/files/
|
||
```
|
||
|
||
### 完整流程(实际测试版)
|
||
|
||
```bash
|
||
# =============================================
|
||
# 步骤 1:复制原文到 n8n 容器可访问的目录
|
||
# =============================================
|
||
scp "/Users/weishen/Workspace/nexus/openclaw/content-queue/3 Essential Tools for OpenClaw.md" \
|
||
"macmini:/Users/weishen/docker/n8n/n8n_data/files/3 Essential Tools for OpenClaw.md"
|
||
|
||
# =============================================
|
||
# 步骤 2:触发 Webhook
|
||
# =============================================
|
||
curl -X POST "https://n8n.ishenwei.online/webhook/content-translation-v6" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"note_name": "3 Essential Tools for OpenClaw.md",
|
||
"output_name": "3 Essential Tools for OpenClaw"
|
||
}' \
|
||
-m 180 \
|
||
-w "\nHTTP_CODE:%{http_code}\n"
|
||
|
||
# =============================================
|
||
# 步骤 3-4:复制输出文件回 Obsidian
|
||
# =============================================
|
||
# ⚠️ 注意:输出文件名是 {output_name}_out.md,不是 _output.md
|
||
ssh macmini "cp \"/Users/weishen/docker/n8n/n8n_data/files/3 Essential Tools for OpenClaw_out.md\" \
|
||
\"/Users/weishen/Workspace/nexus/openclaw/content-output/3 Essential Tools for OpenClaw_out.md\""
|
||
|
||
# =============================================
|
||
# 步骤 5:清理临时文件
|
||
# =============================================
|
||
ssh macmini "rm '/Users/weishen/docker/n8n/n8n_data/files/3 Essential Tools for OpenClaw.md'"
|
||
ssh macmini "rm '/Users/weishen/docker/n8n/n8n_data/files/3 Essential Tools for OpenClaw_out.md'"
|
||
```
|
||
|
||
---
|
||
|
||
## Python 自动化脚本模板(v6a — 勘误版)
|
||
|
||
```python
|
||
import subprocess
|
||
import sys
|
||
|
||
# ========== 配置区 ==========
|
||
N8N_BASE_URL = "https://n8n.ishenwei.online"
|
||
WEBHOOK_PATH = "content-translation-v6"
|
||
SOURCE_FILE = "/path/to/source/file.md" # <-- 修改为实际源文件路径
|
||
N8N_FILES_HOST = "/Users/weishen/docker/n8n/n8n_data/files"
|
||
NOTE_NAME = "source-file.md" # <-- 实际文件名
|
||
OUTPUT_NAME = "source-file" # <-- output_name(无扩展名)
|
||
OUTPUT_DIR = "/Users/weishen/Workspace/nexus/openclaw/content-output"
|
||
# ============================
|
||
|
||
n8n_input = f"{N8N_FILES_HOST}/{NOTE_NAME}"
|
||
n8n_output = f"{N8N_FILES_HOST}/{OUTPUT_NAME}_out.md" # <-- 命名规则:_out.md
|
||
|
||
print(f"[Step 1/5] 复制输入文件到 N8N 容器: {NOTE_NAME}")
|
||
result = subprocess.run(
|
||
['scp', SOURCE_FILE, f'macmini:{n8n_input}'],
|
||
capture_output=True, text=True
|
||
)
|
||
if result.returncode != 0:
|
||
print(f"X 文件复制失败: {result.stderr}")
|
||
sys.exit(1)
|
||
print(f"V 已复制到 {n8n_input}")
|
||
|
||
print(f"[Step 2/5] 触发 N8N Webhook: {WEBHOOK_PATH}")
|
||
webhook_url = f"{N8N_BASE_URL}/webhook/{WEBHOOK_PATH}"
|
||
payload = ("{"
|
||
"\"note_name\": \"" + NOTE_NAME + "\", "
|
||
"\"output_name\": \"" + OUTPUT_NAME + "\""
|
||
"}")
|
||
result = subprocess.run(
|
||
['curl', '-X', 'POST', webhook_url,
|
||
'-H', 'Content-Type: application/json',
|
||
'-d', payload,
|
||
'-m', '180'],
|
||
capture_output=True, text=True
|
||
)
|
||
print(f"Webhook 触发响应: {result.stdout[:200]}")
|
||
|
||
print("[Step 3/5] 等待 N8N 执行完成(Telegram 通知将发送到本会话)...")
|
||
print("请等待 Telegram 通知,收到后继续执行步骤 4。")
|
||
sys.exit(0)
|
||
|
||
# ---- 以下为步骤 4 和 5,需在收到 Telegram 通知后执行 ----
|
||
|
||
print(f"[Step 4/5] 复制输出文件回 Obsidian...")
|
||
src = f"macmini:{n8n_output}"
|
||
dst = f"{OUTPUT_DIR}/{OUTPUT_NAME}_out.md"
|
||
result = subprocess.run(['scp', src, dst], capture_output=True, text=True)
|
||
if result.returncode == 0:
|
||
print(f" V {OUTPUT_NAME}_out.md")
|
||
else:
|
||
print(f" X {result.stderr}")
|
||
|
||
print("[Step 5/5] 清理 N8N 容器临时文件...")
|
||
for f in [NOTE_NAME, f"{OUTPUT_NAME}_out.md"]:
|
||
r = subprocess.run(['ssh', 'macmini', 'rm', f"{N8N_FILES_HOST}/{f}"],
|
||
capture_output=True, text=True)
|
||
status = "V" if r.returncode == 0 else f"X ({r.stderr})"
|
||
print(f" {status} {f}")
|
||
|
||
print("🎉 工作流执行完毕!")
|
||
```
|
||
|
||
---
|
||
|
||
## 验收标准
|
||
|
||
1. Webhook 触发后 n8n 自动执行完整流程,无需人工干预
|
||
2. `content-output/` 目录生成 `{output_name}_out.md` 文件
|
||
3. Markdown 文件包含:标题 + 摘要 + 公众号正文 + Twitter 文案 + 视频脚本 + 封面关键词
|
||
4. Telegram 收到含标题的完成通知
|
||
5. 错误时 n8n 记录错误节点并停止流程
|
||
|
||
---
|
||
|
||
## 与 v6 文档的差异汇总(本文档为正确版本)
|
||
|
||
| 项目 | v6 文档(错) | v6a 实际版本(对) |
|
||
|------|-------------|-----------------|
|
||
| n8n-files 宿主机路径 | `/home/node/.n8n-files/` | `/Users/weishen/docker/n8n/n8n_data/files/` |
|
||
| 输出文件名 | `{output_name}_output.md` | `{output_name}_out.md` |
|
||
| 节点数 | 7 个 | 9 个(含 Convert to File + Structured Output Parser) |
|
||
| Build Markdown JSON 路径 | `$input.first().json.message` | `$input.first().json.output` |
|
||
| Telegram 通知内容 | 含文件路径 | 仅含标题 |
|
||
| webhook body | 包含 source_path | source_path 可选 |
|
||
| Write Markdown File FileName | `...output_name}_output.md` | `...output_name}_out.md` |
|
||
|
||
---
|
||
|
||
---
|
||
|
||
# 📖 附录:OpenClaw ↔ N8N 通用工作流调用规范(v1.1)
|
||
|
||
> 本规范旨在为 OpenClaw 与 N8N 之间的每次交互建立统一标准,使星辉(XingHui)或其他 Agent 能够通过标准化步骤执行任意 N8N 工作流。
|
||
>
|
||
> **适用范围**:任何由 OpenClaw 触发、N8N 执行的工作流
|
||
>
|
||
> **核心约束**:必须确认 n8n 容器的 volume mount 宿主机路径,所有 OpenClaw 侧文件操作使用宿主机路径。
|
||
|
||
---
|
||
|
||
## 路径映射规则
|
||
|
||
n8n Docker 部署的 volume mount 映射关系:
|
||
- 容器内 `/home/node/.n8n-files/` → 宿主机 `{docker-root}/n8n_data/files/`
|
||
- 当前部署:`/Users/weishen/docker/n8n/n8n_data/files/`
|
||
|
||
> 新增工作流时,先确认 docker-compose.yml 中的 volume 配置,用宿主机路径执行所有 `scp` / `ssh cp` / `ssh rm` 命令。
|
||
|
||
---
|
||
|
||
## 标准化执行步骤
|
||
|
||
### 步骤 ①:准备输入文件
|
||
|
||
**通用格式**:`scp <源路径> macmini:{N8N_FILES_HOST}/<文件名>`
|
||
|
||
### 步骤 ②:触发 Webhook
|
||
|
||
**通用格式**:
|
||
```bash
|
||
curl -X POST "<N8N_BASE_URL>/webhook/<webhook-path>" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"note_name": "<文件名>", "output_name": "<输出名>"}' \
|
||
-m 180 -w "\nHTTP_CODE:%{http_code}\n"
|
||
```
|
||
|
||
### 步骤 ③:等待执行完成
|
||
|
||
**通知方式**:Telegram Bot(推荐)> Callback URL > 轮询 n8n API
|
||
|
||
### 步骤 ④:复制输出文件
|
||
|
||
**⚠️ 输出文件名规则因工作流而异,必须对照具体工作流确认(常见规则:`_out.md` / `_output.md` / `{name}_result.md`)**
|
||
|
||
**通用格式**:`ssh macmini "cp {N8N_FILES_HOST}/<输出文件名> <目标路径>"`
|
||
|
||
### 步骤 ⑤:清理临时文件
|
||
|
||
**通用格式**:`ssh macmini "rm {N8N_FILES_HOST}/<输入文件> {N8N_FILES_HOST}/<输出文件>"`
|
||
|
||
---
|
||
|
||
## 速查表
|
||
|
||
| 工作流 | Webhook Path | 输入文件规则 | 输出文件规则 | 通知方式 |
|
||
|--------|-------------|-------------|-------------|---------|
|
||
| 内容转化 v6 | `content-translation-v6` | `content-queue/*.md` | `content-output/*_out.md` | Telegram(仅标题) |
|
||
| (待补充) | | | | |
|
||
|
||
---
|
||
|
||
## 异常处理规范
|
||
|
||
| 异常情况 | 处理方式 |
|
||
|---------|---------|
|
||
| `scp` 文件复制失败 | 检查源文件是否存在、检查 docker volume 路径是否正确 |
|
||
| `curl` Webhook 超时 | 增加 `-m` 超时时间(如 180 秒),重试 1 次 |
|
||
| Telegram 未收到通知 | 登录 n8n 管理界面 → 查看工作流执行记录 |
|
||
| 输出文件复制失败 | 确认实际输出文件名(对照工作流的 Write 节点配置) |
|
||
| N8N 工作流报错 | 登录 n8n 管理界面 → 查看错误节点日志 |
|
||
|
||
---
|
||
|
||
## 关键约束汇总
|
||
|
||
1. **n8n volume 宿主机路径**:`/Users/weishen/docker/n8n/n8n_data/files/`
|
||
2. **文件传输**:OpenClaw 侧统一用宿主机路径(`scp` / `ssh cp` / `ssh rm`)
|
||
3. **输出文件名**:必须对照具体工作流的 Write 节点确认,不得假设
|
||
4. **Webhook 触发**:`curl -X POST`,`-m 180` 超时,响应含 `HTTP_CODE`
|
||
5. **通知等待**:必须等待 Telegram/callback 再复制输出文件
|
||
6. **清理时机**:步骤 4 成功后再清理,失败时保留现场
|