# qwenwork2api **Repository Path**: mp0106/qwenwork2api ## Basic Information - **Project Name**: qwenwork2api - **Description**: No description available - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-26 - **Last Updated**: 2026-10-27 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # qwenwork2api 参考 [workbuddy2api-panel](https://github.com/linguo2625469/workbuddy2api-panel) 架构,把 **qwenwork.cn(阿里「千问办公」AI 办公平台)账号池** 化成 **OpenAI 兼容 API 网关** 的 Go 项目。 > **✅ 当前状态:真实协议已完整接入并实测打通。** > qwenwork.cn 的对话接口位于登录墙后(`/api` 路径,`robots.txt` 明确 Disallow)。本项目已通过逆向 qwenwork 网页端前端 bundle + 真实账号 JWT 实测,把完整的 **HTTP 会话管理 + WebSocket 双步骤对话协议** 实现为 `internal/upstream/qwenwork.go`(Provider),非流式 / 流式 / 模型列表均以真实 qwenwork 账号端到端验证通过。账号池、OpenAI 兼容层、面板、任务调度全部可运行。 --- ## 特性 - **账号凭证管理**(`internal/auth`):token/cookie 凭证按账号一个 JSON 文件落盘(`auths/` 目录),原子写回,目录热加载(增删文件 30s 内自动入池/出池,无需重启) - **多账号池**(`internal/pool`):轮换 / 加权轮换(weighted_random / round_robin)、会话粘性(`X-Sticky-Key`)、冷却(软冷却指数退避 / 余额硬冷却)、熔断(连续失败指数升级)、降权(连败权重减半)、在途请求上限、state.json 持久化 - **上游抽象层**(`internal/upstream`):`Provider` 接口(Chat / ChatStream / RefreshToken / CheckQuota / ListModels),中性域类型与 OpenAI / qwenwork 格式解耦;三种实现:**qwenwork 真实协议**(默认)、**mock 演示**、**配置驱动 HTTP 客户端** - **真实 qwenwork 协议**(`internal/upstream/qwenwork.go`):逆向实测打通的 HTTP + WebSocket 对话协议——创建/复用会话、sandbox 就绪等待、WS join → new_prompt → session/prompt 三步握手、session/update 流式增量、final_message + usage 收尾;每次请求独立新会话(时序最稳、上下文由 messages 传入) - **OpenAI 兼容 API**(`internal/server`):`POST /v1/chat/completions`(含流式 SSE)、`GET /v1/models`、`POST /v1/responses`(骨架)、`GET /healthz`;Bearer 鉴权;请求级换号重试 - **Web 管理面板**(`internal/panel`):go:embed 单文件前端,账号池总览、单号运维(启用/禁用/刷新/查额度/移除)、添加账号、配置页在线读写 - **自动任务调度**(`internal/scheduler`):Task 接口 + 注册式调度框架,内置额度探测演示任务 - **配置系统**:config.json 读取/校验/自动生成,端点、并发、账号池参数全部可配置 --- ## 快速开始 要求:Go 1.23+(仅一个外部依赖:`github.com/gorilla/websocket`)。 ```bash cd qwenwork2api go mod tidy go build ./... # 首次运行:config.json 不存在会自动生成(含随机 api_key) ./qwenwork2api -config config.json ``` 首次运行日志(qwenwork 真实模式): ``` loaded 2 account(s) from ./auths qwenwork2api listening on :7865 (api_key=true, upstream=qwenwork),管理面板 http://127.0.0.1:7865/panel/ quota_probe: uid=qwen1 remain=0 ``` ### qwenwork 真实链路(需要账号 JWT) 1. 在 `auths/` 目录放凭证文件(一账号一 JSON),token 为 qwenwork.cn 网页端签发的 JWT: ```json {"uid":"qwen1","nickname":"我的千问办公账号","token":"","expires_at":1794296049} ``` 2. 确保 config.json 的 `upstream.mode` 为 `"qwenwork"`(默认),调用 OpenAI 兼容接口: ```bash # 非流式(真实 qwenwork 回复) curl http://127.0.0.1:7865/v1/chat/completions \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"model":"flash","messages":[{"role":"user","content":"用一句话回答:中国的首都是哪里?"}]}' # → {"choices":[{"message":{"role":"assistant","content":"中国的首都是北京。"}}], ...} # 流式(SSE,真实增量) curl -N http://127.0.0.1:7865/v1/chat/completions \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"model":"flash","messages":[{"role":"user","content":"1+1等于几?"}],"stream":true}' # → data: {"choices":[{"delta":{"content":"1+1 等于 **2**。"}}]} … data: [DONE] # 模型列表(来自 /api/chat-modes 真实目录) curl http://127.0.0.1:7865/v1/models -H "Authorization: Bearer " ``` ### mock 链路演示(开箱即跑) 把 `upstream.mode` 改为 `"mock"`,凭证文件放任意 token 即可跑通全链路(返回固定回复),用于无真实账号时体验面板与 API 行为。 --- ## 项目结构 ``` qwenwork2api/ ├── cmd/server/ # 入口与装配 │ ├── main.go # 装配:config → auth → pool → upstream → scheduler → panel → server │ └── config.go # Config 结构、Load/WriteDefault/Normalize(校验与默认值) ├── internal/ │ ├── auth/ # 账号凭证:Credential、LoadDir、SaveAtomic、RemoveFile │ ├── pool/ # 账号池:entry 运行态、Pick 选号、Report 状态机、state.json 持久化 │ ├── upstream/ # 上游抽象层(最关键) │ │ ├── types.go # 中性域类型(ChatRequest/ChatResponse/StreamEvent/Model/Quota) │ │ ├── provider.go # Provider 接口 + ErrKind 错误分类 + Factory 工厂类型 │ │ ├── qwenwork.go # ★ 真实 qwenwork 协议实现(HTTP 会话 + WebSocket 对话,已实测) │ │ ├── qwenwork_real_test.go # 真实账号集成冒烟测试(QWENWORK_TOKEN 环境变量) │ │ ├── mock.go # 内置演示实现(无需真实上游) │ │ ├── httpclient.go # 配置驱动 HTTP 客户端(OpenAI 兼容假设) │ │ └── factory.go # NewFactory:按配置选择 qwenwork / mock / http │ ├── server/ # OpenAI 兼容 API 层:/v1/chat/completions(SSE)、/v1/models、/v1/responses │ ├── panel/ # Web 管理面板:go:embed 单文件前端 + /panel/api/* 运维接口 │ └── scheduler/ # 自动任务调度框架:Task 接口 + 注册式循环 + 演示任务 ├── config.example.json # 配置样例 ├── go.mod # module qwenwork2api,go 1.23,依赖 gorilla/websocket └── README.md ``` ### 分层依赖(单向,不循环) ``` cmd/server (装配) ├── internal/auth ← 被 pool / upstream / server / panel 依赖 ├── internal/pool ← 依赖 auth ├── internal/upstream ← 依赖 auth ├── internal/server ← 依赖 pool + upstream(把 pool 选号 + upstream 能力封装成 OpenAI 格式) ├── internal/panel ← 依赖 pool + upstream └── internal/scheduler← 依赖 pool + upstream ``` --- ## 配置说明(config.json) | 字段 | 默认 | 说明 | |---|---|---| | `listen` | `:7865` | HTTP 监听地址 | | `api_key` | 自动生成 | 网关 / 面板的 Bearer 鉴权密钥(空 = 不鉴权) | | `auth_dir` | `./auths` | 凭证目录(一账号一 JSON 文件) | | `state_file` | `./data/state.json` | 账号池运行时状态持久化(冷却/熔断/降权/粘性/权重) | | `upstream.mode` | `mock` | 上游实现:`qwenwork`(真实网页端协议)/ `mock`(演示)/ `http`(配置驱动客户端) | | `upstream.base_url` | 空 | qwenwork 模式根地址,默认 `https://qwenwork.cn` | | `upstream.chat_path` | 空 | (qwenwork 模式内部固定使用 `/api/chat-ws` 等,此字段仅供 http 模式) | | `upstream.models_path` | 空 | (同上) | | `upstream.refresh_path` | 空 | (同上) | | `upstream.quota_path` | 空 | (同上) | | `upstream.headers` | 空 | http 模式附加请求头;值内 `{token}` 占位符替换为账号 token | | `upstream.timeout_seconds` | `120` | qwenwork 模式 HTTP/WS 超时 | | `pool.strategy` | `weighted_random` | 选号策略:`weighted_random` / `round_robin` | | `pool.soft_cooldown` | `60s` | 软冷却基数(429/限流,指数退避) | | `pool.soft_cooldown_max` | `2h` | 软冷却退避封顶 | | `pool.hard_cooldown` | `30m` | 余额/配额不足的长冷却 | | `pool.breaker_threshold` | `3` | 连续服务失败达此数触发熔断 | | `pool.breaker_cooldown` | `30m` | 熔断基数(按熔断级别指数升级) | | `pool.breaker_cooldown_max` | `6h` | 熔断退避封顶 | | `pool.degrade_threshold` | `5` | 连续请求级失败达此数触发降权 | | `pool.degrade_cooldown` | `10m` | 降权持续时长(窗口内权重减半) | | `pool.max_in_flight` | `3` | 单账号在途请求上限(0 = 不限) | | `pool.sticky_ttl` | `30m` | 会话粘性绑定有效期 | 面板「配置」页可在线读写 config.json(保存后需重启生效的字段会在响应中列出)。 --- ## OpenAI 兼容 API | 端点 | 说明 | |---|---| | `POST /v1/chat/completions` | 非流式 + 流式(SSE);`X-Sticky-Key` 请求头可启用会话粘性 | | `GET /v1/models` | 聚合启用账号的模型列表(最多遍历 3 个账号取并集) | | `POST /v1/responses` | 骨架(501),后续可选实现 | | `GET /healthz` | 健康检查 | 错误均为 OpenAI 风格:`{"error":{"message":"...","type":"...","code":"..."}}`。 **请求级换号重试**:单请求最多换 3 个账号;请求本身错误(4xx 非 401/429)不罚号不轮转;上游错误按分类回报账号池(软冷却/硬冷却/禁用/熔断/降权)后换下一账号。 --- ## 账号池语义(参考 workbuddy2api-panel) - **选号**:候选 = 启用 ∧ 不在冷却/熔断窗口 ∧ 在途未满;加权随机按权重抽签(降权窗口内权重减半);`round_robin` 按 uid 序游标推进;**全冷却兜底**——无候选时选冷却截止最早的一个,禁用号永不参与。 - **会话粘性**:带 `X-Sticky-Key` 的请求绑定同一账号(TTL 内),绑定失效则重新选号并改绑。 - **冷却**:软冷却(限流)指数退避,硬冷却(余额不足)固定长冷却。 - **熔断**:连续服务失败达阈值熔断,按熔断级别指数升级退避。 - **降权**:连续请求级失败达阈值,权重减半(最低 1)持续一个降权窗口。 - 所有状态持久化到 `state.json`,重启恢复。 --- ## qwenwork 真实协议说明(已逆向并实测打通) > 无需再抓包。`internal/upstream/qwenwork.go` 已实现完整协议,以下为协议摘要,供维护与排查。 ### 认证与 HTTP 接口 - 认证:`Authorization: Bearer `(qwenwork.cn 网页端签发的 JWT,放账号凭证文件的 `token` 字段) - `GET /api/chat-sessions` 会话列表;`POST /api/chat-sessions`(body `{"name","mode"}`)创建会话,返回会话 UUID - `GET /api/m/v1/chat-sessions/{id}` 会话详情(信封 `{code,data,trace_id}`,含 sandbox_status) - `GET /api/chat-modes` 模型目录(映射到 `/v1/models`) ### WebSocket 对话协议(wss://qwenwork.cn/api/chat-ws?token=) 外层信封 `{"type":"control"|"data","payload":{jsonrpc,method,params[,id]}}`。一次生成分三步(实测必须严格按序,②③连发会被服务端丢弃): 1. **join**(control,id=1):`params={sessionId, lastSeqId:0}` → 服务端回 replay_start → session_status → sandbox_status → join_ack 2. **new_prompt**(control,id=2):`params={prompt:[{text,type:"text"}], _meta, sessionId}` → 服务端回 `result{promptId,turnId}` + new_prompt_ack(accepted:true) 3. **session/prompt**(data):同 params → 服务端流式返回 `session/update`(data 信封,seq 递增) - 增量结构:`payload.params.update.sessionUpdate`(`agent_message_chunk` = 正文 / `agent_thought_chunk` = 思考 / `tool_call`)/ `payload.params.update.content.text`(增量文本) 4. **收尾**:`type:"data"` 且 `payload.result` 含 `final_message`(完整回复)、`stopReason:"end_turn"`、`usage{inputTokens,outputTokens,totalTokens}` `_meta` 构造:`{seq_id, seq_ids:[seq], event_id:uuid, created_at:ms, traceparent:"00-<32hex>-<16hex>-01"}`;new_prompt 额外带 `turn_id:uuid`。 **设计取舍**:每次请求新建独立会话(不复用)——实测同一会话连续 prompt 会被服务端丢弃;OpenAI 网关每次请求本就独立(上下文由 messages 传入),新会话时序最稳且避免历史 replay 干扰。 ### 真实协议集成测试 ```bash QWENWORK_TOKEN=<你的 qwenwork JWT> go test ./internal/upstream -run TestQwenworkReal -v ``` 验证 Chat(非流式)、ChatStream(流式)、ListModels 三条真实链路(未设置 token 时自动跳过)。 --- ## 已知限制与路线图 - [x] 真实 qwenwork 对话协议(已逆向实测打通,非流式 + 流式 + 模型列表) - [x] 额度查询(`CheckQuota` 已接入 `/api/m/v1/user/wallets`,返回 daily/monthly/longterm 三类积分总和) - [x] Token 过期检测(`RefreshToken` 自动解析 JWT exp 字段,提前 5 分钟告警;qwenwork.cn 无 API 级 refresh 机制,过期后需手动在浏览器重新登录并更新凭证文件) - [ ] `/v1/responses` 完整实现 - [ ] 面板配置热生效(目前保存后需重启;参考 workbuddy2api-panel 的 livecfg 做运行时热改) - [ ] 模型级冷却(`Pick` 的 `model` 参数已预留,未参与过滤) - [ ] Redis 镜像持久化(目前 state.json 本地持久化) - [ ] 多轮上下文:目前每次请求独立会话,对话历史需客户端在 messages 中自带(qwenwork 会话内上下文能力待评估) - [ ] 每日签到:qwenwork.cn 无签到功能,daily_credits 每日 00:00 自动刷新发放,无需手动领取 ## 测试 ```bash go test -race ./... # 7 个包全部通过(含 mock 链路、轮换、SSE、面板、调度、qwenwork 单测) go vet ./... QWENWORK_TOKEN= go test ./internal/upstream -run TestQwenworkReal -v # 真实协议集成冒烟 ```