Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.git/
.venv/
.env
.env.*
!.env.example
!.env.docker.example
.claude/
.codex/
.codex_tmp/

__pycache__/
*.py[cod]
*.pyd
.pytest_cache/
.mypy_cache/
.ruff_cache/

data/
logs/

node_modules/
web/node_modules/
web/dist/
web/.vite/

Dockerfile
docker-compose*.yml
20 changes: 20 additions & 0 deletions .env.docker.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
APP_PORT=8787
BIGMODEL_API_BASE=https://www.bigmodel.cn/api
BIGMODEL_ORIGIN=https://www.bigmodel.cn
BIGMODEL_REFERER=https://www.bigmodel.cn/glm-coding
BROWSER_IMPERSONATE=chrome124
BOOTSTRAP_FINGERPRINT_MAX_RETRIES=99
REQUEST_TIMEOUT_SECONDS=20
DEFAULT_LANGUAGE=zh-CN
TENCENT_CAPTCHA_DOMAIN=https://turing.captcha.qcloud.com
TENCENT_CAPTCHA_AID=196026326
TENCENT_CAPTCHA_ENTRY_URL=https://www.bigmodel.cn/glm-coding
TENCENT_CAPTCHA_MAX_RETRIES=3
TENCENT_CAPTCHA_MIN_CONFIDENCE=0.55
TENCENT_OCR_ENABLED=1
TENCENT_OCR_INCLUDE_DEBUG=0
TENCENT_OCR_WORKERS=4
TENCENT_OCR_TIMEOUT_SECONDS=6
TENCENT_OCR_IDLE_SHRINK_SECONDS=60
RUNTIME_LOG_LEVEL=INFO
RUNTIME_LOG_RETENTION_DAYS=7
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ DATA_DIR=data
BIGMODEL_API_BASE=https://www.bigmodel.cn/api
BIGMODEL_ORIGIN=https://www.bigmodel.cn
BIGMODEL_REFERER=https://www.bigmodel.cn/glm-coding
BROWSER_IMPERSONATE=chrome124
BROWSER_IMPERSONATE=chrome146
BOOTSTRAP_FINGERPRINT_MAX_RETRIES=99
REQUEST_TIMEOUT_SECONDS=20
DEFAULT_LANGUAGE=zh-CN
TENCENT_CAPTCHA_DOMAIN=https://turing.captcha.qcloud.com
Expand All @@ -17,5 +18,6 @@ TENCENT_OCR_ENABLED=1
TENCENT_OCR_INCLUDE_DEBUG=0
TENCENT_OCR_WORKERS=4
TENCENT_OCR_TIMEOUT_SECONDS=6
TENCENT_OCR_IDLE_SHRINK_SECONDS=60
RUNTIME_LOG_LEVEL=INFO
RUNTIME_LOG_RETENTION_DAYS=7
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ data/sessions/*.json
data/tdc_cache/

node_modules/

web/dist/
44 changes: 44 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
FROM node:20-bookworm-slim AS web-builder

WORKDIR /build/web

COPY web/package.json web/package-lock.json ./
RUN npm ci

COPY web/ ./
RUN npm run build


FROM python:3.12-slim-bookworm AS runtime

ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \
DATA_DIR=/app/data \
XDG_CACHE_HOME=/app/data/cache \
TENCENT_CAPTCHA_NODE=node

WORKDIR /app

RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates \
libglib2.0-0 \
libgl1 \
libgomp1 \
nodejs \
npm \
&& rm -rf /var/lib/apt/lists/*

COPY requirements.txt ./
RUN python -m pip install --upgrade pip \
&& python -m pip install -r requirements.txt

COPY app/ ./app/
COPY --from=web-builder /build/web/dist ./web/dist

RUN mkdir -p /app/data

EXPOSE 8787

CMD ["sh", "-c", "uvicorn app.main:app --host 0.0.0.0 --port ${APP_PORT:-8787}"]
140 changes: 121 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# GLM Desk
# GLM Desk

`GLM Desk` 是一个本地运行的 `GLM Coding` 支付运营后台,用来管理多账号导入、套餐同步、自动验证码链路、预览下单、签单出二维码,以及定时启动任务。

Expand Down Expand Up @@ -62,6 +62,87 @@
- `APP_HOST=127.0.0.1`
- `APP_PORT=8787`

## Docker / 1Panel 部署

项目已提供 Docker 单容器部署配置,适合在 `1Panel` 的 Compose 编排里直接使用。

### 1Panel 推荐方式

1. 在服务器上准备项目目录,例如 `/opt/glm-desk`
2. 上传或拉取本项目代码到该目录
3. 在 `1Panel` 中进入 `容器` / `Compose`,选择项目目录里的 `docker-compose.yml`
4. 首次启动选择构建镜像
5. 启动后访问 `http://服务器IP:8787`

默认 Compose 会做这些事:

- 构建 Vue 前端并复制到镜像内的 `web/dist`
- 启动 FastAPI 服务并监听 `0.0.0.0:8787`
- 安装运行 TDC VM 所需的 `node`
- 安装 OCR 所需的 Linux 系统库
- 挂载 `./data:/app/data` 保存账号、会话、日志、二维码任务和 TDC 缓存

### Docker 环境变量

Docker 部署可以参考 `.env.docker.example`。

如果使用 `1Panel` 的 Compose 页面,常用配置直接在环境变量区域填写即可:

```env
APP_PORT=8787
TENCENT_OCR_WORKERS=4
TENCENT_OCR_IDLE_SHRINK_SECONDS=60
BOOTSTRAP_FINGERPRINT_MAX_RETRIES=99
RUNTIME_LOG_RETENTION_DAYS=7
```

说明:

- Docker 内部固定使用 `DATA_DIR=/app/data`
- Docker 内部固定使用 `APP_HOST=0.0.0.0`
- Docker 内部固定使用 `TENCENT_CAPTCHA_NODE=node`
- `TENCENT_OCR_WORKERS` 是 OCR 最大并发上限,4 核 24G 机器可以设为 `4`
- `TENCENT_OCR_IDLE_SHRINK_SECONDS` 控制 OCR 空闲多久后回收多余 worker,默认 `60` 秒,最少保留 `1` 个热 worker

### 命令行启动

如果不用 1Panel,也可以直接运行:

```bash
docker compose up -d --build
```

查看日志:

```bash
docker compose logs -f glm-desk
```

停止服务:

```bash
docker compose down
```

### Docker 数据持久化

Compose 默认挂载:

```text
./data:/app/data
```

这个目录必须保留,里面包含:

- `accounts.json`
- `tasks.json`
- `sessions/`
- `logs/runtime/`
- `tdc_cache/`
- `cache/`

别把 `data` 当临时目录删了,不然账号和运行记录就没了。

## 启动进程与 OCR Worker 说明

本项目本地默认通过 [start.bat](E:/开源项目/glmDesk/start.bat) 启动:
Expand All @@ -79,18 +160,17 @@
- 项目没有配置 `uvicorn --workers`,所以 Web 服务本身不是多 worker 部署
- OCR 并发是独立进程池,不是 `uvicorn` worker
- 多账号调度是单应用进程里多线程拉任务,真正重 CPU 的 OCR 再交给 OCR 进程池
- 启动预热阶段现在只主动预热 `1` 个 OCR worker,避免首次下载 RapidOCR 模型时多个进程同时抢同一个 `.onnx` 文件
- 真正运行时 OCR 并发上限仍然由 `TENCENT_OCR_WORKERS` 控制
- 启动预热阶段会先预热基础 OCR worker,避免首次下载 RapidOCR 模型时多个进程同时抢同一个 `.onnx` 文件`n- 支付链路启动前会按前端 `Preview 并发` 配置和当前活跃任务需求继续预热`n- 真正运行时 OCR 并发上限由 `TENCENT_OCR_WORKERS` 控制`n- 当多个账号或同账号并发子任务同时进入验证码 OCR 阶段时,进程池会按活跃需求扩到上限`n- 当 OCR 空闲超过 `TENCENT_OCR_IDLE_SHRINK_SECONDS` 后,会自动回收多余 OCR 进程并重新保留 `1` 个热 worker

`TENCENT_OCR_WORKERS` 默认值不是写死 `4`,而是按 CPU 自动算
`TENCENT_OCR_WORKERS` 是系统 OCR worker 最大数量,不配置时默认 `4`:

- `max(1, min(4, os.cpu_count() or 1))`
- `TENCENT_OCR_WORKERS=4`

举例:

- `1` 核机器默认 `1`
- `2` 核机器默认 `2`
- `8` 核机器默认 `4`
- `Preview 并发=4` 且 `TENCENT_OCR_WORKERS=4`,最多预热 4 个 OCR worker
- 两个账号同时运行,账号 A `Preview 并发=3`,账号 B `Preview 并发=1`,活跃 OCR 需求为 `4`,最多预热 4 个 OCR worker
- 如果活跃 OCR 需求为 `8`,但 `TENCENT_OCR_WORKERS=4`,仍然最多只跑 4 个 OCR worker,其余 OCR 请求排队

如果你想强制单路 OCR,直接把:

Expand Down Expand Up @@ -121,7 +201,7 @@

- 账号备注
- 购买模式:`新购 / 升级`
- 当前账号指纹伪装:`chrome / edge / firefox`
- 当前账号指纹 profile:例如 `chrome146 / chrome145 / edge146 / firefox149`
- 套餐下拉选择器
- 定时启动配置
- 账号状态
Expand Down Expand Up @@ -158,7 +238,13 @@

### 6. 同步并换指纹

点击 `同步并换指纹` 后,会先给该账号分配一个新的账号级伪装指纹,再重新同步账号上下文和套餐。
点击 `同步并换指纹` 后,会按“换指纹 -> 同步账号上下文 -> 同步套餐”的顺序执行。

如果同步失败,后端会继续换下一个指纹并重试,直到同步成功或达到最大重试次数。

默认最大重试次数:

- `BOOTSTRAP_FINGERPRINT_MAX_RETRIES=99`

这适合在上游风控、链路异常、套餐状态异常时主动切换一套新的网络指纹继续尝试。

Expand Down Expand Up @@ -258,14 +344,23 @@

`GLM Desk` 当前不是完整浏览器驱动,而是后端 HTTP 请求链路,所以做的是账号级稳定伪装:

- 每个账号首次导入时随机分配一个 `browser_impersonate`
- 候选值为:`chrome / edge / firefox`
- 后续这个账号的 BigModel、腾讯验证码、TDC 请求都复用同一个伪装
- 每个账号首次导入时随机分配一个具体版本的 `browser_impersonate`
- 当前随机候选值为:`chrome146 / chrome145 / edge146 / firefox149`
- 随机分配带权重,默认更偏向 Chrome,少量分配 Edge / Firefox,贴近真实桌面浏览器分布
- 每个 profile 同时绑定 `curl-cffi` TLS/HTTP 指纹和匹配的 Windows 桌面 `User-Agent`
- 后续这个账号的 BigModel、腾讯验证码、TDC 请求都复用同一个 profile

这样做的目的:

- 保持同一账号整条链路的指纹一致
- 避免“每次请求随机换脸”导致风控更容易命中
- 避免新版 UA 搭配旧版 TLS 指纹这种很假的组合

浏览器版本说明:

- 2026-04-26 查询桌面浏览器版本占有率后,Chrome 侧优先使用高占比的 `145/146`
- Edge 侧使用 `edge146` 对应的 Windows UA;由于 `curl-cffi 0.15.0` 缺少新版 Edge TLS profile,transport 暂时复用同代 Chrome 146 指纹
- Firefox 侧使用 `firefox149` 对应的 Windows UA;由于 `curl-cffi 0.15.0` 支持的最新 Firefox transport 为 `firefox147`,底层 TLS 暂时落到 `firefox147`

## 本地数据目录

Expand Down Expand Up @@ -379,7 +474,8 @@ DATA_DIR=data
BIGMODEL_API_BASE=https://www.bigmodel.cn/api
BIGMODEL_ORIGIN=https://www.bigmodel.cn
BIGMODEL_REFERER=https://www.bigmodel.cn/glm-coding
BROWSER_IMPERSONATE=chrome124
BROWSER_IMPERSONATE=chrome146
BOOTSTRAP_FINGERPRINT_MAX_RETRIES=99
REQUEST_TIMEOUT_SECONDS=20
DEFAULT_LANGUAGE=zh-CN
TENCENT_CAPTCHA_DOMAIN=https://turing.captcha.qcloud.com
Expand All @@ -392,6 +488,7 @@ TENCENT_OCR_ENABLED=1
TENCENT_OCR_INCLUDE_DEBUG=0
TENCENT_OCR_WORKERS=4
TENCENT_OCR_TIMEOUT_SECONDS=6
TENCENT_OCR_IDLE_SHRINK_SECONDS=60
RUNTIME_LOG_LEVEL=INFO
RUNTIME_LOG_RETENTION_DAYS=7
```
Expand All @@ -411,7 +508,8 @@ RUNTIME_LOG_RETENTION_DAYS=7
| `BIGMODEL_API_BASE` | `https://www.bigmodel.cn/api` | BigModel API 根地址 |
| `BIGMODEL_ORIGIN` | `https://www.bigmodel.cn` | BigModel 请求头 `Origin` 默认值 |
| `BIGMODEL_REFERER` | `https://www.bigmodel.cn/glm-coding` | BigModel 请求头 `Referer` 默认值 |
| `BROWSER_IMPERSONATE` | `chrome124` | `curl-cffi` 的全局兜底 `impersonate` 值;账号实际请求优先用账号自己的随机 `browser_impersonate` |
| `BROWSER_IMPERSONATE` | `chrome146` | 全局兜底浏览器指纹 profile;账号实际请求优先用账号自己的随机 `browser_impersonate` |
| `BOOTSTRAP_FINGERPRINT_MAX_RETRIES` | `99` | 点击“同步并换指纹”时的最大尝试次数;每轮先换一个账号级指纹,再完整同步上下文和套餐,失败才进入下一轮 |
| `REQUEST_TIMEOUT_SECONDS` | `20` | 上游 HTTP 请求超时时间,单位秒 |
| `DEFAULT_LANGUAGE` | `zh-CN` | 默认请求语言,会写入 `Accept-Language` 和 `Set-Language` |
| `TENCENT_CAPTCHA_DOMAIN` | `https://turing.captcha.qcloud.com` | 腾讯验证码域名 |
Expand All @@ -422,17 +520,20 @@ RUNTIME_LOG_RETENTION_DAYS=7
| `TENCENT_CAPTCHA_NODE` | `node` | 跑腾讯 TDC VM 时使用的 Node.js 命令 |
| `TENCENT_OCR_ENABLED` | `1` | 是否启用本地 OCR;关闭后自动识别不可用 |
| `TENCENT_OCR_INCLUDE_DEBUG` | `0` | 是否在 OCR 结果中附带调试图像 base64,开启后日志和响应会更重 |
| `TENCENT_OCR_WORKERS` | 自动计算,最大不超过 `4` | OCR 进程池并发上限;建议普通机器配 `1-2`,高配机器可配 `3-4` |
| `TENCENT_OCR_WORKERS` | `4` | 系统 OCR worker 最大数量;支付链路启动前会按活跃 `Preview 并发` 需求预热,多账号和同账号并发子任务都会计入,但不会超过这个上限 |
| `TENCENT_OCR_TIMEOUT_SECONDS` | `6` | 单次 OCR worker 超时秒数 |
| `TENCENT_OCR_IDLE_SHRINK_SECONDS` | `60` | OCR 空闲多少秒后回收多余 worker,最少保留 `1` 个热 worker |
| `RUNTIME_LOG_LEVEL` | `INFO` | 正式运行日志级别 |
| `RUNTIME_LOG_RETENTION_DAYS` | `7` | `app.log` 按天轮转保留天数 |

补充说明:

- `BROWSER_IMPERSONATE` 现在主要是全局兜底值和 transport 展示值
- `BROWSER_IMPERSONATE` 现在主要是全局兜底 profile 和 transport 展示值
- 真正运行时优先用账号自己的 `browser_impersonate`
- 账号级 `browser_impersonate` 在首次导入账号时随机分配为 `chrome / edge / firefox`
- 如果你把 `TENCENT_OCR_WORKERS` 配得太高,OCR 并发会更猛,但内存占用也会跟着往上窜,别一上来就梭哈
- 账号级 `browser_impersonate` 在首次导入账号时随机分配为 `chrome146 / chrome145 / edge146 / firefox149`
- 历史账号里的 `chrome / edge / firefox / chrome124 / chrome136 / firefox137 / firefox147` 会自动映射到当前支持的具体 profile
- `BOOTSTRAP_FINGERPRINT_MAX_RETRIES` 小于 `1` 时会自动按 `1` 处理,避免配置错误导致完全不尝试
- 如果你把 `TENCENT_OCR_WORKERS` 配得太高,OCR 并发峰值会更猛,但空闲后会按 `TENCENT_OCR_IDLE_SHRINK_SECONDS` 回收到 `1` 个热 worker

## 已知说明

Expand All @@ -445,3 +546,4 @@ RUNTIME_LOG_RETENTION_DAYS=7
- `glm-coding-new-purchase-field-map.md`
- `glm-coding-new-purchase-detailed-spec.md`
- `glm-coding-tencent-captcha-verify.md`

Loading