diff --git a/README-en.md b/README-en.md index e6c70accfe34f5e236911a9fe59961ba3fdaf9a3..7d5c5793cbab2ae5469ba097bbf7b00d702e1232 100644 --- a/README-en.md +++ b/README-en.md @@ -37,13 +37,12 @@

-

- 👉 International Station:https://jpom.dromara.org 👈 -

👉Mainland Station:https://jpom.top/ 👈

+[https://jpom.top/pages/project-suspended/](https://jpom.top/pages/project-suspended/) + ## 😭 Do you experience these pain points in your daily development? - **No dedicated operations team, so developers have to handle operations tasks**, including manual project building and deployment. diff --git a/README.md b/README.md index 356846340543da4d29778232458c97314f9cbfe0..0f2b25ecb46630ccdf5a5624ae526d95d8cd6a87 100644 --- a/README.md +++ b/README.md @@ -40,14 +40,12 @@

-

- 👉 国际站:https://jpom.dromara.org 👈 -

-

👉 大陆站:https://jpom.top 👈

+[https://jpom.top/pages/project-suspended/](https://jpom.top/pages/project-suspended/) + ## 😭 日常开发中,您是否有以下痛点? - **团队中没有专业的运维,开发还要做运维的活**,需要自己手动构建、部署项目。 diff --git a/modules/server/src/main/java/io/jpom/JpomServerApplication.java b/modules/server/src/main/java/io/jpom/JpomServerApplication.java index 608bf89b9b5cd39dc9dff816453d99ad4221b251..394335d6aeabd1211eb1125f695e84480c459c53 100644 --- a/modules/server/src/main/java/io/jpom/JpomServerApplication.java +++ b/modules/server/src/main/java/io/jpom/JpomServerApplication.java @@ -5,7 +5,7 @@ * You may obtain a copy of Mulan PSL v2 at: * http://license.coscl.org.cn/MulanPSL2 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. + * See the Mulan PSL v2 for more details. */ package io.jpom; diff --git a/modules/server/src/main/java/io/jpom/system/ServerLogbackConfig.java b/modules/server/src/main/java/io/jpom/system/ServerLogbackConfig.java index d9ee6e7e19a41276858202ee95edee398b53224e..d7bec0dbbe98122e4f355b678233446b2e62af6f 100644 --- a/modules/server/src/main/java/io/jpom/system/ServerLogbackConfig.java +++ b/modules/server/src/main/java/io/jpom/system/ServerLogbackConfig.java @@ -5,7 +5,7 @@ * You may obtain a copy of Mulan PSL v2 at: * http://license.coscl.org.cn/MulanPSL2 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. + * See the Mulan PSL v2 for more details. */ package io.jpom.system; diff --git a/modules/server/src/main/java/org/dromara/jpom/JpomServerApplication.java b/modules/server/src/main/java/org/dromara/jpom/JpomServerApplication.java index 8e71c80467bfc519d8f0078389e371a8661f3b24..512ca4745f3145f3960c5efa98f2b09c6e21f400 100644 --- a/modules/server/src/main/java/org/dromara/jpom/JpomServerApplication.java +++ b/modules/server/src/main/java/org/dromara/jpom/JpomServerApplication.java @@ -5,7 +5,7 @@ * You may obtain a copy of Mulan PSL v2 at: * http://license.coscl.org.cn/MulanPSL2 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. + * See the Mulan PSL v2 for more details. */ package org.dromara.jpom; @@ -36,7 +36,7 @@ import org.springframework.context.ApplicationContext; public class JpomServerApplication { /** - * 启动执行 + * 启动执行 *

* --rest:ip_config 重置 IP 授权配置 *

diff --git a/modules/server/src/main/java/org/dromara/jpom/build/BuildExecuteManage.java b/modules/server/src/main/java/org/dromara/jpom/build/BuildExecuteManage.java index 410a0e4d3040ca3337f3147a9f1b39097c353d8c..3b2e4109ddf13e4b906b66cfa83c07cd6dcc5af7 100644 --- a/modules/server/src/main/java/org/dromara/jpom/build/BuildExecuteManage.java +++ b/modules/server/src/main/java/org/dromara/jpom/build/BuildExecuteManage.java @@ -485,6 +485,34 @@ public class BuildExecuteManage implements Runnable { logRecorder.systemError(format); throw new DiyInterruptException(format); } + // 文件路径级差异过滤:只有指定路径下的文件变更才触发构建 + if (StrUtil.isNotEmpty(repositoryLastCommitId)) { + String includePath = buildExtraModule.getDiffIncludePath(); + String excludePath = buildExtraModule.getDiffExcludePath(); + if (StrUtil.isNotEmpty(includePath) || StrUtil.isNotEmpty(excludePath)) { + List changedFiles = getGitChangedFiles(repositoryLastCommitId, result[0]); + if (changedFiles == null) { + logRecorder.system("获取变更文件列表失败,跳过文件级差异检查"); + } else if (changedFiles.isEmpty()) { + String format = StrUtil.format("两次提交间无文件变更: {} -> {}", repositoryLastCommitId, result[0]); + logRecorder.systemError(format); + throw new DiyInterruptException(format); + } else { + boolean matched = changedFiles.stream() + .anyMatch(f -> AntPathUtil.matchDiffPath(f, includePath, excludePath)); + if (!matched) { + String format = StrUtil.format("变更文件不在监听范围[include: {}, exclude: {}], 变更文件: {}", + includePath, excludePath, CollUtil.join(changedFiles, ", ")); + logRecorder.systemError(format); + throw new DiyInterruptException(format); + } + logRecorder.system("变更文件中匹配路径规则的文件: {}", + changedFiles.stream() + .filter(f -> AntPathUtil.matchDiffPath(f, includePath, excludePath)) + .collect(Collectors.joining(", "))); + } + } + } } taskData.repositoryLastCommitId = result[0]; taskData.repositoryLastCommitMsg = msg; @@ -504,6 +532,34 @@ public class BuildExecuteManage implements Runnable { logRecorder.systemError(format); throw new DiyInterruptException(format); } + // 文件路径级差异过滤:只有指定路径下的文件变更才触发构建 + if (StrUtil.isNotEmpty(repositoryLastCommitId)) { + String includePath = buildExtraModule.getDiffIncludePath(); + String excludePath = buildExtraModule.getDiffExcludePath(); + if (StrUtil.isNotEmpty(includePath) || StrUtil.isNotEmpty(excludePath)) { + List changedFiles = getSvnChangedFiles(repositoryLastCommitId, result[0]); + if (changedFiles == null) { + logRecorder.system("获取SVN变更文件列表失败,跳过文件级差异检查"); + } else if (changedFiles.isEmpty()) { + String format = StrUtil.format("两次提交间无文件变更: r{} -> r{}", repositoryLastCommitId, result[0]); + logRecorder.systemError(format); + throw new DiyInterruptException(format); + } else { + boolean matched = changedFiles.stream() + .anyMatch(f -> AntPathUtil.matchDiffPath(f, includePath, excludePath)); + if (!matched) { + String format = StrUtil.format("变更文件不在监听范围[include: {}, exclude: {}], 变更文件: {}", + includePath, excludePath, CollUtil.join(changedFiles, ", ")); + logRecorder.systemError(format); + throw new DiyInterruptException(format); + } + logRecorder.system("变更文件中匹配路径规则的文件: {}", + changedFiles.stream() + .filter(f -> AntPathUtil.matchDiffPath(f, includePath, excludePath)) + .collect(Collectors.joining(", "))); + } + } + } } taskData.repositoryLastCommitId = result[0]; taskData.repositoryLastCommitMsg = msg; @@ -540,6 +596,82 @@ public class BuildExecuteManage implements Runnable { return null; } + /** + * 获取Git两次提交间的变更文件列表 + * + * @param oldCommitId 旧提交hash + * @param newCommitId 新提交hash + * @return 以/开头的相对路径列表,失败返回null + */ + private List getGitChangedFiles(String oldCommitId, String newCommitId) { + try { + List lines = new ArrayList<>(); + int exitCode = CommandUtil.exec(this.gitFile, null, + line -> { + if (StrUtil.isNotEmpty(line)) { + lines.add(StrUtil.trim(line)); + } + }, + "git", "diff", "--name-only", oldCommitId, newCommitId); + if (exitCode != 0) { + logRecorder.system("git diff 退出码: {}", exitCode); + return null; + } + return lines.stream() + .filter(StrUtil::isNotEmpty) + .map(s -> FileUtil.normalize(StrUtil.SLASH + s)) + .collect(Collectors.toList()); + } catch (Exception e) { + logRecorder.system("获取Git变更文件异常: {}", e.getMessage()); + return null; + } + } + + /** + * 获取SVN两个版本间的变更文件列表 + *

+ * svn diff --summarize 输出格式示例: + *

+     * M       src/main/App.java
+     * A       src/main/NewFile.java
+     * D       docs/old.txt
+     * 
+ * 第一个字符为状态码(M/A/D/R),后面是空格+文件路径 + * + * @param oldRevision 旧版本号 + * @param newRevision 新版本号 + * @return 以/开头的相对路径列表,失败返回null + */ + private List getSvnChangedFiles(String oldRevision, String newRevision) { + try { + List lines = new ArrayList<>(); + int exitCode = CommandUtil.exec(this.gitFile, null, + line -> { + if (StrUtil.isNotEmpty(line)) { + lines.add(line); + } + }, + "svn", "diff", "--summarize", + "-r", oldRevision + ":" + newRevision); + if (exitCode != 0) { + logRecorder.system("svn diff 退出码: {}", exitCode); + return null; + } + return lines.stream() + .filter(s -> StrUtil.isNotEmpty(s) && s.length() > 1) + .map(s -> { + // 格式: "M path/to/file" - 跳过第一个状态字符,trim 得到纯路径 + String path = StrUtil.trim(StrUtil.subSuf(s, 1)); + return FileUtil.normalize(StrUtil.SLASH + path); + }) + .filter(s -> StrUtil.isNotEmpty(s) && s.length() > 1) + .collect(Collectors.toList()); + } catch (Exception e) { + logRecorder.system("获取SVN变更文件异常: {}", e.getMessage()); + return null; + } + } + private String dockerCommand() { BuildInfoModel buildInfoModel = taskData.buildInfoModel; String script = buildInfoModel.getScript(); diff --git a/modules/server/src/main/java/org/dromara/jpom/build/BuildExtraModule.java b/modules/server/src/main/java/org/dromara/jpom/build/BuildExtraModule.java index d46f59e0b54c99d08c979fe2479b96d6640fd24e..cd282ed210870d74c70a10f4abe6e7f68746855f 100644 --- a/modules/server/src/main/java/org/dromara/jpom/build/BuildExtraModule.java +++ b/modules/server/src/main/java/org/dromara/jpom/build/BuildExtraModule.java @@ -109,6 +109,20 @@ public class BuildExtraModule extends BaseModel { */ private Boolean checkRepositoryDiff; + /** + * 差异构建 — 包含路径规则 (Ant风格,逗号分隔多个) + * 只有变更文件匹配这些规则时才触发构建,为空表示所有文件变更都触发 + * 例如: /modules/server/**,/modules/common/**,/pom.xml + */ + private String diffIncludePath; + + /** + * 差异构建 — 排除路径规则 (Ant风格,逗号分隔多个) + * 匹配这些规则的变更文件即使匹配了includePath也会被排除 + * 例如: /**/*.md + */ + private String diffExcludePath; + /** * 事件通知执行的脚本 ID */ diff --git a/modules/server/src/main/java/org/dromara/jpom/common/ServerExceptionHandler.java b/modules/server/src/main/java/org/dromara/jpom/common/ServerExceptionHandler.java index 5b8f4382fba4c069dde2590383f17ebd2ab57814..c0e5daeff66be2770934e020f2953c741f9c3701 100644 --- a/modules/server/src/main/java/org/dromara/jpom/common/ServerExceptionHandler.java +++ b/modules/server/src/main/java/org/dromara/jpom/common/ServerExceptionHandler.java @@ -25,7 +25,7 @@ import javax.servlet.http.HttpServletRequest; /** * 全局异常处理 * - * @author bwcx_jzy + * @author bwcx_jzy * @since 2019/04/17 */ @RestControllerAdvice diff --git a/modules/server/src/main/java/org/dromara/jpom/util/AntPathUtil.java b/modules/server/src/main/java/org/dromara/jpom/util/AntPathUtil.java index 886defc4a1fe0c3fc52dd6ca9b9bc211d2f7c811..b131193ff718010deec8f5639464be28b8e676ae 100644 --- a/modules/server/src/main/java/org/dromara/jpom/util/AntPathUtil.java +++ b/modules/server/src/main/java/org/dromara/jpom/util/AntPathUtil.java @@ -59,4 +59,58 @@ public class AntPathUtil { }); return paths; } + + /** + * 判断路径是否匹配任一Ant规则 + *

+ * 支持直接配置目录路径(如 /modules/server),会自动尝试目录下的所有文件匹配。 + * 即配置 /modules/server 等价于同时匹配 /modules/server 和 /modules/server/** + * + * @param path 以/开头的文件路径 + * @param rules 逗号分隔的Ant规则,null或空返回false + * @return true 如果匹配任一规则 + */ + public static boolean matchAny(String path, String rules) { + if (StrUtil.isEmpty(rules)) { + return false; + } + List ruleList = StrUtil.splitTrim(rules, StrUtil.COMMA); + for (String rule : ruleList) { + String normalizedRule = FileUtil.normalize(StrUtil.SLASH + rule); + if (ANT_PATH_MATCHER.match(normalizedRule, path)) { + return true; + } + // 如果规则不含通配符,自动尝试作为目录匹配(追加 /**) + // 这样用户配置 /modules/server 就能匹配该目录下所有文件 + if (!StrUtil.containsAny(normalizedRule, "*", "?", "{")) { + String dirRule = normalizedRule + "/**"; + if (ANT_PATH_MATCHER.match(dirRule, path)) { + return true; + } + } + } + return false; + } + + /** + * 差异路径过滤:include匹配 + exclude排除 + *

+ * includePath为空 → 通过(向后兼容) + * includePath非空 → 至少匹配一个 → 再检查excludePath排除 + * + * @param path 变更文件的仓库相对路径 + * @param includePath 包含规则(逗号分隔),null或空表示不过滤 + * @param excludePath 排除规则(逗号分隔),null或空表示不排除 + * @return true 表示该文件变更有效(触发构建) + */ + public static boolean matchDiffPath(String path, String includePath, String excludePath) { + String normalizedPath = FileUtil.normalize(StrUtil.SLASH + path); + if (StrUtil.isNotEmpty(includePath) && !matchAny(normalizedPath, includePath)) { + return false; + } + if (StrUtil.isNotEmpty(excludePath) && matchAny(normalizedPath, excludePath)) { + return false; + } + return true; + } } diff --git a/web-vue/src/i18n/locales/en_us.json b/web-vue/src/i18n/locales/en_us.json index 4ff062766d4519f57c5217e88f39121aa5bafde6..99677f171b151bc50e71595381030f42a13ce305 100644 --- a/web-vue/src/i18n/locales/en_us.json +++ b/web-vue/src/i18n/locales/en_us.json @@ -109,6 +109,12 @@ "i18n_0af04cdc22":"Supports filling in two ways:", "i18n_0af5d9f8e8":"The current area is a system management and asset management center", "i18n_0b23d2f584":"differential construction", + "i18n_0b23d2f584_include":"Watch Path", + "i18n_0b23d2f584_exclude":"Exclude Path", + "i18n_diff_include_desc":"Directory path or Ant-style wildcards, comma separated. Build only when changed files match. Empty = watch all", + "i18n_diff_include_ph":"e.g.: /modules/server,/modules/common,/pom.xml", + "i18n_diff_exclude_desc":"Directory path or Ant-style wildcards, comma separated. Changed files matching these are excluded", + "i18n_diff_exclude_ph":"e.g.: /docs,/**/*.md", "i18n_0b2fab7493":"For the current SSH authorization directory (file directory, file suffix, prohibit command), please go to [System Management] - > [Asset Management] - > [SSH Management] - > Operation Bar - > Association Button - > Corresponding Workspace - > Operation Bar - > Configuration Button", "i18n_0b3edfaf28":"Set memory limits.", "i18n_0b58866c3e":"Breakpoint/sharding single file download", diff --git a/web-vue/src/i18n/locales/zh_cn.json b/web-vue/src/i18n/locales/zh_cn.json index 918c628037b62924c7263fee1648556489c9c546..11afd90eca8adc5e3692e34b5c25fe609f4e9f88 100644 --- a/web-vue/src/i18n/locales/zh_cn.json +++ b/web-vue/src/i18n/locales/zh_cn.json @@ -109,6 +109,12 @@ "i18n_0af04cdc22": "支持两种方式填充:", "i18n_0af5d9f8e8": "当前区域为系统管理、资产管理中心", "i18n_0b23d2f584": "差异构建", + "i18n_0b23d2f584_include": "监听路径", + "i18n_0b23d2f584_exclude": "排除路径", + "i18n_diff_include_desc": "支持目录路径或Ant风格通配,逗号分隔多个。仅当变更文件匹配时才触发构建。为空=全部监听", + "i18n_diff_include_ph": "如: /modules/server,/modules/common,/pom.xml", + "i18n_diff_exclude_desc": "支持目录路径或Ant风格通配,逗号分隔。匹配的变更文件将被排除", + "i18n_diff_exclude_ph": "如: /docs,/**/*.md", "i18n_0b2fab7493": "当前 SSH 的授权目录(文件目录、文件后缀、禁止命令)需要请到 【系统管理】-> 【资产管理】-> 【SSH 管理】-> 操作栏中->关联按钮->对应工作空间->操作栏中->配置按钮", "i18n_0b3edfaf28": "设置内存限制。", "i18n_0b58866c3e": "断点/分片单文件下载", diff --git a/web-vue/src/pages/build/edit.vue b/web-vue/src/pages/build/edit.vue index 144e48cc5c3ef50910d7658befdf45e34fd04479..bbb57a2f053fffda6888a79700d90009a01507fc 100644 --- a/web-vue/src/pages/build/edit.vue +++ b/web-vue/src/pages/build/edit.vue @@ -963,6 +963,38 @@ + + + + + + + +