瀏覽代碼

!40 git store插件
Merge pull request !40 from Sor1e/sorie_2.x_git2

小东 3 年之前
父節點
當前提交
b3ba2ae4ac

+ 30 - 0
magic-api-plugins/magic-api-plugin-git-store/pom.xml

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.ssssssss</groupId>
+        <artifactId>magic-api-plugins</artifactId>
+        <version>2.0.0-alpha.4</version>
+    </parent>
+    <artifactId>magic-api-plugin-git-store</artifactId>
+    <packaging>jar</packaging>
+    <name>magic-api-plugin-git-store</name>
+    <description>magic-api-plugin-git-store</description>
+    <properties>
+        <jgit.version>5.13.0.202109080827-r</jgit.version>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>org.eclipse.jgit</groupId>
+            <artifactId>org.eclipse.jgit</artifactId>
+            <version>${jgit.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jgit</groupId>
+            <artifactId>org.eclipse.jgit.ssh.jsch</artifactId>
+            <version>${jgit.version}</version>
+        </dependency>
+    </dependencies>
+</project>

+ 160 - 0
magic-api-plugins/magic-api-plugin-git-store/src/main/java/org/ssssssss/magicapi/git/GitRepo.java

@@ -0,0 +1,160 @@
+package org.ssssssss.magicapi.git;
+
+import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.Session;
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.jgit.api.*;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
+import org.eclipse.jgit.transport.*;
+import org.eclipse.jgit.util.FS;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.ssssssss.magicapi.core.exception.MagicAPIException;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * git仓库
+ *
+ * @author soriee
+ * @date 2022/2/20 22:48
+ */
+public class GitRepo {
+    private static final Logger logger = LoggerFactory.getLogger(GitRepo.class);
+    /**
+     * 文件路径地址
+     */
+    private String rootPath;
+    private String gitFilePath;
+    private GitStoreProperties properties;
+    private Git git;
+
+    public GitRepo(String rootPath, GitStoreProperties properties) {
+        this.rootPath = rootPath;
+        this.gitFilePath = rootPath + File.separator + ".git";
+        this.properties = properties;
+    }
+
+    private void valid() {
+        File repoDir = new File(rootPath);
+        File gitFile = new File(gitFilePath);
+        // 如果文件夹不存在 则创建文件夹
+        if (!repoDir.exists()) {
+            repoDir.mkdirs();
+        }
+        if (!gitFile.exists() && repoDir.list().length > 0) {
+            throw new MagicAPIException("初次项目启动时,请保持文件夹为空。");
+        }
+    }
+
+    /**
+     * 设置ssh秘钥或者账号密码
+     *
+     * @param transportCommand
+     * @return
+     * @author soriee
+     * @date 2022/2/28 20:06
+     */
+    private void setSshOrCredentials(TransportCommand transportCommand) {
+        if (this.getProperties().getPrivateKey() != null) {
+            // ssh
+            final SshSessionFactory sshSessionFactory = new JschConfigSessionFactory() {
+                @Override
+                protected void configure(OpenSshConfig.Host host, Session session) {
+                }
+
+                @Override
+                protected JSch createDefaultJSch(FS fs) throws JSchException {
+                    JSch defaultJSch = super.createDefaultJSch(fs);
+                    defaultJSch.addIdentity(GitRepo.this.getProperties().getPrivateKey());
+                    return defaultJSch;
+                }
+            };
+            transportCommand.setTransportConfigCallback(new TransportConfigCallback() {
+                @Override
+                public void configure(Transport transport) {
+                    SshTransport sshTransport = (SshTransport) transport;
+                    sshTransport.setSshSessionFactory(sshSessionFactory);
+                }
+            });
+        } else if (StringUtils.isNotBlank(properties.getUsername())
+                && StringUtils.isNotBlank(properties.getPassword())) {
+            // 账号密码
+            transportCommand.setCredentialsProvider(new UsernamePasswordCredentialsProvider(
+                    properties.getUsername(),
+                    properties.getPassword()));
+        }
+    }
+
+    /**
+     * 项目设置仓库
+     *
+     * @return
+     * @author soriee
+     * @date 2022/2/24 20:43
+     */
+    public void setupRepo() throws IOException, GitAPIException {
+        this.valid();
+        File gitFile = new File(gitFilePath);
+        try {
+            if (gitFile.exists()) {
+                // 项目存在,则打开为仓库, 并且强制更新一次
+                FileRepositoryBuilder builder = new FileRepositoryBuilder();
+                Repository repository = builder.create(gitFile);
+                git = new Git(repository);
+                // 更新两次,避免删除文件未更新
+                this.update(false);
+                this.update(true);
+            } else {
+                CloneCommand cloneCommand = Git.cloneRepository()
+                        .setURI(properties.getUrl())
+                        .setDirectory(new File(rootPath))
+                        .setBranch(properties.getBranch());
+                this.setSshOrCredentials(cloneCommand);
+                git = cloneCommand.call();
+            }
+        } catch (IOException | GitAPIException e) {
+            logger.error("初始化git仓库失败", e);
+            throw e;
+        }
+    }
+
+    /**
+     * 更新
+     * 1.git add .
+     * 2.git commit -m "同步数据"
+     * 3.git pull
+     * 4.git push
+     * @param update
+     * @return
+     * @author soriee
+     * @date 2022/2/20 22:54
+     */
+    public boolean update(boolean update) {
+        try {
+            git.add().setUpdate(update).addFilepattern(".").call();
+            git.commit().setMessage("同步数据").call();
+            PullCommand pull = git.pull();
+            this.setSshOrCredentials(pull);
+            PullResult pullResult = pull.call();
+            if (!pullResult.isSuccessful()) {
+                throw new MagicAPIException("git更新失败, 请重试或尝试手动更新");
+            }
+            PushCommand pushCommand = git.push();
+            this.setSshOrCredentials(pushCommand);
+            pushCommand.call();
+        } catch (GitAPIException e) {
+            logger.error("git更新失败", e);
+            throw new MagicAPIException("git更新失败, 请重试或尝试手动更新");
+        }
+        return true;
+    }
+
+    public GitStoreProperties getProperties() {
+        return properties;
+    }
+}

+ 124 - 0
magic-api-plugins/magic-api-plugin-git-store/src/main/java/org/ssssssss/magicapi/git/GitResource.java

@@ -0,0 +1,124 @@
+package org.ssssssss.magicapi.git;
+
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.ssssssss.magicapi.core.resource.FileResource;
+import org.ssssssss.magicapi.core.resource.Resource;
+import org.ssssssss.magicapi.core.resource.ResourceAdapter;
+import org.ssssssss.magicapi.git.GitStoreProperties;
+import org.ssssssss.magicapi.utils.IoUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 文件存储实现
+ *
+ * @author mxd
+ */
+public class GitResource extends FileResource {
+	private GitRepo gitRepo;
+
+	public static GitResource of(org.ssssssss.magicapi.core.config.Resource config, GitStoreProperties properties) throws IOException, GitAPIException {
+		File file = new File(config.getLocation());
+		GitRepo gitRepo = new GitRepo(file.getAbsolutePath(), properties);
+		GitResource gitResource = new GitResource(config.isReadonly(), file,
+				file.getAbsolutePath(), gitRepo);
+		// 初始化
+		gitResource.setup();
+		return gitResource;
+	}
+
+
+	public GitResource(boolean readonly, File file, String rootPath, GitRepo gitRepo) {
+		super(file, readonly, rootPath);
+		this.gitRepo = gitRepo;
+	}
+	/**
+	 * 进行初始化操作, 仅仅在项目启动时进行初始化
+	 * @author soriee
+	 * @date 2022/2/20 22:30
+	 * @return
+	 */
+	private void setup() throws IOException, GitAPIException {
+		synchronized(GitResource.class) {
+			gitRepo.setupRepo();
+		}
+	}
+	private boolean update(boolean update) {
+		return gitRepo.update(update);
+	}
+
+
+	@Override
+	public boolean delete() {
+		return super.delete() && this.update(true);
+	}
+
+	@Override
+	public boolean mkdir() {
+		return super.mkdir() && this.update(false);
+	}
+	@Override
+	public Resource getResource(String name) {
+		return new GitResource(super.readonly(), new File(super.file, name), super.rootPath, this.gitRepo);
+	}
+
+	@Override
+	public Resource getDirectory(String name) {
+		return getResource(name);
+	}
+	@Override
+	public boolean write(byte[] bytes) {
+		return super.write(bytes)  && this.update(false);
+	}
+
+	@Override
+	public boolean write(String content) {
+		return super.write(content) && this.update(false);
+	}
+
+	@Override
+	public List<Resource> resources() {
+		File[] files = this.file.listFiles();
+		return files == null ? Collections.emptyList() : Arrays.stream(files).map(it -> new GitResource(this.readonly(),
+				it, this.rootPath, this.gitRepo)).collect(Collectors.toList());
+	}
+	@Override
+	public Resource parent() {
+		return this.rootPath.equals(this.file.getAbsolutePath()) ? null : new GitResource(this.readonly(),
+				this.file.getParentFile(), this.rootPath, this.gitRepo);
+	}
+	@Override
+	public List<Resource> dirs() {
+		return IoUtils.dirs(this.file).stream().map(it -> new GitResource(this.readonly(),
+				it, this.rootPath, this.gitRepo)).collect(Collectors.toList());
+	}
+	@Override
+	public List<Resource> files(String suffix) {
+		return IoUtils.files(this.file, suffix).stream().map(it -> new GitResource(this.readonly(),
+				it, this.rootPath, this.gitRepo)).collect(Collectors.toList());
+	}
+	@Override
+	public boolean renameTo(Resource resource) {
+		if (!this.readonly()) {
+			File target = ((GitResource) resource).file;
+			if (this.file.renameTo(target)) {
+				this.file = target;
+				// 更新两次,新增文件和删除文件都要更新
+				this.update(false);
+				this.update(true);
+				return true;
+			}
+		}
+		return false;
+	}
+
+	@Override
+	public String toString() {
+		return this.gitRepo.getProperties().getUrl();
+	}
+}

+ 68 - 0
magic-api-plugins/magic-api-plugin-git-store/src/main/java/org/ssssssss/magicapi/git/GitStoreProperties.java

@@ -0,0 +1,68 @@
+package org.ssssssss.magicapi.git;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@ConfigurationProperties(prefix = "magic-api.resource.git")
+public class GitStoreProperties {
+    /**
+     * git仓库地址
+     */
+    private String url;
+    /**
+     * git分支
+     */
+    private String branch;
+    /**
+     * ssh 密钥地址
+     * 仅支持-m PEM参数生产的ssh key
+     */
+    private String privateKey;
+    /**
+     * git账号
+     */
+    private String username;
+    /**
+     * git密码
+     */
+    private String password;
+
+    public String getUrl() {
+        return url;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    public String getBranch() {
+        return branch;
+    }
+
+    public void setBranch(String branch) {
+        this.branch = branch;
+    }
+
+    public String getPrivateKey() {
+        return privateKey;
+    }
+
+    public void setPrivateKey(String privateKey) {
+        this.privateKey = privateKey;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+}

+ 48 - 0
magic-api-plugins/magic-api-plugin-git-store/src/main/java/org/ssssssss/magicapi/git/MagicGitStoreConfiguration.java

@@ -0,0 +1,48 @@
+package org.ssssssss.magicapi.git;
+
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.ssssssss.magicapi.core.config.MagicAPIProperties;
+import org.ssssssss.magicapi.core.config.MagicPluginConfiguration;
+import org.ssssssss.magicapi.core.config.Resource;
+import org.ssssssss.magicapi.core.model.Plugin;
+import org.ssssssss.magicapi.git.GitStoreProperties;
+
+import java.io.IOException;
+
+@Configuration
+@EnableConfigurationProperties(GitStoreProperties.class)
+public class MagicGitStoreConfiguration implements MagicPluginConfiguration {
+
+	private final MagicAPIProperties properties;
+	private final GitStoreProperties gitStoreProperties;
+
+	public MagicGitStoreConfiguration(MagicAPIProperties properties, GitStoreProperties gitStoreProperties) {
+		this.properties = properties;
+		this.gitStoreProperties = gitStoreProperties;
+	}
+
+	/**
+	 * git存储
+	 * @author soriee
+	 * @date 2022/2/28 19:50
+	 * @return
+	 */
+	@Bean
+	@ConditionalOnMissingBean
+	@ConditionalOnProperty(prefix = "magic-api", name = "resource.type", havingValue = "git")
+	public org.ssssssss.magicapi.core.resource.Resource magicGitResource() throws IOException, GitAPIException {
+		Resource resourceConfig = properties.getResource();
+		return GitResource.of(resourceConfig, this.gitStoreProperties);
+	}
+
+
+	@Override
+	public Plugin plugin() {
+		return new Plugin("Git");
+	}
+}

+ 2 - 0
magic-api-plugins/magic-api-plugin-git-store/src/main/resources/META-INF/spring.factories

@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+  org.ssssssss.magicapi.git.MagicGitStoreConfiguration

+ 1 - 0
magic-api-plugins/pom.xml

@@ -20,6 +20,7 @@
         <module>magic-api-plugin-mongo</module>
         <module>magic-api-plugin-elasticsearch</module>
         <module>magic-api-plugin-cluster</module>
+        <module>magic-api-plugin-git-store</module>
     </modules>
     <dependencies>
         <dependency>