mirror of
https://github.com/geekgeekrun/geekgeekrun.git
synced 2026-05-26 10:40:18 +08:00
- 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
205 lines
8.4 KiB
Markdown
205 lines
8.4 KiB
Markdown
# 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 handlers:fetch/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)
|
||
|
||
```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 结构
|
||
|
||
```json
|
||
{
|
||
"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`。此时系统仅发出索取请求、不下载 PDF,`resumeFile` 字段在 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 的文档上传 API:`POST /api/documents/post_document/`
|
||
|
||
配置示例:
|
||
```json
|
||
{
|
||
"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、candidate(JSON)、document(简历文件),支持直传 Paperless 等。
|
||
- **重试机制**:配置项 `retryTimes`、`retryDelayMs`,失败时指数退避重试;`queueFileOnFailure: true` 时最终失败写入 `~/.geekgeekrun/storage/webhook-failed-queue.jsonl`。
|
||
- **手动触发使用真实数据**:`trigger-webhook-manually` 支持第二参数 `useRealData`;为 true 时从 SQLite `CandidateContactLog` + `CandidateInfo` 查最近 50 条联系人组装 payload,无数据时回退为 Mock。
|