Files
geekgeekrun/plan/webhook_integration.md
rqi14 95c1e54c66 recruiter: add boss auto browse/chat flows, webhook, and candidate tables
- Add recruiter-side automation core and run-core entry
- Extend sqlite-plugin with candidate info + contact logs
- Add UI routes/pages, IPC handlers, progress + log panel
- Document current status and plans under plan/

Made-with: Cursor
2026-03-18 17:37:24 +08:00

8.4 KiB
Raw Blame History

Webhook / 外部集成

本文档描述招聘端 Webhook 功能的设计、配置结构、数据流与扩展方式,适用于对接 Paperless-ngx、自定义 API 等外部系统。


1. 功能概述

每轮自动化任务(推荐牛人 + 沟通页)结束后,系统将本轮处理的所有候选人数据汇总成一条 JSON Payload通过 HTTP 请求发送到用户配置的 URL。

支持:

  • 开关控制(关闭后任务结束时跳过发送)
  • 「保存并测试发送」(用 mock 数据验证接口通不通)
  • 「手动触发」(用 mock 数据模拟一次 manual 发送)
  • 自定义 Headers用于 Token 认证等)
  • Payload 内容裁剪(可关闭某些字段)
  • 简历以本地路径或 Base64 附带

2. 相关文件

文件 说明
packages/ui/src/renderer/src/page/MainLayout/WebhookIntegration/index.vue 设置页 UI
packages/ui/src/main/features/webhook/index.ts 发送逻辑、类型定义、mock 数据生成
packages/ui/src/main/flow/BOSS_AUTO_BROWSE_AND_CHAT_MAIN/index.ts 任务完成后自动触发(afterChatStarted 收集 + 轮次结束后发送)
packages/ui/src/main/flow/OPEN_SETTING_WINDOW/ipc/index.ts IPC handlersfetch/save/test/trigger
packages/ui/src/renderer/src/router/index.ts 路由注册 WebhookIntegration
packages/ui/src/renderer/src/page/MainLayout/LeftNavBar/RecruiterPart.vue 左侧导航入口

配置文件路径:~/.geekgeekrun/config/webhook.json(通过 boss-auto-browse 的 runtime-file-utils.mjs 读写)


3. 配置结构webhook.json

{
  "enabled": true,
  "url": "https://your-paperless.example.com/api/documents/post_document/",
  "method": "POST",
  "sendMode": "batch",
  "contentType": "application/json",
  "headers": {
    "Authorization": "Token YOUR_TOKEN",
    "X-Custom-Header": "value"
  },
  "payloadOptions": {
    "includeBasicInfo": true,
    "includeFilterReason": true,
    "includeLlmConclusion": true,
    "includeResume": "path"
  },
  "retryTimes": 3,
  "retryDelayMs": 1000,
  "queueFileOnFailure": false
}

字段说明:

字段 类型 说明
enabled boolean 是否启用自动触发
url string 目标 URL须以 http:// 或 https:// 开头)
method "POST" | "PUT" | "PATCH" HTTP 方法
sendMode "batch" | "realtime" 轮次结束汇总发送 / 每打招呼后立即发送一条
contentType "application/json" | "multipart/form-data" 请求体格式multipart 时每条候选人一个请求(直传 Paperless 等)
headers object 自定义请求头 key-value 对
payloadOptions.includeBasicInfo boolean 是否包含候选人基本信息
payloadOptions.includeFilterReason boolean 是否包含筛选理由/评分
payloadOptions.includeLlmConclusion boolean 是否包含 LLM 评估结论
payloadOptions.includeResume "none" | "path" | "base64" 简历文件携带方式;若沟通页开启了 chatPage.attachmentResume.skipDownload(见下),resumeFile 在 Payload 中将始终为空,与此选项无关
retryTimes number 失败重试次数0 不重试
retryDelayMs number 首次重试延迟(毫秒),之后指数退避
queueFileOnFailure boolean 最终失败时是否写入本地队列文件webhook-failed-queue.jsonl

4. Payload 结构

{
  "runId": "run-<runRecordId 或时间戳>",
  "timestamp": "2026-03-16T10:00:00.000Z",
  "summary": {
    "total": 10,
    "matched": 7,
    "skipped": 3
  },
  "candidates": [
    {
      "basicInfo": {
        "name": "张三",
        "education": "本科",
        "workExpYears": 3,
        "city": "北京",
        "salary": "15-25K",
        "skills": ["Vue", "React", "TypeScript"]
      },
      "filterReport": {
        "matched": true,
        "matchedRules": ["education", "workExp", "skills"],
        "score": 85
      },
      "llmConclusion": "候选人技能与岗位匹配度较高,建议优先沟通。",
      "resumeFile": {
        "path": "/Users/xxx/.geekgeekrun/storage/resumes/张三.pdf",
        "filename": "张三.pdf"
      }
    }
  ]
}
  • resumeFile.base64 仅在 includeResume = "base64" 时出现
  • 若某字段对应的 payloadOptions 为 false则该字段在所有候选人对象中省略
  • chatPage.attachmentResume.skipDownload 的影响:若 BOSS 直聘已配置「接收附件简历自动发到邮箱」,可在 boss-recruiter.json 中将 chatPage.attachmentResume.skipDownload 设为 true。此时系统仅发出索取请求、不下载 PDFresumeFile 字段在 Payload 中将始终缺失(无论 payloadOptions.includeResume 取何值)。若下游系统依赖 resumeFile,请保持 skipDownload: false(默认值)。

5. 数据流

afterChatStarted hook每次打招呼后
  ↓
BOSS_AUTO_BROWSE_AND_CHAT_MAIN/index.ts
  收集到 sessionCandidates[]candidate.info / matchedRules / score / llmConclusion / resumeFilePath
  ↓
startBossAutoBrowse + startBossChatPageProcess 均完成
  ↓
读取 webhook.json
  ├── enabled=false → 跳过sessionCandidates.length = 0
  └── enabled=true, url 非空
        ↓
      features/webhook/index.ts: sendWebhook(config, payload)
        ├── 按 payloadOptions 过滤字段
        ├── includeResume="base64" → fs.readFileSync(path).toString('base64')
        └── fetch(url, { method, headers, body: JSON.stringify(payload) })
              ↓
            返回 { status, body },记录日志
  ↓
sessionCandidates.length = 0等待下轮

6. IPC 接口

Channel 说明
fetch-webhook-config 读取 webhook.json不存在时返回 null
save-webhook-config payload 为 JSON 字符串,与 existing 合并后写入
test-webhook buildMockPayload() 发送到已配置 URL返回 { status, body }
trigger-webhook-manually 同上runId 前缀为 manual-

7. UI 页面说明

入口: 「招聘BOSS」左侧导航 → Webhook / 外部集成

布局:

  1. 基础设置卡片 — 启用开关 / URL 输入 / 请求方法选择
  2. 请求头卡片 — 动态 key/value 列表 + 「Authorization Token」「X-API-Key」快速模板按钮
  3. Payload 选项卡片 — 基本信息/筛选报告/LLM 结论 checkbox + 简历携带方式 radio
  4. 操作栏 — 仅保存 / 保存并测试发送 / 手动触发
  5. 测试结果卡片 — 显示 HTTP 状态码(带颜色 tag+ 格式化响应体

8. 与 Paperless-ngx 对接示例

Paperless-ngx 的文档上传 APIPOST /api/documents/post_document/

配置示例:

{
  "enabled": true,
  "url": "http://paperless.local/api/documents/post_document/",
  "method": "POST",
  "headers": {
    "Authorization": "Token <your-paperless-token>"
  },
  "payloadOptions": {
    "includeBasicInfo": true,
    "includeFilterReason": true,
    "includeLlmConclusion": true,
    "includeResume": "base64"
  }
}

注意: Paperless 上传 API 期望 multipart/form-data 格式,而当前实现发送的是 JSON。若需直接上传到 Paperless建议在外部用一个中间服务如 n8n、自定义脚本接收本 webhook JSON再转发给 Paperless。这也是「允许调用自定义 API」的典型用法。


9. 扩展方向(已实现)

  • 逐条实时触发:配置项 sendMode: 'realtime',在 afterChatStarted hook 中每打招呼后立即调用 sendWebhook 发送单条候选人;sendMode: 'batch' 保持轮次结束汇总发送。
  • 支持 multipart/form-data:配置项 contentType: 'multipart/form-data'每条候选人单独一个请求FormData 含 runId、timestamp、summary、candidateJSON、document简历文件支持直传 Paperless 等。
  • 重试机制:配置项 retryTimesretryDelayMs,失败时指数退避重试;queueFileOnFailure: true 时最终失败写入 ~/.geekgeekrun/storage/webhook-failed-queue.jsonl
  • 手动触发使用真实数据trigger-webhook-manually 支持第二参数 useRealData;为 true 时从 SQLite CandidateContactLog + CandidateInfo 查最近 50 条联系人组装 payload无数据时回退为 Mock。