任务
实验室开发官网,简化纳新流程,减少纳新负担。在系统后端(项目地址)需要实现的功能之一就是通过JGit从Github及Gitee拉取代码。
该部分第一阶段要求如下:
本系统需要从远程仓库上拉取代码,进行拉取、查看、构建、运行,并进行管理。需要支持
远程仓库:GitHub、Gitee
支持Clone方式:HTTPS、SSH
可以通过Github / Gitee用户信息,克隆public或有权限的private仓库
通过配置代理或者其他方案,加速与GitHub的连接速度
完成之后,需要:
一个完善的工具类和静态方法,用于完成以上逻辑
一个RestFul Api接口,访问该接口即可以执行拉取代码逻辑(之后会安排前端对接口)
必须有完善的代码规范和异常处理,需要把异常处理情景返回给前端。(如仓库不存在 / 没有访问权限 / 网络问题clone失败 / 登录用户信息错误 / 连接超时……)
实现
config.GitHubProxySelector
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.SocketAddress;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
public class GitHubProxySelector extends ProxySelector {
private static final Logger LOGGER = Logger.getLogger(GitHubProxySelector.class.getName());
private final Proxy githubProxy;
public GitHubProxySelector(String proxyHost, int proxyPort) {
this.githubProxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort));
}
@Override
public List<Proxy> select(URI uri) {
// 检查 URI 是否指向 GitHub
if (isGitHubUri(uri)) {
// 如果是,则返回 GitHub 代理
return Collections.singletonList(githubProxy);
} else {
// 否则,返回默认的代理(通常是无代理)
return Collections.singletonList(Proxy.NO_PROXY);
}
}
@Override
public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
// 连接失败时的处理逻辑(可选)
LOGGER.log(Level.SEVERE, "Failed to connect to " + uri + " via " + sa);
}
// 私有方法,用于检查 URI 是否指向 GitHub
private boolean isGitHubUri(URI uri) {
// 这里只是一个简单的示例,只检查主机名是否包含 "github.com"
// 在实际应用中,你可能需要更精确的检查逻辑
return uri.getHost() != null && uri.getHost().contains("github.com");
}
// 静态方法,用于设置默认的 ProxySelector
public static void setDefaultGitHubProxy(String proxyHost, int proxyPort) {
ProxySelector.setDefault(new GitHubProxySelector(proxyHost, proxyPort));
}
}
controller.JGitController
import com.example.jgitdemo.entity.CloneRequest;
import com.example.jgitdemo.utils.JGitUtils;
import com.example.jgitdemo.utils.MavenUtils;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.net.MalformedURLException;
@RestController
@RequestMapping
public class JGitController {
@PostMapping("/clone")
public String cloneRepo(@RequestBody CloneRequest cloneRequest) throws GitAPIException, IOException {
JGitUtils jGitUtils = new JGitUtils();
return jGitUtils.cloneRepository(cloneRequest.getRepoUrl(), cloneRequest.getLocalPath(), cloneRequest.getUsername(), cloneRequest.getPassword(), cloneRequest.isUseSsh(), cloneRequest.getPrivateKeyPath());
}
@PostMapping("/build")
public String buildAndRun(@RequestBody CloneRequest cloneRequest)
{
MavenUtils mavenUtils = new MavenUtils();
return mavenUtils.buildAndRun(cloneRequest.getLocalPath());
}
}
entity.CloneRequest
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CloneRequest {
private String repoUrl;
private String localPath;
private String username;
private String password;
private boolean useSsh;
private String privateKeyPath;
}
utils.JGitUtils
import com.example.jgitdemo.config.GitHubProxySelector;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.TransportException;
import org.eclipse.jgit.errors.UnsupportedCredentialItem;
import org.eclipse.jgit.transport.*;
import org.eclipse.jgit.util.FS;
import java.io.File;
import java.io.IOException;
@Slf4j
public class JGitUtils {
// private final String repoUrl = "https://github.com/Werun-backend/anser-demo.git";
// private final String localPath = "D:\\IJ\\IJProjects\\anser\\demosPull";
public String cloneRepository(String repoUrl, String localPath, String username, String password, boolean useSsh,String privateKeyPath) throws GitAPIException, IOException {
// System.setProperty("https.proxyHost", "140.82.113.4");
File localDir = new File(localPath);
if (localDir.exists()) {
return "本地仓库已存在";
}
try {
Git git ;
if (useSsh) {
log.info("cloning from use ssh :{}", repoUrl);
// 使用 SSH 克隆仓库
git = Git.cloneRepository()
.setURI(repoUrl)
.setTransportConfigCallback(transport -> {
SshTransport sshTransport = (SshTransport) transport;
sshTransport.setSshSessionFactory(createSshSessionFactory(privateKeyPath));
})
.setDirectory(localDir)
.setCloneAllBranches(true)
.call();
log.info("clone success");
git.close();
} else {
log.info("cloning from use username and password:{}", repoUrl);
//TODO:这里配代理,由于暂时没有我在这里把他设置为本机的Clash默认端口
GitHubProxySelector.setDefaultGitHubProxy("127.0.0.1",7890);
// 使用 HTTPS 克隆仓库
if (username != null && !username.isEmpty() && password != null && !password.isEmpty()) {
// 用户名和密码提供时,设置认证
git = Git.cloneRepository()
.setURI(repoUrl)
.setCredentialsProvider(new UsernamePasswordCredentialsProvider(username, password))
.setDirectory(localDir)
.call();
log.info("clone success");
} else {
log.info("cloning from public repo:{}", repoUrl);
// 公共仓库,不需要认证
git = Git.cloneRepository()
.setURI(repoUrl)
.setDirectory(localDir)
.call();
log.info("clone success");
}
}
return "克隆成功:" + git.getRepository().getDirectory().getAbsolutePath();
} catch (TransportException e) {
log.error("克隆失败 - 网络问题或仓库不可达: {}", e.getMessage());
return "克隆失败 - 网络问题或仓库不可达";
} catch (GitAPIException e) {
log.error("克隆失败 - Git操作错误: {}", e.getMessage());
return "克隆失败 - Git操作错误";
} catch (UnsupportedCredentialItem e) {
log.error("克隆失败 - 登录信息错误: {}", e.getMessage());
return "克隆失败 - 登录信息错误";
} catch (Exception e) {
log.error("克隆失败 - 其他错误: {}", e.getMessage());
return "克隆失败 - 其他错误";
}
}
private static SshSessionFactory createSshSessionFactory(String privateKeyPath) {
return new JschConfigSessionFactory() {
@Override
protected void configure(OpenSshConfig.Host hc, Session session) {
session.setConfig("StrictHostKeyChecking", "no");
// session.setProxy(new ProxySOCKS5("your.proxy.host", proxy_port));
}
@Override
protected JSch createDefaultJSch(FS fs) throws JSchException {
JSch jSch = super.createDefaultJSch(fs);
jSch.addIdentity(privateKeyPath);
return jSch;
}
};
}
}
utils.MavenUtils
import lombok.extern.slf4j.Slf4j;
import org.apache.maven.shared.invoker.*;
import java.io.File;
import java.util.Collections;
@Slf4j
public class MavenUtils {
public String buildAndRun(String localPath){
Invoker invoker = new DefaultInvoker();
invoker.setMavenHome(new File("D:\\maven\\apache-maven-3.6.1"));
InvocationRequest request = new DefaultInvocationRequest();
request.setPomFile(new File(localPath + "\\pom.xml"));
request.setGoals(Collections.singletonList("clean install"));
try {
InvocationResult result = invoker.execute(request);
if (result.getExitCode() == 0) {
log.info("Maven build successful.");
// 运行 Spring Boot 应用
return runSpringBoot(localPath);
} else {
return "Maven build failed.";
}
} catch (MavenInvocationException e) {
return "Maven invocation failed: " + e.getMessage();
}
}
JGitDemoApplication
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import java.io.IOException;
import java.net.*;
import java.util.Arrays;
import java.util.List;
@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})
public class JGitDemoApplication {
public static void main(String[] args) {
ProxySelector.setDefault(new ProxySelector() {
final ProxySelector delegate = ProxySelector.getDefault();
@Override
public List<Proxy> select(URI uri) {
// Filter the URIs to be proxied
if (uri.toString().contains("github")
&& uri.toString().contains("https")) {
return Arrays.asList(new Proxy(Proxy.Type.HTTP, InetSocketAddress
.createUnresolved("207.148.73.152", 443)));
}
// revert to the default behaviour
return delegate == null ? Arrays.asList(Proxy.NO_PROXY)
: delegate.select(uri);
}
@Override
public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
if (uri == null || sa == null || ioe == null) {
throw new IllegalArgumentException(
"Arguments can't be null.");
}
}
});
SpringApplication.run(JGitDemoApplication.class, args);
}
}
注意/问题
问题一
由于Github不支持rsa1加密的key,且jsch不能够解析高版本ssh,所以ssh的生成必须使用以下命令
ssh-keygen -t ecdsa -m PEM
见博客:https://www.cnblogs.com/jjjhs/p/17503077.html
问题二
由于github不支持远程密码登陆,所以需要使用token,先在github中生成token令牌,复制并将其设置为git密码
git config --global user.password "token"