feat: 添加版本号显示和自动构建/删除 Docker 镜像功能

- 添加前端版本号显示(桌面端和移动端)
- 实现 GitHub Actions 自动构建和推送 Docker 镜像
- 实现删除 release 时自动删除对应 Docker 镜像
- 支持版本号格式:v数字.数字.数字 或 v数字.数字.数字-后缀
- 添加版本号管理文档
- 更新 Dockerfile 和部署脚本支持版本号构建参数
This commit is contained in:
WrBug
2025-12-07 16:46:18 +08:00
parent 07d1b50b9b
commit b069e54b89
10 changed files with 499 additions and 9 deletions
+105
View File
@@ -0,0 +1,105 @@
name: Build and Push Docker Image
on:
release:
types:
- published # 当通过 GitHub Releases 页面创建 release 时触发
- deleted # 当删除 release 时触发
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout code
if: github.event.action == 'published'
uses: actions/checkout@v4
with:
ref: ${{ github.event.release.tag_name }} # 使用 release 对应的 tag
# 构建和推送镜像(仅在发布 release 时)
- name: Extract version from release
if: github.event.action == 'published'
id: extract_version
run: |
# 从 release tag 中提取版本号(例如 v1.0.0 -> 1.0.0
TAG_NAME="${{ github.event.release.tag_name }}"
if [ -z "$TAG_NAME" ]; then
TAG_NAME=${GITHUB_REF#refs/tags/}
fi
# 验证版本号格式:v数字.数字.数字[-后缀](例如 v1.0.0, v2.10.102, v1.0.0-beta
if [[ ! "$TAG_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?$ ]]; then
echo "错误: 版本号格式不正确,应为 v数字.数字.数字 或 v数字.数字.数字-后缀 (例如: v1.0.0, v1.0.0-beta)"
exit 1
fi
VERSION=${TAG_NAME#v} # 移除 v 前缀
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
echo "TAG=$TAG_NAME" >> $GITHUB_OUTPUT
echo "Extracted version: $VERSION"
echo "Full tag: $TAG_NAME"
- name: Set up Docker Buildx
if: github.event.action == 'published'
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker image
if: github.event.action == 'published'
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: true
tags: |
wrbug/polyhermes:${{ steps.extract_version.outputs.TAG }}
wrbug/polyhermes:latest
build-args: |
VERSION=${{ steps.extract_version.outputs.VERSION }}
GIT_TAG=${{ steps.extract_version.outputs.TAG }}
GITHUB_REPO_URL=https://github.com/WrBug/PolyHermes
cache-from: type=registry,ref=wrbug/polyhermes:latest
cache-to: type=inline
# 删除镜像(仅在删除 release 时)
- name: Delete Docker image (when release is deleted)
if: github.event.action == 'deleted'
run: |
TAG_NAME="${{ github.event.release.tag_name }}"
# 验证版本号格式:v数字.数字.数字[-后缀](精准匹配)
if [[ ! "$TAG_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?$ ]]; then
echo "警告: 版本号格式不正确 ($TAG_NAME),应为 v数字.数字.数字 或 v数字.数字.数字-后缀 (例如: v1.0.0, v1.0.0-beta)"
echo "跳过删除操作"
exit 0
fi
IMAGE_NAME="wrbug/polyhermes"
DOCKER_USERNAME="${{ secrets.DOCKER_USERNAME }}"
DOCKER_PASSWORD="${{ secrets.DOCKER_PASSWORD }}"
echo "正在删除 Docker 镜像: ${IMAGE_NAME}:${TAG_NAME}"
# 使用 Docker Hub API v2 删除镜像标签
RESPONSE=$(curl -s -w "\n%{http_code}" -X DELETE \
-u "${DOCKER_USERNAME}:${DOCKER_PASSWORD}" \
"https://hub.docker.com/v2/repositories/${IMAGE_NAME}/tags/${TAG_NAME}/")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | head -n-1)
if [ "$HTTP_CODE" = "204" ]; then
echo "✅ 成功删除 Docker 镜像标签: ${IMAGE_NAME}:${TAG_NAME}"
elif [ "$HTTP_CODE" = "404" ]; then
echo "⚠️ 镜像标签不存在: ${IMAGE_NAME}:${TAG_NAME},可能已被删除或不存在"
else
echo "❌ 删除失败 (HTTP $HTTP_CODE): $BODY"
exit 1
fi
+11
View File
@@ -4,6 +4,16 @@ FROM node:18-alpine AS frontend-build
WORKDIR /app/frontend
# 定义构建参数(版本号信息)
ARG VERSION=dev
ARG GIT_TAG=
ARG GITHUB_REPO_URL=https://github.com/WrBug/PolyHermes
# 设置环境变量(用于 Vite 构建时注入)
ENV VERSION=${VERSION}
ENV GIT_TAG=${GIT_TAG}
ENV GITHUB_REPO_URL=${GITHUB_REPO_URL}
# 复制前端文件
COPY frontend/package*.json ./
RUN npm ci
@@ -11,6 +21,7 @@ RUN npm ci
COPY frontend/ ./
# 构建前端(使用相对路径,通过 Nginx 代理)
# 版本号会通过环境变量注入到构建产物中
RUN npm run build
# 阶段2:构建后端
+29 -2
View File
@@ -91,8 +91,21 @@ EOF
# 构建并启动
deploy() {
info "构建 Docker 镜像..."
docker-compose build
# 检查是否使用 Docker Hub 镜像
USE_DOCKER_HUB="${USE_DOCKER_HUB:-false}"
if [ "$USE_DOCKER_HUB" = "true" ]; then
info "使用 Docker Hub 镜像(推荐生产环境)..."
info "拉取最新镜像..."
docker pull wrbug/polyhermes:latest || warn "拉取镜像失败,将使用本地构建"
# 修改 docker-compose.yml 使用镜像而不是构建
# 注意:这里需要手动修改 docker-compose.yml,或者使用环境变量
warn "请确保 docker-compose.yml 中已配置使用 image: wrbug/polyhermes:latest"
else
info "构建 Docker 镜像(本地构建,版本号将显示为 dev)..."
docker-compose build
fi
info "启动服务..."
docker-compose up -d
@@ -114,6 +127,13 @@ main() {
echo "=========================================="
echo ""
# 解析参数
if [ "$1" = "--use-docker-hub" ] || [ "$1" = "-d" ]; then
export USE_DOCKER_HUB=true
info "将使用 Docker Hub 镜像(生产环境推荐)"
echo ""
fi
check_docker
create_env_file
deploy
@@ -121,6 +141,13 @@ main() {
echo ""
info "部署完成!"
info "访问地址: http://localhost:${SERVER_PORT:-80}"
echo ""
if [ "$USE_DOCKER_HUB" != "true" ]; then
info "提示:本地构建的版本号显示为 'dev'"
info "生产环境推荐使用 Docker Hub 镜像:"
info " ./deploy.sh --use-docker-hub"
info " 或修改 docker-compose.yml 使用 image: wrbug/polyhermes:latest"
fi
}
main "$@"
+8
View File
@@ -2,9 +2,17 @@ version: '3.8'
services:
app:
# 使用 Docker Hub 镜像(推荐生产环境)
# 取消注释下面一行,并注释掉 build 部分,即可使用 Docker Hub 的镜像
# image: wrbug/polyhermes:latest
build:
context: .
dockerfile: Dockerfile
# 本地构建时可以传递版本号参数(可选)
# args:
# VERSION: ${VERSION:-dev}
# GIT_TAG: ${GIT_TAG:-}
# GITHUB_REPO_URL: https://github.com/WrBug/PolyHermes
container_name: polyhermes
ports:
- "${SERVER_PORT:-80}:80"
+205
View File
@@ -0,0 +1,205 @@
# 版本号管理说明
## 概述
本项目支持自动版本号管理和显示。当在 GitHub 创建 release tag 时,会自动触发 GitHub Actions 构建 Docker 镜像并推送到 Docker Hub,同时在前端标题后显示版本号。
## 功能特性
1. **自动构建**:创建 release tag 时自动触发 GitHub Actions
2. **版本号显示**:前端标题后显示版本号(小字号)
3. **点击跳转**:点击版本号跳转到对应的 GitHub tag 页面
4. **Docker 推送**:自动构建并推送到 Docker Hub
5. **自动删除**:删除 release 时自动删除对应的 Docker 镜像标签
6. **版本号验证**:精准匹配版本号格式 `v数字.数字.数字`(例如:`v1.0.0`, `v2.10.102`
## 使用方法
### 1. 配置 Docker Hub 凭证
在 GitHub 仓库设置中添加以下 Secrets:
- `DOCKER_USERNAME`: Docker Hub 用户名(例如:`wrbug`
- `DOCKER_PASSWORD`: Docker Hub 密码或访问令牌
**设置步骤**
1. 访问 GitHub 仓库 → Settings → Secrets and variables → Actions
2. 点击 "New repository secret"
3. 添加 `DOCKER_USERNAME``DOCKER_PASSWORD`
### 2. 创建 Release(必须通过 GitHub Releases 页面)
**重要**:只有通过 [GitHub Releases 页面](https://github.com/WrBug/PolyHermes/releases/new) 创建 release 时才会触发自动构建。
**创建步骤**
1. 访问 [GitHub Releases 页面](https://github.com/WrBug/PolyHermes/releases/new)
2. 点击 "Choose a tag" 下拉菜单,输入新的 tag 名称(例如:`v1.0.0``v1.0.0-beta`
- 如果 tag 不存在,GitHub 会自动创建
- Tag 格式:`v数字.数字.数字``v数字.数字.数字-后缀`(例如:`v1.0.0`, `v1.0.0-beta`, `v2.10.102-rc.1`
3. 填写 Release 标题(例如:`v1.0.0``v1.0.0-beta`
4. 填写 Release 描述(可选,建议填写更新内容)
5. 点击 "Publish release" 按钮
**注意**
- ⚠️ 直接通过 `git push` 推送 tag **不会**触发构建
- ✅ 只有通过 Releases 页面点击 "Publish release" 才会触发构建
- 这样可以确保只有正式发布的版本才会构建 Docker 镜像
### 3. 自动构建流程
点击 "Publish release" 后,GitHub Actions 会自动:
1. **提取版本号**:从 tag 中提取版本号(例如:`v1.0.0``1.0.0`
2. **构建 Docker 镜像**:使用版本号作为构建参数
3. **注入版本号**:在构建前端时注入版本号到代码中
4. **推送镜像**:推送到 Docker Hub,标签为:
- `wrbug/polyhermes:v1.0.0`(具体版本)
- `wrbug/polyhermes:latest`(最新版本)
### 4. 版本号显示
前端会在标题 "PolyHermes" 后显示版本号,格式为:`PolyHermes v1.0.0`
- **显示位置**:桌面端左侧导航栏标题,移动端顶部标题
- **样式**:小字号,半透明,正常展示(无下划线等特殊样式)
- **点击行为**:点击版本号跳转到对应的 GitHub tag 页面
### 5. 删除 Release 和 Docker 镜像
当在 GitHub Releases 页面删除 release 时,会自动删除对应的 Docker 镜像标签:
1. 访问 [GitHub Releases 页面](https://github.com/WrBug/PolyHermes/releases)
2. 找到要删除的 release
3. 点击 "Delete" 按钮
4. GitHub Actions 会自动触发删除流程
5. 删除对应的 Docker 镜像标签(例如:`wrbug/polyhermes:v1.0.0`
**注意事项**
- ⚠️ 只有格式为 `v数字.数字.数字``v数字.数字.数字-后缀` 的版本号才会被删除(例如:`v1.0.0`, `v1.0.0-beta`, `v2.10.102`
- ⚠️ 如果镜像标签不存在,会显示警告但不会失败
- ⚠️ `latest` 标签不会被删除(即使删除最新的 release)
## 技术实现
### 版本号注入流程
1. **GitHub Actions** 提取 tag 中的版本号
2. **Dockerfile** 接收构建参数(`VERSION``GIT_TAG``GITHUB_REPO_URL`
3. **Vite 构建** 通过环境变量注入版本号到 `window.__VERSION__`
4. **前端代码**`window.__VERSION__` 读取版本号并显示
### 文件说明
- `.github/workflows/docker-build.yml`: GitHub Actions 工作流配置
- `Dockerfile`: 支持版本号构建参数
- `frontend/vite.config.ts`: Vite 配置,注入版本号到全局变量
- `frontend/src/utils/version.ts`: 版本号工具函数
- `frontend/src/components/Layout.tsx`: 显示版本号的组件
### 环境变量
构建时使用的环境变量:
- `VERSION`: 版本号(例如:`1.0.0`
- `GIT_TAG`: Git tag(例如:`v1.0.0`
- `GITHUB_REPO_URL`: GitHub 仓库 URL(默认:`https://github.com/WrBug/PolyHermes`
## 开发环境
在开发环境中,版本号默认为 `dev`,不会显示为链接。
如果需要测试版本号显示,可以在 `.env` 文件中设置:
```env
VITE_APP_VERSION=1.0.0
VITE_APP_GIT_TAG=v1.0.0
VITE_APP_GITHUB_REPO_URL=https://github.com/WrBug/PolyHermes
```
## 常见问题
### Q1: 创建 release 后没有触发构建?
**A:** 检查以下几点:
1. 确认是通过 [GitHub Releases 页面](https://github.com/WrBug/PolyHermes/releases/new) 创建的 release,而不是直接推送 tag
2. 确认点击了 "Publish release" 按钮(不是 "Save draft"
3. 检查 GitHub Actions 是否已启用
4. 查看 Actions 标签页中的工作流运行情况
5. 确认 release 状态为 "Published"(不是 "Draft" 或 "Prerelease"
### Q2: Docker 推送失败?
**A:** 检查以下几点:
1. 确认已正确配置 `DOCKER_USERNAME``DOCKER_PASSWORD` Secrets
2. 确认 Docker Hub 账户有权限推送镜像
3. 检查 Docker Hub 仓库名称是否正确(`wrbug/polyhermes`
### Q3: 前端没有显示版本号?
**A:** 检查以下几点:
1. 确认构建时传递了版本号环境变量
2. 检查浏览器控制台是否有错误
3. 确认使用的是构建后的镜像,而不是开发环境
### Q4: 版本号点击没有跳转?
**A:** 检查以下几点:
1. 确认 `GIT_TAG` 环境变量已正确设置
2. 确认 GitHub 仓库 URL 正确
3. 检查浏览器是否阻止了弹窗
### Q5: 删除 release 后 Docker 镜像没有被删除?
**A:** 检查以下几点:
1. 确认版本号格式为 `v数字.数字.数字`(例如:`v1.0.0`
2. 确认 Docker Hub 凭证(`DOCKER_USERNAME``DOCKER_PASSWORD`)正确配置
3. 确认 Docker Hub 访问令牌有删除镜像的权限
4. 查看 GitHub Actions 日志,确认删除操作是否执行
5. 如果镜像标签不存在,会显示警告但不会失败(这是正常的)
### Q6: 版本号格式要求是什么?
**A:** 版本号必须严格匹配格式:`v数字.数字.数字``v数字.数字.数字-后缀`
- ✅ 正确:`v1.0.0`, `v2.10.102`, `v1.0.0-beta`, `v1.0.0-rc.1`, `v2.10.102-alpha`
- ❌ 错误:`v1.0`, `1.0.0`, `v1.0.0.1`, `v1.0.0_beta`(下划线不支持)
## 示例
### 创建 Release 示例
**步骤 1:访问 Releases 页面**
访问:https://github.com/WrBug/PolyHermes/releases/new
**步骤 2:创建 Release**
1. 在 "Choose a tag" 中输入 `v1.0.0`(如果不存在会自动创建)
2. 填写 Release 标题:`v1.0.0`
3. 填写 Release 描述(可选)
4. 点击 "Publish release"
**步骤 3:自动构建**
- GitHub Actions 会自动触发构建
- 构建完成后,Docker 镜像会自动推送到 Docker Hub
- 前端会显示 "PolyHermes v1.0.0"
**注意**:直接通过 `git push` 推送 tag 不会触发构建,必须通过 Releases 页面创建。
### 使用 Docker 镜像示例
```bash
# 拉取特定版本
docker pull wrbug/polyhermes:v1.0.0
# 拉取最新版本
docker pull wrbug/polyhermes:latest
# 运行容器
docker run -d -p 80:80 wrbug/polyhermes:v1.0.0
```
## 注意事项
1. **Tag 格式**:必须使用 `v*` 格式(例如:`v1.0.0`),否则不会触发构建
2. **版本号格式**:建议使用语义化版本号(Semantic Versioning
3. **Docker Hub**:确保 Docker Hub 仓库已创建
4. **权限**:确保 GitHub Actions 有权限访问 Docker Hub
+57 -7
View File
@@ -23,7 +23,7 @@ import {
} from '@ant-design/icons'
import type { MenuProps } from 'antd'
import type { ReactNode } from 'react'
import { removeToken } from '../utils'
import { removeToken, getVersionText, getGitHubTagUrl } from '../utils'
import { wsManager } from '../services/websocket'
import Logo from './Logo'
@@ -200,10 +200,33 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
alignItems: 'center',
justifyContent: 'space-between'
}}>
<Logo
size="normal"
darkMode={true}
/>
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
<Logo
size="normal"
darkMode={true}
/>
<a
href={getGitHubTagUrl()}
target="_blank"
rel="noopener noreferrer"
onClick={(e) => {
const version = getVersionText()
if (version === 'dev') {
e.preventDefault()
}
}}
style={{
color: 'rgba(255, 255, 255, 0.7)',
fontSize: '12px',
fontWeight: 'normal',
textDecoration: 'none',
cursor: getVersionText() === 'dev' ? 'default' : 'pointer'
}}
title={getVersionText() === 'dev' ? '' : '查看版本发布'}
>
v{getVersionText()}
</a>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<a
href="https://github.com/WrBug/PolyHermes"
@@ -292,9 +315,36 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
fontSize: '18px',
fontWeight: 'bold',
marginBottom: '12px',
textAlign: 'center'
textAlign: 'center',
display: 'flex',
alignItems: 'flex-end',
justifyContent: 'center',
gap: '6px'
}}>
PolyHermes
<span>PolyHermes</span>
<a
href={getGitHubTagUrl()}
target="_blank"
rel="noopener noreferrer"
onClick={(e) => {
const version = getVersionText()
if (version === 'dev') {
e.preventDefault()
}
}}
style={{
color: 'rgba(255, 255, 255, 0.7)',
fontSize: '12px',
fontWeight: 'normal',
textDecoration: 'none',
cursor: getVersionText() === 'dev' ? 'default' : 'pointer',
lineHeight: '1',
paddingBottom: '2px'
}}
title={getVersionText() === 'dev' ? '' : '查看版本发布'}
>
v{getVersionText()}
</a>
</div>
<div style={{
display: 'flex',
+7
View File
@@ -45,3 +45,10 @@ export {
hasToken
} from './auth'
// 统一导出 version 相关工具函数
export {
getVersionInfo,
getVersionText,
getGitHubTagUrl
} from './version'
+52
View File
@@ -0,0 +1,52 @@
/**
* 版本号配置
* 在构建时通过环境变量注入
*/
export interface VersionInfo {
version: string
gitTag: string
githubRepoUrl: string
}
/**
* 获取版本信息
* 从 window.__VERSION__ 或环境变量中读取
*/
export const getVersionInfo = (): VersionInfo => {
// 优先从 window.__VERSION__ 读取(构建时注入)
const windowVersion = window.__VERSION__
if (windowVersion) {
return {
version: windowVersion.version || 'dev',
gitTag: windowVersion.gitTag || '',
githubRepoUrl: windowVersion.githubRepoUrl || 'https://github.com/WrBug/PolyHermes'
}
}
// 从环境变量读取(开发环境)
return {
version: import.meta.env.VITE_APP_VERSION || 'dev',
gitTag: import.meta.env.VITE_APP_GIT_TAG || '',
githubRepoUrl: import.meta.env.VITE_APP_GITHUB_REPO_URL || 'https://github.com/WrBug/PolyHermes'
}
}
/**
* 获取版本号显示文本
*/
export const getVersionText = (): string => {
const info = getVersionInfo()
return info.version
}
/**
* 获取 GitHub tag 页面 URL
*/
export const getGitHubTagUrl = (): string => {
const info = getVersionInfo()
if (info.gitTag) {
return `${info.githubRepoUrl}/releases/tag/${info.gitTag}`
}
return info.githubRepoUrl
}
+12
View File
@@ -3,9 +3,21 @@
interface ImportMetaEnv {
readonly VITE_API_URL?: string
readonly VITE_WS_URL?: string
readonly VITE_APP_VERSION?: string
readonly VITE_APP_GIT_TAG?: string
readonly VITE_APP_GITHUB_REPO_URL?: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
// 版本号全局变量类型定义
interface Window {
__VERSION__?: {
version: string
gitTag: string
githubRepoUrl: string
}
}
+13
View File
@@ -11,8 +11,21 @@ export default defineConfig(({ mode }) => {
const API_URL = env.VITE_API_URL || 'http://localhost:8000'
const WS_URL = env.VITE_WS_URL || 'ws://localhost:8000'
// 从环境变量获取版本信息(构建时注入)
const VERSION = env.VERSION || 'dev'
const GIT_TAG = env.GIT_TAG || ''
const GITHUB_REPO_URL = env.GITHUB_REPO_URL || 'https://github.com/WrBug/PolyHermes'
return {
plugins: [react()],
define: {
// 注入版本信息到全局变量
'window.__VERSION__': JSON.stringify({
version: VERSION,
gitTag: GIT_TAG,
githubRepoUrl: GITHUB_REPO_URL
})
},
server: {
port: 3000,
proxy: {