Skip to content
This repository was archived by the owner on Feb 18, 2026. It is now read-only.

Commit 236db63

Browse files
committed
跟随上游更新,增加自动创建镜像
1 parent 6ac73cb commit 236db63

7 files changed

Lines changed: 252 additions & 12 deletions

File tree

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
name: Docker Publish
2+
3+
on:
4+
# Tag push:仅预热缓存,不推镜像
5+
push:
6+
tags: ['v*.*.*']
7+
# Release:发布时才推 latest + vX.Y.Z
8+
release:
9+
types: [published]
10+
# 手动触发:自动发现当前最新 vX.Y.Z,并与 latest 一起发布(同一构建)
11+
workflow_dispatch:
12+
13+
env:
14+
REGISTRY: ghcr.io
15+
IMAGE_NAME: ${{ github.repository }} # 将在步骤中强制转小写
16+
17+
jobs:
18+
# 1) Tag push:构建但不推送(预热缓存)
19+
build-cache-on-tag:
20+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
21+
runs-on: ubuntu-latest
22+
permissions:
23+
contents: read
24+
packages: write
25+
steps:
26+
- uses: actions/checkout@v4
27+
28+
- name: Force IMAGE_NAME lowercase (GHCR requires lowercase)
29+
run: echo "IMAGE_NAME=${GITHUB_REPOSITORY,,}" >> "$GITHUB_ENV"
30+
31+
- uses: docker/setup-qemu-action@v3
32+
33+
- name: Set up Docker Buildx (pin BuildKit version)
34+
uses: docker/setup-buildx-action@v3
35+
with:
36+
driver-opts: |
37+
image=moby/buildkit:v0.25.1
38+
39+
- name: Log in to GitHub Container Registry
40+
uses: docker/login-action@v3
41+
with:
42+
registry: ${{ env.REGISTRY }}
43+
username: ${{ github.actor }}
44+
password: ${{ secrets.GITHUB_TOKEN }}
45+
46+
- name: Build (no push, warm cache)
47+
uses: docker/build-push-action@v5
48+
with:
49+
context: .
50+
platforms: linux/amd64,linux/arm64
51+
push: false
52+
cache-from: type=gha
53+
cache-to: type=gha,mode=max
54+
55+
# 2) Release published:推送 latest + vX.Y.Z(同一构建 -> 同 digest)
56+
build-and-push-on-release:
57+
if: github.event_name == 'release' && github.event.action == 'published'
58+
runs-on: ubuntu-latest
59+
permissions:
60+
contents: read
61+
packages: write
62+
steps:
63+
- uses: actions/checkout@v4
64+
65+
- name: Force IMAGE_NAME lowercase (GHCR requires lowercase)
66+
run: echo "IMAGE_NAME=${GITHUB_REPOSITORY,,}" >> "$GITHUB_ENV"
67+
68+
- uses: docker/setup-qemu-action@v3
69+
70+
- name: Set up Docker Buildx (pin BuildKit version)
71+
uses: docker/setup-buildx-action@v3
72+
with:
73+
driver-opts: |
74+
image=moby/buildkit:v0.25.1
75+
76+
- name: Log in to GitHub Container Registry
77+
uses: docker/login-action@v3
78+
with:
79+
registry: ${{ env.REGISTRY }}
80+
username: ${{ github.actor }}
81+
password: ${{ secrets.GITHUB_TOKEN }}
82+
83+
- name: Docker metadata (latest only on Release)
84+
id: meta
85+
uses: docker/metadata-action@v5
86+
with:
87+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
88+
tags: |
89+
type=raw,value=latest,enable=${{ github.event_name == 'release' && github.event.action == 'published' }}
90+
type=ref,event=tag
91+
92+
- name: Compute VERSION
93+
id: version
94+
run: |
95+
V="${{ steps.meta.outputs.version }}"
96+
if [ -z "$V" ]; then V="${{ github.event.release.tag_name }}"; fi
97+
echo "VALUE=$V" >> "$GITHUB_OUTPUT"
98+
99+
- name: Build and push (Release)
100+
uses: docker/build-push-action@v5
101+
with:
102+
context: .
103+
platforms: linux/amd64,linux/arm64
104+
push: true
105+
tags: ${{ steps.meta.outputs.tags }}
106+
labels: ${{ steps.meta.outputs.labels }}
107+
cache-from: type=gha
108+
cache-to: type=gha,mode=max
109+
build-args: |
110+
VCS_REF=${{ github.sha }}
111+
VERSION=${{ steps.version.outputs.VALUE }}
112+
113+
# 3) 手动触发:自动发现“当前最新的 vX.Y.Z”,一次构建推 latest + vX.Y.Z(同 digest)
114+
manual-publish-latest:
115+
if: github.event_name == 'workflow_dispatch'
116+
runs-on: ubuntu-latest
117+
permissions:
118+
contents: read
119+
packages: write
120+
steps:
121+
- uses: actions/checkout@v4
122+
with:
123+
fetch-depth: 0 # 保证能拿到 tags 历史
124+
125+
- name: Force IMAGE_NAME lowercase (GHCR requires lowercase)
126+
run: echo "IMAGE_NAME=${GITHUB_REPOSITORY,,}" >> "$GITHUB_ENV"
127+
128+
- name: Fetch all tags
129+
run: |
130+
git fetch --tags --force --prune --prune-tags
131+
132+
- name: Resolve latest semver tag (vX.Y.Z)
133+
id: pick
134+
shell: bash
135+
run: |
136+
TAG=$(git tag -l 'v*' --sort=-v:refname | head -n 1)
137+
if [[ -z "$TAG" ]]; then
138+
echo "No tags found matching pattern v*"
139+
exit 1
140+
fi
141+
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
142+
echo "Latest tag is '$TAG' but not pure vX.Y.Z. Adjust logic if you want to allow pre-releases."
143+
exit 1
144+
fi
145+
VER="${TAG#v}"
146+
echo "FULL=$TAG" >> "$GITHUB_OUTPUT"
147+
echo "VER=$VER" >> "$GITHUB_OUTPUT"
148+
echo "Resolved latest tag: $TAG"
149+
150+
- uses: docker/setup-qemu-action@v3
151+
152+
- name: Set up Docker Buildx (pin BuildKit version)
153+
uses: docker/setup-buildx-action@v3
154+
with:
155+
driver-opts: |
156+
image=moby/buildkit:v0.25.1
157+
158+
- name: Log in to GitHub Container Registry
159+
uses: docker/login-action@v3
160+
with:
161+
registry: ${{ env.REGISTRY }}
162+
username: ${{ github.actor }}
163+
password: ${{ secrets.GITHUB_TOKEN }}
164+
165+
- name: Docker metadata (manual latest + vX.Y.Z)
166+
id: meta
167+
uses: docker/metadata-action@v5
168+
with:
169+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
170+
tags: |
171+
type=raw,value=latest
172+
type=raw,value=${{ steps.pick.outputs.FULL }}
173+
174+
- name: Build and push (manual)
175+
uses: docker/build-push-action@v5
176+
with:
177+
context: .
178+
platforms: linux/amd64,linux/arm64
179+
push: true
180+
tags: ${{ steps.meta.outputs.tags }}
181+
labels: ${{ steps.meta.outputs.labels }}
182+
cache-from: type=gha
183+
cache-to: type=gha,mode=max
184+
build-args: |
185+
VCS_REF=${{ github.sha }}
186+
VERSION=${{ steps.pick.outputs.VER }}

README.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,29 @@
4141

4242
## 🚀 快速开始
4343

44-
### 方式一:Docker Compose(推荐)
44+
### 方式一:docker镜像拉取和更新(推荐)
45+
46+
**拉取命令**
47+
```bash
48+
docker run -d \
49+
--name qwen2api \
50+
--restart unless-stopped \
51+
-p 3000:3000 \
52+
-v /root/qwen2api/data:/app/data \
53+
-v /root/qwen2api/.env:/app/.env:ro \
54+
--env-file /root/qwen2api/.env \
55+
ghcr.io/iptag/qwen2api-docker:latest
56+
```
57+
58+
**更新命令**
59+
```bash
60+
docker run --rm \
61+
-v /var/run/docker.sock:/var/run/docker.sock \
62+
containrrr/watchtower \
63+
--run-once qwen2api
64+
```
65+
66+
### 方式二:Docker Compose
4567

4668
#### 1. 克隆项目
4769

docker-compose.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
services:
22
qwen2api:
33
build: .
4-
image: qwen2api:latest
5-
container_name: qwen-api
4+
image: qwen2api
5+
container_name: qwen2api
66
restart: unless-stopped
77
ports:
88
- "${SERVICE_PORT:-3000}:3000"

src/config/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ const config = {
1313
enableFileLog: process.env.ENABLE_FILE_LOG === 'true',
1414
logDir: process.env.LOG_DIR || "./logs",
1515
maxLogFileSize: parseInt(process.env.MAX_LOG_FILE_SIZE) || 10,
16-
maxLogFiles: parseInt(process.env.MAX_LOG_FILES) || 5
16+
maxLogFiles: parseInt(process.env.MAX_LOG_FILES) || 5,
17+
qwenCookies: process.env.QWEN_COOKIES || ""
1718
}
1819

1920
module.exports = config

src/controllers/chat.image.video.js

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ const { sleep } = require('../utils/tools.js')
66
const { generateChatID } = require('../utils/request.js')
77
const config = require('../config/index.js')
88

9+
const getCookieValue = (cookieString, key) => {
10+
const match = cookieString.match(new RegExp(`${key}=([^;]+)`));
11+
return match ? match[1] : null;
12+
};
13+
914
/**
1015
* 主要的聊天完成处理函数
1116
* @param {object} req - Express 请求对象
@@ -61,7 +66,7 @@ const handleImageVideoCompletion = async (req, res) => {
6166
for (const item of messagesHistory) {
6267
if (item.role == "assistant") {
6368
// 使用matchAll提取所有图片链接
64-
const matches = [...item.content.matchAll(/!\[image\]\((.*?)\)/g)]
69+
const matches = [...item.content.matchAll(/!\\[image\\]\((.*?)\\) /g)]
6570
// 将所有匹配到的图片url添加到图片列表
6671
for (const match of matches) {
6772
select_image_list.push(match[1])
@@ -145,12 +150,16 @@ const handleImageVideoCompletion = async (req, res) => {
145150
logger.info(`使用提示: ${reqBody.messages[0].content}`, 'CHAT')
146151
// console.log(JSON.stringify(reqBody))
147152
const newChatType = reqBody.messages[0].chat_type
153+
154+
const ssxmodItna = getCookieValue(config.qwenCookies, 'ssxmod_itna');
155+
const ssxmodItna2 = getCookieValue(config.qwenCookies, 'ssxmod_itna2');
156+
148157
const response_data = await axios.post(`https://chat.qwen.ai/api/v2/chat/completions?chat_id=${chat_id}`, reqBody, {
149158
headers: {
150159
"Authorization": `Bearer ${token}`,
151160
'Content-Type': 'application/json',
152161
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
153-
...(config.ssxmodItna && { 'Cookie': `ssxmod_itna=${config.ssxmodItna}` })
162+
...(ssxmodItna && ssxmodItna2 && { 'Cookie': `ssxmod_itna=${ssxmodItna};ssxmod_itna2=${ssxmodItna2}` })
154163
},
155164
responseType: newChatType == 't2i' ? 'stream' : 'json',
156165
timeout: 1000 * 60 * 5
@@ -291,12 +300,15 @@ ${content}
291300

292301
const getVideoTaskStatus = async (videoTaskID, token) => {
293302
try {
303+
const ssxmodItna = getCookieValue(config.qwenCookies, 'ssxmod_itna');
304+
const ssxmodItna2 = getCookieValue(config.qwenCookies, 'ssxmod_itna2');
305+
294306
const response_data = await axios.get(`https://chat.qwen.ai/api/v1/tasks/status/${videoTaskID}`, {
295307
headers: {
296308
"Authorization": `Bearer ${token}`,
297309
'Content-Type': 'application/json',
298310
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
299-
...(config.ssxmodItna && { 'Cookie': `ssxmod_itna=${config.ssxmodItna}` })
311+
...(ssxmodItna && ssxmodItna2 && { 'Cookie': `ssxmod_itna=${ssxmodItna};ssxmod_itna2=${ssxmodItna2}` })
300312
}
301313
})
302314

@@ -314,4 +326,4 @@ const getVideoTaskStatus = async (videoTaskID, token) => {
314326

315327
module.exports = {
316328
handleImageVideoCompletion
317-
}
329+
}

src/models/models-map.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ const config = require('../config/index.js')
55
let cachedModels = null
66
let fetchPromise = null
77

8+
const getCookieValue = (cookieString, key) => {
9+
const match = cookieString.match(new RegExp(`${key}=([^;]+)`));
10+
return match ? match[1] : null;
11+
};
12+
813
const getLatestModels = async (force = false) => {
914
// 如果有缓存且不强制刷新,直接返回
1015
if (cachedModels && !force) {
@@ -15,13 +20,16 @@ const getLatestModels = async (force = false) => {
1520
if (fetchPromise) {
1621
return fetchPromise
1722
}
23+
24+
const ssxmodItna = getCookieValue(config.qwenCookies, 'ssxmod_itna');
25+
const ssxmodItna2 = getCookieValue(config.qwenCookies, 'ssxmod_itna2');
1826

1927
fetchPromise = axios.get('https://chat.qwen.ai/api/models', {
2028
headers: {
2129
'Authorization': `Bearer ${accountManager.getAccountToken()}`,
2230
'Content-Type': 'application/json',
2331
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
24-
...(config.ssxmodItna && { 'Cookie': `ssxmod_itna=${config.ssxmodItna}` })
32+
...(ssxmodItna && ssxmodItna2 && { 'Cookie': `ssxmod_itna=${ssxmodItna};ssxmod_itna2=${ssxmodItna2}` })
2533
}
2634
}).then(response => {
2735
// console.log(response)

src/utils/request.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ const config = require('../config/index.js')
33
const accountManager = require('./account.js')
44
const { logger } = require('./logger')
55

6+
const getCookieValue = (cookieString, key) => {
7+
const match = cookieString.match(new RegExp(`${key}=([^;]+)`));
8+
return match ? match[1] : null;
9+
};
10+
611

712
/**
813
* 发送聊天请求
@@ -22,13 +27,16 @@ const sendChatRequest = async (body) => {
2227
}
2328
}
2429

30+
const ssxmodItna = getCookieValue(config.qwenCookies, 'ssxmod_itna');
31+
const ssxmodItna2 = getCookieValue(config.qwenCookies, 'ssxmod_itna2');
32+
2533
// 构建请求配置
2634
const requestConfig = {
2735
headers: {
2836
'Authorization': `Bearer ${currentToken}`,
2937
'Content-Type': 'application/json',
3038
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
31-
...(config.ssxmodItna && { 'Cookie': `ssxmod_itna=${config.ssxmodItna}` })
39+
...(ssxmodItna && ssxmodItna2 && { 'Cookie': `ssxmod_itna=${ssxmodItna};ssxmod_itna2=${ssxmodItna2}` })
3240
},
3341
responseType: body.stream ? 'stream' : 'json',
3442
timeout: 60 * 1000,
@@ -72,6 +80,9 @@ const sendChatRequest = async (body) => {
7280
*/
7381
const generateChatID = async (currentToken,model) => {
7482
try {
83+
const ssxmodItna = getCookieValue(config.qwenCookies, 'ssxmod_itna');
84+
const ssxmodItna2 = getCookieValue(config.qwenCookies, 'ssxmod_itna2');
85+
7586
const response_data = await axios.post("https://chat.qwen.ai/api/v2/chats/new", {
7687
"title": "New Chat",
7788
"models": [
@@ -85,7 +96,7 @@ const generateChatID = async (currentToken,model) => {
8596
'Authorization': `Bearer ${currentToken}`,
8697
'Content-Type': 'application/json',
8798
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
88-
...(config.ssxmodItna && { 'Cookie': `ssxmod_itna=${config.ssxmodItna}` })
99+
...(ssxmodItna && ssxmodItna2 && { 'Cookie': `ssxmod_itna=${ssxmodItna};ssxmod_itna2=${ssxmodItna2}` })
89100
}
90101
})
91102

@@ -100,4 +111,4 @@ const generateChatID = async (currentToken,model) => {
100111
module.exports = {
101112
sendChatRequest,
102113
generateChatID
103-
}
114+
}

0 commit comments

Comments
 (0)