← 返回介绍页 下载 Markdown 健康检查

云机 Worker 对接 API 文档

版本:v2.0.2(对齐 云机大脑-后端 / 云机大脑-前端 当前实现)
更新日期:2026-07-02
适用对象:云手机脚本、自动化 Worker、协议层以外的执行端

1. 概述

云机 Worker 通过调度大脑后端领取任务,在本地调用抖音相关能力后 上报(report) 回服务端。

系统支持两种 任务模式task_mode / 导入时 job_mode):

模式job_modepop 下发report 上报(api_status=success 时)
手机号筛查(默认)phone_screen一个候选手机号 + bucket必须 nickname_roi_base64(± nickname_roi_base64s[]);不要求 api_nickname_first / api_nickname_firsts
抖音号找回douyin_recover一个抖音号 douyin_id必须 phone_prefix(3 位)+ nickname_roi_base64(75×45 PNG base64)
强制约定(v1.8)

>

- phone_screen(手机号筛查)api_status=success必须 上报符合规格的 PNG base64 ROI;命中 由 ROI 评分裁决。不要求、不读取 api_nickname_first / api_nickname_firsts(若上报亦被忽略)。用户不存在(error_1011 / error_code=1011不要求 ROI。
- douyin_recover(抖音号找回):规则不变,见 §6.4;本模式 从未要求 昵称首字字段。
- 缺 ROI 或格式/尺寸错误时服务端返回 HTTP 400msg 见 §6.8。

手机号筛查详见 §5~§7;抖音号找回详见 §9.5


1.1 双 Worker 脚本架构(推荐)

手机号筛查抖音号找回 在云机侧采用 两套独立 Worker 脚本,不合并为同一个通用脚本:

Worker 脚本领取接口对接 task_modepop 领到report 上报典型部署
手机号筛查 WorkerGET /task/pop/phonephone_screenphone + bucket必须 nickname_roi_base64(± nickname_roi_base64s[]);不要求 昵称首字手机号筛查专用云机池
抖音号找回 WorkerGET /task/pop/douyindouyin_recoverdouyin_id + uid必须 phone_prefix + nickname_roi_base64抖音号找回专用云机池

为何拆成两个脚本:

部署约定:

1. 同一云机实例同一时间只跑一种 Worker,不要在同一台云机上混跑两个脚本。

2. 推荐云机池隔离:筛查任务只部署「手机号筛查 Worker」;找回任务只部署「抖音号找回 Worker」。

3. 领取任务必须使用对应专用接口(§5.1):手机号池调用 /task/pop/phone,抖音号池调用 /task/pop/douyin;服务端在对应 job_moderunning 批次间轮询,无需每次上传任务配置 job_id

4. pop 后仍应读取 data.task_mode 作兜底校验;若与当前脚本不符,说明云机池或接口配置错误。

5. device-info 心跳、GET /worker/proxy(若启用)、worker_key 鉴权、device_id=names 规则 两种 Worker 完全相同;差异仅在领取接口与 report Body。

┌─────────────────────┐   GET /task/pop/phone      ┌──────────┐
│ 手机号筛查 Worker 池 │ ─────────────────────────► │          │
└─────────────────────┘                            │ 调度大脑  │
                                                   │          │
┌─────────────────────┐   GET /task/pop/douyin     │          │
│ 抖音号找回 Worker 池 │ ─────────────────────────► └──────────┘
└─────────────────────┘

一号多抖音(仅 phone_screen): 同一手机号可能绑定多个抖音账号。推荐上报 nickname_roi_base64s[] 多张 ROI,服务端取 max(roi_match_score) 判定(详见 §7.4)。无需上报 api_nickname_firsts

手机号筛查任务:导入 JSON 须含 account_unique_key(来自抖音号找回 export)并成功绑定期望 ROI。命中判定 仅看 ROI 评分;Worker 须截取并上报 75×45 昵称 ROI PNG base64。
硬规则: 期望 ROI 必须来自找回侧 真实截图;禁止用昵称首字合成假图顶替;仅有首字、无 ROI 的账号无法进入筛查命中(详见 手机号模式-昵称ROI比对方案.md §14)。

Worker 需对接以下接口(另可选健康检查):

方法路径说明
POST/api/v1/worker/device-info上报云机设备信息(≤ 60 秒一次)
GET/api/v1/worker/proxy按需获取出口代理(启用代理网关时)
GET/api/v1/task/pop/phone手机号池专用:领取一条手机号筛查任务
GET/api/v1/task/pop/douyin抖音号池专用:领取一条抖音号找回任务
POST/api/v1/task/report上报该子任务结果(字段随 task_mode 不同)
旧版 GET /api/v1/task/pop 仍可用(在所有 running 批次间轮询),新部署请改用上述两个专用接口,避免混池误领。job_id可选调试参数,日常上传任务无需配置。

典型循环(启用出口代理时):

                    device-info(≤60s)
┌─────────┐ ─────────────────────────► ┌──────────┐
│ 云机脚本 │                            │ 调度大脑  │
└─────────┘                            └──────────┘
     │  GET /worker/proxy(需要出口 IP 时)
     └────────────────────────────────►  配置本地代理
     │    pop     ┌──────────┐  本地查号   ┌─────────┐  report
     └──────────► │ 调度大脑  │ ─────────► │ 抖音 API │ ────────► ...
                  └──────────┘             └─────────┘
     ▲                                              │
     └────────────── 无任务则等待后重试 ───────────────┘

未启用代理网关时: 省略 GET /worker/proxy,其余流程不变。

典型循环(未启用代理):

                    device-info(≤60s)
┌─────────┐ ─────────────────────────► ┌──────────┐
│ 云机脚本 │                            │ 调度大脑  │
└─────────┘                            └──────────┘
     │    pop     ┌──────────┐  本地查号   ┌─────────┐  report
     └──────────► │ 调度大脑  │ ─────────► │ 抖音 API │ ────────► ...
                  └──────────┘             └─────────┘
     ▲                                              │
     └────────────── 无任务则等待后重试 ───────────────┘

2. 基础信息

2.1 Base URL

环境示例
本地开发http://127.0.0.1:8080
生产由运维提供,如 https://your-domain.com

所有业务接口前缀:/api/v1

2.2 鉴权:worker_key

服务端在 configs/config.yaml 中配置:

auth:
  worker_api_key: "local-e2e-worker-key"   # 生产环境请更换为强密钥
规则说明
配置非空所有 Worker 请求必须携带正确 worker_key,否则 401
配置为空开发模式可关闭校验(生产禁止

传递方式:

接口推荐方式
POST /worker/device-infoQuery:?worker_key=xxx
GET /worker/proxyQuery:?worker_key=xxx&device_id=xxx
GET /task/pop/phoneQuery:?worker_key=xxx
GET /task/pop/douyinQuery:?worker_key=xxx
GET /task/pop(兼容)Query:?worker_key=xxx
POST /task/reportQuery:?worker_key=xxx,或 JSON Body 字段 "worker_key": "xxx"

2.3 统一响应格式

成功时 HTTP 状态码一般为 200,Body 为 JSON:

{
  "code": 0,
  "msg": "success",
  "data": { }
}
字段说明
code业务码,0 表示成功
msg人类可读说明
data业务数据,可能为 null

错误时:

HTTPcode典型 msg
400400参数错误、task 不存在等
401401invalid or missing worker_key
20050301proxy_unavailable(代理网关不可用,见 §4.5)

3. 设备标识 device_id(= Names

接口是否必填说明
device-info必填Body 字段 names
proxy必填Query 参数 device_id;为空返回 400 device_id required
pop必填Query 参数 device_id;为空返回 400 device_id required
report建议Body 字段 device_id;用于 Admin 云机看板 Worker 统计

统一约定: device_id 必须等于 设备信息中的 names 字段(云机实例唯一名)。

示例:

names / device_id: a3a5a0b3e1591722075c5c95da8dab20_1_T1001

看板数据模型(Admin /admin/workers):

层级分组键说明
宿主机ip一台物理机可挂载多个云机实例
云机实例namesdevice_id 相同

双状态(请勿混淆):

状态来源含义
实例状态 statedevice-info 上报云手机是否已开机(如 running
Worker 活跃状态pop/report 时间窗脚本是否在领任务/上报(活跃/处理中/空闲)

看板可见条件:

行为宿主机摘要实例明细
GET /health
device-info(按 ip 分组)(实例属性 + Worker 可能为空)
pop / report(带 device_id=names是(含 Worker 活跃/错误率等)
前端管理端与云机 Worker 必须连接同一后端实例。本地开发时前端代理默认 http://127.0.0.1:8080

4. 设备信息上报 — POST /api/v1/worker/device-info

云机脚本应 每 ≤ 60 秒 调用一次,上报宿主机与实例信息。管理端看板按 ip 分组 展示宿主机摘要,展开后查看该 IP 下各实例。

4.1 请求

POST /api/v1/worker/device-info?worker_key={worker_key}
Content-Type: application/json

Body(JSON):

{
  "names": "a3a5a0b3e1591722075c5c95da8dab20_1_T1001",
  "state": "running",
  "data": "/mmc/data/data1_1780849293633",
  "index": 1,
  "ip": "192.168.1.104"
}
字段必填说明
names云机实例唯一名;同时作为 pop/report 的 device_id
state云手机开机/实例运行状态;当前为 running 表示已开机
data实例数据目录路径
index宿主机上的槽位序号
ip宿主机 IP(全局唯一);看板分组键
state=running 仅表示云手机已开机,不代表 Worker 脚本正在 pop/report。脚本是否在干活请看管理端「Worker 活跃」列。

4.2 成功响应

{
  "code": 0,
  "msg": "recorded"
}

4.3 错误示例

{
  "code": 400,
  "msg": "names required"
}

4.4 信息在线与开机状态判定(后端兜底)

管理端 不会 因 device-info 心跳缺失而误判「关机」,前提是 pop/report 仍在进行。云机侧无需额外逻辑。

判定项优先规则兜底规则(后端推断)
信息在线last_heartbeat_at 距今 ≤ 60 秒近 2 分钟内有 report,或近 5 分钟内有 pop,或 Worker 状态为「活跃/处理中」
已开机信息在线且 state=runningpop/report 仍在进行 → 推断为已开机(响应字段 instance_running=trueinfo_inferred=true
信息离线 / 未开机心跳超时 pop/report 均已停滞两者同时满足才显示离线

典型场景:

心跳pop/report管理端展示
正常(≤60s)正常已开机(来自 device-info)
超时仍在进行已开机(推断),不会判为关机
超时均已停滞信息离线
info_inferred=true 表示当前「在线/已开机」由 Worker 活动推断,而非最新 device-info 心跳。

4.5 出口代理 — GET /api/v1/worker/proxy

云机 只对接调度大脑,不直连快代理等代理商。当管理端开启 proxy.enabled=true 且配置快代理(KDL)凭证后,云机在需要出口 IP 时调用本接口;不与 pop 捆绑,按需获取即可。

双模式并行(v2.0+): 管理端通过 proxy.dps.enabled / proxy.tps.enabled 独立开关,可同时启用;云机通过 Query proxy_mode=dps|tps 指定产品线:

proxy_mode产品云机 connection 含义后端内部 API
dps(默认)私密代理动态出口 IP:端口:账号:密码getdps / checkdpsvalid / getdpsvalidtime
tps隧道代理固定隧道 tunnel_host:端口:账号:密码getorderinfo / getproxyauthorization / tpscurrentip / changetpsip
v2.0 变更: 废弃全局 proxy.mode 二选一;DPS 与 TPS 绑定在 device_proxy(device_id, proxy_mode) 双行并存,互不影响。

云机脚本 仍解析 connection 配置本地 HTTP 代理,一般无需区分模式;若需打日志或排查,可读 proxy_modeegress_ip(TPS 当前出口 IP,仅展示用)、kdl_order_id(启用订单池时,本次分配使用的快代理订单号)。详见 快代理-隧道代理对接文档 §9。

多订单池(proxy.pool.enabled=true): Worker 侧 请求格式不变;后端在 getdps / 隧道解析前从 kdl_order_pool 按策略 Pick 可用订单,并将订单号写入响应 kdl_order_id 与表 device_proxy.kdl_order_id。单订单凭证(pool.enabled=false)时仍走 providers.kdl_dps / kdl_tps 静态配置,kdl_order_id 通常为空。运维见 出口代理网关-多订单池优化方案

4.5.1 请求

GET /api/v1/worker/proxy?device_id={names}&worker_key={worker_key}&proxy_mode=dps&refresh=0
Query 参数必填说明
device_id与 device-info 的 names 一致
worker_key是*与服务端配置一致
proxy_mode推荐dps \tps;省略时使用 default_mode(兼容旧脚本)
refresh1true 强制换 IP;默认 0 复用当前绑定

proxy_mode 缺省解析顺序: Query 合法且对应模式已启用 → 使用;否则读 default_mode;仅一种模式开启 → 用该模式;双开且无法确定 → 400 proxy_mode required

4.5.2 成功响应

{
  "code": 0,
  "msg": "success",
  "data": {
    "proxy_id": "a1b2c3...",
    "device_id": "a3a5a0b3e1591722075c5c95da8dab20_1_T1001",
    "host": "113.31.123.45",
    "port": 15818,
    "username": "user001",
    "password": "pass001",
    "addr": "113.31.123.45:15818",
    "connection": "113.31.123.45:15818:user001:pass001",
    "scheme": "http",
    "status": "active",
    "assigned_at": "2026-06-12T10:00:00+08:00",
    "valid_until": "2026-06-12T10:30:00+08:00",
    "remaining_seconds": 1800,
    "last_check_at": "2026-06-12T10:05:00+08:00",
    "last_check_ok": true,
    "reused": true,
    "provider": "kdl",
    "proxy_mode": "dps",
    "kdl_order_id": "987807746559625"
  }
}

TPS 模式示例(字段差异): host / connection 中为 隧道入口(非出口 IP);egress_ip 为当前出口 IP(可选阅读):

{
  "code": 0,
  "msg": "success",
  "data": {
    "proxy_id": "a1b2c3...",
    "device_id": "a3a5a0b3e1591722075c5c95da8dab20_1_T1001",
    "host": "tps-xxx.kdlapi.com",
    "port": 15818,
    "username": "user001",
    "password": "pass001",
    "addr": "tps-xxx.kdlapi.com:15818",
    "connection": "tps-xxx.kdlapi.com:15818:user001:pass001",
    "scheme": "http",
    "status": "active",
    "proxy_mode": "tps",
    "egress_ip": "113.31.123.45",
    "valid_until": "2026-06-12T10:30:00+08:00",
    "remaining_seconds": 1800,
    "last_check_ok": true,
    "reused": true,
    "provider": "kdl",
    "kdl_order_id": "997807654380916"
  }
}
字段说明
connection推荐直接使用:四段 host:端口:账号:密码。DPS 时 host 为动态出口 IP;TPS 时 host固定隧道入口
host / port / username / password分项字段,与 connection 等价
proxy_mode本次请求指定的模式(dps / tps),与 Query 一致
kdl_order_id订单池开启时:本次分配使用的快代理订单号(日志/排查);未启用订单池时通常省略
egress_ip仅 TPS:隧道当前出口 IP(展示/日志用);配置本地代理仍以 connection 为准
valid_until / remaining_seconds绑定有效期。DPS:当前出口 IP 剩余可用时间(秒,来自 getdpsvalidtime / getdpsf_et);TPS:隧道换 IP 周期估算
reusedtrue 表示复用已有绑定;false 表示本次新分配或刷新
last_check_ok最近一次有效性检测结果
provider固定为 kdl(具体产品线看 proxy_mode

复用与换 IP 策略(按模式):

场景DPS(私密代理)TPS(隧道代理)
refresh=0 且绑定仍有效checkdpsvalid,通过则复用同一出口 IPtpscurrentip 校验当前出口,通过则复用同一隧道绑定
check 失败或已过期重新 getdps 提取新 IP重新解析隧道并查询 tpscurrentip
refresh=1重新 getdps 换出口 IPchangetpsip 更换整条隧道出口(影响共享该隧道的全部云机,见 §13 Q16)
同设备 DPS + TPS分别调用 proxy_mode=dps / proxy_mode=tps两套绑定 并存,互不覆盖
禁用某模式该模式新请求返回 50301;历史绑定行保留同左

管理端看板亦提供 检测 / 换 IP,行为与上表一致。

4.5.3 代理不可用

{
  "code": 50301,
  "msg": "proxy_unavailable",
  "data": null
}

含义: 代理网关未启用、凭证无效、订单池无可用 ACTIVE 订单、或代理商暂时无可用 IP。

常见原因(v2.0+):

原因处理
proxy.enabled=false 或对应 dps/tps.enabled=false管理端开启开关
单订单凭证错误或余额耗尽检查 providers.kdl_* 或快代理控制台
pool.enabled=true 但账户密钥未配 / 同步失败配置 pool.account_secret_*,在 订单池看板 点「立即同步」
订单池全部 SUSPECT / EXHAUSTED / 临期禁用看板告警处理对应订单

云机建议:

1. 不要 pop(无可用出口时不应领任务)。

2. 等待 10~30 秒后重试 GET /worker/proxy

3. 仍失败则记录日志并告警,勿空转 pop。

4.5.4 推荐脚本顺序

# 伪代码
while True:
    post_device_info()  # 每 ≤60s

    proxy = get_proxy(device_id, proxy_mode="dps", refresh=0)
    if proxy.code == 50301:
        sleep(15)
        continue
    configure_local_proxy(proxy.data.connection)  # host:port:user:pass(DPS=出口IP,TPS=隧道入口)
    # 可选日志:proxy.data.proxy_mode, proxy.data.kdl_order_id, proxy.data.remaining_seconds

    task = pop(device_id)
    if task.code == 40401:
        sleep(3)
        continue
    result = query_douyin(task.data.phone)
    report(task.data.task_id, result)

4.5.5 connection 字段解析(云机侧)

data.connection 格式固定为 四段,以英文冒号分隔:

host:端口:账号:密码
模式第一段 host 含义示例
dps动态出口 IP113.31.123.45:15818:user001:pass001
tps固定隧道入口域名/IPtps-xxx.kdlapi.com:15818:user001:pass001
TPS 模式下实际出口 IP 在 egress_ip 字段;配置本地 HTTP 代理时仍使用 connection 中的隧道 host:port,不要将 egress_ip 当作代理服务器地址。

解析示例(Python):

def parse_connection(connection: str):
    host, port, username, password = connection.split(":", 3)
    return host, int(port), username, password

云机脚本建议:

1. 优先使用 connection 一次性解析;也可分别读取 host / port / username / password

2. 按本地环境配置 HTTP 代理(scheme 默认 http,见 data.scheme)。

3. 在 remaining_seconds 接近 0 或查号失败需换 IP 时,再次调用 GET /worker/proxy?proxy_mode={mode}&refresh=1(须与当前业务使用的模式一致)。

4. 同一 (device_id, proxy_mode) 在有效期内默认复用绑定reused=true),避免频繁换 IP 触发代理商限流。

5. 可选:读取 proxy_mode / egress_ip / kdl_order_id / remaining_seconds 写日志;配置本地代理不必分支,两种模式解析 connection 方式相同。

4.5.6 错误响应

HTTPcodemsg说明
400400device_id required未传 device_id
400400proxy_mode requiredDPS 与 TPS 同时开启 且 Query 未传合法 proxy_mode
20050301proxy_unavailable网关未启用、KDL 凭证无效、订单池无可用订单或代理商无可用 IP

proxy_mode required 示例:

{
  "code": 400,
  "msg": "proxy_mode required"
}

device_id required 示例:

{
  "code": 400,
  "msg": "device_id required"
}

5. 领取任务 — 双池专用接口(推荐)

手机号筛查与抖音号找回使用 两个独立领取地址。服务端仅在对应 job_modestatus=running 的批次间公平轮询;管理端上传并启动任务后,云机无需配置 job_id

5.1 手机号筛查 — GET /api/v1/task/pop/phone

GET /api/v1/task/pop/phone?device_id={device_id}&worker_key={worker_key}&job_id={optional}
Query 参数必填说明
device_id云机实例 names,与 device-info 一致
worker_key是*与服务端配置一致(*配置为空时可省略)
job_id可选:仅调试或强制指定单批次 UUID;日常留空即可

调度范围:job_mode=phone_screenstatus=running 的批次。

5.2 抖音号找回 — GET /api/v1/task/pop/douyin

GET /api/v1/task/pop/douyin?device_id={device_id}&worker_key={worker_key}&job_id={optional}

参数与 §5.1 相同。

调度范围:job_mode=douyin_recoverstatus=running 的批次。

5.3 兼容接口 — GET /api/v1/task/pop(不推荐)

GET /api/v1/task/pop?device_id={device_id}&worker_key={worker_key}&job_id={optional}

在所有 running 批次(不区分 job_mode)间公平轮询。两种模式批次同时运行时可能领到错误类型任务,仅保留给旧脚本;新部署请使用 §5.1 / §5.2。

5.4 成功 — 领到任务(手机号筛查)

{
  "code": 0,
  "msg": "success",
  "data": {
    "task_id": "12345",
    "job_id": "882fb1ae-b1e6-45c6-aba5-86a8e9ae9fd4",
    "task_mode": "phone_screen",
    "uid": "104474489018",
    "phone": "15664076367",
    "bucket": "top"
  }
}
字段类型说明
task_idstring任务 ID,手机号模式下为 candidate_phones.id;抖音号找回模式下为 douyin_records.id;report 时必须原样回传
job_idstring所属导入批次 ID
task_modestringphone_screen(默认)或 douyin_recover
uidstring抖音 UID(导入数据)
phonestring待筛查手机号(phone_screen
bucketstring候选桶:top / other / segmentphone_screen

领取后的服务端状态(phone_screen):

领取后的服务端状态(douyin_recover):

5.5 成功 — 暂无任务

{
  "code": 40401,
  "msg": "no_task",
  "data": null
}

含义: 当前接口对应模式下没有可领取的待办(无 running 批次、已全部处理完毕、或指定 job_id 无待办)。

建议: 等待 2~5 秒后重试 pop,避免空转打满 CPU。

使用专用接口时:抖音号池在仅有手机号批次 running 时会收到 40401(反之亦然),属正常现象,表示该池当前无任务,不会误领另一模式任务。

5.6 错误示例

{
  "code": 400,
  "msg": "device_id required"
}

5.7 调度说明(手机号筛查 Worker)

1. 每次 pop 只返回 1 个手机号,不是一次返回整个 Top 组。

2. 同一抖音号 同一时刻只会有一条 号码处于 dispatched。

3. 未指定 job_id 时,多个同模式 running 批次按 公平轮询 分配。

4. 单批次内,候选号按 Top → Other → Segment 顺序尝试(sort_order 升序)。

5.8 抖音号找回模式 — pop 响应(douyin_recover Worker)

{
  "code": 0,
  "msg": "success",
  "data": {
    "task_id": "67890",
    "job_id": "882fb1ae-b1e6-45c6-aba5-86a8e9ae9fd4",
    "task_mode": "douyin_recover",
    "douyin_id": "Lai.1204",
    "uid": "4029078185206584"
  }
}
字段说明
douyin_id下发给云机执行的抖音号,来自导入 JSON 的 data.buyer.douyin_id
uid导入 JSON 的 data.buyer.uid,用于去重与展示

每个抖音号 仅 pop 一次;无 phone / bucket 字段。锁定时长与超时回收规则与 §8 相同。


6. 上报结果 — POST /api/v1/task/report

手机号筛查与抖音号找回 共用同一 report 接口,但 Body 字段与校验规则 完全不同。服务端根据 task_id 路由:

1. 先在 douyin_recover 批次中按 douyin_records.id 查找;

2. 未命中再在 phone_screen 批次中按 candidate_phones.id 查找。

pop 响应 不含 has_expected_roi 等子类型标记。Worker 须按 本池任务模式管理端导入约定 选择 report 形态(见 §6.0)。

6.0 按任务模式:强制上报字段总览

task_mode场景api_status必须上报可省略 / 忽略
phone_screen手机号筛查(须已绑定期望 ROI)successdata.nickname_roi_base64 非空 data.nickname_roi_base64s[]api_nickname_first / api_nickname_firsts手机号模式不要求,上报亦忽略)
phone_screen用户不存在error_1011 / error_code=1011无(勿伪造 ROI)ROI、昵称首字均可省略
douyin_recover找回成功successrecover_successdata.phone_prefix(恰好 3 位数字)+ data.nickname_roi_base64api_nickname_first*(本模式从未要求)
douyin_recover找回失败非 success / 非 recover_success(如 recover_failed无(data{}

PNG base64 统一规格(凡要求 ROI 时):

要求
格式PNG(不接受 JPG/WebP/URL)
尺寸宽 75 × 高 45 像素
编码标准 Base64;允许 data:image/png;base64, 前缀
一号多抖音(仅手机号 ROI 绑定)使用 nickname_roi_base64s[] 逐张上报;服务端取 max(roi_match_score)

6.1 请求公共约定

POST /api/v1/task/report?worker_key={worker_key}
Content-Type: application/json
字段必填说明
task_idpop 返回的 task_id 字符串,不得篡改
device_id强烈建议云机实例 names,与 device-info 一致
api_status强烈建议见 §6.6;空字符串时服务端按 success 处理
data条件成功场景按 §6.2~§6.4 填充;失败场景可 {}

成功响应:

{
  "code": 0,
  "msg": "recorded"
}

校验失败时 HTTP 400,Body 示例:{"code":400,"msg":"nickname_roi_base64 required for ROI-bound record"}(完整列表见 §6.8)。

6.2 手机号筛查 — Legacy 昵称首字(已废弃,v1.8 起不再支持

v1.8 起phone_screen 模式 统一为 ROI 判定,不再接受 Legacy 首字比对路径。无绑定期望 ROI 的记录在 success report 时将返回 HTTP 400record requires ROI binding)。本节内容 仅作历史参考,新部署 Worker 请勿 再上报 api_nickname_first* 作为命中依据。

6.3 手机号筛查 — ROI 图像比对(phone_screen

适用:管理端导入手机号 JSON 时,每条携带有效 account_unique_key 且服务端已成功绑定期望 ROI(expected_roi_path 非空)。

导入前提: 对应抖音号找回记录须已落盘真实 75×45 昵称小图。仅有昵称首字、无 recover_nickname_roi_path 时,import 无法绑定期望 ROI,该条会被 跳过,不进入筛查队列。禁止用首字「画一张假 ROI」冒充基准(见 手机号模式-昵称ROI比对方案.md §14)。

查询成功 — 仅需上报 PNG base64:

{
  "task_id": "12345",
  "device_id": "a3a5a0b3e1591722075c5c95da8dab20_1_T1001",
  "api_status": "success",
  "data": {
    "nickname_roi_base64": "iVBORw0KGgoAAAANSUhEUgAAAEsAAAAtCAIAAABpgQH6...",
    "nickname_roi_base64s": []
  }
}

一号多抖音 — 推荐多张 ROI:

{
  "task_id": "12345",
  "device_id": "a3a5a0b3e1591722075c5c95da8dab20_1_T1001",
  "api_status": "success",
  "data": {
    "nickname_roi_base64s": [
      "iVBORw0KGgoAAAANSUhEUg...",
      "iVBORw0KGgoAAAANSUhEUg..."
    ]
  }
}
字段success说明
data.nickname_roi_base64必填(可与 nickname_roi_base64s 二选一)单张 75×45 PNG base64;与绑定期望 ROI 评分比对(命中判定依据
data.nickname_roi_base64s推荐(一号多抖音)字符串数组;与 nickname_roi_base64 合并去重后逐张算分,取 最高分
data.api_nickname_first不需要手机号模式 不要求;若上报服务端 忽略
data.api_nickname_firsts不需要同上

用户不存在:

{
  "task_id": "12345",
  "api_status": "error_1011",
  "data": { "error_code": 1011 }
}
场景服务端行为
success未提供 nickname_roi_base64 / nickname_roi_base64sHTTP 400msg: nickname_roi_base64 required for ROI-bound record
success 但 base64 非法 / 非 PNG / 尺寸非 75×45HTTP 400,见 §6.8
success 且 ROI 合法但 roi_match_score < 97.5(默认)未命中,候选号记为已查询,继续下一候选号
successroi_match_score ≥ 97.5命中,抖音号 passed(§7.4)
data.api_register_time 已废弃:Worker 无需上报。手机号筛查 命中判定仅看 roi_match_score(默认阈值 97.5,可配置 roi.pass_score)。api_nickname_first* 手机号模式不要求

6.4 抖音号找回 — douyin_recover report

GET /task/pop/douyin 领到的任务,task_id = douyin_records.id不是 candidate_phones.id)。

找回成功 — phone_prefixnickname_roi_base64 均为必填:

{
  "task_id": "67890",
  "device_id": "a3a5a0b3e1591722075c5c95da8dab20_1_T1001",
  "api_status": "success",
  "data": {
    "phone_prefix": "156",
    "nickname_roi_base64": "iVBORw0KGgoAAAANSUhEUgAAAEsAAAAtCAIAAABpgQH6..."
  }
}
字段success / recover_success说明
data.phone_prefix必填恰好 3 位数字(如 "156"
data.nickname_roi_base64必填75×45 PNG base64;用于落盘、生成 account_unique_key
data.nickname_roi_base64s不支持找回模式仅读取单字段 nickname_roi_base64
data.api_nickname_first不需要不参与找回判定;不可 代替 ROI
缺 ROI 仅报首字:不算找回完成,不能产生可用于手机号筛查的期望图绑定。历史仅有首字的数据须 补采(重跑本模式并强制截图)后再 export。

找回失败:

{
  "task_id": "67890",
  "device_id": "a3a5a0b3e1591722075c5c95da8dab20_1_T1001",
  "api_status": "recover_failed",
  "data": {}
}
场景服务端行为
success 但缺少 phone_prefixnickname_roi_base64HTTP 400
phone_prefix 非 3 位数字HTTP 400msg: phone_prefix must be 3 digits
ROI base64 非法 / 非 PNG / 尺寸错误HTTP 400,见 §6.8
非 success / 非 recover_success记录标为 faileddata 可省略
记录已 passed / failed 后重复 report幂等成功(§6.7)

6.5 管理端回写字段(ROI 绑定手机号任务)

Worker 无需上报以下字段;服务端比对后写入任务详情供运维复核:

字段说明
roi_match_score0~100 综合分
match_tierL1 / L2 / L3 / none
binary_match_rateL2 二值化像素一致率
dhash_hammingL3 感知哈希汉明距离

管理端展示 查询结果(用户存在 / 不存在)由服务端根据 api_status 与 ROI 判定自动回写;Worker 无需 上报昵称首字。

6.6 api_status 约定

适用模式含义服务端处理
success通用查询/找回成功按 §6.3~§6.4 校验必填字段并判定命中
recover_successdouyin_recover找回成功(与 success 等效)success
error_1011phone_screen手机号未注册抖音候选号 skipped不要求 ROI / 首字
recover_faileddouyin_recover找回失败记录 failed
其他非空值通用脚本/业务错误记入 Worker 错误统计;手机号模式按未命中处理

也可仅通过 data.error_code: 1011 表达用户不存在(与 api_status: error_1011 等效)。

6.7 重复上报(幂等)

模式条件行为
phone_screen候选号已是 已查询已跳过直接返回 recorded,不重复改判
douyin_recover记录已是 passedfailed直接返回 recorded,不重复改判

6.8 错误示例(与后端实现一致)

通用:

{ "code": 400, "msg": "task not found" }
{ "code": 400, "msg": "invalid task_id" }

手机号 ROI 绑定:

{ "code": 400, "msg": "nickname_roi_base64 required for ROI-bound record" }
{ "code": 400, "msg": "invalid nickname_roi_base64" }
{ "code": 400, "msg": "nickname_roi must be valid PNG" }
{ "code": 400, "msg": "nickname_roi must be 75x45" }

抖音号找回:

{ "code": 400, "msg": "phone_prefix must be 3 digits" }
{ "code": 400, "msg": "nickname_roi_base64 required" }
{ "code": 400, "msg": "record not locked" }
以上 msg 为服务端 tasksvc / verify 包实际返回字符串;云机脚本应对 400 打日志并区分「缺字段」与「图像规格错误」。

7. 命中判定规则(report 后)

政策原因:不再使用注册年份 作为通过条件。

服务端按任务类型选择判定路径:

任务类型判定方式
phone_screen(手机号筛查)图像 ROI 评分(§7.4),阈值默认 97.5
douyin_recover(抖音号找回)找回成功即记录 passed(§6.4);不涉及昵称首字
v1.8 起:手机号筛查 不再 使用昵称首字文本比对(Legacy 路径已移除)。

7.1~7.2 Legacy 昵称首字(已废弃

v1.8 之前 JSON/JSONL 无 ROI 绑定时曾用 api_nickname_first(s) 与导入期望首字比对。当前版本不再支持;所有 phone_screen 任务须走 ROI 判定。

7.3 命中后的数据

字段说明
verified_phone命中的手机号
verified_bucket来自 pop 的 bucket
strict_verifiedtop / strict 桶命中
loose_verifiedother / segment / loose 桶命中

7.4 手机号筛查 — 昵称 ROI 图像比对

当导入记录已通过 account_unique_key 绑定期望 ROI 后,手机号 report 在 api_status=success必须 上报符合 §6.3 规格的 nickname_roi_base64(或 nickname_roi_base64s[])。缺 ROI 返回 HTTP 400,不会进入评分流程。

硬门槛(V2):

评分公式:

若 PNG 字节完全一致:roi_match_score = 100(L1)
否则:
  s2 = binary_match_rate × 100
  s3 = (1 - dhash_hamming / 63) × 100
  tier_bonus = (s2 ≥ 98% ? +5 : 0)    # 仅 L2 达标加分
  roi_match_score = min(100, 0.70×s2 + 0.30×s3 + tier_bonus)
条件结果
error_code == 1011api_status == error_1011未命中,该号 skipped
api_status == successmax(roi_match_score) ≥ 97.5(默认)且通过硬门槛命中
api_status == success 但缺少 ROI、未过硬门槛或 score < 阈值未命中,继续下一候选号

一号多抖音:nickname_roi_base64s[] 逐张算分,取 max 与阈值比较。

report 成功示例(手机号筛查):

{
  "task_id": "12345",
  "device_id": "a3a5a0b3e1591722075c5c95da8dab20_1_T1001",
  "api_status": "success",
  "data": {
    "nickname_roi_base64": "iVBORw0KGgoAAAANSUhEUg...",
    "nickname_roi_base64s": ["iVBORw0KGgo...", "iVBORw0KGgo..."]
  }
}
手机号筛查 命中判定仅看 roi_match_score 与硬门槛;不要求 api_nickname_first。管理端 ROI 评分回写字段见 §6.5

导入后缓存预扫: ES 缓存 用于 user_not_exists(1011)预跳过;不再 用昵称首字预 pass/skip。须等待 Worker 实际上报 PNG base64 后评分。

与「仅有首字」的关系: 期望图必须是找回真截图。算法「首字区」= 左 35% 像素相似度,不是文本字符。禁止合成假期望图;详见方案 §14 / FAQ Q18。


8. 锁定时长与超时回收

pop 成功后,服务端对该抖音号加锁,默认 120 秒import.lock_timeout_seconds)。

阶段行为
0 ~ 锁定时长内未 report号码保持 dispatched,其他 Worker 不能 pop 该抖音号
超过锁定时长仍未 report后台 daemon 自动回收:候选号 → 待筛查,抖音号 → 待筛查,可被再次 pop

daemon 扫描间隔默认 30 秒daemon.interval_seconds),故最坏回收时间约为 锁定时长 + 扫描间隔

8.1 云机脚本建议

1. pop 后 尽快 report;若本地脚本约需 60 秒,当前默认 120 秒锁通常足够。

2. 本地记录 pop 时间;若接近锁超时仍无法完成,放弃本次 report,重新 pop(避免过期 report 干扰)。

3. 若脚本 P99 耗时 > 90 秒,请运维调大 lock_timeout_seconds(如 180)。


9. bucket 字段说明

bucket含义尝试顺序
topTop 综合命中最先
otherOther 综合命中其次
segmentSegment 综合命中再次
strict / looseJSONL 等模式的 strict/loose 桶按 sort_order

bucket 不影响 命中判定规则,仅影响统计分类与尝试顺序。


9.5 抖音号找回模式(douyin_recover Worker 专章)

本节供 抖音号找回 Worker 对接;手机号筛查 Worker 无需阅读 report 字段部分

9.5.1 管理端导入格式

管理端选择 任务模式 = 抖音号找回,上传 JSON 数组(非 JSONL)。单条结构示例:

{
  "data": {
    "buyer": {
      "douyin_id": "Lai.1204",
      "uid": "4029078185206584",
      "user_name": "不再打翻牛奶"
    }
  }
}
字段必填说明
data.buyer.douyin_idpop 时下发的抖音号
data.buyer.uid去重与展示
data.buyer.user_name / nickname_search昵称展示用

任务完成后 export JSON(v1.6) 会在 data.buyer 内写入 account_unique_key(32 位 hex,UI:账号唯一值),供后续手机号筛查 import 绑定 ROI。不含 ROI 原图 / base64 / 内部路径。

{
  "data": {
    "buyer": {
      "uid": "4029078185206584",
      "recover_phone_prefix": "156",
      "account_unique_key": "a1b2c3d4e5f6789012345678abcdef01"
    }
  },
  "recover_api_status": "success"
}

9.5.2 与手机号筛查的差异

项目手机号筛查 Worker抖音号找回 Worker
子任务粒度1 抖音号 × N 候选手机号1 抖音号 = 1 次 pop
导入后ES 缓存 user_not_exists 预跳过跳过缓存预扫,直接 running
pop 接口GET /task/pop/phoneGET /task/pop/douyin
pop 字段phone + bucketdouyin_id + uid
report 字段nickname_roi_base64(± nickname_roi_base64s[]);不要求 昵称首字phone_prefix + nickname_roi_base64
锁 / 超时§8相同

9.5.3 Worker 主循环(伪代码)

# 抖音号找回 Worker — 独立脚本,部署在抖音号专用云机池
while True:
    r = requests.get(f"{BASE}/api/v1/task/pop/douyin", params={
        "device_id": NAMES,
        "worker_key": WORKER_KEY,
    }, timeout=30)
    body = r.json()
    if body["code"] == 40401:
        sleep(3)
        continue

    task = body["data"]
    if task.get("task_mode") != "douyin_recover":
        raise RuntimeError(f"unexpected task_mode: {task.get('task_mode')}")

    douyin_id = task["douyin_id"]
    task_id = task["task_id"]

    # --- 云机本地:按 douyin_id 执行找回流程,截取 75×45 昵称 ROI ---
    ok, phone_prefix, roi_png_b64 = run_douyin_recover(douyin_id)

    if ok:
        report(task_id, api_status="success", data={
            "phone_prefix": phone_prefix,           # 恰好 3 位数字
            "nickname_roi_base64": roi_png_b64,     # PNG base64,75×45
        })
    else:
        report(task_id, api_status="recover_failed", data={})

9.5.4 report 要点

场景api_statusdata
找回成功successrecover_success必填 phone_prefix + nickname_roi_base64
找回失败recover_failed可省略或 {}

10. 完整对接示例

10.1 cURL

设备信息心跳(≤ 60 秒一次):

curl -s -X POST "http://127.0.0.1:8080/api/v1/worker/device-info?worker_key=local-e2e-worker-key" \
  -H "Content-Type: application/json" \
  -d '{
    "names": "a3a5a0b3e1591722075c5c95da8dab20_1_T1001",
    "state": "running",
    "data": "/mmc/data/data1_1780849293633",
    "index": 1,
    "ip": "192.168.1.104"
  }'

获取出口代理(启用代理网关时,pop 前调用):

curl -s "http://127.0.0.1:8080/api/v1/worker/proxy?device_id=a3a5a0b3e1591722075c5c95da8dab20_1_T1001&worker_key=local-e2e-worker-key&proxy_mode=dps&refresh=0"

强制换 IP:

curl -s "http://127.0.0.1:8080/api/v1/worker/proxy?device_id=a3a5a0b3e1591722075c5c95da8dab20_1_T1001&worker_key=local-e2e-worker-key&proxy_mode=dps&refresh=1"

领取(手机号筛查 Worker):

curl -s "http://127.0.0.1:8080/api/v1/task/pop/phone?device_id=a3a5a0b3e1591722075c5c95da8dab20_1_T1001&worker_key=local-e2e-worker-key"

上报命中(手机号筛查 / ROI):

curl -s -X POST "http://127.0.0.1:8080/api/v1/task/report?worker_key=local-e2e-worker-key" \
  -H "Content-Type: application/json" \
  -d '{
    "task_id": "12345",
    "device_id": "a3a5a0b3e1591722075c5c95da8dab20_1_T1001",
    "api_status": "success",
    "data": {
      "nickname_roi_base64": "iVBORw0KGgoAAAANSUhEUgAAAEsAAAAtCAIAAABpgQH6..."
    }
  }'

上报一号多抖音(多张 ROI):

curl -s -X POST "http://127.0.0.1:8080/api/v1/task/report?worker_key=local-e2e-worker-key" \
  -H "Content-Type: application/json" \
  -d '{
    "task_id": "12345",
    "device_id": "a3a5a0b3e1591722075c5c95da8dab20_1_T1001",
    "api_status": "success",
    "data": {
      "nickname_roi_base64s": [
        "iVBORw0KGgoAAAANSUhEUg...",
        "iVBORw0KGgoAAAANSUhEUg..."
      ]
    }
  }'

上报用户不存在:

curl -s -X POST "http://127.0.0.1:8080/api/v1/task/report?worker_key=local-e2e-worker-key" \
  -H "Content-Type: application/json" \
  -d '{
    "task_id": "12345",
    "device_id": "a3a5a0b3e1591722075c5c95da8dab20_1_T1001",
    "api_status": "error_1011",
    "data": { "error_code": 1011 }
  }'

(抖音号找回 Worker)领取任务:

curl -s "http://127.0.0.1:8080/api/v1/task/pop/douyin?device_id=a3a5a0b3e1591722075c5c95da8dab20_1_T1001&worker_key=local-e2e-worker-key"

(抖音号找回 Worker)上报成功:

curl -s -X POST "http://127.0.0.1:8080/api/v1/task/report?worker_key=local-e2e-worker-key" \
  -H "Content-Type: application/json" \
  -d '{
    "task_id": "67890",
    "device_id": "a3a5a0b3e1591722075c5c95da8dab20_1_T1001",
    "api_status": "success",
    "data": {
      "phone_prefix": "156",
      "nickname_roi_base64": "iVBORw0KGgoAAAANSUhEUg..."
    }
  }'

(抖音号找回 Worker)上报失败:

curl -s -X POST "http://127.0.0.1:8080/api/v1/task/report?worker_key=local-e2e-worker-key" \
  -H "Content-Type: application/json" \
  -d '{
    "task_id": "67890",
    "device_id": "a3a5a0b3e1591722075c5c95da8dab20_1_T1001",
    "api_status": "recover_failed",
    "data": {}
  }'

10.2 Python 伪代码 — 手机号筛查 Worker(phone_screen

import time
import requests
import threading

BASE = "http://127.0.0.1:8080"
WORKER_KEY = "local-e2e-worker-key"
NAMES = "a3a5a0b3e1591722075c5c95da8dab20_1_T1001"
HOST_IP = "192.168.1.104"

def heartbeat_loop():
    while True:
        requests.post(
            f"{BASE}/api/v1/worker/device-info",
            params={"worker_key": WORKER_KEY},
            json={
                "names": NAMES,
                "state": "running",
                "data": "/mmc/data/data1_1780849293633",
                "index": 1,
                "ip": HOST_IP,
            },
            timeout=30,
        ).raise_for_status()
        time.sleep(60)

threading.Thread(target=heartbeat_loop, daemon=True).start()

PROXY_ENABLED = True  # 与后端 proxy.enabled 一致;关闭时可设为 False 并跳过 get_proxy
PROXY_MODE = "dps"    # dps | tps;双模式均开启时必传,与 UI worker_proxy_mode 一致

def get_proxy(refresh: int = 0, proxy_mode: str = PROXY_MODE):
    r = requests.get(
        f"{BASE}/api/v1/worker/proxy",
        params={
            "device_id": NAMES,
            "worker_key": WORKER_KEY,
            "proxy_mode": proxy_mode,
            "refresh": refresh,
        },
        timeout=30,
    )
    r.raise_for_status()
    return r.json()

def configure_local_proxy(connection: str):
    host, port, user, password = connection.split(":", 3)
    # TODO: 写入云机系统/脚本 HTTP 代理配置
    pass

while True:
    if PROXY_ENABLED:
        body = get_proxy(refresh=0)
        if body.get("code") == 50301:
            time.sleep(15)
            continue
        if body.get("code") != 0:
            raise RuntimeError(body)
        configure_local_proxy(body["data"]["connection"])

    r = requests.get(f"{BASE}/api/v1/task/pop/phone", params={
        "device_id": NAMES,
        "worker_key": WORKER_KEY,
    }, timeout=30)
    body = r.json()

    if body.get("code") == 40401:
        time.sleep(3)
        continue

    if body.get("code") != 0:
        raise RuntimeError(body)

    task = body["data"]
    if task.get("task_mode", "phone_screen") != "phone_screen":
        raise RuntimeError(f"unexpected task_mode for phone worker: {task.get('task_mode')}")

    task_id = task["task_id"]
    phone = task["phone"]
    uid = task["uid"]

    # --- 云机本地:用 phone + uid 调抖音查询,裁切 75×45 昵称 ROI ---
    roi_png_b64, roi_png_b64_list, api_status, error_code = run_phone_lookup_roi(phone, uid)

    data = {"error_code": error_code}
    if api_status == "success":
        if roi_png_b64_list:
            data["nickname_roi_base64s"] = roi_png_b64_list
        else:
            data["nickname_roi_base64"] = roi_png_b64

    requests.post(
        f"{BASE}/api/v1/task/report",
        params={"worker_key": WORKER_KEY},
        json={
            "task_id": task_id,
            "device_id": NAMES,
            "api_status": api_status,
            "data": data,
        },
        timeout=30,
    ).raise_for_status()
v1.8:手机号筛查 不要求 上报 api_nickname_first / api_nickname_firsts;命中仅由 ROI 评分决定。

10.2.1 Python 伪代码 — 一号多抖音 ROI 片段

一号多抖音时改用 nickname_roi_base64s[] 上报多张 ROI,服务端取 max(roi_match_score)

# 在 §10.2 report 片段中,success 时:
if api_status == "success":
    data["nickname_roi_base64s"] = roi_png_b64_list  # 多张 ROI

10.3 Python 伪代码 — 抖音号找回 Worker

完整示例见 §9.5.3;以下为与 §10.2 同结构的独立脚本骨架:

# 抖音号找回 Worker — 独立脚本,部署在找回专用云机池
while True:
    # device-info 心跳、proxy 配置与 §10.2 相同,此处省略

    r = requests.get(f"{BASE}/api/v1/task/pop/douyin", params={
        "device_id": NAMES,
        "worker_key": WORKER_KEY,
    }, timeout=30)
    body = r.json()

    if body.get("code") == 40401:
        time.sleep(3)
        continue
    if body.get("code") != 0:
        raise RuntimeError(body)

    task = body["data"]
    if task.get("task_mode") != "douyin_recover":
        raise RuntimeError(f"unexpected task_mode for recover worker: {task.get('task_mode')}")

    task_id = task["task_id"]
    douyin_id = task["douyin_id"]

    ok, phone_prefix, roi_b64 = run_douyin_recover(douyin_id)

    requests.post(
        f"{BASE}/api/v1/task/report",
        params={"worker_key": WORKER_KEY},
        json={
            "task_id": task_id,
            "device_id": NAMES,
            "api_status": "success" if ok else "recover_failed",
            "data": {
                "phone_prefix": phone_prefix,
                "nickname_roi_base64": roi_b64,
            } if ok else {},
        },
        timeout=30,
    ).raise_for_status()

11. 可选:健康检查

Worker 启动时可探测服务是否可用(无需 worker_key):

GET /health
{
  "status": "ok",
  "mysql": "up"
}

12. 服务端配置参考

配置项默认值说明
auth.worker_api_keyWorker 鉴权密钥
import.lock_timeout_seconds120pop 后锁定时长(秒)
daemon.interval_seconds30超时锁回收扫描间隔
daemon.scheduler_refresh_seconds10running 批次列表刷新间隔
server.addr:8080监听地址
proxy.enabledfalse是否启用出口代理网关
proxy.dps.enabledtrue是否启用私密代理(可与 TPS 同时 true)
proxy.tps.enabledfalse是否启用隧道代理
proxy.default_modedpsWorker 未传 proxy_mode 时的默认(迁移自旧 proxy.mode
~~proxy.mode~~废弃;读取时映射到 default_mode + enabled 开关
proxy.pool.enabledfalse多订单池;开启后使用账户 API 发现订单并 Pick
proxy.pool.account_secret_id / account_secret_key快代理账户级密钥(订单池同步用)
proxy.pool.sync_interval_seconds60后台 SyncWorker 基准间隔(秒);空闲期自动拉长
proxy.pool.min_ip_balance10DPS PRE_PAY_IP Pick 最低 IP 余额
proxy.pool.tps_load_factor0.8TPS 并发上限系数(相对 tunnel_req
proxy.pool.max_pick_retries5Pick 失败 failover 次数
proxy.pool.strategies.dpsprefer_high_balanceDPS 选单策略
proxy.pool.strategies.tpsleast_devicesTPS 选单策略
proxy.pool.alerts.ip_balance_warn100订单池 IP 余额告警阈值
proxy.pool.alerts.expire_days_warn7订单临期告警天数
proxy.default_providerkdl默认 Provider 名称(当前固定走 KDL)
proxy.providers.kdl_dps.secret_id私密代理订单 Secret ID
proxy.providers.kdl_dps.signature私密代理订单 Signature
proxy.providers.kdl_dps.base_urlhttps://dps.kdlapi.comDPS API 地址
proxy.providers.kdl_dps.default_usernameDPS 写入 connection 的代理账号(可与 KDL 订单默认账号不同)
proxy.providers.kdl_dps.default_passwordDPS 写入 connection 的代理密码
proxy.providers.kdl_dps.timeout_seconds15调用 DPS API 超时(秒)
proxy.providers.kdl_tps.secret_id隧道代理订单 Secret ID
proxy.providers.kdl_tps.signature隧道代理订单 Signature
proxy.providers.kdl_tps.tunnel_host隧道入口(可选;留空时由 getorderinfo 自动解析)
proxy.providers.kdl_tps.tunnel_port_http0HTTP 隧道端口(可选;留空时由订单详情填充)
proxy.providers.kdl_tps.auth_plaintexttruegetproxyauthorization 时是否请求明文账号密码
proxy.providers.kdl_tps.valid_seconds_default300TPS 绑定默认有效秒数(订单换 IP 周期未知时的兜底)
proxy.providers.kdl_tps.timeout_seconds15调用 TPS / dev API 超时(秒)
roi.pass_score97.5ROI 命中阈值(roi_match_score ≥ 此值 即 passed)
roi.bind_modecopy手机号 import 绑定期望 ROI:copy(复制到本 job)或 reference(引用源路径)

配置文件路径:云机大脑-后端/configs/config.yaml

proxy 段示例 — 双模式 + 订单池(推荐 v2.0+):

proxy:
  enabled: true
  default_mode: dps
  dps:
    enabled: true
  tps:
    enabled: false
  default_provider: kdl
  pool:
    enabled: true
    account_secret_id: "your-account-secret-id"
    account_secret_key: "your-account-secret-key"
    sync_interval_seconds: 60
    min_ip_balance: 10
    tps_load_factor: 0.8
    max_pick_retries: 5
    strategies:
      dps: prefer_high_balance
      tps: least_devices
    alerts:
      ip_balance_warn: 100
      expire_days_warn: 7
  providers:
    kdl_dps:
      secret_id: ""          # pool.enabled=true 时可留空,由订单池提供
      signature: ""
      base_url: "https://dps.kdlapi.com"
      default_username: ""
      default_password: ""
      timeout_seconds: 15
    kdl_tps:
      secret_id: ""
      signature: ""
      tunnel_host: ""
      tunnel_port_http: 0
      auth_plaintext: true
      valid_seconds_default: 300
      timeout_seconds: 15

proxy 段示例 — 单订单 DPS(未启用订单池):

proxy:
  enabled: true
  default_mode: dps
  dps:
    enabled: true
  tps:
    enabled: false
  default_provider: kdl
  pool:
    enabled: false
  providers:
    kdl_dps:
      secret_id: "your-dps-secret-id"
      signature: "your-dps-signature"
      base_url: "https://dps.kdlapi.com"
      default_username: "proxy_user"
      default_password: "proxy_pass"
      timeout_seconds: 15

proxy 段示例 — 单订单 TPS:

proxy:
  enabled: true
  default_mode: tps
  dps:
    enabled: false
  tps:
    enabled: true
  default_provider: kdl
  pool:
    enabled: false
  providers:
    kdl_tps:
      secret_id: "your-tps-secret-id"
      signature: "your-tps-signature"
      tunnel_host: ""              # 可选,留空则 getorderinfo 自动填充
      tunnel_port_http: 0
      auth_plaintext: true
      valid_seconds_default: 300
      timeout_seconds: 15
旧配置兼容: 若 YAML 仍使用顶层 mode: dps|tpsproviders.kdl,启动时会自动映射到 default_mode + dps/tps.enabled,并合并到 providers.kdl_dps

>

管理端可在 系统配置 → 出口代理 Tab,或 云机集群看板 → 出口代理配置 弹窗中配置 DPS/TPS 独立开关default_mode、订单池与凭证;保存后走 PUT /api/v1/admin/config 热更新,无需重启。

>

代理绑定持久化于表 device_proxymigrations/008);v1.9 增加 proxy_modeegress_ip015);v2.0 改为 uk_device_mode (device_id, proxy_mode) 双行并存,并增加 kdl_order_id017)。订单池快照见 kdl_order_pool016019)。

13. 常见问题

Q1:pop 一直返回 40401 no_task

Q2:report 成功但抖音号仍显示筛查中?

Q3:api_register_time / api_nickname_first 还要填吗?

Q14:ROI 绑定 / 抖音号找回为什么必须传 base64?不传会怎样?

Q18:找回时只有昵称首字、没有 ROI 小图,能否用合成图或文本首字做高精度筛查?

Q7:一个手机号绑了两个抖音,只上报一张 ROI 怎么办?

Q11:手机号 Worker 和抖音号 Worker 可以写成同一个脚本吗?

Q12:抖音号找回 Worker pop 一直 40401

Q13:每次上传任务都要配置 job_id 吗?

Q4:pop 后 60 秒才 report 有没有问题?

Q5:同一 task_id 可以 report 两次吗?

Q6:Worker 需要 JWT 吗?

Q8:state=running 和 Worker「活跃」是一回事吗?

Q9:device-info 没按时上报,但 pop/report 还在跑,会被判关机吗?

Q10:50301 proxy_unavailable 时还能 pop 吗?

Q11:管理端点「检测」提示尚未分配代理?

Q15:云机需要直连快代理吗?

Q16:TPS 模式下「换 IP」会影响其他云机吗?

Q17:响应里的 kdl_order_id / remaining_seconds 是什么?订单池和 Worker 有关系吗?


14. 联调脚本

后端仓库提供 E2E 脚本,含 pop + report 完整流程:

cd 云机大脑-后端
make run          # 终端 1
./scripts/e2e_test.sh   # 终端 2

环境变量:

变量默认
BASE_URLhttp://127.0.0.1:8080
WORKER_KEYlocal-e2e-worker-key
DEVICEe2e_device_001

15. 相关文档

文档说明
云机大脑-后端 README部署、全量 API 索引
快代理-私密代理API对接示例KDL DPS 原始 API(后端内部调用,Worker 无需直连)
快代理-隧道代理对接文档KDL TPS 原始 API 与调度大脑集成说明(§9)
手机号模式-昵称ROI比对方案ROI 评分、账号唯一值、§14 禁止首字合成期望图
出口代理网关-多订单池优化方案订单池架构、SyncWorker、Pick 策略(运维/后端)
WORKER_PROXY_FETCH_FOR_BACKEND.md云机 StepPro 脚本实际请求与排查对照
抖音号手机号筛查系统-Go-MySQL-v1开发文档系统架构与数据模型
云机大脑-前端页面开发与设计详细方案管理端 UI(非 Worker 对接)

16. 管理端看板 API(Admin,JWT)

管理端按 宿主机 IP 分组 展示云机集群,默认仅加载摘要,展开 IP 后懒加载实例明细。需 Header:Authorization: Bearer {admin_jwt},且用户 role=admin

方法路径说明
GET/api/v1/admin/workers/hosts宿主机摘要列表
GET/api/v1/admin/workers/hosts/{ip}/devices指定 IP 下实例明细(含出口代理摘要)
PATCH/api/v1/admin/workers/hosts/{ip}更新宿主机别名/备注
GET/api/v1/admin/config读取系统配置(含 proxy 段)
PUT/api/v1/admin/config保存系统配置(含出口代理,热更新)
GET/api/v1/admin/workers/devices/{names}/proxy查询实例当前代理绑定
POST/api/v1/admin/workers/devices/{names}/proxy/check检测代理有效性(调 KDL check)
POST/api/v1/admin/workers/devices/{names}/proxy/refresh强制换 IP(等同 Worker refresh=1
GET/api/v1/admin/proxy/pool/stats订单池统计(DPS/TPS 可用数等)
GET/api/v1/admin/proxy/pool/orders订单池列表(Query `product=DPS\TPS`)
GET/api/v1/admin/proxy/pool/alerts订单池分组告警
POST/api/v1/admin/proxy/pool/sync立即同步订单池
PATCH/api/v1/admin/proxy/pool/orders/{order_id}调整权重 / 禁用订单
GET/api/v1/admin/workers旧版扁平 Worker 列表(兼容)
路径中的 {names} 为 URL 编码后的云机实例名(与 Worker device_id 相同),例如 a3a5a0b3e1591722075c5c95da8dab20_1_T1001

16.1 宿主机备注 — PATCH .../hosts/{ip}

Body 示例:

{
  "alias": "机房A-03号机",
  "remark": "40 槽主力筛查机"
}

16.2 实例明细中的代理字段

GET /api/v1/admin/workers/hosts/{ip}/devices 返回的每个 devices[] 项包含 DPS / TPS 双行摘要(v2.0+):

字段说明
proxy_dps私密代理绑定摘要(未分配时各子字段为空)
proxy_tps隧道代理绑定摘要
proxy_dps.proxy_connection / proxy_tps.proxy_connection脱敏 host:端口:账号:密码(DPS=出口 IP,TPS=隧道入口)
proxy_dps.proxy_valid_until / proxy_tps.proxy_valid_until绑定过期时间
proxy_dps.proxy_remaining_seconds / proxy_tps.proxy_remaining_seconds剩余有效秒数
proxy_dps.proxy_last_check_ok / proxy_tps.proxy_last_check_ok最近一次检测是否通过
proxy_tps.egress_ipTPS 当前出口 IP(展示用)

兼容字段(等同 proxy_dps,便于旧前端): proxy_idproxy_connectionproxy_statusproxy_valid_untilproxy_remaining_secondsproxy_last_check_ok

未分配时上述字段为空;完整 ProxyDTO(含 proxy_modekdl_order_id)见 §16.3 GET .../proxy。看板抽屉按模式 检测 / 换 IP

16.3 查询实例代理 — GET .../devices/{names}/proxy

Query说明
proxy_modedps \tps;双模式均开启时必填(规则同 §4.5.1)

成功 — 已绑定(DPS 示例):

{
  "code": 0,
  "data": {
    "proxy_id": "a1b2c3...",
    "device_id": "a3a5a0b3e1591722075c5c95da8dab20_1_T1001",
    "host": "113.31.123.45",
    "port": 15818,
    "username": "user001",
    "password": "pass001",
    "addr": "113.31.123.45:15818",
    "connection": "113.31.123.45:15818:user001:pass001",
    "scheme": "http",
    "status": "active",
    "proxy_mode": "dps",
    "kdl_order_id": "987807746559625",
    "valid_until": "2026-06-12T10:30:00+08:00",
    "remaining_seconds": 1800,
    "last_check_ok": true,
    "provider": "kdl"
  }
}

TPS 模式下 data 还会包含 proxy_mode: "tps"egress_ip(当前出口 IP),且 host / connection 为隧道入口(结构同 §4.5.2)。

成功 — 未绑定: datanull(HTTP 200)。

16.4 检测代理 — POST .../devices/{names}/proxy/check

无 Body。后端按绑定记录的 proxy_mode 调用对应 KDL 接口,并更新 last_check_at / last_check_ok

模式内部 API说明
dpscheckdpsvalid校验当前绑定的出口 IP 是否仍有效
tpstpscurrentip查询隧道当前出口,与库中 egress_ip 比对

成功:

{
  "code": 0,
  "msg": "checked",
  "data": { "...": "同 GET proxy 的 data 结构" }
}

失败 — 尚未分配:

{
  "code": 400,
  "msg": "该实例尚未分配出口代理,请云机先调用 GET /worker/proxy 或由管理员点击「换 IP」"
}

16.5 强制换 IP — POST .../devices/{names}/proxy/refresh

无 Body。行为等同 Worker 侧 GET /worker/proxy?proxy_mode={mode}&refresh=1,按 Query proxy_mode(或 default_mode)执行:

模式内部 API影响范围
dps重新 getdps仅更新当前 device_id 绑定
tpschangetpsip + tpscurrentip整条隧道出口 IP 变更,共享隧道的实例均受影响(见 §13 Q16)

成功后 upsert 到 device_proxy

成功:

{
  "code": 0,
  "msg": "refreshed",
  "data": { "...": "新分配的 ProxyDTO" }
}

代理不可用:

{
  "code": 50301,
  "msg": "proxy_unavailable",
  "data": null
}

16.6 出口代理网关配置(Admin)

通过 GET/PUT /api/v1/admin/config 读写 config.proxy 对象,字段与 §12 一致。前端入口:

保存后即时生效(热更新)。关闭某模式后该模式请求返回 50301;关闭 proxy.enabled 后 Worker 全部返回 50301


17. 变更记录

日期版本说明
2026-07-02v2.0.2FAQ Q18:仅有昵称首字不可筛查;禁止合成期望 ROI;交叉引用 ROI 方案 §14/§15;§6.3/§6.4 补采说明
2026-07-02v2.0.1补全订单池、kdl_order_idproxy_mode required、§12 双模式 YAML、Admin 订单池 API、看板 proxy_dps/proxy_tps 双列、Q17
2026-07-02v2.0.0双模式并行dps.enabled + tps.enabled;Worker Query proxy_modedevice_proxy 双行 uk_device_mode;多订单池;Admin 代理操作带 proxy_mode
2026-06-25v1.9.0快代理双模式proxy.mode 支持 dps 私密代理 / tps 隧道代理;Worker 响应新增 proxy_modeegress_ip;TPS 下 connection 为隧道入口;§12 配置拆分为 kdl_dps / kdl_tps;Admin 检测/换 IP 分模式说明;迁移 015_device_proxy_mode.sql
2026-06-21v1.8.0废弃 Legacy 首字比对phone_screen 不要求 api_nickname_first*;命中 仅 ROI;管理端剔除首字展示;缓存预扫仅保留 user_not_exists 跳过;§7.4 对齐 V2 硬门槛与评分公式
2026-06-18 23:00v1.7.1§6.3 修订(已被 v1.8 取代):曾要求 ROI 绑定同时上报首字供管理端展示
2026-06-18v1.7.0§6 重写:按 phone_screen / douyin_recover 分列 强制上报字段;明确 ROI 绑定 / 抖音找回 success 时必须 PNG base64;补全 HTTP 400 msg 列表;§6.5 管理端 ROI 回写字段;§7.4 缓存预扫跳过 ROI 记录
2026-06-18v1.6.0ROI 昵称比对:手机号 ROI 绑定 report 须上报 nickname_roi_base64;命中阈值 97.5;抖音 export 含 account_unique_key
2026-06-18v1.5.0新增双池专用领取接口 GET /task/pop/phoneGET /task/pop/douyin;按 job_mode 隔离调度;job_id 改为可选;旧 /task/pop 保留兼容
2026-06-17v1.4.1明确双 Worker 脚本架构(§1.1);扩充抖音号找回专章、cURL/Python 示例与 FAQ
2026-06-12v1.4.0新增抖音号找回模式 douyin_recover:pop 下发 douyin_id;report 上报 phone_prefix + 75×45 PNG base64
2026-06-12v1.3.1补全 proxy:connection 解析、cURL/Python 示例、proxy 配置项、Admin 代理 API 详情与 FAQ
2026-06-12v1.3新增出口代理网关 GET /worker/proxy;Admin 代理检测/换 IP;看板展示代理列
2026-06-12v1.2.1心跳缺失时以 pop/report 兜底推断在线/已开机,避免误判关机
2026-06-12v1.2新增 device-info 心跳;device_id=names;看板按 IP 分组;区分实例开机状态与 Worker 活跃
2026-06-12v1.1.1看板可见条件说明;pop 未 report 亦可在看板展示为「处理中」
2026-06-10v1.1支持 api_nickname_firsts 一号多抖音;命中改为任一首字匹配;任务详情展示多首字
2026-06-09v1.0首版:pop/report、鉴权、命中规则(仅昵称首字)、锁超时