- 新增 GET /api/tags 接口返回标签列表及项目计数 - 新增 POST /api/tags/maintenance 接口支持批量 nameEn 补全和标签合并 - 合并逻辑包含 ProjectTag 去重处理(避免复合主键冲突) - 成功后触发 ISR revalidatePath 刷新项目列表页 - 新增 Zod 校验 schemas(TagMaintenanceRequestSchema 等) - 包含 n8n workflow JSON 模板及文档
3.6 KiB
3.6 KiB
n8n Tag Janitor Workflow
Overview
Daily automated workflow to clean up duplicate/similar tags using AI semantic analysis.
Workflow Structure
[Cron] → [HTTP GET /api/tags] → [Code: Preprocess] → [AI: Generate Plan] → [Code: Validate JSON] → [HTTP POST /api/tags/maintenance] → [Notification]
Node Configuration
1. Schedule Trigger (Cron)
- Trigger: Daily at 03:00 UTC
- Timezone: UTC
2. HTTP Request - Fetch Tags
- Method: GET
- URL:
{{$env.SITE_BASE_URL}}/api/tags - Response: JSON with
.tagsarray
3. Code Node - Preprocess
Purpose: Format tags for LLM input, handle chunking if >200 tags
const tags = $input.first().json.tags;
const formatted = tags.map((t) => ({
id: t.id,
name: t.name,
nameEn: t.nameEn || "",
projectCount: t._count.projects,
}));
// Sort by projectCount desc for prioritization
formatted.sort((a, b) => b.projectCount - a.projectCount);
return [{ json: { tags: formatted, total: formatted.length } }];
4. AI Node - Generate Merge Plan
Model: GPT-4o / Claude 3.5 Sonnet Temperature: 0.1 (deterministic)
Prompt Template:
You are a tag management expert. Analyze these tags and identify:
1. Semantic duplicates that should be merged (e.g., "机器学习" and "ML" → keep "机器学习" with nameEn "Machine Learning")
2. Tags missing English names that need nameEn补全
Tags (JSON):
{{$json.tags}}
Output STRICT JSON (no markdown, no explanation):
{
"merges": [
{
"target": { "name": "保留的标签名", "nameEn": "Canonical English Name" },
"sourceTagIds": ["id1", "id2"]
}
],
"updates": [
{ "tagId": "id", "nameEn": "English Name" }
]
}
Rules:
- Keep the tag with higher projectCount as target
- For merges, target can be { "id": "existing_id" } if keeping existing tag, or { "name": "...", "nameEn": "..." } to create new
- Only include tags that NEED action (empty arrays if nothing to do)
- nameEn should be proper English, not pinyin
- Common tech terms: 机器学习=Machine Learning, 深度学习=Deep Learning, 自然语言处理=NLP
5. Code Node - Validate & Parse
const response = $input.first().json;
let plan;
try {
plan = typeof response === "string" ? JSON.parse(response) : response;
} catch (e) {
throw new Error("Invalid JSON from AI: " + e.message);
}
// Validate structure
if (!Array.isArray(plan.merges)) plan.merges = [];
if (!Array.isArray(plan.updates)) plan.updates = [];
// Self-merge check: target.id cannot be in sourceTagIds
for (const merge of plan.merges) {
if (merge.target.id && merge.sourceTagIds.includes(merge.target.id)) {
throw new Error("Self-merge detected: " + merge.target.id);
}
}
return [{ json: plan }];
6. HTTP Request - Execute Maintenance
- Method: POST
- URL:
{{$env.SITE_BASE_URL}}/api/tags/maintenance - Body:
{
"apiKey": "{{$env.WEBHOOK_API_KEY}}",
"updates": {{$json.updates}},
"merges": {{$json.merges}}
}
7. Notification (Slack/Email/Webhook)
Send summary:
- Tags merged: X
- Tags deleted: Y
- Names updated: Z
- Errors: [list]
Environment Variables Required
SITE_BASE_URL: https://your-site.comWEBHOOK_API_KEY: API key for authentication
Chunking Strategy (for >200 tags)
- Split tags into batches of 100
- Process each batch sequentially
- Aggregate results before final notification
Error Handling
- Retry HTTP requests 3x with exponential backoff
- On AI parse failure: skip and alert
- On maintenance failure: log error, continue with notification
Testing
- Dry run: Comment out HTTP POST node, check AI output only
- Real run: Enable all nodes, monitor /api/tags count before/after