From 451496f102823f7ffc99371ef9eb5288c40c7775 Mon Sep 17 00:00:00 2001 From: yukaidi Date: Sun, 31 May 2026 14:00:45 +0800 Subject: [PATCH 1/4] =?UTF-8?q?CI:=20=E6=96=B0=E5=A2=9E=20Linux/Windows=20?= =?UTF-8?q?=E5=8E=9F=E7=94=9F=E7=8E=AF=E5=A2=83=E6=89=93=E5=8C=85=20(jlink?= =?UTF-8?q?=20+=20=E7=B2=BE=E7=AE=80=20JRE)=20=E5=8F=8A=20Docker=20?= =?UTF-8?q?=E5=A4=9A=E5=B9=B3=E5=8F=B0=E6=9E=84=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/maven.yml | 244 ++++++++++++++++++++++++++++++------ .gitignore | 1 + 2 files changed, 207 insertions(+), 38 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index b075d3c..c355ba1 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -1,14 +1,5 @@ -# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven +name: Java CI(Maven 构建 + Docker 镜像 + 原生环境打包) -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -name: Java CI with Maven - -# The API requires write permission on the repository to submit dependencies permissions: contents: write packages: write @@ -16,9 +7,9 @@ permissions: on: push: tags: - - '*' # 只有推送tag时才会触发构建 + - '*' branches-ignore: - - '*' # 排除所有分支的提交 + - '*' paths-ignore: - 'bin/**' - '.github/**' @@ -32,71 +23,248 @@ on: - "main" jobs: + # ================================================================ + # 阶段一:构建前端 + Maven 打包(只执行一次,产物共享) + # ================================================================ build: - + name: 编译构建 runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 + - name: 检出代码 + uses: actions/checkout@v3 - - uses: actions/setup-node@v4 + - name: 设置 Node.js 18 + uses: actions/setup-node@v4 with: node-version: '18' - - name: Set up JDK 17 + - name: 设置 JDK 17 uses: actions/setup-java@v3 with: java-version: '17' distribution: 'temurin' cache: maven - - name: Build Frontend + - name: 构建前端 run: cd web-front && yarn install && yarn run build - - name: Build with Maven + - name: Maven 编译打包 run: mvn -B package -DskipTests --file pom.xml - # Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive - - name: Update dependency graph + - name: 更新依赖图谱 uses: advanced-security/maven-dependency-submission-action@v3 if: github.event_name != 'pull_request' continue-on-error: true with: ignore-maven-wrapper: true -# - uses: release-drafter/release-drafter@v5 -# env: -# GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} - - - name: Upload Artifact + - name: 分享应用打包目录(供原生包和 Docker 复用) + if: github.event_name != 'pull_request' uses: actions/upload-artifact@v4 with: + name: app-package + path: web-service/target/package/ + + - name: 分享 bin-zip(供 Docker 复用) + if: github.event_name != 'pull_request' + uses: actions/upload-artifact@v4 + with: + name: app-bin-zip path: web-service/target/netdisk-fast-download-bin.zip - - name: Login to GitHub Container Registry - if: github.event_name != 'pull_request' + # ================================================================ + # 阶段二-A:Docker 镜像构建(并行) + # ================================================================ + docker: + name: Docker 镜像 + needs: build + if: github.event_name != 'pull_request' + runs-on: ubuntu-latest + steps: + - name: 检出代码 + uses: actions/checkout@v3 + + - name: 下载 bin-zip 产物 + uses: actions/download-artifact@v4 + with: + name: app-bin-zip + path: web-service/target/ + + - name: 登录 GitHub 容器仓库 uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Set up Docker Buildx + - name: 设置 QEMU(多平台构建支持) + uses: docker/setup-qemu-action@v3 + + - name: 设置 Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Extract git tag - id: tag - run: | - GIT_TAG=$(git tag --points-at HEAD | head -n 1) - echo "tag=$GIT_TAG" >> $GITHUB_OUTPUT - - - name: Build and push Docker image - if: github.event_name != 'pull_request' + - name: 构建并推送 Docker 镜像 uses: docker/build-push-action@v5 with: context: . push: true platforms: linux/amd64,linux/arm64,linux/arm/v7 tags: | - ghcr.io/${{ github.repository }}:${{ steps.tag.outputs.tag }} + ghcr.io/${{ github.repository }}:${{ github.ref_name }} ghcr.io/${{ github.repository }}:latest + + + # ================================================================ + # 阶段二-B:原生环境打包 Linux + Windows(并行) + # ================================================================ + native-package: + name: 原生环境打包 → ${{ matrix.artifact-name }} + needs: build + if: github.event_name != 'pull_request' + strategy: + matrix: + include: + - os: ubuntu-latest + artifact-name: netdisk-fast-download-linux-amd64 + - os: windows-latest + artifact-name: netdisk-fast-download-windows-amd64 + runs-on: ${{ matrix.os }} + defaults: + run: + shell: bash + + steps: + - name: 设置 JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + + - name: 下载 Maven 构建产物 + uses: actions/download-artifact@v4 + with: + name: app-package + path: web-service/target/package + + # ============================================================ + # jdeps 分析 → 确定所需 JDK 模块 + # ============================================================ + - name: 分析所需 JDK 模块(jdeps) + run: | + MAIN_JAR="web-service/target/package/netdisk-fast-download.jar" + LIB_DIR="web-service/target/package/lib" + + CP="" + for jar in "$LIB_DIR"/*.jar; do + CP="$CP${CP:+:}$jar" + done + + RAW_MODULES=$(jdeps --print-module-deps --ignore-missing-deps --multi-release 17 \ + --class-path "$CP" "$MAIN_JAR" 2>/dev/null | head -n 1 | tr -d '\r\n' || true) + + if [ -z "$RAW_MODULES" ] || [[ "$RAW_MODULES" == *"Missing"* ]] || [[ "$RAW_MODULES" == *"Error"* ]]; then + RAW_MODULES="java.base,java.logging,java.sql,java.naming,java.management,java.xml,jdk.unsupported,java.net.http,java.instrument,java.security.jgss,java.security.sasl,java.desktop,jdk.crypto.ec" + echo "jdeps 分析失败,使用回退模块列表" + else + # 补上 jdeps 无法检测的反射/SPI依赖 + RAW_MODULES="$RAW_MODULES,java.desktop,jdk.crypto.ec" + fi + + echo "detected modules: $RAW_MODULES" + printf 'JDK_MODULES=%s\n' "$RAW_MODULES" >> $GITHUB_ENV + + # ============================================================ + # jlink 生成精简 JRE + # ============================================================ + - name: 生成精简 JRE(jlink) + run: | + jlink \ + --module-path "$JAVA_HOME/jmods" \ + --add-modules "$JDK_MODULES" \ + --output "native-package/netdisk-fast-download/jre" \ + --strip-debug \ + --compress=2 \ + --no-header-files \ + --no-man-pages + + echo "JRE size:" + du -sh native-package/netdisk-fast-download/jre || true + + # Windows: 确保 MSVC 运行时 DLL 到位 + if [[ "$RUNNER_OS" == "Windows" ]]; then + JRE_BIN="native-package/netdisk-fast-download/jre/bin" + for dll in vcruntime140.dll msvcp140.dll vcruntime140_1.dll; do + if [ ! -f "$JRE_BIN/$dll" ] && [ -f "$JAVA_HOME/bin/$dll" ]; then + echo "jlink 未包含 $dll,从 JDK 补拷" + cp "$JAVA_HOME/bin/$dll" "$JRE_BIN/" + fi + done + echo "=== JRE bin 目录 DLL 清单 ===" + ls -la "$JRE_BIN"/*.dll 2>/dev/null || echo "(无 .dll 文件)" + fi + + # ============================================================ + # 组装包目录 + # ============================================================ + - name: 组装包目录 + run: | + PKG="native-package/netdisk-fast-download" + SRC="web-service/target/package" + + cp "$SRC/netdisk-fast-download.jar" "$PKG/" + cp -r "$SRC/lib" "$PKG/" + cp -r "$SRC/resources" "$PKG/" + cp -r "$SRC/webroot" "$PKG/" + + mkdir -p "$PKG/db" + mkdir -p "$PKG/logs" + + # ============================================================ + # 生成启动脚本 + # ============================================================ + - name: 生成启动脚本(Linux) + run: | + PKG="native-package/netdisk-fast-download" + echo '#!/bin/bash' > "$PKG/run.sh" + echo 'DIR="$(cd "$(dirname "$0")" && pwd)"' >> "$PKG/run.sh" + echo 'cd "$DIR" || exit 1' >> "$PKG/run.sh" + echo 'exec "$DIR/jre/bin/java" -Xmx512M -Dfile.encoding=utf-8 -jar "$DIR/netdisk-fast-download.jar" "$@"' >> "$PKG/run.sh" + chmod +x "$PKG/run.sh" + + - name: 生成启动脚本(Windows) + run: | + PKG="native-package/netdisk-fast-download" + echo '@echo off' > "$PKG/run.bat" + echo 'chcp 65001 > nul' >> "$PKG/run.bat" + echo 'pushd %~dp0' >> "$PKG/run.bat" + echo '"%~dp0jre\bin\java.exe" -Xmx512M -Dfile.encoding=utf-8 -jar "%~dp0netdisk-fast-download.jar" %*' >> "$PKG/run.bat" + + # ============================================================ + # 打包为 zip + # ============================================================ + - name: 打包 ZIP(Linux) + if: runner.os == 'Linux' + run: | + cd native-package + zip -r "../${{ matrix.artifact-name }}.zip" netdisk-fast-download/ + + - name: 打包 ZIP(Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + Compress-Archive -Path native-package/netdisk-fast-download/* -DestinationPath "${{ matrix.artifact-name }}.zip" + + # ============================================================ + # 上传产物 + # ============================================================ + - name: 上传原生安装包 + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.artifact-name }} + path: ${{ matrix.artifact-name }}.zip + + - name: 上传到 Release + uses: softprops/action-gh-release@v2 + with: + files: ${{ matrix.artifact-name }}.zip + tag_name: ${{ github.ref_name }} diff --git a/.gitignore b/.gitignore index 6cfc6c6..9802f52 100644 --- a/.gitignore +++ b/.gitignore @@ -92,3 +92,4 @@ yarn-error.log* **/${project.build.directory}/ **/${project.basedir}/target/ **/${basedir}/target/ +.spec-workflow/ From d55d8edd2ffc8e490802f15d1e7f9f685f7068dc Mon Sep 17 00:00:00 2001 From: yukaidi Date: Sun, 31 May 2026 14:00:45 +0800 Subject: [PATCH 2/4] =?UTF-8?q?AppMain:=20=E5=90=AF=E5=8A=A8=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E5=A2=9E=E5=8A=A0=E5=89=8D=E7=AB=AF=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E8=AE=BF=E9=97=AE=E5=9C=B0=E5=9D=80=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/cn/qaiu/lz/AppMain.java | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/web-service/src/main/java/cn/qaiu/lz/AppMain.java b/web-service/src/main/java/cn/qaiu/lz/AppMain.java index 7e0d30a..3460dbc 100644 --- a/web-service/src/main/java/cn/qaiu/lz/AppMain.java +++ b/web-service/src/main/java/cn/qaiu/lz/AppMain.java @@ -21,6 +21,8 @@ import io.vertx.core.shareddata.LocalMap; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.time.DateFormatUtils; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Date; import static cn.qaiu.vx.core.util.ConfigConstant.LOCAL; @@ -78,12 +80,33 @@ public class AppMain { System.out.println("数据库连接成功"); // 加载演练场解析器 - loadPlaygroundParsers(); - String addr = jsonObject.getJsonObject(ConfigConstant.SERVER).getString("domainName"); if (addr == null || addr.isBlank()) { addr = "http://127.0.0.1:" + jsonObject.getJsonObject(ConfigConstant.SERVER).getInteger("port", 6400); } + // 读取代理配置获取前端页面端口(同步读文件,避免阻塞 event loop) + String proxyConfName = jsonObject.getString("proxyConf", "server-proxy"); + String pageAddr = addr; + try { + String configFile = proxyConfName + ".yml"; + Path configPath = Path.of("resources", configFile); + if (!Files.exists(configPath)) { + configPath = Path.of(configFile); + } + if (Files.exists(configPath)) { + String yamlContent = Files.readString(configPath); + java.util.regex.Matcher m = java.util.regex.Pattern + .compile("^\\s*-\\s+listen:\\s*(\\d+)", java.util.regex.Pattern.MULTILINE) + .matcher(yamlContent); + if (m.find()) { + int pagePort = Integer.parseInt(m.group(1)); + pageAddr = "http://127.0.0.1:" + pagePort; + } + } + } catch (Exception e) { + log.warn("读取代理配置失败,使用默认页面地址: {}", e.getMessage()); + } + loadPlaygroundParsers(pageAddr); System.out.println("启动成功: \n本地服务地址: " + addr); }); }); @@ -125,7 +148,7 @@ public class AppMain { /** * 在启动时加载所有已发布的演练场解析器 */ - private static void loadPlaygroundParsers() { + private static void loadPlaygroundParsers(String accessAddr) { DbService dbService = AsyncServiceUtil.getAsyncServiceInstance(DbService.class); dbService.getPlaygroundParserList().onSuccess(result -> { @@ -163,6 +186,7 @@ public class AppMain { } else { log.info("未找到已发布的演练场解析器"); } + log.info("服务已启动,可通过 {} 访问页面", accessAddr); }).onFailure(e -> { log.error("加载演练场解析器列表失败", e); }); From 0feb8e798a3a4c60eb2730f668aec0e7db0b6f4c Mon Sep 17 00:00:00 2001 From: yukaidi Date: Sun, 31 May 2026 16:18:12 +0800 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20PR#190=20review=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=20=E2=80=94=20=E9=85=8D=E7=BD=AE=E6=9F=A5=E6=89=BE=E9=A1=BA?= =?UTF-8?q?=E5=BA=8F/=E9=A1=B5=E9=9D=A2=E6=97=A5=E5=BF=97/ZIP=E7=BB=93?= =?UTF-8?q?=E6=9E=84/=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 配置文件查找顺序与 Deploy 保持一致(先当前目录,再 resources/) - 页面地址日志改用 onComplete,无论演练场加载成功失败均输出 - Windows ZIP 移除 /* 通配符,与 Linux 保持一致的顶层目录结构 - 修正注释:同步读文件会阻塞 event loop,不再声称'避免阻塞' - YAML 正则和 jdeps 回退列表补充适用范围说明 --- .github/workflows/maven.yml | 3 ++- web-service/src/main/java/cn/qaiu/lz/AppMain.java | 11 +++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index c355ba1..2ada315 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -163,6 +163,7 @@ jobs: --class-path "$CP" "$MAIN_JAR" 2>/dev/null | head -n 1 | tr -d '\r\n' || true) if [ -z "$RAW_MODULES" ] || [[ "$RAW_MODULES" == *"Missing"* ]] || [[ "$RAW_MODULES" == *"Error"* ]]; then + # ⚠️ 回退列表:若项目新增了需要 java.* / jdk.* 模块的依赖,需同步更新此处 RAW_MODULES="java.base,java.logging,java.sql,java.naming,java.management,java.xml,jdk.unsupported,java.net.http,java.instrument,java.security.jgss,java.security.sasl,java.desktop,jdk.crypto.ec" echo "jdeps 分析失败,使用回退模块列表" else @@ -252,7 +253,7 @@ jobs: if: runner.os == 'Windows' shell: pwsh run: | - Compress-Archive -Path native-package/netdisk-fast-download/* -DestinationPath "${{ matrix.artifact-name }}.zip" + Compress-Archive -Path native-package/netdisk-fast-download -DestinationPath "${{ matrix.artifact-name }}.zip" # ============================================================ # 上传产物 diff --git a/web-service/src/main/java/cn/qaiu/lz/AppMain.java b/web-service/src/main/java/cn/qaiu/lz/AppMain.java index 3460dbc..d60090c 100644 --- a/web-service/src/main/java/cn/qaiu/lz/AppMain.java +++ b/web-service/src/main/java/cn/qaiu/lz/AppMain.java @@ -84,17 +84,19 @@ public class AppMain { if (addr == null || addr.isBlank()) { addr = "http://127.0.0.1:" + jsonObject.getJsonObject(ConfigConstant.SERVER).getInteger("port", 6400); } - // 读取代理配置获取前端页面端口(同步读文件,避免阻塞 event loop) + // 读取代理配置获取前端页面端口(同步读取小文件,仅启动时执行一次) String proxyConfName = jsonObject.getString("proxyConf", "server-proxy"); String pageAddr = addr; try { String configFile = proxyConfName + ".yml"; - Path configPath = Path.of("resources", configFile); + // 与 Deploy 保持一致:优先当前目录,其次 resources/ + Path configPath = Path.of(configFile); if (!Files.exists(configPath)) { - configPath = Path.of(configFile); + configPath = Path.of("resources", configFile); } if (Files.exists(configPath)) { String yamlContent = Files.readString(configPath); + // 匹配项目约定的 YAML 格式: "- listen: 8080" java.util.regex.Matcher m = java.util.regex.Pattern .compile("^\\s*-\\s+listen:\\s*(\\d+)", java.util.regex.Pattern.MULTILINE) .matcher(yamlContent); @@ -186,9 +188,10 @@ public class AppMain { } else { log.info("未找到已发布的演练场解析器"); } - log.info("服务已启动,可通过 {} 访问页面", accessAddr); }).onFailure(e -> { log.error("加载演练场解析器列表失败", e); + }).onComplete(ar -> { + log.info("服务已启动,可通过 {} 访问页面", accessAddr); }); } } From dd8f2efb375aec95aa1bb1ab2c9e8390b4715552 Mon Sep 17 00:00:00 2001 From: yukaidi Date: Sun, 31 May 2026 16:39:13 +0800 Subject: [PATCH 4/4] =?UTF-8?q?ci:=20Release=20=E8=87=AA=E5=8A=A8=E7=94=9F?= =?UTF-8?q?=E6=88=90=E6=9B=B4=E6=96=B0=E8=AF=B4=E6=98=8E=EF=BC=88generate?= =?UTF-8?q?=5Frelease=5Fnotes=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/maven.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 2ada315..18710c2 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -269,3 +269,4 @@ jobs: with: files: ${{ matrix.artifact-name }}.zip tag_name: ${{ github.ref_name }} + generate_release_notes: true