keyfil/文档/技术实现/20250613-Java使用Maven及Gitlab.md
liuxiaohua 26a842b04f
All checks were successful
Publish to Confluence / confluence (push) Successful in 52s
[2025-06-17] 完善文档
2025-06-17 18:12:00 +08:00

503 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- Space: qifu -->
<!-- Parent: 后端技术&知识&规范 -->
<!-- Parent: 技术方案 -->
<!-- Parent: 基建 -->
<!-- Parent: 07-技术实现 -->
<!-- Title: 20250613-Java使用Maven及Gitlab -->
<!-- Macro: :anchor\((.*)\):
Template: ac:anchor
Anchor: ${1} -->
<!-- Macro: \!\[.*\]\((.+)\)\<\!\-\- width=(.*) \-\-\>
Template: ac:image
Url: ${1}
Width: ${2} -->
<!-- Macro: \<\!\-\- :toc: \-\-\>
Template: ac:toc
Printable: 'false'
MinLevel: 2
MaxLevel: 4 -->
<!-- Include: 杂项/声明文件.md -->
<!-- :toc: -->
# Java使用Maven及Gitlab
## Java使用Maven
### 所需依赖
```xml
<dependencies>
<!-- Maven Embedder 核心 -->
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-embedder</artifactId>
<version>3.9.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.maven/maven-compat -->
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-compat</artifactId>
<version>3.9.6</version>
</dependency>
<!-- 网络传输支持 -->
<dependency>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-transport-http</artifactId>
<version>1.9.18</version>
</dependency>
<dependency>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-connector-basic</artifactId>
<version>1.9.18</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.maven/maven-resolver-provider -->
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-resolver-provider</artifactId>
<version>3.9.6</version>
</dependency>
<!-- 配置文件支持 -->
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-settings-builder</artifactId>
<version>3.9.6</version>
</dependency>
</dependencies>
```
### 使用Archetype生成项目
```java
package org.jeecg.modules.devops.archetype;
import lombok.extern.slf4j.Slf4j;
import org.apache.maven.cli.MavenCli;
import org.jeecg.modules.devops.model.YuanMengOnlineService;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@Slf4j
public class MavenUtils {
private static final String ARCHETYPE_GROUP_ID = "com.yuanmeng.engine";
private static final String ARCHETYPE_ARTIFACT_ID = "engine-archetype-web";
private static final String ARCHETYPE_VERSION = "2.0.2-SNAPSHOT";
public static Path createProject(YuanMengOnlineService archetypeInfo) {
try {
// 配置项目目录
Path projectDir = Files.createTempDirectory(archetypeInfo.getArchetypeArtifactId());
log.info("项目生成目录: {}", projectDir);
// 创建 settings.xml 文件
Path settingsFile = createTempSettingsFile();
// 下面这行代码不能缺少,否则无法执行
configureSystemProperties(settingsFile);
// 使用 Maven Embedder 生成项目
generateMavenProject(projectDir.toFile(), settingsFile, archetypeInfo);
Path path = projectDir.resolve(archetypeInfo.getArchetypeArtifactId());
log.info("✅ 项目成功创建于: {}", path);
return path;
} catch (Exception e) {
log.error("❌ 出现错误: {}, {}", e.getMessage(), archetypeInfo, e);
throw new RuntimeException("项目初始化失败:" + e.getMessage());
}
}
private static void generateMavenProject(File baseDir, Path settingsFile, YuanMengOnlineService archetypeInfo) {
// 构建 Maven 参数
String[] mvnArgs = new String[]{
"archetype:generate",
"-DinteractiveMode=false",
"-DarchetypeGroupId=" + ARCHETYPE_GROUP_ID,
"-DarchetypeArtifactId=" + ARCHETYPE_ARTIFACT_ID,
"-DarchetypeVersion=" + ARCHETYPE_VERSION,
"-DgroupId=" + archetypeInfo.getArchetypeGroupId(),
"-DartifactId=" + archetypeInfo.getArchetypeArtifactId(),
"-Dversion=" + archetypeInfo.getArchetypeVersion(),
"-Dapp=" + archetypeInfo.getArchetypeApp(),
"-Dpackage=" + archetypeInfo.getArchetypePackage(),
"-B", // 开启批处理模式
"-s", settingsFile.toString()
};
log.info("Maven 项目生成指令:{}", String.join(" ", mvnArgs));
// 使用 Maven Embedder
MavenCli cli = new MavenCli();
int result = cli.doMain(mvnArgs, baseDir.getAbsolutePath(), System.out, System.err);
if (result != 0) {
throw new RuntimeException("Maven 项目生成失败 (退出码: " + result + ")");
}
log.info("✅ Maven 项目生成成功");
}
private static Path createTempSettingsFile() {
try {
// 创建临时 settings.xml 文件
Path settingsPath = Files.createTempFile("maven-settings", ".xml");
// 构建 settings.xml 内容
String settingsContent = buildSettingsXml();
// 写入文件
Files.write(settingsPath, settingsContent.getBytes());
// 设置文件删除钩子(程序退出时删除)
settingsPath.toFile().deleteOnExit();
return settingsPath;
} catch (IOException e) {
throw new RuntimeException("Failed to create temp settings.xml", e);
}
}
private static String buildSettingsXml() {
return "<settings xmlns=\"http://maven.apache.org/SETTINGS/1.2.0\"\n" +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
" xsi:schemaLocation=\"http://maven.apache.org/SETTINGS/1.2.0 https://maven.apache.org/xsd/settings-1.2.0.xsd\">\n" +
" <localRepository>/tmp/repository</localRepository>\n" +
" <servers>\n" +
" <server>\n" +
" <id>private-repo</id>\n" +
" <username>developer</username>\n" +
" <password>deve@123</password>\n" +
" </server>\n" +
" </servers>\n" +
" <mirrors>\n" +
" <mirror>\n" +
" <id>private-repo</id>\n" +
" <name>Private Repository Mirror</name>\n" +
" <url>http://120.79.68.191:8081/repository/maven-public/</url>\n" +
" <mirrorOf>*</mirrorOf>\n" +
" </mirror>\n" +
" </mirrors>\n" +
" <profiles>\n" +
" <profile>\n" +
" <id>default</id>\n" +
" <repositories>\n" +
" <repository>\n" +
" <id>private-repo</id>\n" +
" <url>http://120.79.68.191:8081/repository/maven-public/</url>\n" +
" <releases><enabled>true</enabled></releases>\n" +
" <snapshots><enabled>true</enabled></snapshots>\n" +
" </repository>\n" +
" </repositories>\n" +
" <pluginRepositories>\n" +
" <pluginRepository>\n" +
" <id>private-repo</id>\n" +
" <url>http://120.79.68.191:8081/repository/maven-public/</url>\n" +
" <releases><enabled>true</enabled></releases>\n" +
" <snapshots><enabled>true</enabled></snapshots>\n" +
" </pluginRepository>\n" +
" </pluginRepositories>\n" +
" </profile>\n" +
" </profiles>\n" +
" <activeProfiles>\n" +
" <activeProfile>default</activeProfile>\n" +
" </activeProfiles>\n" +
"</settings>";
}
private static void configureSystemProperties(Path settingsPath) {
System.setProperty("maven.multiModuleProjectDirectory", System.getProperty("user.dir"));
// 使用自定义 settings.xml
System.setProperty("maven.user.settings", settingsPath.toString());
// 配置超时(可选)
System.setProperty("maven.wagon.http.readTimeout", "300000"); // 5分钟
System.setProperty("maven.wagon.http.connectionTimeout", "120000");
// 禁用 SSL 验证(如果私服使用 HTTP
System.setProperty("maven.wagon.http.ssl.insecure", "true");
System.setProperty("maven.wagon.http.ssl.allowall", "true");
// 启用调试日志(可选)
System.setProperty("org.slf4j.simpleLogger.log.org.apache.maven.cli", "info");
}
}
```
----
## Java操作Git及Gitlab
### 依赖引入
```xml
<dependencies>
<!-- https://mvnrepository.com/artifact/org.eclipse.jgit/org.eclipse.jgit -->
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit</artifactId>
<version>6.10.1.202505221210-r</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.gitlab4j/gitlab4j-api -->
<dependency>
<groupId>org.gitlab4j</groupId>
<artifactId>gitlab4j-api</artifactId>
<version>5.3.0</version>
</dependency>
</dependencies>
```
### Java代码调用
#### Gitlab调用工具类
```java
package org.jeecg.modules.devops.archetype;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.gitlab4j.api.GitLabApi;
import org.gitlab4j.api.GitLabApiException;
import org.gitlab4j.api.models.*;
import java.util.List;
import java.util.Objects;
/**
* insert description here
*
* @author liuxiaohua
* @since 2025-06-11
*/
@Slf4j
public class GitlabUtils {
private static volatile GitLabApi gitLabApi;
public static synchronized void init(ArchetypeProperties archetypeProperties) {
if (gitLabApi == null) {
synchronized (GitlabUtils.class) {
if (gitLabApi == null) {
gitLabApi = new GitLabApi(archetypeProperties.getGitlabUrl(), archetypeProperties.getAccessToken());
}
}
}
}
@SneakyThrows
public static void deleteProject(Long projectId) {
gitLabApi.getProjectApi().deleteProject(projectId);
}
@SneakyThrows
public static Project createProject(String group, String project, String desc) {
Group groupInfo = getGroupId(group);
if (Objects.isNull(groupInfo) || Objects.isNull(groupInfo.getId())) {
throw new RuntimeException("获取分组信息失败");
}
Project gitLabRepository = createRepository(groupInfo, project, desc);
if (gitLabRepository == null || gitLabRepository.getId() == null) {
throw new RuntimeException("创建仓库失败");
}
return gitLabRepository;
}
@SneakyThrows
public static ProtectedBranch protectedBranch(Long projectId, Integer retryTimes) {
try {
// 获取已保护的分支
List<ProtectedBranch> protectedBranches = gitLabApi.getProtectedBranchesApi().getProtectedBranches(projectId);
log.info("protected branches: {}", JSONObject.toJSONString(protectedBranches));
// 保护分支
if (CollUtil.isNotEmpty(protectedBranches)
&& protectedBranches.stream().map(ProtectedBranch::getName).anyMatch("master"::equalsIgnoreCase)) {
// 取消旧的配置
unprotectedBranch(projectId);
}
// 添加分支保护
return gitLabApi.getProtectedBranchesApi()
.protectBranch(projectId, "master", AccessLevel.NONE, AccessLevel.MAINTAINER);
} catch (GitLabApiException e) {
log.warn("protected branch failed: {}", e.getMessage());
}
if (retryTimes < 3) {
return protectedBranch(projectId, ++retryTimes);
}
log.warn("protected branch failure: {}", retryTimes);
throw new RuntimeException(ArchetypeUtils.WARN_MESSAGE_1);
}
private static void unprotectedBranch(Long projectId) {
try {
gitLabApi.getProtectedBranchesApi().unprotectBranch(projectId, "master");
log.info("unprotected branch master success");
} catch (Exception e) {
log.warn("unprotected branch error: {}", e.getMessage());
}
}
/**
* 根据分组路径获取分组ID
*/
private static Group getGroupId(String groupPath) throws GitLabApiException {
return gitLabApi.getGroupApi().getOptionalGroup(groupPath).orElse(null);
}
/**
* 在 GitLab 创建新仓库
*/
private static Project createRepository(Group group,
String projectName,
String desc) {
Project exists = gitLabApi.getProjectApi().getOptionalProject(group.getName(), projectName).orElse(null);
if (Objects.nonNull(exists)) {
throw new RuntimeException("项目已经存在");
}
try {
Project project = new Project()
.withName(projectName)
.withDefaultBranch("master")
.withDescription(desc)
.withNamespaceId(group.getId())
.withVisibility(Visibility.PRIVATE); // 私有库用 PRIVATE
return gitLabApi.getProjectApi().createProject(project); // 返回 HTTPS 仓库地址
} catch (Exception e) {
log.warn("create project error: {}", e.getMessage());
}
return null;
}
public static ProjectHook addWebhook(Long projectId, String webhookUrl, Integer retryTimes) {
try {
// 配置 webhook
ProjectHook projectHook = gitLabApi.getProjectApi().addHook(
projectId, webhookUrl, Boolean.FALSE, Boolean.FALSE, Boolean.TRUE);
log.info("add project hook: {}", JSONObject.toJSONString(projectHook));
return projectHook;
} catch (GitLabApiException e) {
log.warn("add project hook failed: {}", e.getMessage());
}
if (retryTimes < 3) {
return addWebhook(projectId, webhookUrl, ++retryTimes);
}
log.warn("add project hook failure: {}", retryTimes);
throw new RuntimeException(ArchetypeUtils.WARN_MESSAGE_2);
}
public static Member addMembers(Long projectId, String email, Integer retryTimes) {
try {
if (StrUtil.isBlank(email)) {
email = "liuxiaohua@keyfil.com";
}
if (!email.endsWith("@keyfil.com")) {
email += "@keyfil.com";
}
User userByEmail = gitLabApi.getUserApi().getOptionalUserByEmail(email).orElse(null);
if (userByEmail == null) {
log.warn("get user by email error");
throw new RuntimeException(ArchetypeUtils.WARN_MESSAGE_3);
}
log.info("add members user: {}", JSONObject.toJSONString(userByEmail));
Member member = gitLabApi.getProjectApi().getOptionalMember(projectId, userByEmail.getId()).orElse(null);
log.info("member exists: {}", JSONObject.toJSONString(member));
if (Objects.nonNull(member)) {
if (member.getAccessLevel().value < AccessLevel.MAINTAINER.value) {
member = gitLabApi.getProjectApi().updateMember(projectId, userByEmail.getId(), AccessLevel.MAINTAINER);
}
} else {
member = gitLabApi.getProjectApi()
.getOptionalMember(projectId, userByEmail.getId(), Boolean.TRUE)
.orElse(null);
if (Objects.isNull(member) || member.getAccessLevel().value < AccessLevel.MAINTAINER.value) {
member = gitLabApi.getProjectApi().addMember(projectId, userByEmail.getId(), AccessLevel.MAINTAINER);
}
}
return member;
} catch (GitLabApiException e) {
log.warn("add members user: failed: {}", e.getMessage());
}
if (retryTimes < 3) {
return addMembers(projectId, email, ++retryTimes);
}
log.warn("add members user failure: {}", retryTimes);
throw new RuntimeException(ArchetypeUtils.WARN_MESSAGE_3);
}
}
```
#### Git调用工具类
```java
package org.jeecg.modules.devops.archetype;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.springframework.stereotype.Component;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* insert description here
*
* @author liuxiaohua
* @since 2025-06-11
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class GitUtils {
/**
* 初始化本地仓库并推送到 GitLab
*/
public static void pushLocalCodeToGitLab(String remoteUrl,
String codePath,
ArchetypeProperties archetypeProperties) {
File localPath = new File(codePath);
CredentialsProvider credentials = new UsernamePasswordCredentialsProvider(
archetypeProperties.getUsername(), archetypeProperties.getAccessToken());
try (Git git = Git.init().setDirectory(localPath).call()) {
// 添加所有文件
git.add().addFilepattern(".").call();
// 提交更改
git.commit()
.setMessage(String.format("\uD83C\uDF89 [%s] 初始化提交",
new SimpleDateFormat("yyyy-MM-dd").format(new Date())))
.call();
// 添加远程仓库
git.remoteAdd()
.setName("origin")
.setUri(new URIish(remoteUrl))
.call();
// 推送到 GitLab
git.push()
.setRemote("origin")
.setCredentialsProvider(credentials)
.setForce(true)
.setPushAll()
.call();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
```