云机 Worker 对接 API 文档
版本:v2.0.2(对齐云机大脑-后端/云机大脑-前端当前实现)
更新日期:2026-07-02
适用对象:云手机脚本、自动化 Worker、协议层以外的执行端
1. 概述
云机 Worker 通过调度大脑后端领取任务,在本地调用抖音相关能力后 上报(report) 回服务端。
系统支持两种 任务模式(task_mode / 导入时 job_mode):
| 模式 | job_mode | pop 下发 | 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 400,msg 见 §6.8。手机号筛查详见 §5~§7;抖音号找回详见 §9.5。
1.1 双 Worker 脚本架构(推荐)
手机号筛查 与 抖音号找回 在云机侧采用 两套独立 Worker 脚本,不合并为同一个通用脚本:
| Worker 脚本 | 领取接口 | 对接 task_mode | pop 领到 | report 上报 | 典型部署 |
|---|---|---|---|---|---|
| 手机号筛查 Worker | GET /task/pop/phone | phone_screen | phone + bucket | 必须 nickname_roi_base64(± nickname_roi_base64s[]);不要求 昵称首字 | 手机号筛查专用云机池 |
| 抖音号找回 Worker | GET /task/pop/douyin | douyin_recover | douyin_id + uid | 必须 phone_prefix + nickname_roi_base64 | 抖音号找回专用云机池 |
为何拆成两个脚本:
- 本地自动化流程完全不同(查号 vs 按抖音号找回)
- report 字段与成功判定逻辑不同
- 独立发版、排错、扩缩容,互不影响
部署约定:
1. 同一云机实例同一时间只跑一种 Worker,不要在同一台云机上混跑两个脚本。
2. 推荐云机池隔离:筛查任务只部署「手机号筛查 Worker」;找回任务只部署「抖音号找回 Worker」。
3. 领取任务必须使用对应专用接口(§5.1):手机号池调用 /task/pop/phone,抖音号池调用 /task/pop/douyin;服务端仅在对应 job_mode 的 running 批次间轮询,无需每次上传任务配置 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-info | Query:?worker_key=xxx |
GET /worker/proxy | Query:?worker_key=xxx&device_id=xxx |
GET /task/pop/phone | Query:?worker_key=xxx |
GET /task/pop/douyin | Query:?worker_key=xxx |
GET /task/pop(兼容) | Query:?worker_key=xxx |
POST /task/report | Query:?worker_key=xxx,或 JSON Body 字段 "worker_key": "xxx" |
2.3 统一响应格式
成功时 HTTP 状态码一般为 200,Body 为 JSON:
{
"code": 0,
"msg": "success",
"data": { }
}| 字段 | 说明 |
|---|---|
code | 业务码,0 表示成功 |
msg | 人类可读说明 |
data | 业务数据,可能为 null |
错误时:
| HTTP | code | 典型 msg |
|---|---|---|
| 400 | 400 | 参数错误、task 不存在等 |
| 401 | 401 | invalid or missing worker_key |
| 200 | 50301 | proxy_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 | 一台物理机可挂载多个云机实例 |
| 云机实例 | names | 与 device_id 相同 |
双状态(请勿混淆):
| 状态 | 来源 | 含义 |
|---|---|---|
实例状态 state | device-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/jsonBody(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=running | pop/report 仍在进行 → 推断为已开机(响应字段 instance_running=true,info_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_mode、egress_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(兼容旧脚本) |
refresh | 否 | 1 或 true 强制换 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 / getdps 的 f_et);TPS:隧道换 IP 周期估算 |
reused | true 表示复用已有绑定;false 表示本次新分配或刷新 |
last_check_ok | 最近一次有效性检测结果 |
provider | 固定为 kdl(具体产品线看 proxy_mode) |
复用与换 IP 策略(按模式):
| 场景 | DPS(私密代理) | TPS(隧道代理) |
|---|---|---|
refresh=0 且绑定仍有效 | 调 checkdpsvalid,通过则复用同一出口 IP | 调 tpscurrentip 校验当前出口,通过则复用同一隧道绑定 |
| check 失败或已过期 | 重新 getdps 提取新 IP | 重新解析隧道并查询 tpscurrentip |
refresh=1 | 重新 getdps 换出口 IP | 调 changetpsip 更换整条隧道出口(影响共享该隧道的全部云机,见 §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 | 动态出口 IP | 113.31.123.45:15818:user001:pass001 |
tps | 固定隧道入口域名/IP | tps-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 错误响应
| HTTP | code | msg | 说明 |
|---|---|---|---|
| 400 | 400 | device_id required | 未传 device_id |
| 400 | 400 | proxy_mode required | DPS 与 TPS 同时开启 且 Query 未传合法 proxy_mode |
| 200 | 50301 | proxy_unavailable | 网关未启用、KDL 凭证无效、订单池无可用订单或代理商无可用 IP |
proxy_mode required 示例:
{
"code": 400,
"msg": "proxy_mode required"
}device_id required 示例:
{
"code": 400,
"msg": "device_id required"
}5. 领取任务 — 双池专用接口(推荐)
手机号筛查与抖音号找回使用 两个独立领取地址。服务端仅在对应 job_mode 且 status=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_screen 且 status=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_recover 且 status=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_id | string | 任务 ID,手机号模式下为 candidate_phones.id;抖音号找回模式下为 douyin_records.id;report 时必须原样回传 |
job_id | string | 所属导入批次 ID |
task_mode | string | phone_screen(默认)或 douyin_recover |
uid | string | 抖音 UID(导入数据) |
phone | string | 待筛查手机号(仅 phone_screen) |
bucket | string | 候选桶:top / other / segment(仅 phone_screen) |
领取后的服务端状态(phone_screen):
- 该抖音号进入 筛查中(locked)
- 该候选号进入 已派发(dispatched)
- 启动锁定时长计时(见 §8)
领取后的服务端状态(douyin_recover):
- 该抖音号进入 筛查中(locked)
- 无候选手机号;1 抖音号 = 1 次 pop
- 启动锁定时长计时(见 §8)
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) | success | data.nickname_roi_base64 或 非空 data.nickname_roi_base64s[] | api_nickname_first / api_nickname_firsts(手机号模式不要求,上报亦忽略) |
phone_screen | 用户不存在 | error_1011 / error_code=1011 | 无(勿伪造 ROI) | ROI、昵称首字均可省略 |
douyin_recover | 找回成功 | success 或 recover_success | data.phone_prefix(恰好 3 位数字)+ data.nickname_roi_base64 | api_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_id | 是 | pop 返回的 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 的记录在successreport 时将返回 HTTP 400(record 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_base64s | HTTP 400,msg: nickname_roi_base64 required for ROI-bound record |
success 但 base64 非法 / 非 PNG / 尺寸非 75×45 | HTTP 400,见 §6.8 |
success 且 ROI 合法但 roi_match_score < 97.5(默认) | 未命中,候选号记为已查询,继续下一候选号 |
success 且 roi_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_prefix 与 nickname_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_prefix 或 nickname_roi_base64 | HTTP 400 |
phone_prefix 非 3 位数字 | HTTP 400,msg: phone_prefix must be 3 digits |
| ROI base64 非法 / 非 PNG / 尺寸错误 | HTTP 400,见 §6.8 |
| 非 success / 非 recover_success | 记录标为 failed;data 可省略 |
| 记录已 passed / failed 后重复 report | 幂等成功(§6.7) |
6.5 管理端回写字段(ROI 绑定手机号任务)
Worker 无需上报以下字段;服务端比对后写入任务详情供运维复核:
| 字段 | 说明 |
|---|---|
roi_match_score | 0~100 综合分 |
match_tier | L1 / L2 / L3 / none |
binary_match_rate | L2 二值化像素一致率 |
dhash_hamming | L3 感知哈希汉明距离 |
管理端展示 查询结果(用户存在 / 不存在)由服务端根据 api_status 与 ROI 判定自动回写;Worker 无需 上报昵称首字。
6.6 api_status 约定
| 值 | 适用模式 | 含义 | 服务端处理 |
|---|---|---|---|
success | 通用 | 查询/找回成功 | 按 §6.3~§6.4 校验必填字段并判定命中 |
recover_success | 仅 douyin_recover | 找回成功(与 success 等效) | 同 success |
error_1011 | phone_screen | 手机号未注册抖音 | 候选号 skipped,不要求 ROI / 首字 |
recover_failed 等 | douyin_recover | 找回失败 | 记录 failed |
| 其他非空值 | 通用 | 脚本/业务错误 | 记入 Worker 错误统计;手机号模式按未命中处理 |
也可仅通过 data.error_code: 1011 表达用户不存在(与 api_status: error_1011 等效)。
6.7 重复上报(幂等)
| 模式 | 条件 | 行为 |
|---|---|---|
phone_screen | 候选号已是 已查询 或 已跳过 | 直接返回 recorded,不重复改判 |
douyin_recover | 记录已是 passed 或 failed | 直接返回 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_verified | top / strict 桶命中 |
loose_verified | other / 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):
- 全图二值一致率 s2 ≥ 98%
- 左侧首字区(宽 35%)二值一致率 ≥ 95%
评分公式:
若 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 == 1011 或 api_status == error_1011 | 未命中,该号 skipped |
api_status == success 且 max(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 | 含义 | 尝试顺序 |
|---|---|---|
top | Top 综合命中 | 最先 |
other | Other 综合命中 | 其次 |
segment | Segment 综合命中 | 再次 |
strict / loose | JSONL 等模式的 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_id | 是 | pop 时下发的抖音号 |
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/phone | GET /task/pop/douyin |
| pop 字段 | phone + bucket | douyin_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_status | data |
|---|---|---|
| 找回成功 | success 或 recover_success | 必填 phone_prefix + nickname_roi_base64 |
| 找回失败 | recover_failed 等 | 可省略或 {} |
phone_prefix:恰好 3 位数字
nickname_roi_base64:PNG base64(可带data:image/png;base64,前缀),尺寸 75×45,不接受 URL
- 接口路径与鉴权与手机号 Worker 相同,见 §6.3 抖音号找回 report 示例
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 # 多张 ROI10.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_key | — | Worker 鉴权密钥 |
import.lock_timeout_seconds | 120 | pop 后锁定时长(秒) |
daemon.interval_seconds | 30 | 超时锁回收扫描间隔 |
daemon.scheduler_refresh_seconds | 10 | running 批次列表刷新间隔 |
server.addr | :8080 | 监听地址 |
proxy.enabled | false | 是否启用出口代理网关 |
proxy.dps.enabled | true | 是否启用私密代理(可与 TPS 同时 true) |
proxy.tps.enabled | false | 是否启用隧道代理 |
proxy.default_mode | dps | Worker 未传 proxy_mode 时的默认(迁移自旧 proxy.mode) |
~~proxy.mode~~ | — | 废弃;读取时映射到 default_mode + enabled 开关 |
proxy.pool.enabled | false | 多订单池;开启后使用账户 API 发现订单并 Pick |
proxy.pool.account_secret_id / account_secret_key | — | 快代理账户级密钥(订单池同步用) |
proxy.pool.sync_interval_seconds | 60 | 后台 SyncWorker 基准间隔(秒);空闲期自动拉长 |
proxy.pool.min_ip_balance | 10 | DPS PRE_PAY_IP Pick 最低 IP 余额 |
proxy.pool.tps_load_factor | 0.8 | TPS 并发上限系数(相对 tunnel_req) |
proxy.pool.max_pick_retries | 5 | Pick 失败 failover 次数 |
proxy.pool.strategies.dps | prefer_high_balance | DPS 选单策略 |
proxy.pool.strategies.tps | least_devices | TPS 选单策略 |
proxy.pool.alerts.ip_balance_warn | 100 | 订单池 IP 余额告警阈值 |
proxy.pool.alerts.expire_days_warn | 7 | 订单临期告警天数 |
proxy.default_provider | kdl | 默认 Provider 名称(当前固定走 KDL) |
proxy.providers.kdl_dps.secret_id | — | 私密代理订单 Secret ID |
proxy.providers.kdl_dps.signature | — | 私密代理订单 Signature |
proxy.providers.kdl_dps.base_url | https://dps.kdlapi.com | DPS API 地址 |
proxy.providers.kdl_dps.default_username | — | DPS 写入 connection 的代理账号(可与 KDL 订单默认账号不同) |
proxy.providers.kdl_dps.default_password | — | DPS 写入 connection 的代理密码 |
proxy.providers.kdl_dps.timeout_seconds | 15 | 调用 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_http | 0 | HTTP 隧道端口(可选;留空时由订单详情填充) |
proxy.providers.kdl_tps.auth_plaintext | true | 调 getproxyauthorization 时是否请求明文账号密码 |
proxy.providers.kdl_tps.valid_seconds_default | 300 | TPS 绑定默认有效秒数(订单换 IP 周期未知时的兜底) |
proxy.providers.kdl_tps.timeout_seconds | 15 | 调用 TPS / dev API 超时(秒) |
roi.pass_score | 97.5 | ROI 命中阈值(roi_match_score ≥ 此值 即 passed) |
roi.bind_mode | copy | 手机号 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: 15proxy 段示例 — 单订单 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: 15proxy 段示例 — 单订单 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|tps或providers.kdl,启动时会自动映射到default_mode+dps/tps.enabled,并合并到providers.kdl_dps。
>
管理端可在 系统配置 → 出口代理 Tab,或 云机集群看板 → 出口代理配置 弹窗中配置 DPS/TPS 独立开关、default_mode、订单池与凭证;保存后走PUT /api/v1/admin/config热更新,无需重启。
>
代理绑定持久化于表device_proxy(migrations/008);v1.9 增加proxy_mode、egress_ip(015);v2.0 改为uk_device_mode (device_id, proxy_mode)双行并存,并增加kdl_order_id(017)。订单池快照见kdl_order_pool(016~019)。
13. 常见问题
Q1:pop 一直返回 40401 no_task?
- 确认任务批次状态为
running(管理端任务大厅可见)。
- 确认云机调用的是对应专用接口:手机号池用
/task/pop/phone,抖音号池用/task/pop/douyin。
- 可能 ES 缓存阶段已全部命中/跳过(手机号模式),或任务已
succeeded。
- 未指定
job_id时,确认至少有一个同模式的running批次。
Q2:report 成功但抖音号仍显示筛查中?
- 未命中且仍有候选号:记录会回到 pending,继续 pop 下一个。
- 仅当 命中 或 候选耗尽 时,抖音号才会 passed / failed。
Q3:api_register_time / api_nickname_first 还要填吗?
api_register_time:不需要,云机脚本 不要上报。
api_nickname_first/api_nickname_firsts:仅phone_screen(手机号筛查)不要求。该模式命中 仅看 ROI;若上报首字字段,服务端 忽略。
douyin_recover:本模式 从未要求 昵称首字;仍须phone_prefix+nickname_roi_base64。
1011表示用户不存在,不要求 ROI 与首字。
Q14:ROI 绑定 / 抖音号找回为什么必须传 base64?不传会怎样?
- 手机号筛查(§6.3):
api_status=success时 必须 提供nickname_roi_base64或非空nickname_roi_base64s[];缺 ROI 则 HTTP 400,msg:nickname_roi_base64 required for ROI-bound record。
- 抖音号找回(§6.4):
success/recover_success时 必须 同时提供phone_prefix+nickname_roi_base64;缺一即 HTTP 400。
- base64 须解码为 75×45 PNG;格式/尺寸错误同样 HTTP 400(见 §6.8)。
- 用户不存在(
error_1011)时 不要 传 ROI,也不要伪造昵称首字。
Q18:找回时只有昵称首字、没有 ROI 小图,能否用合成图或文本首字做高精度筛查?
- 不能。 命中闸门 仅 为找回真实截图 ROI ↔ 手机号真实截图 ROI(评分 + 硬门槛)。详见 手机号模式-昵称ROI比对方案.md §14。
- 禁止:服务端/客户端用首字字体渲染合成「假期望 ROI」;禁止恢复 Legacy「首字文本相等即命中」。
- 正确做法:对该 uid 补采——重跑
douyin_recover,强制上报 75×45nickname_roi_base64,再 exportaccount_unique_key并导入手机号任务。
- 算法中的「首字区」是图像左侧约 35% 的二值相似度,不是 OCR/文本字符「张」。
- 二期可选:对实际上报 ROI 左区做 OCR 与找回首字 审计(方案 §15),不参与
HitPass;Worker 协议不变。
Q7:一个手机号绑了两个抖音,只上报一张 ROI 怎么办?
- 手机号筛查:推荐上报
nickname_roi_base64s[]多张 ROI;服务端取 max(roi_match_score) 与阈值比较。
- 无需 上报
api_nickname_firsts;首字字段不参与判定,管理端也不再展示。
Q11:手机号 Worker 和抖音号 Worker 可以写成同一个脚本吗?
- 不推荐。 本项目约定 两套独立脚本 + 两个领取接口 + 两个云机池(见 §1.1)。
- 技术上可在同一脚本内分支,但会增加维护成本。
- 推荐做法:手机号池固定
GET /task/pop/phone,抖音号池固定GET /task/pop/douyin;日常无需配置job_id。
Q12:抖音号找回 Worker pop 一直 40401?
- 确认管理端已导入 抖音号找回 批次且状态为
running。
- 确认 pop 地址为
/task/pop/douyin(不是/task/pop/phone或旧版/task/pop)。
- 若线上只有手机号筛查批次在跑,抖音号池会收到
40401(不会误领手机号任务)——属正常现象,待抖音批次启动即可。
nickname_roi_base64须为 75×45 PNG;尺寸或格式错误会 report 失败。
Q13:每次上传任务都要配置 job_id 吗?
- 不需要。 专用 pop 接口已按
job_mode过滤调度;管理端上传并启动后,对应云机池自动领取。
job_id仅用于调试或强制指定单批次(可选 Query 参数)。
Q4:pop 后 60 秒才 report 有没有问题?
- 默认锁 120 秒,60 秒内 安全。
- 超过锁定时长会被回收,号码可能被其他 Worker 再次领取。
Q5:同一 task_id 可以 report 两次吗?
- 可以,第二次起服务端幂等返回成功,不重复改判。
Q6:Worker 需要 JWT 吗?
- 不需要。Worker 接口仅使用
worker_key,与用户登录 JWT 无关。
Q8:state=running 和 Worker「活跃」是一回事吗?
- 不是。
device-info中的state=running表示云手机已开机。
- Worker「活跃/处理中/空闲」由 pop/report 时间窗判定,表示筛查脚本是否在接任务。
- 云机已开机但脚本未启动时,可能出现
running+ Workeridle。
Q9:device-info 没按时上报,但 pop/report 还在跑,会被判关机吗?
- 不会。 后端会以 pop/report 活动作为兜底:脚本仍在领任务/上报时,推断实例仍在线、已开机(
info_inferred=true)。
- 仅当心跳超时 且 pop/report 均已停滞时,才显示「信息离线」。
- 云机无需为此增加任何额外请求或逻辑。
Q10:50301 proxy_unavailable 时还能 pop 吗?
- 不建议。 应先重试
GET /worker/proxy直至拿到可用connection,再配置本地代理后 pop。
- 代理网关关闭(
proxy.enabled=false)时,本接口恒返回50301;若业务不需要出口代理,可跳过该接口直接 pop。
Q11:管理端点「检测」提示尚未分配代理?
- 检测仅针对
device_proxy表中已有绑定的记录;未分配时返回400,msg 为「该实例尚未分配出口代理…」。
- 处理方式:云机先调用
GET /worker/proxy,或管理端在实例抽屉点击 「换 IP」 主动分配。
- 看板抽屉中无
proxy_id/ 出口代理列为-时,「检测」按钮应置灰(前端已做禁用与 Tooltip 提示)。
Q15:云机需要直连快代理吗?
- 不需要。 云机只调用调度大脑
GET /worker/proxy?proxy_mode=...;后端按请求模式内部对接 KDL:
- DPS:
getdps/checkdpsvalid/getdpsvalidtime
- TPS:
getorderinfo/getproxyauthorization/tpscurrentip/changetpsip
- 快代理原始 API 说明见 快代理-私密代理API对接示例、快代理-隧道代理对接文档(供运维/后端开发参考,非 Worker 必读本)。
Q16:TPS 模式下「换 IP」会影响其他云机吗?
- 会。 TPS 使用 共享隧道;
refresh=1或管理端「换 IP」会调用changetpsip,更换的是 整条隧道的出口 IP,而非单台device_id独占 IP。
- 同一隧道订单下的所有云机实例会 同时 获得新出口 IP;请勿在高并发场景对多台云机连续点「换 IP」。
- DPS 模式下换 IP 为 按实例重新 getdps,互不影响(仍受代理商提取频率限制)。
Q17:响应里的 kdl_order_id / remaining_seconds 是什么?订单池和 Worker 有关系吗?
kdl_order_id:启用proxy.pool.enabled时,后端从订单池 Pick 的快代理订单号,便于日志与运维对照 订单池看板;Worker 无需据此改请求参数。
remaining_seconds:当前这次绑定的出口 剩余可用秒数(DPS≈单 IP 可用时长,TPS≈换 IP 周期),不是订单级「IP 套餐总分钟数」。接近 0 时可refresh=1换新绑定。
- 订单池 仅影响后端选哪张 KDL 订单出代理;Worker 仍只调
GET /worker/proxy?proxy_mode=...,与是否启用订单池无关。
14. 联调脚本
后端仓库提供 E2E 脚本,含 pop + report 完整流程:
cd 云机大脑-后端
make run # 终端 1
./scripts/e2e_test.sh # 终端 2环境变量:
| 变量 | 默认 |
|---|---|
BASE_URL | http://127.0.0.1:8080 |
WORKER_KEY | local-e2e-worker-key |
DEVICE | e2e_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 编码后的云机实例名(与 Workerdevice_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_ip | TPS 当前出口 IP(展示用) |
兼容字段(等同 proxy_dps,便于旧前端): proxy_id、proxy_connection、proxy_status、proxy_valid_until、proxy_remaining_seconds、proxy_last_check_ok。
未分配时上述字段为空;完整 ProxyDTO(含 proxy_mode、kdl_order_id)见 §16.3 GET .../proxy。看板抽屉按模式 检测 / 换 IP。
16.3 查询实例代理 — GET .../devices/{names}/proxy
| Query | 说明 | |
|---|---|---|
proxy_mode | dps \ | 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)。
成功 — 未绑定: data 为 null(HTTP 200)。
16.4 检测代理 — POST .../devices/{names}/proxy/check
无 Body。后端按绑定记录的 proxy_mode 调用对应 KDL 接口,并更新 last_check_at / last_check_ok:
| 模式 | 内部 API | 说明 |
|---|---|---|
dps | checkdpsvalid | 校验当前绑定的出口 IP 是否仍有效 |
tps | tpscurrentip | 查询隧道当前出口,与库中 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 绑定 |
tps | changetpsip + 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 一致。前端入口:
- 系统配置 页 → 「出口代理」Tab:DPS / TPS 独立开关 +
default_mode+ 订单池配置 + 凭证分块
- 出口代理 · 订单池 看板(
/admin/proxy-pool):同步、告警、订单权重/禁用
- 云机集群看板 页头 → 「出口代理配置」弹窗(同上);抽屉内 DPS/TPS 分列 展示代理,可选
proxy_mode后 检测 / 换 IP
保存后即时生效(热更新)。关闭某模式后该模式请求返回 50301;关闭 proxy.enabled 后 Worker 全部返回 50301。
17. 变更记录
| 日期 | 版本 | 说明 |
|---|---|---|
| 2026-07-02 | v2.0.2 | FAQ Q18:仅有昵称首字不可筛查;禁止合成期望 ROI;交叉引用 ROI 方案 §14/§15;§6.3/§6.4 补采说明 |
| 2026-07-02 | v2.0.1 | 补全订单池、kdl_order_id、proxy_mode required、§12 双模式 YAML、Admin 订单池 API、看板 proxy_dps/proxy_tps 双列、Q17 |
| 2026-07-02 | v2.0.0 | 双模式并行:dps.enabled + tps.enabled;Worker Query proxy_mode;device_proxy 双行 uk_device_mode;多订单池;Admin 代理操作带 proxy_mode |
| 2026-06-25 | v1.9.0 | 快代理双模式:proxy.mode 支持 dps 私密代理 / tps 隧道代理;Worker 响应新增 proxy_mode、egress_ip;TPS 下 connection 为隧道入口;§12 配置拆分为 kdl_dps / kdl_tps;Admin 检测/换 IP 分模式说明;迁移 015_device_proxy_mode.sql |
| 2026-06-21 | v1.8.0 | 废弃 Legacy 首字比对:仅 phone_screen 不要求 api_nickname_first*;命中 仅 ROI;管理端剔除首字展示;缓存预扫仅保留 user_not_exists 跳过;§7.4 对齐 V2 硬门槛与评分公式 |
| 2026-06-18 23:00 | v1.7.1 | §6.3 修订(已被 v1.8 取代):曾要求 ROI 绑定同时上报首字供管理端展示 |
| 2026-06-18 | v1.7.0 | §6 重写:按 phone_screen / douyin_recover 分列 强制上报字段;明确 ROI 绑定 / 抖音找回 success 时必须 PNG base64;补全 HTTP 400 msg 列表;§6.5 管理端 ROI 回写字段;§7.4 缓存预扫跳过 ROI 记录 |
| 2026-06-18 | v1.6.0 | ROI 昵称比对:手机号 ROI 绑定 report 须上报 nickname_roi_base64;命中阈值 97.5;抖音 export 含 account_unique_key |
| 2026-06-18 | v1.5.0 | 新增双池专用领取接口 GET /task/pop/phone、GET /task/pop/douyin;按 job_mode 隔离调度;job_id 改为可选;旧 /task/pop 保留兼容 |
| 2026-06-17 | v1.4.1 | 明确双 Worker 脚本架构(§1.1);扩充抖音号找回专章、cURL/Python 示例与 FAQ |
| 2026-06-12 | v1.4.0 | 新增抖音号找回模式 douyin_recover:pop 下发 douyin_id;report 上报 phone_prefix + 75×45 PNG base64 |
| 2026-06-12 | v1.3.1 | 补全 proxy:connection 解析、cURL/Python 示例、proxy 配置项、Admin 代理 API 详情与 FAQ |
| 2026-06-12 | v1.3 | 新增出口代理网关 GET /worker/proxy;Admin 代理检测/换 IP;看板展示代理列 |
| 2026-06-12 | v1.2.1 | 心跳缺失时以 pop/report 兜底推断在线/已开机,避免误判关机 |
| 2026-06-12 | v1.2 | 新增 device-info 心跳;device_id=names;看板按 IP 分组;区分实例开机状态与 Worker 活跃 |
| 2026-06-12 | v1.1.1 | 看板可见条件说明;pop 未 report 亦可在看板展示为「处理中」 |
| 2026-06-10 | v1.1 | 支持 api_nickname_firsts 一号多抖音;命中改为任一首字匹配;任务详情展示多首字 |
| 2026-06-09 | v1.0 | 首版:pop/report、鉴权、命中规则(仅昵称首字)、锁超时 |