From b069e54b89f713f376da3400d80c6e49432c36c8 Mon Sep 17 00:00:00 2001 From: WrBug Date: Sun, 7 Dec 2025 16:46:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=8F=B7=E6=98=BE=E7=A4=BA=E5=92=8C=E8=87=AA=E5=8A=A8=E6=9E=84?= =?UTF-8?q?=E5=BB=BA/=E5=88=A0=E9=99=A4=20Docker=20=E9=95=9C=E5=83=8F?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加前端版本号显示(桌面端和移动端) - 实现 GitHub Actions 自动构建和推送 Docker 镜像 - 实现删除 release 时自动删除对应 Docker 镜像 - 支持版本号格式:v数字.数字.数字 或 v数字.数字.数字-后缀 - 添加版本号管理文档 - 更新 Dockerfile 和部署脚本支持版本号构建参数 --- .github/workflows/docker-build.yml | 105 +++++++++++++++ Dockerfile | 11 ++ deploy.sh | 31 ++++- docker-compose.yml | 8 ++ docs/VERSION_MANAGEMENT.md | 205 +++++++++++++++++++++++++++++ frontend/src/components/Layout.tsx | 64 ++++++++- frontend/src/utils/index.ts | 7 + frontend/src/utils/version.ts | 52 ++++++++ frontend/src/vite-env.d.ts | 12 ++ frontend/vite.config.ts | 13 ++ 10 files changed, 499 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/docker-build.yml create mode 100644 docs/VERSION_MANAGEMENT.md create mode 100644 frontend/src/utils/version.ts diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml new file mode 100644 index 0000000..ae5381f --- /dev/null +++ b/.github/workflows/docker-build.yml @@ -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 + diff --git a/Dockerfile b/Dockerfile index dc094d7..123c5cd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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:构建后端 diff --git a/deploy.sh b/deploy.sh index 4075ef8..ce044ff 100755 --- a/deploy.sh +++ b/deploy.sh @@ -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 "$@" diff --git a/docker-compose.yml b/docker-compose.yml index cfc6450..386c68f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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" diff --git a/docs/VERSION_MANAGEMENT.md b/docs/VERSION_MANAGEMENT.md new file mode 100644 index 0000000..97c62f5 --- /dev/null +++ b/docs/VERSION_MANAGEMENT.md @@ -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 + diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index aedb3ee..2fbb694 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -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 = ({ children }) => { alignItems: 'center', justifyContent: 'space-between' }}> - +
+ + { + 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()} + +
= ({ children }) => { fontSize: '18px', fontWeight: 'bold', marginBottom: '12px', - textAlign: 'center' + textAlign: 'center', + display: 'flex', + alignItems: 'flex-end', + justifyContent: 'center', + gap: '6px' }}> - PolyHermes + PolyHermes + { + 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()} +
{ + // 优先从 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 +} + diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts index fd00d2d..ac7e235 100644 --- a/frontend/src/vite-env.d.ts +++ b/frontend/src/vite-env.d.ts @@ -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 + } +} + diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 6168db4..ece6963 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -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: {