跳转到内容

控制台总览

读完这篇,你就能完成首次进入控制台的全过程、看懂侧边栏的组织方式,并读懂四个用来判断实例是否健康的页面——总览、系统信息、日志、诊断——包括每个面板的含义,以及看到某个结果之后该做什么。

控制台的一次完整浏览:总览、身份池、调度器、调试台、资料库、下载、接口文档与 MCP

本页提到的每个页面,上面这段录屏里都走了一遍。

控制台是一个 React 单页应用,由 api 容器自己提供。它不是独立服务,没有自己的端口,和 API 同源——这也是会话 Cookie 对两者都生效的原因。使用默认的 compose 文件时,整套东西都在 http://127.0.0.1:8000/。

终端窗口
docker compose -p dtk -f docker/compose.yml up -d
# 然后打开 http://127.0.0.1:8000/

这种部署方式带来几个后果,先知道可以少查一个不存在的 bug:

  • 路由在浏览器里。 任何 API 没有认领的路径都会回落到 index.html,所以控制台的深链接和刷新都能正常工作。
  • API 的前缀是保留的。 /api、/healthz、/readyz、/swagger、/redoc、/openapi.json 和 /mcp 一律不会返回控制台页面。/api 下写错路径返回的是 JSON 404,不是一个页面。
  • /docs 属于控制台。 那是控制台自带的接口文档页,会按你当前的界面语言渲染 Swagger UI。不需要凭据的原始文档在 /swagger。
  • 源码开发环境里根本没有控制台。 构建产物是在镜像里复制进去的;如果 web/dist 不存在,上面这些路由就完全不存在,此时 npm run dev 会把请求代理给 API 进程。

控制台做的每一件事,都是通过 REST API 指南 里描述的那套公开 REST API 完成的。没有私有通道:控制台能展示给你的任何东西,你自己的脚本也能取到。

全新部署没有任何账号。这就留下了一个「谁先到谁就是管理员」的窗口,而最直觉的修法——「只接受内网地址」——并不成立:Docker 的用户态端口代理会把来源地址改写成网桥网关,于是全世界的请求看起来都是 172.17.0.1。因此这道门用的是一次性令牌,而且只出现在容器日志里。

启动时,只要 users 表还是空的,api 容器就会打印一段横幅:

==========================================================================
dtk is not initialized yet.
Open this URL to create the administrator account:
http://127.0.0.1:8000/setup?token=...
The token expires in 24 hours.
To issue a new one: docker compose restart api
==========================================================================

横幅最后那一行是简写,照抄到仓库根目录是跑不起来的:那里没有 compose.yml,只有 docker/compose.yml。请用下面「重新签发」那一行里的完整形式—— docker compose -p dtk -f docker/compose.yml restart api。

用这条命令查看:

终端窗口
docker compose -p dtk -f docker/compose.yml logs api

关于这个令牌的几个事实,全部由服务端强制:

属性 取值 为什么
有效期 自令牌签发那一刻起 24 小时——重启只会把同一个令牌重新打印一遍,不会延长它 足够你翻到那行日志,又不至于让一个被遗弃的部署长期可被认领
存储位置 只在 Redis 里 它不进数据库、不落盘,也永远不出现在任何 HTTP 响应里
允许输错次数 4 次 第 5 次失败会直接删除该令牌。威胁不是有人去猜 32 字节随机数,而是脚本猛刷这个接口
重新签发 docker compose -p dtk -f docker/compose.yml restart api 未过期的令牌在重启时会被复用并重新打印,所以重启不会让你手上的链接失效
账号创建之后 永久关闭 POST /api/setup/init 会在看令牌之前就返回 409

在实例尚未初始化期间,控制台的每一条路由都会重定向到 /setup。如果这个状态查询本身遇到网络错误,控制台会放行并显示一个带重试按钮的面板,同时保留语言与主题切换,而不是把你锁在能解释问题的页面之外。

向导有四步,只有第一步是必须的。

步骤 做什么 可否跳过
1. 创建管理员 消耗令牌,创建第一个 admin 账号,然后立刻用你刚输入的凭据登录(init 接口本身不签发会话) 否
2. 配置代理 粘贴代理列表并导入,随后逐个探测导入的代理,按行显示出口国家与延迟 是
3. 铸造首个身份 在所选平台上铸造 1–5 个身份——抖音和 TikTok 通过无头浏览器,B 站只需一个普通 HTTP 请求 是
4. 冒烟测试 用你提供的一条真实链接调用 POST /api/v1/parse 走完整链路,并显示平台、作品 ID、耗时与请求 ID 是

几个容易踩的细节:

  • 用户名规则比看上去严格。 3 到 64 个字符,字母、数字、点、短横或下划线,且必须以字母或数字开头。

  • 向导要求 12 位密码,服务端下限是 8 位。 多出来的 4 位是向导自己的策略,因为这个账号没有邮件找回。

  • 令牌输错会告诉你还剩几次机会。 服务端返回 SETUP_TOKEN_INVALID 并带上 attempts_remaining,向导会把它显示出来。

  • 第 2 步的导入与探测是一个动作。 「列表被接受了但从没真正拨通过」正是这一步要抓的失败。支持的行格式为 host:port、host:port:user:pass、user:pass@host:port 和 scheme://user:pass@host:port。

  • 没有浏览器容器时第 3 步只提供 B 站。 铸造抖音和 TikTok 要驱动真实浏览器,而浏览器在一个可选的 compose profile 后面。这一步会向身份池询问哪些平台能在这里铸造(GET /api/v1/admin/identities/pool 里每个平台的 can_mint),只列出这些;表单下方会点名其余平台,并给出命令而不是直接失败:

    终端窗口
    echo 'DTK_BROWSER_RPC_URL=http://browser-rpc:9000' >> .env
    docker compose -p dtk -f docker/compose.yml --profile browser up -d --build

    控制台只打印其中 docker compose 那一行,而它省掉的那一行很关键: DTK_BROWSER_RPC_URL 是 api 与 worker 得知这个容器存在的唯一途径。 镜像里装哪个 CloakBrowser 版本则不用你操心——docker/compose.yml 里写着这份代码 验证过的那一个。见安装与部署。

    不启用它也是受支持的运行方式——改为在「身份池」页面手动导入 Cookie。见身份与代理。

  • 第 4 步用的是你给的链接,不是内置链接。 这一点和诊断页第 6 步正好相反,后者用的是服务端自带的固定公开链接。如果这一步失败,向导会把你指向诊断页——它把同一条链路拆成一步一步跑,并指出坏在哪一步。

/login 是控制台唯一的入口。没有 OAuth,没有免密登录链接,也没有注册表单。

  • 所有凭据类失败的提示都一样。 「用户不存在」和「密码错误」是两个不同的事实,把它们区分开就等于免费送给攻击者一个账号枚举探针。措辞不同的只有你真的能采取行动的那两种:账号被锁(会给出剩余秒数)和 API 不可达。

  • 失败次数按账号和按来源地址分别计数。 同一用户名失败 5 次会锁定 15 分钟。按地址的阈值高得多(20 次),而且只有在该地址确实代表单一调用方时才生效——在 TLS 终端或 Docker 发布端口的代理后面,全世界的登录共用一个对端地址,此时按地址拒绝就意味着陌生人 20 次乱试即可把唯一的管理员反复关在自己的控制台外面。

  • 登录成功后会种下有效期 7 天的会话 Cookie,带 HttpOnly、SameSite=Lax,只有请求确实走 HTTPS 时才带 Secure。

  • 没有邮件重置密码。 找回方式是在运行 api 容器的宿主机上执行命令:

    终端窗口
    docker compose -p dtk -f docker/compose.yml exec api dtk user passwd <username>

登录页上带有语言与主题切换,以及指向源码仓库和许可协议的外链——一个意外撞见这个登录页的人,应该不用先有账号就能弄清这是什么。它故意不链接控制台自己的「关于」页:那个页面需要会话,点过去只会把未登录的人弹回这里。

控制台内部的每个页面都在同一副框架里:左侧边栏、吸顶的顶栏,以及最宽 1440px 的内容列。

宽 240px,可折叠成 56px 的图标条,折叠状态会记在这台浏览器里。宽度低于 1280px 时它会自动折叠成图标;低于 768px 时它整体消失,改为从顶栏打开的抽屉。页面顶部有给键盘用户的「跳到主内容」链接,左上角的标识是返回首页的链接。

导航的分组依据是「出事时你会去找什么」,而不是任何东西的开发顺序。

分组 页面
监控 总览(/)
资源池 身份池(/identities)、代理(/proxies)、调度器(/scheduler)
内容 资料库(/library)、下载(/downloads)、视频转写(/transcriptions)、关注列表(/watchlist)
开发者 调试台(/playground)、基础工具(/tools)、接口文档(/docs)、MCP(/mcp-guide)
访问控制 API Key(/api-keys)、接口访问控制(/endpoint-access)、用户(/users)
运维 日志(/logs)、系统信息(/system)、诊断(/diagnose)、备份(/backup)、通知(/notifications)、设置(/settings)

「内容」和「开发者」以前是同一个八项的「工具」分组,把这个实例收集下来的东西和对接它的 API 用的页面混在了一张列表里。每一行也都有自己独立的图标,因为侧边栏折叠成图标条之后,能区分两个页面的就只剩图标了。

有两样东西被固定在可滚动导航的下方,而不属于任何分组:

  • 关于(/about)——版本说明页:许可协议、版权、仓库与 issue 链接,以及捐赠地址。它本身就是控制台里的一个页面,所以看起来就像多一行导航。
  • 赞助位 ——指向项目赞助商的、明确标注的外链,侧边栏折叠成图标时会隐藏。这里有两个刻意的选择:Logo 图片由你自己的实例提供,绝不从赞助商的主机拉取,因此打开控制台不会向第三方报告你的部署存在;并且它明写「赞助」二字,而不是伪装成某个功能。链接携带固定的推广参数,描述的是项目本身和展示位置,绝不涉及你、你的实例或你的访问者——每一份自部署副本发出的字符串都完全一样。

MCP 那一项指向 /mcp-guide 而不是 /mcp,因为 /mcp 属于挂在 API 上的 MCP 端点本身。端点自己的路径是带末尾斜杠的 /mcp/;/mcp 只会以 307 Temporary Redirect 跳到它,所以配置客户端时要带上这个斜杠——见MCP 与 AI 客户端。/parse 会重定向到 /downloads:解析工具已经变成下载页的一个模式,旧路径保留是因为它在 v5 的整个生命周期里都在导航中。

侧边栏是控制台的地图,下面这张是从控制台回到文档的地图——每一行导航,以及讲它的那篇。

页面 路径 对应文档
总览 / 本篇,见下
身份池 /identities 身份与代理
代理 /proxies 身份与代理
调度器 /scheduler 身份与代理
资料库 /library 下载、资料库与关注列表
下载 /downloads 下载、资料库与关注列表
视频转写 /transcriptions 下载、资料库与关注列表
关注列表 /watchlist 下载、资料库与关注列表
调试台 /playground 调试台与工具
基础工具 /tools 调试台与工具
接口文档 /docs 调试台与工具
MCP /mcp-guide MCP 与 AI 客户端
API Key /api-keys 用户与 API 密钥
接口访问控制 /endpoint-access 身份与代理
用户 /users 用户与 API 密钥
日志 /logs 本篇,见下
系统信息 /system 本篇,见下
诊断 /diagnose 本篇,见下
备份 /backup 运维
通知 /notifications 运维
设置 /settings 运维
关于 /about 本篇,见上
元素 是什么
菜单按钮 打开导航抽屉。仅在宽度低于 768px 时显示
面包屑 控制台 / <当前页面>
主机标签 你实际连接的主机,取自 window.location.host。低于 768px 时隐藏
语言切换 English / 中文
主题切换 暗色 / 亮色 / 跟随系统
账号菜单 显示已登录用户名,以及退出登录

语言。 切换立即生效、无需刷新,同时也会改变控制台发出的 Accept-Language 头,所以服务端渲染的文字——设置项说明、熔断原因、任务错误、OpenAPI 文档——会跟着一起换语言。判定顺序是:本次导航(或本标签页更早)的 ?lang= → 你显式保存的选择 → 服务端为这个文档协商出的语言 → navigator.language → 英文。?lang= 链接只在当前标签页内记住,绝不持久化:同事发来的链接说明的是这一次访问该用什么语言,而不是你从此以后的偏好。

主题。 是三档而不是两档:「跟随系统」跟随操作系统。选择保存在这台浏览器里。

语言、主题和侧边栏状态都是保存在本地的浏览器级偏好。服务端不存这些,清除站点数据即恢复默认。

这些约定读一次,可以省下在后面四节里重复读四次。

  • 表格可排序、可隐藏列、可切换密度。 每个数据表都有列菜单和密度菜单,两者的选择会按表分别记在这台浏览器里。有些列默认隐藏,下文按页面逐一列出。
  • 手机上表格会变成卡片。 不适合窄屏的列会从卡片视图里丢掉,而不是硬挤进去。
  • ID 点一下就能复制。 请求 ID、身份 ID、代理 ID 和提交哈希都用等宽字体完整显示并带复制入口——半个 UUID 毫无价值。
  • 状态永远同时用颜色、图标和文字表达。 大家会把这些表格粘到聊天里(只有文字能活下来),也会截图进 issue(颜色可能偏移)。文字才是内容本体。
  • 刷新时不做任何动画。 只有状态真的变化时那一行才会高亮,轮询本身不会——每五秒淡入一次的表格是没法读的。
  • 时间按你的本地时区显示,服务端存的是 UTC。 涉及时间戳的表格会在筛选区下方注明这一点。
  • 页面是轮询而不是长连接。 身份池状态以及出事时你会盯着的东西每 5 秒刷新,图表每 8 秒,变化慢的清单每 10 秒。
  • 角色限制的是你能做什么,而不是你能看什么。 viewer 可以读本文提到的每个页面;执行自检(以及查看自检报告)或修改设置需要 operator 或 admin。见用户与 API 密钥。
  • 卡片和抽屉会链接到存放详情的那个页面。 对公开演示账号,它打不开的页面不会给出链接——与其给一个点进去就是 403 的链接,不如不给:它能点开「下载」卡片,但「关注中」卡片不带链接。

/ ——「现在这套东西健康吗?它到底积累下了什么?」 这是落地页,也是适合常驻第二块屏幕的那一页。

页头显示运行中的版本;如果镜像构建时带了 DTK_COMMIT,还会显示可复制的构建提交号。旁边有两个选择器:

选择器 选项
接口 全部接口,或某一个具体接口。选项来自接口健康度看板
时间范围 最近 1 小时(60 秒桶)、最近 6 小时(300 秒)、最近 24 小时(300 秒)、最近 7 天(1800 秒)

桶宽不是随手定的:24 小时按 5 分钟是 288 个点,画起来是瞬时的;一周按同样精度就不是了。

如果身份池计数为零并且所选时间范围内没有记录到任何流量,页面会整体换成「还没有请求」和一个跳转到身份池页面的按钮。只有两个条件同时成立才会触发,所以一个正常实例安静了一小时并不会看到它。

告警横幅显示在所有内容之上,而且只针对你必须处理的情况。什么都报的告警等于没人看的告警。

告警 色调 含义 该做什么
没有可用身份 危险 没有任何平台还有 active 或 degraded 身份。在恢复之前所有请求都会以 IDENTITY_POOL_EXHAUSTED 被拒绝 去身份池页面铸造或导入——横幅上直接带按钮
没有可用的某平台身份 危险 该平台原本有身份,现在一个 active 或 degraded 的都不剩。它的请求会以 IDENTITY_POOL_EXHAUSTED 被拒绝,其他平台不受影响 横幅上的按钮直接打开该平台的身份
还没有某平台身份 警示 该平台从来没有过身份,worker 马上会为它铸造,第一个到位之前对它的调用都会被拒绝。详情会说明是哪种情况:正在铸造、一分钟内会开始铸造,或者铸造连续失败、在某个时间之前暂停重试 等它铸造完;如果在暂停重试,去身份池页面看铸造记录
N 个身份处于降级状态 警示 这些身份仍会承接流量,但失败率高于其它身份 去身份池页面排查;长期降级通常意味着代理已死或 Cookie 过期
每个熔断接口一条横幅 危险 该接口已熔断。横幅会写明接口名、原因和距离恢复的时间 见下面的接口健康度表

身份池告警是按平台判断的。以前按各平台之和判断,只有三个平台全空时才会报警:抖音可能已经在拒绝每一个调用,而 TikTok 的身份把总数撑在零以上,页面始终是绿的。degraded 身份算作还能承接请求,因为可用身份用完之后调度器会退而使用降级身份。所有平台都空了时只出一条「没有可用身份」,而不是三条;从没配置过的平台用琥珀色而不是红色。

从来没有过身份、也不会被铸造的平台根本不出横幅。比如没有浏览器容器的实例上的抖音和 TikTok——它们必须用浏览器铸造——以及下限设为 0 的平台。对它的调用确实会被拒绝,但这是实例本来的配置,不是故障;以前一个只用 B 站的实例每次打开概览都会被告知「还没有抖音身份」「还没有 TikTok 身份」。这种平台为什么是空的,改由身份池状态分布卡片说明。一旦该平台有过身份又全部失去,横幅会重新出现:不管配置如何,那都是故障。这条规则依赖身份池页面的自动补充状态,所以打不开身份池页面的会话仍然像以前一样,对每个空平台都显示「还没有某平台身份」。

身份池相关的告警在计数尚未加载完成或加载失败时会被抑制。没有这条规则,页面每次加载都会闪一次红色的「没有可用身份」,而且只要状态接口出错就会永久挂着一条。

指标卡片,第一行——请求跑得怎么样

Section titled “指标卡片,第一行——请求跑得怎么样”
卡片 数据来源 配色规则
可用身份 各平台身份池计数之和。脚注:每个平台各自的可用数,为零的平台用警示色。链接到身份池 没有任何平台还有能用的身份时为红色;某个原本有身份的平台一个都不剩时为琥珀色;否则为绿色
请求量 所选时间范围内的请求总数,带迷你折线。链接到请求日志 —
成功率 窗口内 ok ÷ 总数 低于 90% 为警示色,达到或超过为绿色
风控率 risk_control ÷ 总数 只要大于零就是红色
平均耗时 按请求数加权的平均耗时 —
熔断接口 处于熔断状态的接口数量 大于零为红色。脚注给出已声明接口总数

90% 这个下限是卡片和逐接口表格共用的同一个常量,所以两者不可能对「多少算正常」产生分歧。写死成绿色会把 12% 的成功率画得和 99% 一样好看。

指标卡片,第二行——它留下了什么

Section titled “指标卡片,第二行——它留下了什么”

上面每张卡片说的都是请求,没有一张回答这个实例到底积累了什么,而后者是另一个、也更常见的问题。

卡片 数据来源 配色规则
已归档作品 内容归档表的行数。脚注:涉及多少位不同作者。链接到资料库 —
已下载媒体 已归档且媒体已保存的作品数。脚注:占已归档总数的多少。链接到下载 —
已占用磁盘 下载器有响应时取自媒体卷的实测值,否则取数据库里记录的已存媒体总量——是下载记录求和,不是数据库大小。脚注:来自 media.max_bytes 的上限。链接到下载 达到上限 75% 为警告色,90% 为危险色
正在下载 在途任务数。脚注:多少个失败或部分完成。链接到下载 —
关注中 关注列表条目数。脚注:多少个正在失败。链接到关注列表 有失败项时为警告色
转写 已转成文字的已存视频数。链接到视频转写。viewer 及以上才显示,因为这个接口拒绝演示账号 —

磁盘这张卡片在快满之前一直安静,接近上限时才变吵,因为超过上限之后最旧的未置顶下载就会开始被清理。见下载、资料库与关注列表。

每个平台一条按比例分段的横条,各带每个状态的计数:minting(铸造中)、active(可用)、cooling(冷却中)、degraded(降级)、retired(已退休)。平台名链接到该平台的身份,旁边是它的可用数;一个身份都没有的平台画一条空轨道,而不是整行消失。本实例铸造不了、或者因为下限为 0 而不铸造的平台,会在名字下面用灰色而不是琥珀色说明原因。以前所有平台合成一条横条,某个平台空了的时候它照样显示一片健康的绿色——而这恰恰是这张卡片要让你发现的事。cooling 不是问题——那是调度器有意让身份休息。绝大部分处于 degraded 或 retired 才是问题。状态机在核心概念里有说明。

三张,全部来自 request_log:

  1. 成功率与风控率 ——每个时间桶内的请求占比,纵轴固定为 0–100%,这样不同时间范围之间的形状可比。
  2. 请求量 ——每个时间桶的请求数。
  3. 各接口成功率 ——只有在没有选定单个接口、且有一个以上接口有流量时才显示。最多画该时间范围内请求量最高的六个接口,因为分类配色只有六个条目能在明暗两种主题下达到 3:1 对比度,而且折线图超过六条本来也就读不动了。

平台适配器声明的每一个接口都会列出来,不管有没有流量。把没流量的也列出来是刻意的:一个没出现在看板上的接口,和一个从来没被调用过的接口一样看不见。

列 含义
熔断 正常或熔断中。默认排序把熔断中的排在最前
接口 逻辑接口名,例如 douyin.content_detail
成功率 熔断器的 300 秒滚动窗口内的成功率。低于 90% 显示为警示色
风控率 同一窗口内的风控率。大于零显示为红色
样本数 该窗口内有多少个请求。3 个样本算出的比率几乎没有意义
命中身份数 遇到风控的不同身份数量。默认隐藏
恢复剩余 熔断的接口距离下次试探还有多久

这里的数字来自 Redis,也就是熔断器自己据以判断的那个滚动窗口,而不是上面来自请求日志的图表。它们回答的是不同的问题,因而不会永远一致:看板说的是「现在能不能用」,图表说的是「过去一天长什么样」。

如果这张表是空的,说明平台适配器根本没有报告任何接口。那是部署问题,不是数据为空。

  • 某一个接口成功率掉、风控率升 → 去日志,按该接口加 risk_control 结果筛选,看牵涉到哪些身份。
  • 所有接口成功率一起掉 → 通常是出口而不是平台的问题。跑一次诊断。
  • 接口熔断 → 它会按计时自行重试。同一个接口反复熔断,通常意味着签名或接口定义已经漂移;诊断报告就是提 issue 时要附上的东西。
  • 身份池空了或偏少 → 去身份池页面。
  • 磁盘卡片进入警告 → 去下载页面,或在配置参考里调高 media.max_bytes。

/system ——「这里到底跑着什么,它能不能连上它需要的东西?」 这一页就是提 bug 时用来截图的那一页。

页头显示版本和提交号,还有一个「刷新」按钮,可以立刻重新读取而不用等轮询。

卡片 含义
版本 运行中的 dtk 版本。脚注是运行配置版本号,每次改设置都会递增
运行时长 自 api 进程启动起算,用的是单调时钟,因此系统时间被调整也不会跳变
可用身份 各平台可用身份总数
数据库大小 当前数据库的 pg_database_size

postgres、redis、browser_rpc 以及两个可选的 sidecar——downloader 和 transcriber——的连通性与往返耗时,由服务端测得,而不是由你的浏览器测得。这个区别很重要:这里显示的慢是依赖慢,不是你笔记本慢。

状态 含义
健康 探测有响应。延迟即往返耗时
不可用 探测执行了但失败或超时。postgres 和 redis 的详情列显示的是 timeout 或异常的类型名,原文保留、不翻译。browser_rpc 和两个 sidecar 则用一句话说明原因:已配置但没有响应、已配置但响应太慢,或者有响应但还不能工作——转写服务还在加载模型、或者缺 ffmpeg,就属于最后这种。想看驱动自己的原话,请跑一次诊断并看第 1 步
未知 组件两者都没报告——实际情况就是没有配置 URL 的 browser_rpc、downloader 或 transcriber。此时详情列显示「未配置」,并点名这台机器没有运行的那个 compose profile(browser、downloader、transcriber)

browser_rpc 健康时还会在详情列里报告预热上下文数量。browser_rpc 不可用永远不构成把实例摘出流量的理由:铸造不在请求路径上,所以 /readyz 故意忽略它,只要求 Postgres 和 Redis。两个 sidecar 也一样,它们在你未必会启用的 profile 后面。sidecar 的探测结果会复用 10 秒,因为下载器回答健康检查时要遍历一遍媒体卷,而好几个页面都在轮询这个接口——所以刚启动的 sidecar 可能要再刷新一两次才会变成健康。

并排两个数字:browser_rpc 报告的 Chromium 主版本,以及传输层针对该浏览器实际会采用的 TLS 模拟档案主版本。这张卡片存在的理由是:两者不一致是控制台上别处都看不出来的失败模式——签名照样产出,只是不再符合平台的预期,而你要等几周后风控率上升才会发现。

状态 横幅 含义
两者已知且相同 成功 没有漂移
两者已知且不同 警示 版本漂移。签名仍能产出,但指纹已经和产出它的浏览器对不上。请固定浏览器镜像或更新指纹档案
Chromium 未知 提示 browser-rpc 未配置,或者还没有响应过健康检查
wreq 指纹未知 提示 当前安装的 wreq 完全没有 Chrome 模拟档案,无从比对。签名仍然可用,但 TLS 指纹与产出它的浏览器对不上

两条「未知」提示是分开的,这是有原因的。以前只有一条统一措辞,结果在问题出在 wreq 这一侧时也去怪 browser-rpc,而它上面一行正显示着 browser-rpc 刚刚报上来的 Chromium 版本。

各表的估算行数,卡片描述里带数据库总大小。会报告的表是 request_log、identity_events、content_snapshots、tasks 和 identities。

「估算」两个字请按字面理解。对请求日志做 count(*) 是对全系统最大的表做一次全扫描,对一个会被轮询的状态接口来说太贵了,而「估算」背后其实是三个不同的来源:超表 request_log、identity_events、content_snapshots 来自 TimescaleDB 的分块估算,tasks 来自查询规划器的 reltuples,identities 则是精确的 count(*)——这张表小到跑得起。某张表的估算确实未知时——没人分析过的关系,或者没装 TimescaleDB 的服务器——那一行会被整行略去,而不是显示成空白格,所以「没有这一行」和「这一行是 0」不是一回事。这些数字用来给保留策略定容量是够的,别拿它做对账。

默认开启,请求由你的浏览器发出,绝不由服务端发出。 开关写的是运行配置项 system.check_updates,因此选择在重启后依然有效。viewer 不能改它;演示账号则根本看不到这张卡片:它读不了这张卡片所依赖的设置。

开启时,控制台会在你登录后、每个浏览器每 24 小时最多问一次,只有确实有新版本时才弹出提示——演示账号永远不会看到,因为它什么也升级不了。「立即检查」则是按需发起同样的请求,从你的浏览器发往 api.github.com。结果会把最新发布标签与你运行的版本比对,其中 v5.0.0 和 5.0.0 视为同一个版本。因为发请求的是浏览器而不是服务端,它不携带任何关于这套部署的信息,这也是它默认开启的理由:对自部署的工具来说,一个永远不知道自己已经过时的实例才是更常见的危害。断网环境里请把它关掉。

/logs ——「这一次具体的调用发生了什么?谁改了什么?」 两份日志,刻意分开,做成两个标签页。

页头有一个实时刷新开关,按计时刷新当前标签页(请求日志 5 秒,审计日志 10 秒)。仔细看某一行时请先关掉它,否则表格会在你眼皮底下移动。

高频写入。每一次上游请求对应一条结构化记录。

筛选条件 ——所有条件都是可选的,留空表示不限:

条件 说明
请求 ID UUID 精确匹配
接口 从所有已声明的逻辑接口里选一个,按平台分组——包括 bilibili.*——不管它有没有流量。匹配是精确的,所以这里是下拉列表而不是输入框:以前名字差一个字就什么都查不到,看上去就像日志是空的
身份 ID 某一个身份发起的全部请求
代理 ID 从某一个代理出去的全部请求。要找出平台在拒绝哪个出口,就用这个:B 站的 412 是对一个地址的判决,不是对某份 Cookie 的,而同一个代理后面的所有身份共用这个地址
时间范围 15 分钟、1 小时(默认)、6 小时、24 小时、7 天、30 天
返回条数 100(默认)、200、500
结果 切换按钮,可同时选中多个。「全部」清空选择

ID 类筛选要等你停止输入、并且 ID 完整之后才生效。没输完的 ID 会提示「暂未生效」,而不是被发出去——服务端按 UUID 校验这些参数,否则每敲一个键都会换来一个 422。

其他页面会链接过来的那些筛选条件都保存在地址里:/logs?tab=requests&request_id=&identity_id=&proxy_id=&endpoint= 和 /logs?tab=audit&user_id=。所以「看看这个代理干了什么」就是一个链接,筛好的视图也可以直接发给同事。时间范围、返回条数和结果筛选不进地址。

这里有两条限制属于结构性设计而非界面装饰。时间窗口和条数上限都是必填的,而且都没有「全部」选项:无界地读 request_log 是这套 API 上唯一一个能把实例拖垮的查询。服务端把窗口封顶在 30 天,条数封顶在 500。值得知道的后果是:比你所选窗口更老的请求 ID 会查不到——这时应当扩大时间范围,而不是认定这条记录已经没了。

数据保留是另一条上限:retention.request_log_days 默认 14 天,所以查 30 天是允许的,只是能查到的更少。

结果词汇表是这一页上最有用的东西:

结果 颜色 含义
成功 绿色 平台给了响应,且响应解析成功
内容不存在 灰色(弱化) 平台把话说清楚了:作品已删除、账号私密、地区限制。不是系统故障
风控命中 红色 平台拒绝解释——验证挑战、空响应体、直接拦截。这一类才是真正消耗身份的
网络错误 警示 请求根本没拿到可用的响应:DNS、TLS、代理、超时

「内容不存在」被刻意弱化。一条被删除的视频不是 bug,把它标红只会让人去找一个并不存在的问题。

列(默认隐藏:代理、拒绝原因、命中缓存、签名方式、平台):

列 含义
时间 本地时间
结果 上面那套徽标
接口 逻辑接口名
请求 ID 可复制。提 issue 时引用的就是它
HTTP 状态 平台返回的状态码(如果有)
耗时 上游调用的实际耗时
身份 由哪个身份承接;身份有备注时,备注显示在 ID 下方
代理 从哪个代理出去
错误码 稳定的机器码,永不翻译
拒绝原因 调度器为什么根本没发出去——见下
命中缓存 结果是否来自缓存
签名方式 由哪条签名路径产出的签名
平台 douyin / tiktok / bilibili

一次 B 站视频详情是两行,不是一行。B 站的详情响应里没有可播放的文件,所以实例会再发一个请求去取视频流;这个请求同样经过调度、签名,并以它自己的接口名 bilibili.content_streams 写进日志,和 bilibili.content_detail 并列。只按其中一个名字筛选,你看到的就只是一半的工作。

有拒绝原因就意味着这个请求压根没出门,是调度器拒绝了它:

原因 含义
circuit_open 该接口处于熔断中
no_identity 该平台没有可用或降级的身份
no_token 所有身份在令牌桶里的配额都用尽了
all_inflight 所有身份都在处理请求
queue_full 等待队列已满
wait_timeout 等待身份超时
pinned_unavailable 指定的那个身份无法处理该请求
no_login 该请求需要已登录的身份,而轮换中一个都没有。铸造解决不了,只有导入一份带登录态的 Cookie 才行

如果当前版本没有某个原因码的译文,界面会原样显示它——没有翻译的原因码仍然是可以贴进 issue 的东西,而缺失翻译的占位符不是。

点击某一行会打开抽屉,展示完整记录,底部还有折叠起来的原始 JSON。身份和代理旁边有「只看这个身份」「只看这个代理」,把表格收窄到那一个;还有一个链接,在身份池页面打开这个身份。记录里没有任何凭据:抓取层记录的是它调度时用的逻辑接口名,绝不是它构造出来的签名 URL,代理也只以 ID 出现。身份显示为 ID,有备注时表格和抽屉里都会把备注显示在 ID 下方,这样「是哪个账号承接的」在这一行上就能看出来,不必拿着 UUID 去身份池页面查。演示账号只能看到 ID:这个页面对它开放,而备注正是有人写下某个登录身份是谁的账号的地方。

低频写入,并且和请求日志分开存放,以免被日常流量淹没。它回答的是「谁改了什么」。演示账号读不了这份日志,所以它看不到这个标签页。

筛选条件是操作(精确的操作名,例如 api_key.created 或 settings.updated)、操作者和返回条数。操作者的选项来自当前屏幕上那些行里出现过的账号——账号列表本身只有管理员能读,而读审计记录的 viewer 同样需要「只看这个人做了什么」。表格下方的加载更早的记录会接着已显示的最旧一行往前再取一页。

列 含义
时间 本地时间
操作 点分格式的操作名
操作者 已知则显示用户名,否则用户 ID——如果是用 API Key 操作的,后面再跟上那把 Key。两者都没有的记录显示「系统」
对象 对象类型与 ID,例如 setting 加上配置项名
来源地址 客户端 IP。默认隐藏

抽屉里显示这条记录的详情负载,并提供「只看这个账号的操作」,按它的操作者筛选。设置类变更在返回时会再次脱敏:设置变更会记录旧值和新值,而有些设置项存的是凭据。在源头脱敏机制存在之前写入的行还留在这张表里——没有任何东西会清理它——所以读取时会再脱敏一次。

/diagnose ——「我部署好了但拿不到数据。四个可能的地方里,到底是哪一个?」 原因通常在代理、Cookie、签名链路或网络这四处,而每猜一次都要付出一个来回。这一页把四处全都走一遍,然后交给你一整段文本。

执行它、以及查看它的报告,都需要 operator 或 admin 角色。viewer 可以打开这个页面,但在原本放执行按钮的位置只会看到一行说明。

点击开始自检会发出 POST /api/v1/admin/diagnose,它返回 202 和一个任务 ID,而不是同步把活干完——一次诊断要拨打代理并发起真实外部请求,这些都不该压在那个本应保持响应的容器的 HTTP 处理线程上。随后控制台每 5 秒轮询这个任务直到结束,每一步的结果到达时就标记出来。任务 ID 显示在页头,可复制。

这次运行会记在地址里(?task=)和当前标签页的会话存储里,所以离开页面再回来,看到的还是同一份报告,而不是在任务还留在服务端的时候就把它扔掉。如果记住的那次运行,其结果服务端已经不再保留,页面会直接说明,并请你重新自检。

两个选项:

  • 包含端到端冒烟测试,默认开启。关掉它会跳过第 6 步,让整次运行完全在本地进行——同时也意味着不消耗任何身份。
  • 端到端测试用的平台。 默认的「自动」选轮换中可用身份最多的平台——保留的账号不算,轮换本来就不会把它发出去;一个身份都没有时,配置了浏览器就选抖音,否则选 B 站。以前固定是抖音,于是一个只服务 B 站的实例会在第 6 步因为一个它根本不打算服务的平台而失败,它真正服务的那个平台反而没测到。想测哪个平台,也可以直接选。

那 15 秒是发起外部请求的步骤上的单次网络请求超时,而不是某一步的整体期限:第 1 步的组件探测每个上限 3 秒(下载器和转写服务是 8 秒,因为正在统计一个大媒体卷的下载器只是慢,不是挂了),第 6 步的端到端抓取上限 25 秒。六个步骤是依次串行跑的,所以慢的那一步确实会拖住后面的——最该留意的是第 3 步:它最多拨打 20 个代理(按创建时间从旧到新),每个最长 15 秒。

这份报告的全部意义就是被贴进公开 issue,所以掩码是在渲染器里做的,而不是交给粘贴的人去做。代理密码、Cookie、Authorization 与 X-API-Key 头、Cookie 与签名参数(sessionid、sid_guard、odin_tt、ttwid、msToken、a_bogus、X-Bogus、_signature、verifyFp 等等),以及形如 dtk_..._... 的 API Key 的密钥部分,全部替换为 [REDACTED]。代理的用户名会保留,以便这个代理仍然可辨识;密码绝不保留。API Key 的公开前缀会保留,以便报告仍能说清是哪把 Key 在起作用。

复制报告按钮复制的是服务端渲染出来的那段纯文本本身,而不是浏览器重新拼一遍的结果。

每一步报告的是一个稳定的代码,你读到的句子是按你的语言从这个代码渲染出来的。证据——驱动的报错、异常文本、每一步的 details——刻意保持英文,因为那才是维护者能搜索的内容。

# 步骤 实际做了什么
1 components 往返探测 Postgres、Redis 和 browser-rpc;配置了 profile 的话,还有下载器和转写服务
2 egress 不经代理直接访问 https://www.douyin.com/、https://www.tiktok.com/ 和 https://www.bilibili.com/ ——这是把「没网」和「代理坏了」区分开的办法
3 proxies 按创建时间从旧到新,最多拨通 20 个已配置的代理(MAX_PROXIES_PROBED),向回显服务查询出口地址,然后报告出口 IP、库里记录的国家,以及该行是否被标记为健康
4 pool 按状态统计身份数量,并报告最近一次成功请求是什么时候、最老的可用身份已经闲置多久
5 signing 抖音和 TikTok 各用纯算法签一个固定请求,并与 browser-rpc 比对——就是签名器注册表在生产中跑的那套影子比对。B 站不参与比对:browser-rpc 从不为它签名,没有第二份签名可以拿来对照
6 smoke 用所选平台的一条固定公开链接走完 解析 → 签名 → 抓取 → 分类 的全过程,并在 details 里写明用的是哪个平台

注意第 6 步用的是服务端自带的链接(每个平台一条),而不是你提供的——这一点和初始化向导第四步正好相反,后者会向你要一条。如果那条作品后来被删除或设为私密,这一步依然通过:平台把话说清楚了,而这一步问的就只有这个。

四种状态:通过、警告、失败、已跳过。整次运行的结论取所有步骤中最差的那个——PASS、WARN 或 FAIL。

警告不会让整次运行判失败。 警告的含义是:这一步跑完了、得出了结论,并且想让你知道某件事——身份池偏少、某个设置还停在开发默认值。把警告并进失败里,会让整份报告读起来是「结论 FAIL」,而一个实例明明在正常服务却被告知失败的运维,从此就不再看结论了。

已跳过也不是失败。 依赖不存在的步骤会报「未配置」,而不是一个你无从下手的红色失败。

结果 状态 含义与处理
components_ok 通过 配置了的组件都有响应
components_browser_rpc_down 警告 Postgres 与 Redis 正常,browser-rpc 不可用。身份铸造和签名回退都不可用。启动浏览器容器,或者手动导入 Cookie 并继续使用原生签名器
components_sidecar_down 警告 Postgres 与 Redis 正常,但下载器或转写服务配置了却没有响应。在它恢复之前,媒体下载或转写都会失败。启动它的 compose profile;如果它本来就不该在这里运行,就清空 DTK_DOWNLOADER_URL 或 DTK_TRANSCRIBER_URL。没人启动的 profile 根本不参与评判
components_unreachable 失败 Postgres 和/或 Redis 不可达。确认这些容器在运行,并检查 DTK_DATABASE_URL、DTK_REDIS_URL 是否指向它们。这一项红着的时候,报告里其余部分都不可信
结果 状态 含义与处理
egress_ok 通过 抖音、TikTok 和 B 站的域名都能直连
egress_partial 警告 三个里有不可达的,原因里会写明通了几个。那些平台的请求将完全依赖可用的代理
egress_unreachable 失败 一个都通不了。检查 DNS、主机防火墙,以及这台机器的出站流量是否必须走代理
结果 状态 含义与处理
proxies_ok 通过 被探测到的代理全部有响应。也就是最多 20 个、按创建时间从旧到新——池子更大时,剩下没被测到的那部分既没有通过,也没有被报告
proxies_none 警告 没有配置任何代理。所有身份共用这台机器的出口 IP——初次试用没问题,身份池一忙就是关联风险
proxies_partial 警告 一部分通、一部分不通。请停用或更换失败的那些,否则绑在上面的身份会一直失败
proxies_all_failed 失败 一个都没响应。检查代理账号密码,以及服务商是否仍然放行这台机器的 IP。绑在失效代理上的身份无法自行恢复
proxies_no_session / proxies_unreadable 已跳过 代理表读不出来——先看第 1 步
结果 状态 含义与处理
pool_ok 通过 达到或高于下限
pool_below_minimum 警告 可用身份少于 pool.min_size(默认 3)。再铸造一些,或者如果这是有意为之就把该设置调低
pool_empty 失败 一个可用身份都没有。在你铸造或导入之前,所有请求都会被拒绝或排队
pool_no_session / pool_unreadable 已跳过 身份表读不出来——先看第 1 步
结果 状态 含义与处理
signing_ok 通过 抖音和 TikTok 上纯算法与浏览器签名一致
signing_not_comparable 警告 浏览器参考实现的算法版本不同,因此结构化比对给不出任何结论。纯算路径不受影响,仍在正常服务。 这条读起来像故障,但它不是
signing_mismatch 失败 纯算法已经和平台的实现产生偏差。这些平台目前改由 browser-rpc 签名;请附上这份报告提交 issue,以便更新算法
signing_no_browser_rpc 已跳过 没有配置 browser-rpc,没有第二个签名器可以对照。配置 DTK_BROWSER_RPC_URL 即可启用影子比对,在风控率上升之前就发现失效的算法
signing_no_registry 已跳过 本次运行没有拿到签名器注册表

只要有任何一个平台没能比对成功,这一步就报警告,而不是只在全部失败时才报。「纯算与浏览器签名一致」这句话,在一个平台比对成功、另一个没比对的情况下,会被读成关于两个平台的证据——而它没说的那个,恰恰是你需要被告知的那个。

结果 状态 含义与处理
smoke_ok 通过 一条真实链接完整跑通了整条链路
smoke_failed 失败 先看上面失败的那一步。冒烟失败通常是代理、身份池或签名环节的连带结果,而不是它本身的问题
smoke_not_configured 已跳过 你关掉了端到端冒烟测试
结果 状态 含义与处理
step_crashed 失败 检查本身抛异常了。这是 dtk 的缺陷——请附上报告反馈给我们

控制台和 CLI 跑的是同一份代码,所以即使坏掉的正是控制台,你也不会卡住:

终端窗口
docker compose -p dtk -f docker/compose.yml exec api dtk diagnose
docker compose -p dtk -f docker/compose.yml exec api dtk diagnose --skip-smoke
docker compose -p dtk -f docker/compose.yml exec api dtk diagnose --json

CLI 还额外支持 --smoke-url、--probe-url 和 -o/--output。不传 --platform 时,它和控制台用同样的方式选择冒烟测试的平台。见命令行参考。

问题 页面
现在有没有着火? 总览
哪个接口在失败,有多严重? 总览 → 接口健康度表
请求 0c9f… 到底怎么了? 日志 → 请求日志,按请求 ID 筛选
调度器为什么根本没发出去? 日志 → 拒绝原因列
这个设置是谁在什么时候改的? 日志 → 审计日志
我跑的是什么版本?它能连上 Postgres 吗? 系统信息
浏览器和 TLS 指纹版本一致吗? 系统信息 → Chromium 与 wreq 指纹
数据库长到多大了? 系统信息 → 磁盘占用
部署完了,什么都不工作 诊断
提 bug 时该附上什么? 诊断 → 复制报告
  • 配置参考 ——这些页面读取的每一个设置项,以及每一项的来源
  • 核心概念 ——身份、调度器、熔断器,以及结果词汇表
  • 身份与代理 ——总览页告警会把你送去的那些页面
  • 调试台与工具 ——调试台、基础工具与接口文档这三个页面
  • 用户与 API 密钥 ——角色,以及每种角色在控制台里能做什么
  • 运维 ——设置、备份、通知这三个页面,数据保留,以及长期运行这套东西
  • MCP 与 AI 客户端 ——MCP 页面,以及它所介绍的那个接入点
  • 故障排查 ——从症状出发,适合你已经知道坏在哪的时候