Files
hello_js_reverse_skill/tcaptcha/web/README.md
T
yml2213 5c4ff8c4a1 feat(web): 靶子显示友好名 + 支持连打次数
- targets.py 增加 label 字段(1ms 签到 / mliev 登录),下拉框、说明、历史表统一用友好名
- POST /api/jobs 支持 count(1~20):一次任务连打 N 发,发间自动按靶子频控节流、可取消
- 每发结果记入 stats.rounds,EVT_VERIFY 带 round,结果区展示「每发结果(通过 x/y)」彩色一览
- 滑块不再生成标注图(红圈+序号只表达点击顺序,对滑块是误导)
- 观测台 README 补齐启动/后台运行/端口占用/自检说明
2026-10-15 11:20:24 +08:00

7.2 KiB
Raw Blame History

tcaptcha Web 观测台

把 CLI 的黑盒变成实时可见的过程:启一次任务,就能看到 「抽到哪道题 → PoW 命中 → 求解出点位(带标注图与偏离量)→ collect 段几何 → verify 的 errorCode / ticket」。

它不是另一套求解逻辑:后端就是 TencentCaptchaClient.solve(),与 scripts/verify/send_test.py / send_campaign.py 跑的是同一条链路。

启动

# 首次(或依赖变动后):把 web extra 同步进 .venv
uv sync --extra web

# 启动(默认 127.0.0.1:8770 —— 只有本机能访问)
uv run --extra web python -m tcaptcha.web.app

然后浏览器打开 http://127.0.0.1:8770/。前台运行,Ctrl+C 停止。

启动成功时终端会打印:

  tcaptcha 观测台已启动 →  http://127.0.0.1:8770

  (Ctrl+C 停止;靶子带实测最小间隔,1ms 为 60s,后端会自动节流)

常用变体:

uv run --extra web python -m tcaptcha.web.app --port 8899        # 换端口
uv run --extra web python -m tcaptcha.web.app --host 0.0.0.0    # 局域网访问(⚠️ 界面无鉴权)
uv run --extra web python -m tcaptcha.web.app --no-warmup       # 跳过 AI 引擎预热,启动更快

不想用 uv 也可以直接调项目 venv(两条路径等价,uv run 用的就是 .venv):

.venv/bin/python -m tcaptcha.web.app

后台运行 + 停止(& 直接后台会随终端退出而挂掉,所以用 nohup 并记下 PID):

nohup .venv/bin/python -m tcaptcha.web.app > /tmp/tcaptcha-web.log 2>&1 &
echo $! > /tmp/tcaptcha-web.pid
# 停止
kill "$(cat /tmp/tcaptcha-web.pid)"

端口被占用:启动时会直接给中文提示并以退出码 2 结束(不会打一堆 errno):

  ✗ 端口 8770 已被占用,服务没起来。
    查占用进程:lsof -nP -iTCP:8770 -sTCP:LISTEN
    停掉它    :kill <上面的 PID>
    或换端口  :--port 8771

自检:curl -s http://127.0.0.1:8770/api/health 应返回 {"status":"online","targets":["1ms","mliev"],"cooldown":{...}}。

fastapi / uvicorn 装在 optional extra web 里,核心包(协议 / 遥测 / 求解器 / CLI) 完全不依赖它们 —— import tcaptcha 不会把 fastapi 拉进来(有回归断言)。

架构

flowchart LR
    FE["前端(零构建单页)<br/>index.html + app.js"]
    API["app.py(FastAPI)<br/>建任务 / SSE / 图片"]
    RUN["runner.py<br/>SolveJob + JobManager"]
    HOOK["_Hooks / _LogBridge<br/>(临时、成对安装)"]
    CORE["protocol.client.solve()<br/>solvers/*"]
    R["render.py<br/>标注图 + 点位量尺"]

    FE -->|"POST /api/jobs"| API
    API --> RUN
    RUN -->|"工作线程 + 全局串行 + 频控节流"| CORE
    RUN --> HOOK
    HOOK -.->|"题型/点位/段几何"| RUN
    CORE -.->|"日志"| HOOK
    RUN --> R
    RUN -->|"SSE 事件"| FE
文件 职责
events.py 事件模型;EVT_* 类型名前后端共用;EventLog 只追加 + 带锁(保证 seq 不重复)
runner.py SolveJob(一次任务)与 JobManager(注册表 + 串行 + 节流 + solver 复用)
app.py 路由;SSE 支持 Last-Event-ID 续传
static/ 原生 JS 单页,无打包器、无框架

接口

方法 路径 说明
GET / 单页界面
GET /api/targets 靶子列表(含实测 min_interval)+ 冷却倒计时
POST /api/jobs 建任务 {target, kind, max_retries, max_refreshes} → 快照
GET /api/jobs 最近任务列表
GET /api/jobs/{id} 任务快照
GET /api/jobs/{id}/events SSE 事件流(?since=N,兼容 Last-Event-ID)
GET /api/jobs/{id}/image/{name} 题目图 / 提示图 / 标注图(内存里,任务结束后仍可取)
POST /api/jobs/{id}/cancel 取消(当前这一发结束后生效,见"已知限制")
GET /api/health 健康检查

事件

信封统一为 {"seq", "ts", "type", "data"},前端按 data.type 分派:

type 关键字段
job.started target aid kind mode note
challenge index kind instruction bg_url prompt_url
solve points(按点击顺序)off_px centroids annotated_url note
telemetry source collect_len segments(4 段几何)traj_points
verify success errorCode ticket randstr elapsed_ms
log level logger message(client 原始日志,见下)
job.finished success errorCode ticket elapsed_ms error stats

errorCode 语义(界面里有配色与图例,别误读): 0 通过 / 50·9 答案被否(点位或目标错)/ 12 包级拒答(轨迹指纹或频控)。

三个关键设计(都是被约束逼出来的,不是偏好)

  1. 工作线程 + 全局串行 solve() 同步阻塞 7~60s,不能挂 HTTP 请求上;且 onnxruntime 的 session 不是线程安全的 —— 所以 JobManager._serial 保证同一时刻只跑一个求解。去掉这把锁就会踩 ONNX 并发。

  2. 日志桥接(零侵入) _LogBridge 把 tcaptcha.* / tcaptcha_client 的日志原样变成 log 事件。client 里已有 20+ 条 logger.info(抽题、PoW 命中、题型、求解结论、遥测长度、verify…),接上它就等于免费得到 一份"和 CLI 一模一样"的轨迹 —— 不需要改 protocol/client.py 一行。

  3. 频控节流 靶子自带实测的 min_interval(1ms=60s、mliev=3s,依据见 tcaptcha/targets.py)。 到点前任务停在 throttled 状态并在事件流里说明,否则界面上会出现"一片红"的假故障 —— 那些 12 全是频控噪声,不是求解失败。

已知限制 / 演进接口

  • 取消是"软取消":solve() 内部不可中断,所以取消只在节流等待中、或当前这一发结束后生效。
  • 任务在内存里(上限 50 个,图片每任务 24 张):重启即丢。要持久化(sqlite)属于 P2。
  • 单机自用定位:默认只监听 127.0.0.1,没做鉴权。要暴露出去必须先加认证。
  • B/C 形态的预留接口:POST /api/jobs 的 params 会原样进 SolveJob.params, 所以"人机协作"(回填人工坐标)与"批量/队列"(多靶子调度)都可以在不改事件模型的前提下接上 —— 前端多一个交互面板、后端多一个 params 分支即可。

回归

.venv/bin/python -m tcaptcha.selftest                 # 核心四套(不受 web 影响)
.venv/bin/python -c "import sys, tcaptcha; assert 'fastapi' not in sys.modules"   # 依赖隔离
# SSE 端到端(建任务 -> 事件流 -> 图片可访问)
.venv/bin/python - <<'PY'
import json, httpx
c = httpx.Client(base_url="http://127.0.0.1:8770", timeout=120)
j = c.post("/api/jobs", json={"target": "mliev", "kind": "any"}).json()
print("job", j["id"])
with c.stream("GET", f"/api/jobs/{j['id']}/events") as r:
    for line in r.iter_lines():
        if line.startswith("data: "):
            e = json.loads(line[6:])
            print(e["type"], (e["data"].get("errorCode") if e["type"] == "verify" else ""))
PY