This commit is contained in:
27942
2026-02-25 00:14:22 +08:00
parent 464486cb33
commit e5174d0c3c
4 changed files with 674 additions and 1 deletions

643
API文档.md Normal file
View File

@@ -0,0 +1,643 @@
# BOSS 直聘自动化平台 - API 接口文档
**Base URL**: `http://{服务器IP}:9000`
**认证方式**: Cookie`auth_token`),登录成功后自动通过 `Set-Cookie` 设置,后续请求自动携带。
**请求格式**: 支持 `application/json``multipart/form-data`
**响应规范**: 所有接口的 JSON 响应体均包含 **`code`** 字段,与 HTTP 状态码一致。成功时如 `code: 200``code: 201`;错误时如 `code: 401``code: 404`。原返回数组的接口会包装为 `{"code": 状态码, "data": 原数组}`
---
## 一、健康检查
### GET /health
**说明**: 检查服务器是否正常运行,无需登录。
**状态码**:
| 状态码 | 说明 |
|--------|------|
| 200 | 成功,返回服务状态与在线 Worker 数 |
| 500 | 服务器内部错误(少见) |
**请求参数**: 无
**响应示例**:
```json
{
"status": "ok",
"workers_online": 2
}
```
| 返回字段 | 类型 | 说明 |
|---------|------|------|
| status | string | 服务状态,固定 `"ok"` |
| workers_online | int | 当前在线 Worker 数量 |
---
## 二、认证
### POST /api/auth/login
**说明**: 管理员登录,无需认证。登录成功后通过 `Set-Cookie` 返回 `auth_token`
**状态码**:
| 状态码 | 说明 |
|--------|------|
| 200 | 成功,返回 token 并设置 Cookie |
| 401 | 用户名或密码错误 |
**请求参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| username | string | 是 | 用户名 |
| password | string | 是 | 密码 |
**请求示例**:
```json
{
"username": "admin",
"password": "boss_dp_admin"
}
```
**成功响应** (200):
```json
{
"token": "a1b2c3d4e5f6..."
}
```
**失败响应** (401):
```json
{
"detail": "用户名或密码错误"
}
```
| 返回字段 | 类型 | 说明 |
|---------|------|------|
| token | string | 登录 token同时通过 Cookie 设置) |
**响应 Header**:
```
Set-Cookie: auth_token=a1b2c3d4e5f6...; HttpOnly; Max-Age=31536000; SameSite=Lax
```
---
## 三、Worker 管理
> 以下接口均需登录(携带 `auth_token` Cookie
### GET /api/workers
**说明**: 获取所有已注册 Worker 列表。
**状态码**:
| 状态码 | 说明 |
|--------|------|
| 200 | 成功,返回 Worker 数组 |
| 401 | 未登录或 token 失效 |
**请求参数**: 无
**响应示例**:
```json
[
{
"worker_id": "worker-1",
"worker_name": "本机",
"browsers": [
{
"id": "abd63190eea84dc49429962f7bb330a4",
"name": "第一个",
"remark": ""
}
],
"online": true,
"current_task_id": null
}
]
```
| 返回字段 | 类型 | 说明 |
|---------|------|------|
| worker_id | string | Worker 唯一标识 |
| worker_name | string | Worker 名称(电脑名) |
| browsers | array | 该电脑上的比特浏览器环境列表 |
| browsers[].id | string | 浏览器窗口 ID |
| browsers[].name | string | 浏览器窗口名称(环境名) |
| browsers[].remark | string | 备注 |
| online | boolean | 是否在线 |
| current_task_id | string/null | 当前正在执行的任务 ID |
---
### GET /api/workers/{worker_id}
**说明**: 获取指定 Worker 的详细信息。
**状态码**:
| 状态码 | 说明 |
|--------|------|
| 200 | 成功,返回单个 Worker 对象 |
| 401 | 未登录或 token 失效 |
| 404 | Worker 不存在 |
**路径参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| worker_id | string | 是 | Worker ID |
**成功响应** (200): 同上单个 Worker 对象
**失败响应** (404):
```json
{
"detail": "Worker worker-99 不存在"
}
```
---
## 四、账号管理
> 以下接口均需登录(携带 `auth_token` Cookie
### POST /api/accounts
**说明**: 添加账号(将浏览器环境名称绑定到指定电脑)。若已存在相同绑定则直接返回已有记录。
**状态码**:
| 状态码 | 说明 |
|--------|------|
| 201 | 成功创建或返回已有记录 |
| 400 | 请求参数错误(如缺少 browser_name、worker_id |
| 401 | 未登录或 token 失效 |
**请求参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| browser_name | string | 是 | 浏览器环境名称 |
| worker_id | string | 是 | 目标电脑的 Worker ID |
**请求示例**:
```json
{
"browser_name": "第一个",
"worker_id": "worker-1"
}
```
**成功响应** (201):
```json
{
"id": 1,
"worker_id": "worker-1",
"browser_id": "name:第一个",
"browser_name": "第一个",
"boss_username": "",
"is_logged_in": false,
"current_task_id": null,
"current_task_status": null,
"checked_at": null,
"created_at": "2026-02-14T16:00:00",
"updated_at": "2026-02-14T16:00:00",
"worker_name": "本机",
"worker_online": true
}
```
---
### GET /api/accounts
**说明**: 查询所有账号列表,包含电脑名称、在线状态、任务执行状态。
**状态码**:
| 状态码 | 说明 |
|--------|------|
| 200 | 成功,返回账号数组 |
| 401 | 未登录或 token 失效 |
**查询参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| worker_id | string | 否 | 按 Worker ID 过滤 |
**响应示例**:
```json
[
{
"id": 1,
"worker_id": "worker-1",
"browser_id": "abd63190eea84dc49429962f7bb330a4",
"browser_name": "第一个",
"boss_username": "某用户",
"is_logged_in": true,
"current_task_id": "3f113d90bb32",
"current_task_status": "success",
"checked_at": "2026-02-14T16:05:00",
"created_at": "2026-02-14T16:00:00",
"updated_at": "2026-02-14T16:05:00",
"worker_name": "本机",
"worker_online": true
}
]
```
| 返回字段 | 类型 | 说明 |
|---------|------|------|
| id | int | 账号记录 ID |
| worker_id | string | 绑定的 Worker ID |
| browser_id | string | 比特浏览器窗口 ID |
| browser_name | string | 浏览器环境名称 |
| boss_username | string | BOSS 直聘用户名(检测后填充) |
| is_logged_in | boolean | 是否已登录 BOSS |
| current_task_id | string/null | 当前关联的任务 ID |
| current_task_status | string/null | 当前任务状态:`pending` / `dispatched` / `running` / `success` / `failed` |
| checked_at | string/null | 最近一次检测时间ISO 格式) |
| created_at | string | 创建时间 |
| updated_at | string | 更新时间 |
| worker_name | string | 电脑名称(运行时补充) |
| worker_online | boolean | 电脑是否在线(运行时补充) |
---
### GET /api/accounts/{id}
**说明**: 查询单个账号详情。
**状态码**:
| 状态码 | 说明 |
|--------|------|
| 200 | 成功,返回单个账号对象 |
| 401 | 未登录或 token 失效 |
| 404 | 账号不存在 |
**路径参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | int | 是 | 账号记录 ID |
**成功响应** (200): 同上单个账号对象
**失败响应** (404):
```json
{
"detail": "账号不存在"
}
```
---
### DELETE /api/accounts/{id}
**说明**: 删除指定账号。
**状态码**:
| 状态码 | 说明 |
|--------|------|
| 200 | 成功删除 |
| 401 | 未登录或 token 失效 |
| 404 | 账号不存在 |
**路径参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | int | 是 | 账号记录 ID |
**成功响应** (200):
```json
{
"message": "账号已删除"
}
```
**失败响应** (404):
```json
{
"detail": "账号不存在"
}
```
---
## 五、任务管理
> 以下接口均需登录(携带 `auth_token` Cookie
>
> **统一任务入口**:所有任务(检查登录、招聘等)都通过 `POST /api/tasks` 提交,通过 `task_type` 字段区分任务类型。
### POST /api/tasks
**说明**: 提交新任务。通过 `task_type` 指定任务类型,系统自动路由到目标 Worker 执行。
**状态码**:
| 状态码 | 说明 |
|--------|------|
| 201 | 成功,任务已创建并派发 |
| 400 | 未指定 worker_id 或 account_name |
| 401 | 未登录或 token 失效 |
| 404 | 未找到拥有该浏览器环境的在线 Worker |
| 503 | Worker 不在线 / WebSocket 连接不存在 / 派发失败 |
**请求参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| task_type | string | 是 | 任务类型:`check_login`(检查登录)、`boss_recruit`(招聘) |
| worker_id | string | 否 | 目标 Worker ID与 account_name 二选一) |
| account_name | string | 否 | 浏览器环境名称(系统自动查找对应 Worker |
| params | object | 否 | 任务附加参数,默认 `{}` |
**路由规则**: `worker_id` 优先;若未指定则通过 `account_name` 自动查找对应的在线 Worker。
**请求示例 — 检查登录**:
```json
{
"task_type": "check_login",
"account_name": "第一个"
}
```
**请求示例 — 招聘任务**:
```json
{
"task_type": "boss_recruit",
"worker_id": "worker-1",
"params": {
"keyword": "Python开发",
"count": 10
}
}
```
**成功响应** (201):
```json
{
"task_id": "3f113d90bb32",
"task_type": "check_login",
"status": "dispatched",
"worker_id": "worker-1",
"account_name": "第一个",
"params": {},
"progress": null,
"result": null,
"error": null,
"created_at": 1739520000.123,
"updated_at": 1739520000.456
}
```
**失败响应**:
| 状态码 | 说明 |
|--------|------|
| 400 | 未指定 worker_id 或 account_name |
| 401 | 未登录或 token 失效 |
| 404 | 未找到拥有该浏览器环境的在线 Worker |
| 503 | Worker 不在线 / WebSocket 连接不存在 / 派发失败 |
---
### GET /api/tasks
**说明**: 查询任务列表(内存中的任务)。
**状态码**:
| 状态码 | 说明 |
|--------|------|
| 200 | 成功,返回任务数组 |
| 401 | 未登录或 token 失效 |
**查询参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| worker_id | string | 否 | 按 Worker ID 过滤 |
| status | string | 否 | 按状态过滤:`pending` / `dispatched` / `running` / `success` / `failed` / `cancelled` |
| limit | int | 否 | 返回数量限制,默认 50 |
**响应示例**:
```json
[
{
"task_id": "3f113d90bb32",
"task_type": "check_login",
"status": "success",
"worker_id": "worker-1",
"account_name": "第一个",
"params": {},
"progress": "检测完成: 第一个 → 未登录 ()",
"result": {
"browser_id": "abd63190eea84dc49429962f7bb330a4",
"browser_name": "第一个",
"boss_username": "",
"is_logged_in": false
},
"error": null,
"created_at": 1739520000.123,
"updated_at": 1739520030.789
}
]
```
| 返回字段 | 类型 | 说明 |
|---------|------|------|
| task_id | string | 任务唯一 ID12 位十六进制) |
| task_type | string | 任务类型 |
| status | string | 任务状态:`pending``dispatched``running``success`/`failed` |
| worker_id | string/null | 执行任务的 Worker ID |
| account_name | string/null | 关联的浏览器环境名称 |
| params | object | 任务参数 |
| progress | string/null | 执行进度描述 |
| result | object/null | 任务结果(成功时返回) |
| error | string/null | 错误信息(失败时返回) |
| created_at | float | 创建时间Unix 时间戳) |
| updated_at | float | 最后更新时间Unix 时间戳) |
---
### GET /api/tasks/{task_id}
**说明**: 查询指定任务的状态和结果。
**状态码**:
| 状态码 | 说明 |
|--------|------|
| 200 | 成功,返回单个任务对象 |
| 401 | 未登录或 token 失效 |
| 404 | 任务不存在 |
**路径参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| task_id | string | 是 | 任务 ID |
**成功响应** (200): 同上单个任务对象
**失败响应** (404):
```json
{
"detail": "任务 xxx 不存在"
}
```
---
## 六、任务类型说明
| task_type | 说明 | 必要参数 | 返回 result |
|-----------|------|---------|-------------|
| `check_login` | 检测浏览器环境中 BOSS 账号是否已登录 | `account_name`(环境名) | `{ "browser_id", "browser_name", "boss_username", "is_logged_in" }` |
| `boss_recruit` | 执行 BOSS 直聘自动招聘流程 | `worker_id` + `params` | 根据任务定义返回 |
---
## 七、任务状态流转
```
pending → dispatched → running → success
→ failed
→ cancelled
```
| 状态 | 说明 |
|------|------|
| pending | 任务已创建,等待派发 |
| dispatched | 已通过 WebSocket 发送给 Worker |
| running | Worker 正在执行中 |
| success | 执行成功 |
| failed | 执行失败 |
| cancelled | 已取消 |
---
## 八、联系记录(数据展示)
> 以下接口均需登录(携带 `auth_token` Cookie
### GET /api/contacts/query按电话/微信号查询)
**说明**:根据已获取的电话号码或微信号查询联系记录,用于数据展示。对 `contact` 字段做模糊匹配。
**状态码**
| 状态码 | 说明 |
|--------|------|
| 200 | 成功,返回分页结果 |
| 400 | 未提供 contact 参数 |
| 401 | 未登录或 token 失效 |
**查询参数**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| contact | string | 是 | 电话号码或微信号(支持部分匹配) |
| page | int | 否 | 页码,默认 1 |
| page_size | int | 否 | 每页条数,默认 20 |
**请求示例**
```
GET /api/contacts/query?contact=13800138000
GET /api/contacts/query?contact=wxid_abc&page=1&page_size=10
```
**成功响应** (200)
```json
{
"total": 2,
"page": 1,
"page_size": 20,
"contact_keyword": "13800138000",
"results": [
{
"id": 1,
"name": "张三",
"position": "Python开发",
"contact": "13800138000",
"reply_status": "已回复",
"wechat_exchanged": true,
"account_id": 1,
"worker_id": "worker-1",
"notes": "",
"contacted_at": "2026-02-24T10:00:00",
"created_at": "2026-02-24T10:00:00"
}
]
}
```
| 返回字段 | 类型 | 说明 |
|---------|------|------|
| total | int | 匹配总数 |
| page | int | 当前页 |
| page_size | int | 每页条数 |
| contact_keyword | string | 本次查询使用的关键词 |
| results | array | 联系记录列表(字段同联系记录模型) |
**失败响应** (400):未提供 contact 参数时
```json
{
"detail": "请提供 contact 参数(电话号码或微信号)"
}
```
---
### GET /api/contacts
**说明**:分页查询联系记录,支持按姓名、岗位、联系方式关键词搜索,以及按回复状态、是否交换微信筛选。
**状态码**
| 状态码 | 说明 |
|--------|------|
| 200 | 成功,返回 total、page、page_size、results |
| 401 | 未登录或 token 失效 |
**查询参数**`search`(关键词)、`reply_status``wechat_exchanged``page``page_size`。返回格式同上(含 `total``page``page_size``results`)。
---
## 九、通用错误格式
所有接口的错误响应统一为:
```json
{
"detail": "错误描述信息"
}
```
| 状态码 | 说明 |
|--------|------|
| 400 | 请求参数错误 |
| 401 | 未登录或 token 失效 |
| 403 | 无权限 |
| 404 | 资源不存在 |
| 503 | Worker 不在线或服务不可用 |

View File

@@ -13,9 +13,10 @@ def custom_exception_handler(exc, context):
# 确保使用异常自带的 HTTP 状态码(如 401 登录失效、403 无权限)
if isinstance(exc, APIException) and getattr(exc, "status_code", None):
response.status_code = exc.status_code
# 统一格式:{"detail": "..."}
# 统一格式:{"detail": "...", "code": 状态码}
if isinstance(response.data, list):
response.data = {"detail": response.data[0] if response.data else str(exc)}
elif isinstance(response.data, dict) and "detail" not in response.data:
response.data = {"detail": str(response.data)}
response.data["code"] = response.status_code
return response

View File

@@ -2,6 +2,34 @@
"""
自定义中间件。
"""
import json
class ResponseCodeMiddleware:
"""为所有 JSON 响应 body 统一注入 code 字段(与 HTTP 状态码一致)。"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
if "application/json" not in response.get("Content-Type", ""):
return response
try:
content = response.content.decode(response.charset or "utf-8")
data = json.loads(content)
code = response.status_code
if isinstance(data, dict):
if "code" not in data:
data["code"] = code
elif isinstance(data, list):
data = {"code": code, "data": data}
else:
data = {"code": code, "data": data}
response.content = json.dumps(data, ensure_ascii=False).encode(response.charset or "utf-8")
except (ValueError, TypeError, UnicodeDecodeError):
pass
return response
class CorsMiddleware:

View File

@@ -25,6 +25,7 @@ INSTALLED_APPS = [
MIDDLEWARE = [
"django.middleware.common.CommonMiddleware",
"server.middleware.CorsMiddleware",
"server.middleware.ResponseCodeMiddleware",
]
# ─── URL / ASGI ───