All checks were successful
Publish to Confluence / confluence (push) Successful in 52s
503 lines
18 KiB
Markdown
503 lines
18 KiB
Markdown
<!-- 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);
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
``` |