Skip to content

Commit 55afc15

Browse files
committed
feat: 添加HTTP代理支持,重组项目结构
- 支持通过 PROXY_URL 配置出站代理 - 支持自定义反代 URL (QWEN_CHAT_PROXY_URL, QWEN_CLI_PROXY_URL) - Docker 配置文件移至 docker/ 目录 - 更新文档和版本号
1 parent 9ae9a5a commit 55afc15

19 files changed

+752
-497
lines changed

.env.example

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,21 @@ LOG_DIR=./logs
7070
MAX_LOG_FILE_SIZE=10
7171

7272
# 保留的日志文件数量
73-
MAX_LOG_FILES=5
73+
MAX_LOG_FILES=5
74+
75+
# ========== 代理与反代配置 ==========
76+
77+
# 自定义反代URL配置
78+
# QWEN_CHAT_PROXY_URL: 替代 https://chat.qwen.ai 的反代地址
79+
# 示例: QWEN_CHAT_PROXY_URL=https://your-proxy.com
80+
QWEN_CHAT_PROXY_URL=
81+
82+
# QWEN_CLI_PROXY_URL: 替代 https://portal.qwen.ai 的反代地址
83+
# 示例: QWEN_CLI_PROXY_URL=https://your-cli-proxy.com
84+
QWEN_CLI_PROXY_URL=
85+
86+
# HTTP/HTTPS 代理配置
87+
# 支持 HTTP, HTTPS, SOCKS5 代理
88+
# 示例: PROXY_URL=http://127.0.0.1:7890
89+
# 示例: PROXY_URL=socks5://127.0.0.1:1080
90+
PROXY_URL=

.github/workflows/docker-build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ jobs:
9999
uses: docker/build-push-action@v5
100100
with:
101101
context: .
102-
file: ./Dockerfile
102+
file: ./docker/Dockerfile
103103
platforms: linux/amd64,linux/arm64
104104
push: ${{ github.event_name != 'pull_request' }}
105105
tags: ${{ steps.tags.outputs.tags }}

README.md

Lines changed: 68 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# 🚀 Qwen-Proxy
44

5-
[![Version](https://img.shields.io/badge/version-2025.12.11-blue.svg)](https://github.com/Rfym21/Qwen2API)
5+
[![Version](https://img.shields.io/badge/version-2025.12.14-blue.svg)](https://github.com/Rfym21/Qwen2API)
66
[![Node.js](https://img.shields.io/badge/Node.js-18+-green.svg)](https://nodejs.org/)
77
[![Docker](https://img.shields.io/badge/Docker-supported-blue.svg)](https://hub.docker.com/r/rfym21/qwen2api)
88

@@ -14,7 +14,7 @@
1414

1515
### 项目说明
1616

17-
Qwen-Proxy 是一个将 `https://chat.qwen.ai``Qwen Code / Qwen Cli`端接口和转换为 OpenAI 兼容 API 的代理服务。通过本项目,您只需要一个账户,即可以使用任何支持 OpenAI API 的客户端(如 ChatGPT-Next-Web、LobeChat 等)来调用 `https://chat.qwen.ai``Qwen Code / Qwen Cli`的各种模型。其中 `/cli` 端点下的模型由 `Qwen Code / Qwen Cli` 提供,支持256k上下文,原生 tools 参数支持
17+
Qwen-Proxy 是一个将 `https://chat.qwen.ai``Qwen Code / Qwen Cli` 转换为 OpenAI 兼容 API 的代理服务。通过本项目,您只需要一个账户,即可以使用任何支持 OpenAI API 的客户端(如 ChatGPT-Next-Web、LobeChat 等)来调用 `https://chat.qwen.ai``Qwen Code / Qwen Cli`的各种模型。其中 `/cli` 端点下的模型由 `Qwen Code / Qwen Cli` 提供,支持256k上下文,原生 tools 参数支持
1818

1919
**主要特性:**
2020
- 兼容 OpenAI API 格式,无缝对接各类客户端
@@ -25,6 +25,29 @@ Qwen-Proxy 是一个将 `https://chat.qwen.ai` 和 `Qwen Code / Qwen Cli`端接
2525
- 支持 CLI 端点,提供 256K 上下文和工具调用能力
2626
- 提供 Web 管理界面,方便配置和监控
2727

28+
### ⚠️ 高并发说明
29+
30+
> **重要提示**: `chat.qwen.ai` 对单 IP 有限速策略,目前已知该限制与 Cookie 无关,仅与 IP 相关。
31+
32+
**解决方案:**
33+
34+
如需高并发使用,建议配合代理池实现 IP 轮换:
35+
36+
| 方案 | 配置方式 | 说明 |
37+
|------|----------|------|
38+
| **方案一** | `PROXY_URL` + [ProxyFlow](https://github.com/Rfym21/ProxyFlow) | 直接配置代理地址,所有请求通过代理池轮换 IP |
39+
| **方案二** | `QWEN_CHAT_PROXY_URL` + [UrlProxy](https://github.com/Rfym21/UrlProxy) + [ProxyFlow](https://github.com/Rfym21/ProxyFlow) | 通过反代 + 代理池组合,实现更灵活的 IP 轮换 |
40+
41+
**配置示例:**
42+
43+
```bash
44+
# 方案一:直接使用代理池
45+
PROXY_URL=http://127.0.0.1:8282 # ProxyFlow 代理地址
46+
47+
# 方案二:反代 + 代理池组合
48+
QWEN_CHAT_PROXY_URL=http://127.0.0.1:8000/qwen # UrlProxy 反代地址(UrlProxy 配置 HTTP_PROXY 指向 ProxyFlow)
49+
```
50+
2851
### 环境要求
2952

3053
- Node.js 18+ (源码部署时需要)
@@ -54,6 +77,11 @@ SEARCH_INFO_MODE=table # 搜索信息展示模式 (table/text)
5477
OUTPUT_THINK=true # 是否输出思考过程 (true/false)
5578
SIMPLE_MODEL_MAP=false # 简化模型映射 (true/false)
5679

80+
# 🌐 代理与反代配置
81+
QWEN_CHAT_PROXY_URL= # 自定义 Chat API 反代URL (默认: https://chat.qwen.ai)
82+
QWEN_CLI_PROXY_URL= # 自定义 CLI API 反代URL (默认: https://portal.qwen.ai)
83+
PROXY_URL= # HTTP/HTTPS/SOCKS5 代理地址 (例如: http://127.0.0.1:7890)
84+
5785
# 🗄️ 数据存储
5886
DATA_SAVE_MODE=none # 数据保存模式 (none/file/redis)
5987
REDIS_URL= # Redis 连接地址 (可选,使用TLS时为rediss://)
@@ -74,6 +102,9 @@ CACHE_MODE=default # 图片缓存模式 (default/file)
74102
| `SEARCH_INFO_MODE` | 搜索结果展示格式 | `table``text` |
75103
| `OUTPUT_THINK` | 是否显示 AI 思考过程 | `true``false` |
76104
| `SIMPLE_MODEL_MAP` | 简化模型映射,只返回基础模型不包含变体 | `true``false` |
105+
| `QWEN_CHAT_PROXY_URL` | 自定义 Chat API 反代地址 | `https://your-proxy.com` |
106+
| `QWEN_CLI_PROXY_URL` | 自定义 CLI API 反代地址 | `https://your-cli-proxy.com` |
107+
| `PROXY_URL` | 出站请求代理地址,支持 HTTP/HTTPS/SOCKS5 | `http://127.0.0.1:7890` |
77108
| `DATA_SAVE_MODE` | 数据持久化方式 | `none`/`file`/`redis` |
78109
| `REDIS_URL` | Redis 数据库连接地址,使用TLS加密时需使用 `rediss://` 协议 | `redis://localhost:6379``rediss://xxx.upstash.io` |
79110
| `CACHE_MODE` | 图片缓存存储方式 | `default`/`file` |
@@ -164,7 +195,7 @@ docker run -d \
164195

165196
```bash
166197
# 下载配置文件
167-
curl -o docker-compose.yml https://raw.githubusercontent.com/Rfym21/Qwen2API/refs/heads/main/docker-compose.yml
198+
curl -o docker-compose.yml https://raw.githubusercontent.com/Rfym21/Qwen2API/refs/heads/main/docker/docker-compose.yml
168199

169200
# 启动服务
170201
docker compose pull && docker compose up -d
@@ -221,55 +252,63 @@ npm run dev
221252

222253
```
223254
Qwen2API/
224-
├── Dockerfile
225255
├── README.md
226-
├── docker-compose.yml
227-
├── docker-compose-redis.yml
228256
├── ecosystem.config.js # PM2配置文件
229257
├── package.json
230258
259+
├── docker/ # Docker配置目录
260+
│ ├── Dockerfile
261+
│ ├── docker-compose.yml
262+
│ └── docker-compose-redis.yml
263+
231264
├── caches/ # 缓存文件目录
232265
├── data/ # 数据文件目录
233266
│ ├── data.json
234267
│ └── data_template.json
268+
├── scripts/ # 脚本目录
269+
│ └── fingerprint-injector.js # 浏览器指纹注入脚本
235270
236271
├── src/ # 后端源代码目录
237272
│ ├── server.js # 主服务器文件
238273
│ ├── start.js # 智能启动脚本 (自动判断单进程/多进程)
239274
│ ├── config/
240275
│ │ └── index.js # 配置文件
241276
│ ├── controllers/ # 控制器目录
242-
│ │ ├── chat.js
277+
│ │ ├── chat.js # 聊天控制器
278+
│ │ ├── chat.image.video.js # 图片/视频生成控制器
243279
│ │ ├── cli.chat.js # CLI聊天控制器
244-
│ │ └── models.js
280+
│ │ └── models.js # 模型控制器
245281
│ ├── middlewares/ # 中间件目录
246-
│ │ ├── authorization.js
247-
│ │ └── chat-middleware.js
282+
│ │ ├── authorization.js # 授权中间件
283+
│ │ └── chat-middleware.js # 聊天中间件
248284
│ ├── models/ # 模型目录
249-
│ │ └── models-map.js
285+
│ │ └── models-map.js # 模型映射配置
250286
│ ├── routes/ # 路由目录
251-
│ │ ├── accounts.js
252-
│ │ ├── chat.js
287+
│ │ ├── accounts.js # 账户路由
288+
│ │ ├── chat.js # 聊天路由
253289
│ │ ├── cli.chat.js # CLI聊天路由
254-
│ │ ├── models.js
255-
│ │ ├── settings.js
256-
│ │ └── verify.js
290+
│ │ ├── models.js # 模型路由
291+
│ │ ├── settings.js # 设置路由
292+
│ │ └── verify.js # 验证路由
257293
│ └── utils/ # 工具函数目录
258-
│ ├── account-rotator.js
259-
│ ├── account.js
260-
│ ├── chat-helpers.js
294+
│ ├── account-rotator.js # 账户轮询器
295+
│ ├── account.js # 账户管理
296+
│ ├── chat-helpers.js # 聊天辅助函数
261297
│ ├── cli.manager.js # CLI管理器
262-
│ ├── data-persistence.js
263-
│ ├── img-caches.js
298+
│ ├── cookie-generator.js # Cookie生成器
299+
│ ├── data-persistence.js # 数据持久化
300+
│ ├── fingerprint.js # 浏览器指纹生成
301+
│ ├── img-caches.js # 图片缓存
264302
│ ├── logger.js # 日志工具
265-
│ ├── model-utils.js
266303
│ ├── precise-tokenizer.js # 精确分词器
267-
│ ├── redis.js
268-
│ ├── request.js
269-
│ ├── setting.js
270-
│ ├── token-manager.js
271-
│ ├── tools.js
272-
│ └── upload.js
304+
│ ├── proxy-helper.js # 代理辅助函数
305+
│ ├── redis.js # Redis连接
306+
│ ├── request.js # HTTP请求封装
307+
│ ├── setting.js # 设置管理
308+
│ ├── ssxmod-manager.js # ssxmod参数管理
309+
│ ├── token-manager.js # Token管理器
310+
│ ├── tools.js # 工具调用处理
311+
│ └── upload.js # 文件上传
273312
274313
└── public/ # 前端项目目录
275314
├── dist/ # 编译后的前端文件
@@ -408,7 +447,7 @@ Authorization: Bearer sk-your-api-key
408447

409448
使用 `-image` 模型启用文本到图像生成功能。
410449
使用 `-image-edit` 模型启用图像修改功能。
411-
当使用``-image` 时你可以通过在请求体中添加 `size` 参数或在消息内容中包含特定关键词 `1:1`, `4:3`, `3:4`, `16:9`, `9:16` 来控制图片尺寸。
450+
当使用 `-image` 模型时你可以通过在请求体中添加 `size` 参数或在消息内容中包含特定关键词 `1:1`, `4:3`, `3:4`, `16:9`, `9:16` 来控制图片尺寸。
412451

413452
```http
414453
POST /v1/chat/completions
File renamed without changes.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "qwen2api",
3-
"version": "2025.12.11.17.00",
3+
"version": "2025.12.14.20.00",
44
"main": "src/server.js",
55
"scripts": {
66
"start": "node src/start.js",
@@ -26,6 +26,7 @@
2626
"dotenv": "^16.4.7",
2727
"express": "^4.21.2",
2828
"form-data": "^4.0.2",
29+
"https-proxy-agent": "^7.0.6",
2930
"ioredis": "^5.6.1",
3031
"jwt-decode": "^4.0.0",
3132
"mime-types": "^3.0.1",

src/config/index.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,12 @@ const config = {
3737
enableFileLog: process.env.ENABLE_FILE_LOG === 'true',
3838
logDir: process.env.LOG_DIR || "./logs",
3939
maxLogFileSize: parseInt(process.env.MAX_LOG_FILE_SIZE) || 10,
40-
maxLogFiles: parseInt(process.env.MAX_LOG_FILES) || 5
40+
maxLogFiles: parseInt(process.env.MAX_LOG_FILES) || 5,
41+
// 自定义反代URL配置
42+
qwenChatProxyUrl: process.env.QWEN_CHAT_PROXY_URL || "https://chat.qwen.ai",
43+
qwenCliProxyUrl: process.env.QWEN_CLI_PROXY_URL || "https://portal.qwen.ai",
44+
// 代理配置
45+
proxyUrl: process.env.PROXY_URL || null
4146
}
4247

4348
module.exports = config

src/controllers/chat.image.video.js

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const accountManager = require('../utils/account.js')
55
const { sleep } = require('../utils/tools.js')
66
const { generateChatID } = require('../utils/request.js')
77
const { getSsxmodItna, getSsxmodItna2 } = require('../utils/ssxmod-manager')
8+
const { getProxyAgent, getChatBaseUrl, applyProxyToAxiosConfig } = require('../utils/proxy-helper')
89

910
/**
1011
* 主要的聊天完成处理函数
@@ -36,7 +37,7 @@ const handleImageVideoCompletion = async (req, res) => {
3637
]
3738
}
3839

39-
const chat_id = await generateChatID(token,model)
40+
const chat_id = await generateChatID(token, model)
4041

4142
if (!chat_id) {
4243
// 如果生成chat_id失败,则返回错误
@@ -140,12 +141,16 @@ const handleImageVideoCompletion = async (req, res) => {
140141
}
141142
}
142143

144+
const chatBaseUrl = getChatBaseUrl()
145+
const proxyAgent = getProxyAgent()
146+
143147
logger.info('发送图片视频请求', 'CHAT')
144148
logger.info(`选择图片: ${select_image_list[select_image_list.length - 1] || "未选择图片,切换生成图/视频模式"}`, 'CHAT')
145149
logger.info(`使用提示: ${reqBody.messages[0].content}`, 'CHAT')
146150
// console.log(JSON.stringify(reqBody))
147151
const newChatType = reqBody.messages[0].chat_type
148-
const response_data = await axios.post(`https://chat.qwen.ai/api/v2/chat/completions?chat_id=${chat_id}`, reqBody, {
152+
153+
const requestConfig = {
149154
headers: {
150155
'Authorization': `Bearer ${token}`,
151156
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0",
@@ -158,17 +163,25 @@ const handleImageVideoCompletion = async (req, res) => {
158163
"source": "web",
159164
"Version": "0.1.13",
160165
"bx-v": "2.5.31",
161-
"Origin": "https://chat.qwen.ai",
166+
"Origin": chatBaseUrl,
162167
"Sec-Fetch-Site": "same-origin",
163168
"Sec-Fetch-Mode": "cors",
164169
"Sec-Fetch-Dest": "empty",
165-
"Referer": "https://chat.qwen.ai/c/guest",
170+
"Referer": `${chatBaseUrl}/c/guest`,
166171
"Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
167172
"Cookie": `ssxmod_itna=${getSsxmodItna()};ssxmod_itna2=${getSsxmodItna2()}`,
168173
},
169174
responseType: newChatType == 't2i' ? 'stream' : 'json',
170175
timeout: 1000 * 60 * 5
171-
})
176+
}
177+
178+
// 添加代理配置
179+
if (proxyAgent) {
180+
requestConfig.httpsAgent = proxyAgent
181+
requestConfig.proxy = false
182+
}
183+
184+
const response_data = await axios.post(`${chatBaseUrl}/api/v2/chat/completions?chat_id=${chat_id}`, reqBody, requestConfig)
172185

173186
try {
174187
let contentUrl = null
@@ -307,14 +320,25 @@ ${content}
307320

308321
const getVideoTaskStatus = async (videoTaskID, token) => {
309322
try {
310-
const response_data = await axios.get(`https://chat.qwen.ai/api/v1/tasks/status/${videoTaskID}`, {
323+
const chatBaseUrl = getChatBaseUrl()
324+
const proxyAgent = getProxyAgent()
325+
326+
const requestConfig = {
311327
headers: {
312328
"Authorization": `Bearer ${token}`,
313329
'Content-Type': 'application/json',
314330
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
315331
...(getSsxmodItna() && { 'Cookie': `ssxmod_itna=${getSsxmodItna()};ssxmod_itna2=${getSsxmodItna2()}` })
316332
}
317-
})
333+
}
334+
335+
// 添加代理配置
336+
if (proxyAgent) {
337+
requestConfig.httpsAgent = proxyAgent
338+
requestConfig.proxy = false
339+
}
340+
341+
const response_data = await axios.get(`${chatBaseUrl}/api/v1/tasks/status/${videoTaskID}`, requestConfig)
318342

319343
if (response_data.data?.task_status == "success") {
320344
logger.info('获取视频任务状态成功', 'CHAT', response_data.data?.content)

0 commit comments

Comments
 (0)