diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..1c223dd --- /dev/null +++ b/.dockerignore @@ -0,0 +1,31 @@ +# 排除不需要的文件和目录,减少 Docker 构建上下文大小 + +# 开发工具 +.vscode/ +.idea/ +*.swp +*.swo + +# Git +.git/ +.gitignore + +# 构建产物(本地构建时会重新生成) +docs/ +pkg/ + +# 临时文件 +*.tmp +*.temp +.DS_Store +Thumbs.db + +# CI 相关文件 +.github/ +Dockerfile.ci + +# 其他不需要的目录 +android/app/build/ +android/.gradle/ +resources/ +electron/dist/ \ No newline at end of file diff --git a/.dockerignore.ci b/.dockerignore.ci new file mode 100644 index 0000000..925a66e --- /dev/null +++ b/.dockerignore.ci @@ -0,0 +1,38 @@ +# CI 构建专用的 dockerignore +# 只保留构建产物和必要的配置文件 + +# 排除所有源代码和开发文件 +src/ +public/ +electron/ +node_modules/ +.vscode/ +.idea/ +.git/ +.github/ + +# 排除构建工具配置 +vite.config.ts +tsconfig*.json +*.config.js +*.config.ts +package.json +package-lock.json +pnpm-lock.yaml + +# 排除其他构建产物 +pkg/ +android/ +resources/ + +# 排除临时文件 +*.tmp +*.temp +.DS_Store +Thumbs.db +*.log + +# 只保留以下文件: +# - docs/ (构建产物) +# - nginx.conf (nginx配置) +# - Dockerfile.ci \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7cfb2ea..fc12de9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,12 +23,12 @@ jobs: goarch: arm64 executable: OGame-Vue-Ts-server-linux-arm64 steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v3 + - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v4 with: - version: 8 + version: latest - name: Setup Node & Go - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: 20 cache: 'pnpm' @@ -55,17 +55,17 @@ jobs: name: Build Android APK runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v3 + - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v4 with: - version: 8 + version: latest - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: 22 cache: 'pnpm' - name: Setup Java - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: '21' @@ -136,18 +136,18 @@ jobs: - os: ubuntu-latest platform: linux steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v3 + - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v4 with: - version: 8 + version: latest - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: 20 cache: 'pnpm' - name: Cache Electron Builder - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: | ~/.cache/electron @@ -180,7 +180,7 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Get Version id: get_version @@ -226,7 +226,7 @@ jobs: # 4. 一次性上传,禁止重复匹配 - name: Create GitHub Release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: tag_name: ${{ steps.get_version.outputs.VERSION }} name: Release ${{ steps.get_version.outputs.VERSION }} diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml index 363eced..90bc3ad 100644 --- a/.github/workflows/github-pages.yml +++ b/.github/workflows/github-pages.yml @@ -19,21 +19,45 @@ jobs: - name: 检出代码 uses: actions/checkout@v6 - - name: 安装 pnpm + - name: 设置 pnpm uses: pnpm/action-setup@v4 + with: + version: latest - - name: 安装 Nodejs + - name: 设置 Node.js uses: actions/setup-node@v6 with: - node-version: 20 - cache: 'pnpm' + node-version: '20' + + - name: 缓存 pnpm 依赖 + uses: actions/cache@v5 + with: + path: | + ~/.pnpm-store + node_modules + key: ${{ runner.os }}-pnpm-pages-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-pages- - name: 安装依赖 - run: pnpm install + run: pnpm install --frozen-lockfile - name: 构建前端项目 run: pnpm run build + - name: 验证构建产物 + run: | + if [ ! -d "docs" ]; then + echo "❌ 构建失败:docs 目录不存在" + exit 1 + fi + if [ ! -f "docs/index.html" ]; then + echo "❌ 构建失败:docs/index.html 不存在" + exit 1 + fi + echo "✅ 构建产物验证通过" + ls -la docs/ + # 关键步骤:告诉 GitHub Actions 跳过 Jekyll 检查 - name: 配置 Github Pages uses: actions/configure-pages@v5 @@ -44,4 +68,5 @@ jobs: path: './docs' - name: 部署到 GitHub Pages + id: deployment uses: actions/deploy-pages@v4 diff --git a/.github/workflows/ogame-vue-ts.yml b/.github/workflows/ogame-vue-ts.yml index 48b9d26..7ed5890 100644 --- a/.github/workflows/ogame-vue-ts.yml +++ b/.github/workflows/ogame-vue-ts.yml @@ -1,4 +1,4 @@ -name: Docker 多架构构建并发布 +name: 构建并发布 Docker 镜像 on: push: @@ -15,10 +15,113 @@ jobs: runs-on: ubuntu-latest steps: - name: 检出代码 - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 + # 设置 Node.js 环境 + - name: 设置 Node.js + uses: actions/setup-node@v6 + with: + node-version: '20' + + # 设置 pnpm + - name: 设置 pnpm + uses: pnpm/action-setup@v4 + with: + version: latest + + # 缓存 pnpm 依赖 + - name: 缓存 pnpm 依赖 + uses: actions/cache@v5 + with: + path: | + ~/.pnpm-store + node_modules + key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm- + + # 安装依赖 + - name: 安装依赖 + run: pnpm install --frozen-lockfile + + # 构建项目 + - name: 构建项目 + run: pnpm run build + + # 验证构建产物 + - name: 验证构建产物 + run: | + if [ ! -d "docs" ]; then + echo "❌ 构建失败:docs 目录不存在" + exit 1 + fi + if [ ! -f "docs/index.html" ]; then + echo "❌ 构建失败:docs/index.html 不存在" + exit 1 + fi + + # 检查构建产物的时间戳,确保是最新的 + BUILD_TIME=$(stat -c %Y docs/index.html 2>/dev/null || stat -f %m docs/index.html 2>/dev/null || echo "0") + CURRENT_TIME=$(date +%s) + TIME_DIFF=$((CURRENT_TIME - BUILD_TIME)) + + echo "📊 构建产物信息:" + echo " 构建时间: $(date -d @$BUILD_TIME 2>/dev/null || date -r $BUILD_TIME 2>/dev/null || echo '未知')" + echo " 当前时间: $(date)" + echo " 时间差: ${TIME_DIFF}秒" + + if [ $TIME_DIFF -gt 300 ]; then + echo "⚠️ 警告: 构建产物可能不是最新的(超过5分钟)" + fi + + echo "✅ 构建产物验证通过" + ls -la docs/ + + # 获取当前日期 + - name: 获取当前日期 + id: date + run: echo "date=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT + + # 获取版本号 + - name: 获取版本号 + id: version + run: | + VERSION=$(node -p "require('./package.json').version") + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "📦 当前版本: $VERSION" + + # 准备 CI 构建环境 + - name: 准备 CI 构建环境 + run: | + # 使用 CI 专用的 dockerignore + cp .dockerignore.ci .dockerignore + echo "✅ 已切换到 CI 构建模式" + echo "📁 当前构建上下文文件:" + ls -la | grep -E "(docs|nginx.conf|Dockerfile.ci|\.dockerignore)$" + + # 清理可能冲突的镜像标签 + - name: 清理可能冲突的镜像标签 + continue-on-error: true + run: | + VERSION="${{ steps.version.outputs.version }}" + echo "🧹 尝试清理可能冲突的镜像标签..." + + # 尝试删除 GHCR 中的现有标签(如果存在) + echo "清理 GHCR 标签..." + docker buildx imagetools inspect ghcr.io/${{ github.repository_owner }}/ogame-vue-ts:$VERSION 2>/dev/null && \ + echo "发现现有版本标签,将被覆盖" || echo "版本标签不存在,可以安全推送" + + # 如果配置了 Docker Hub,也尝试检查 + if [ -n "${{ vars.DOCKERHUB_USERNAME }}" ]; then + echo "检查 Docker Hub 标签..." + docker buildx imagetools inspect ${{ vars.DOCKERHUB_USERNAME }}/ogame-vue-ts:$VERSION 2>/dev/null && \ + echo "发现现有 Docker Hub 版本标签,将被覆盖" || echo "Docker Hub 版本标签不存在,可以安全推送" + fi + + echo "✅ 标签冲突检查完成,构建将覆盖任何现有标签" + # QEMU 用于支持多架构构建(必须) - name: 设置 QEMU uses: docker/setup-qemu-action@v3 @@ -43,18 +146,34 @@ jobs: username: ${{ vars.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - # 真正一键构建 + 推送多架构镜像(amd64 + arm64) + # 构建并推送多架构镜像(使用构建产物) - name: 构建并推送多架构镜像 uses: docker/build-push-action@v6 with: context: . + file: ./Dockerfile.ci platforms: linux/amd64,linux/arm64 push: true + no-cache: false + pull: true tags: | ghcr.io/${{ github.repository_owner }}/ogame-vue-ts:latest + ghcr.io/${{ github.repository_owner }}/ogame-vue-ts:${{ steps.version.outputs.version }} ghcr.io/${{ github.repository_owner }}/ogame-vue-ts:${{ github.sha }} - ${{ vars.DOCKERHUB_USERNAME != '' && format('docker.io/{0}/ogame-vue-ts:latest', vars.DOCKERHUB_USERNAME) || '' }} - ${{ vars.DOCKERHUB_USERNAME != '' && format('docker.io/{0}/ogame-vue-ts:{1}', vars.DOCKERHUB_USERNAME, github.sha) || '' }} + ${{ vars.DOCKERHUB_USERNAME && format('{0}/ogame-vue-ts:latest', vars.DOCKERHUB_USERNAME) || '' }} + ${{ vars.DOCKERHUB_USERNAME && format('{0}/ogame-vue-ts:{1}', vars.DOCKERHUB_USERNAME, steps.version.outputs.version) || '' }} + ${{ vars.DOCKERHUB_USERNAME && format('{0}/ogame-vue-ts:{1}', vars.DOCKERHUB_USERNAME, github.sha) || '' }} cache-from: type=gha cache-to: type=gha,mode=max - outputs: type=image,name=target,annotation-index.org.opencontainers.image.description=OGame Vue Ts + build-args: | + BUILDKIT_INLINE_CACHE=1 + BUILD_DATE=${{ steps.date.outputs.date }} + VERSION=${{ steps.version.outputs.version }} + COMMIT_SHA=${{ github.sha }} + labels: | + org.opencontainers.image.title=OGame Vue Ts + org.opencontainers.image.description=OGame Vue TypeScript Implementation + org.opencontainers.image.version=${{ steps.version.outputs.version }} + org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} + org.opencontainers.image.revision=${{ github.sha }} + org.opencontainers.image.created=${{ steps.date.outputs.date }} diff --git a/DOCKER.md b/DOCKER.md new file mode 100644 index 0000000..46bda6b --- /dev/null +++ b/DOCKER.md @@ -0,0 +1,89 @@ +# Docker 构建说明 + +本项目支持两种 Docker 构建方式: + +## 🏠 本地构建 + +### 方式一:使用构建脚本(推荐) + +**Linux/macOS:** +```bash +chmod +x build-docker.sh +./build-docker.sh +``` + +**Windows:** +```cmd +build-docker.bat +``` + +### 方式二:直接使用 Docker 命令 + +```bash +# 构建镜像 +docker build -t ogame-vue-ts:local . + +# 运行容器 +docker run -p 8080:80 ogame-vue-ts:local +``` + +## ☁️ GitHub Actions 自动构建 + +当代码推送到 `main` 分支或创建 tag 时,GitHub Actions 会自动: + +1. 在 Actions 环境中构建项目 +2. 使用构建产物创建 Docker 镜像 +3. 推送到 GitHub Container Registry 和 Docker Hub + +### 使用预构建镜像 + +```bash +# 从 GitHub Container Registry 拉取 +docker pull ghcr.io/your-username/ogame-vue-ts:latest + +# 从 Docker Hub 拉取(如果配置了) +docker pull your-dockerhub-username/ogame-vue-ts:latest + +# 运行 +docker run -p 8080:80 ghcr.io/your-username/ogame-vue-ts:latest +``` + +## 📁 文件说明 + +- `Dockerfile` - 本地构建用,包含完整的源代码构建流程 +- `Dockerfile.ci` - GitHub Actions 构建用,使用预构建产物 +- `.dockerignore` - 本地构建时排除的文件 +- `.dockerignore.ci` - CI 构建时排除的文件 +- `build-docker.sh` / `build-docker.bat` - 本地构建便捷脚本 + +## 🔧 配置说明 + +### GitHub Actions 环境变量 + +需要在 GitHub 仓库设置中配置: + +**Variables (公开):** +- `DOCKERHUB_USERNAME` - Docker Hub 用户名(可选) + +**Secrets (私密):** +- `DOCKERHUB_TOKEN` - Docker Hub 访问令牌(可选) +- `GITHUB_TOKEN` - 自动提供,用于 GHCR + +### 本地构建要求 + +- Docker +- 足够的磁盘空间(构建过程中会下载 Node.js 依赖) + +## 🚀 快速开始 + +1. **本地开发测试:** + ```bash + ./build-docker.sh + docker run -p 8080:80 ogame-vue-ts:local + ``` + +2. **访问应用:** + 打开浏览器访问 `http://localhost:8080` + +3. **生产部署:** + 使用 GitHub Actions 自动构建的镜像进行部署 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index ed4e615..dda88a7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,22 +1,40 @@ -FROM node:lts-alpine AS builder +# 本地构建用的 Dockerfile +# 支持完整的源代码构建流程 -RUN mkdir -p /workspace -WORKDIR /workspace -RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories -RUN apk update && apk add git -RUN npm config set registry https://registry.npmmirror.com -RUN git clone https://github.com/setube/ogame-vue-ts.git -RUN mv ./ogame-vue-ts/* . ; rm -rf ./ogame-vue-ts/ +FROM node:20-alpine AS builder -RUN npm install -g pnpm ; pnpm install; +# 设置工作目录 +WORKDIR /app + +# 复制 package 文件 +COPY package.json pnpm-lock.yaml ./ + +# 安装 pnpm +RUN npm install -g pnpm + +# 安装依赖 +RUN pnpm install --frozen-lockfile + +# 复制源代码 +COPY . . + +# 构建项目 RUN pnpm run build +# 生产阶段 FROM nginx:alpine +# 复制 nginx 配置文件 COPY nginx.conf /etc/nginx/conf.d/default.conf +# 清理默认的 nginx 静态文件 RUN rm -rf /usr/share/nginx/html/* -COPY --from=builder /workspace/docs /usr/share/nginx/html +# 复制构建产物到 nginx 静态文件目录 +COPY --from=builder /app/docs /usr/share/nginx/html + +# 暴露端口 EXPOSE 80 + +# 启动 nginx CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/Dockerfile.ci b/Dockerfile.ci new file mode 100644 index 0000000..844b07b --- /dev/null +++ b/Dockerfile.ci @@ -0,0 +1,35 @@ +# GitHub Actions 构建用的 Dockerfile +# 使用预构建的产物,不包含源代码构建过程 + +FROM nginx:alpine + +# 添加构建参数用于缓存破坏 +ARG BUILD_DATE +ARG VERSION +ARG COMMIT_SHA + +# 添加标签信息 +LABEL build.date="${BUILD_DATE}" \ + build.version="${VERSION}" \ + build.commit="${COMMIT_SHA}" + +# 复制 nginx 配置文件 +COPY nginx.conf /etc/nginx/conf.d/default.conf + +# 清理默认的 nginx 静态文件 +RUN rm -rf /usr/share/nginx/html/* + +# 复制构建产物到 nginx 静态文件目录 +# 这里的 docs 目录是在 GitHub Actions 中构建生成的 +# 使用 --chown 确保正确的文件权限 +COPY --chown=nginx:nginx docs /usr/share/nginx/html + +# 验证构建产物 +RUN ls -la /usr/share/nginx/html/ && \ + test -f /usr/share/nginx/html/index.html || (echo "构建产物验证失败" && exit 1) + +# 暴露端口 +EXPOSE 80 + +# 启动 nginx +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/build-docker.bat b/build-docker.bat new file mode 100644 index 0000000..6dd361c --- /dev/null +++ b/build-docker.bat @@ -0,0 +1,17 @@ +@echo off +REM 本地 Docker 构建脚本 (Windows) +REM 使用完整的源代码构建流程 + +echo 🚀 开始本地 Docker 构建... + +REM 构建镜像 +docker build -t ogame-vue-ts:local . + +if %ERRORLEVEL% EQU 0 ( + echo ✅ Docker 镜像构建成功! + echo 📦 镜像标签: ogame-vue-ts:local + echo 🏃 运行命令: docker run -p 8080:80 ogame-vue-ts:local +) else ( + echo ❌ Docker 镜像构建失败! + exit /b 1 +) \ No newline at end of file diff --git a/build-docker.sh b/build-docker.sh new file mode 100644 index 0000000..5dfc9c2 --- /dev/null +++ b/build-docker.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# 本地 Docker 构建脚本 +# 使用完整的源代码构建流程 + +echo "🚀 开始本地 Docker 构建..." + +# 构建镜像 +docker build -t ogame-vue-ts:local . + +if [ $? -eq 0 ]; then + echo "✅ Docker 镜像构建成功!" + echo "📦 镜像标签: ogame-vue-ts:local" + echo "🏃 运行命令: docker run -p 8080:80 ogame-vue-ts:local" +else + echo "❌ Docker 镜像构建失败!" + exit 1 +fi \ No newline at end of file diff --git a/package.json b/package.json index 69d435c..9cd899b 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,6 @@ "electron" ] }, - "packageManager": "pnpm@10.13.1+sha512.37ebf1a5c7a30d5fabe0c5df44ee8da4c965ca0c5af3dbab28c3a1681b70a256218d05c81c9c0dcf767ef6b8551eb5b960042b9ed4300c59242336377e01cfad", "build": { "appId": "games.wenzi.ogame", "productName": "OGame-Vue-Ts", diff --git a/src/App.vue b/src/App.vue index 323ed28..c2b340f 100644 --- a/src/App.vue +++ b/src/App.vue @@ -765,7 +765,7 @@ return } - if (!settings.types[typeKey]) return + if (!settings.types[typeKey as keyof typeof settings.types]) return // 浏览器通知 if (settings.browser && 'Notification' in window && Notification.permission === 'granted') {