需求
舍友磁盘前两天gg了,里面的论文没有本地备份,最后费劲巴拉的在坚果云上找到了很早前的版本。我说可以上传到github,建一个私人仓库就行了,安全性应该有保证,毕竟不是啥学术大亨,不会有人偷你论文。但是他嫌每次写完还得手动操作,能不能写一个自动检测修改的软件,然后修改后就自动上传到github上。
第一反应是,需要word提供的接口,使用观察者模式,从而检测修改,然后通过github的API,开发一个上传文件的软件。但是通过word开发实在是太难了。
然后今天想了想,完全不需要,直接写个定时任务,10分钟检查下是否有文件进行修改就行了。本来就不要求较高的实时性,所以根本用不到观察者模式。
这样的话就有两种选择了,第一通过Java调用git然后进行文件的提交和上传,第二就是自己开发一个类似于git的工具,针对文件的创建,修改和删除进行对应的github仓库修改。
今天的话,是想着使用第二种方式的,确实有点难,用了一下午时间,才完成了文件的上传功能。
使用第二种方式,需要分析:
- git add .和git commit实现的功能
- git pull和push实现的功能
使用第一种方式,就简单许多
- 监听时间
- 使用jgit进行上传文件
简单设计
- 监听类 Listenner :负责监听目录/文件等的变化。
- 上传类 UpLoader : 负责将文件上传到github或者gitee或者其他云盘上
- GiteeUpLoader
- GithubUpLoader
- GitUploader
- 主类 Main
- utils类
- 常量(使用接口),异常,还有配置文件,工具文件等
差不多类似于这样吧,忽略chapter1,这个是之前的项目。
事件监听
Gitee文件上传类
package autoSendFiles.uploaders;
import autoSendFiles.constants.UploadConstants;
import autoSendFiles.exception.NullPropertiesException;
import java.io.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import autoSendFiles.interfaces.Uploader;
import okhttp3.*;
import org.apache.commons.lang3.StringUtils;
/**
* @author: Zekun Fu
* @date: 2024/3/17 12:12
* @Description: 通过调用git的方式,将文件上传到Gited中
*/
public class GiteeUploader implements Uploader {
private String accessToken;
private String username;
private String repository;
private String branch;
public GiteeUploader() throws NullPropertiesException {
// 读取参数,如果没有配置,抛出异常
try (InputStream input = new FileInputStream(UploadConstants.APP_PROPERTIES_PATH)) {
Properties properties = new Properties();
// 加载 properties 文件
properties.load(input);
// 获取属性值
this.accessToken = properties.getProperty("gitee.accessToken");
this.username = properties.getProperty("gitee.username");
this.repository = properties.getProperty("gitee.repository");
this.branch = properties.getProperty("gitee.branch");
if (StringUtils.isAnyEmpty(accessToken, username, repository, branch))
throw new NullPropertiesException("未配置Gitee属性,请先配置!");
} catch (IOException e) {
System.out.println("系统异常!请联系管理员");
e.printStackTrace();
}
}
public List<File> upload(List<File> files) {
return uploadHelper(files);
}
private List<File> uploadHelper(List<File>files) {
List<File>failUploadFiles = new ArrayList<>();
for (File file : files) {
// 如果没有上传成功,需要放入到传输失败列表中
try {
if (!upload(file))
failUploadFiles.add(file);
} catch (IOException e) {
failUploadFiles.addAll(files);
e.printStackTrace();
}
}
return failUploadFiles;
}
private boolean upload(File file) throws IOException {
// 生成路径,提交信息,以及提交文件的base64编码
String savePath = getSavePath(file);
String message = generatorSendMessage();
String content = this.fileBase64(file);
// 创建 http 客户端
String apiUrl = String.format(UploadConstants.UPLOAT_URL, this.username, this.repository, savePath);
OkHttpClient client = new OkHttpClient();
// 创建请求体
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart(UploadConstants.ACCESS_TOKEN, this.accessToken)
.addFormDataPart(UploadConstants.CONTENT, content)
.addFormDataPart(UploadConstants.MESSAGE, message)
.addFormDataPart(UploadConstants.BRANCH, this.branch)
.build();
// 创建 http 请求
Request request = new Request.Builder()
.url(apiUrl)
.post(requestBody)
.build();
// 接收响应
Response response = client.newCall(request).execute();
// 上传
if (response.isSuccessful()) {
System.out.println("文件上传成功!");
return true;
} else {
System.out.println("文件上传失败:" + response.code() + " " + response.message());
return false;
}
}
private String generatorSendMessage() {
LocalDateTime currentDateTime = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm");
String formattedDateTime = currentDateTime.format(formatter);
String msg = formattedDateTime + "提交";
return msg;
}
private String fileBase64(File file) throws IOException {
return Base64.getEncoder().encodeToString(fileToByteArray(file));
}
private byte[] fileToByteArray(File file) throws IOException {
byte[] data = new byte[(int) file.length()];
try (FileInputStream fis = new FileInputStream(file)) {
fis.read(data);
}
return data;
}
/**
* @return 生成路径, 如果为空,说明生成错了,没有.git文件夹
* */
private String getSavePath(File file) {
StringBuffer savePath = new StringBuffer();
return UploadConstants.DIR_SPLIT + findGitDirectory(file, savePath);
}
/**
* @return 递归获取路径,直到碰到.git为止
* */
private String findGitDirectory(File directory, StringBuffer savePath) {
StringBuffer curPath = new StringBuffer(directory.getName());
if (!StringUtils.isEmpty(savePath)) curPath.append(UploadConstants.DIR_SPLIT).append(savePath);
File gitDirectory = new File(directory, UploadConstants.ROOT_DIR);
if (gitDirectory.exists() && gitDirectory.isDirectory()) {
return savePath.toString();
} else {
File parentDirectory = directory.getParentFile();
if (parentDirectory != null) {
return findGitDirectory(parentDirectory, curPath);
} else {
return null;
}
}
}
public static void testGenerateSendMesage(GiteeUploader uploader) {
System.out.println("提交msg为:" + uploader.generatorSendMessage());
}
public static void testGetPath(GiteeUploader uploader, File file) {
// 1. 如果不包含.git文件
// 2. Linux的和windows的分隔符不一样
// 3. 其他特殊情况
String filePath = uploader.getSavePath(file);
if (!StringUtils.isEmpty(filePath)) {
System.out.println("当前的保存路径为:" + filePath);
}
else System.out.println("测试失败,无法获取当前文件的路径");
}
public static GiteeUploader testCreateUploader() throws IOException, NullPropertiesException{
GiteeUploader uploader = new GiteeUploader();
return uploader;
}
public static void testBase64(GiteeUploader uploader, File file) throws IOException {
String content = uploader.fileBase64(file);
if (StringUtils.isEmpty(content)) {
System.out.println("base64编码后的内容为空!");
return ;
}
System.out.println("base64编码后的内容为:" + content);
}
public static void testUpLoad() throws IOException, NullPropertiesException {
String FilePath = "D:\\learning\\论文\\毕业论文\\毕业论文备份\\test.txt";
GiteeUploader uploader = new GiteeUploader();
File file = new File(FilePath);
uploader.upload(new File(FilePath));
}
public static void test() throws NullPropertiesException, IOException {
System.out.println("测试开始...");
String FilePath = "D:\\learning\\论文\\毕业论文\\毕业论文备份\\test.txt";
GiteeUploader uploader = testCreateUploader();
testGenerateSendMesage(uploader);
File file = new File(FilePath);
testGetPath(uploader, file);
testBase64(uploader, file);
testUpLoad();
System.out.println("测试完成...");
}
public static void main(String[] args) throws NullPropertiesException, IOException{
test();
}
}
定时监听
项目架构
运行效果
运行结果:固定时间进行扫描提交
时间监听器
package autoSendFiles.Listener;
import autoSendFiles.constants.ApplicationConstants;
import autoSendFiles.constants.PropertyConstants;
import autoSendFiles.interfaces.Listenner;
import autoSendFiles.interfaces.Uploader;
import autoSendFiles.utils.AppPropertiesUtils;
/**
* @author: Zekun Fu
* @date: 2024/3/17 23:02
* @Description:
*/
public class TimeListenner implements Listenner, Runnable{
private Thread thread;
private Uploader uploader;
private int listenTime;
public TimeListenner(Uploader uploader) {
this.uploader = uploader;
this.listenTime = Integer.parseInt(AppPropertiesUtils.getProperty(PropertyConstants.LISTEN_TIME));
}
@Override
public void run() {
System.out.println("线程监听开始...");
while (true) {
if (this.thread.isInterrupted()) {
// 实际结束线程的地方
break;
}
try {
// 上传修改的文件
System.out.println("同步中...");
this.uploader.uploadAllChanges();
System.out.println("同步完成...");
// 睡眠
Thread.sleep(ApplicationConstants.TO_SECONDS * this.listenTime);
} catch (InterruptedException e) {
// 这里处理善后工作
// 重新进行标记,从而可以结束线程
this.thread.interrupt();
}
}
}
@Override
public void listen() {
// 开启线程进行监听
System.out.println("开启新线程,启动监听...");
this.thread = new Thread(this);
thread.start();
}
public void stop() {
this.thread.interrupt();
}
}
主线程
package autoSendFiles;
import autoSendFiles.Listener.TimeListenner;
import autoSendFiles.constants.ApplicationConstants;
import autoSendFiles.constants.PropertyConstants;
import autoSendFiles.exception.NullPropertiesException;
import autoSendFiles.interfaces.Uploader;
import autoSendFiles.uploaders.GitUploader;
import autoSendFiles.utils.AppPropertiesUtils;
import javax.imageio.IIOException;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* @author: Zekun Fu
* @date: 2024/3/17 12:12
* @Description:
*/
public class Main {
public static void main(String[] args) throws NullPropertiesException, IIOException {
Uploader uploader = new GitUploader();
TimeListenner listenner = new TimeListenner(uploader);
listenner.listen();
try {
int times = Integer.parseInt(AppPropertiesUtils.getProperty(PropertyConstants.RUN_TIME));
Thread.sleep(times * ApplicationConstants.TEN_MINUTES);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 100min后结束
listenner.stop();
}
}
Git上传器
package autoSendFiles.uploaders;
import autoSendFiles.constants.ApplicationConstants;
import autoSendFiles.constants.PropertyConstants;
import autoSendFiles.constants.UploadConstants;
import autoSendFiles.exception.NullPropertiesException;
import autoSendFiles.interfaces.Uploader;
import autoSendFiles.utils.AppPropertiesUtils;
import autoSendFiles.utils.DateUtils;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jgit.api.AddCommand;
import org.eclipse.jgit.api.CommitCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.PushCommand;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.eclipse.jgit.util.IO;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* @author: Zekun Fu
* @date: 2024/3/17 22:20
* @Description: 静态常量设计的不行! gitee和github的不分了。
* 重新开启一个文件叫做参数常量文件就行了。
*/
public class GitUploader implements Uploader {
private String workDir;
private String remoteName;
private String branch;
private String password;
private String username;
public GitUploader() throws NullPropertiesException {
workDir = AppPropertiesUtils.getProperty(PropertyConstants.WORK_DIR);
remoteName = AppPropertiesUtils.getProperty(PropertyConstants.GITEE_REMOTE_NAME);
branch = AppPropertiesUtils.getProperty(PropertyConstants.GITEE_BRANCH);
password = AppPropertiesUtils.getProperty(PropertyConstants.GITEE_PASSWORD);
username =AppPropertiesUtils.getProperty(PropertyConstants.GITEE_USERNAME);
if (StringUtils.isAnyEmpty(workDir, remoteName, branch, password)) {
throw new NullPropertiesException("没有配置工作文件夹,检查配置文件!");
}
}
@Override
public List<File> upload(List<File> files) {
List<File>failList = this.uploadHelper(files);
this.commit();
this.push();
return failList;
}
@Override
public void uploadAllChanges() {
this.pushAllChanges();
}
/**
* 完成所有修改文件的同步
* */
private void pushAllChanges() {
this.addAll();
this.commit();
this.push();
}
public boolean add(String filePath) {
try (Git git = Git.open(new File(this.workDir))) {
AddCommand addCommand = git.add();
addCommand.addFilepattern(filePath);
addCommand.call();
System.out.println(filePath + " 添加完成。");
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public void addAll() {
try (Git git = Git.open(new File(this.workDir))) {
AddCommand addCommand = git.add();
addCommand.addFilepattern(".");
addCommand.call();
System.out.println("Git add . 操作完成。");
} catch (Exception e) {
e.printStackTrace();
}
}
public void commit() {
try (Git git = Git.open(new File(this.workDir))) {
CommitCommand commitCommand = git.commit();
commitCommand.setMessage(this.getCommitMessage());
commitCommand.call();
System.out.println("Git commit 操作完成。");
} catch (Exception e) {
e.printStackTrace();
}
}
public void commit(String msg) {
try (Git git = Git.open(new File(this.workDir))) {
CommitCommand commitCommand = git.commit();
commitCommand.setMessage(msg);
commitCommand.call();
System.out.println("Git commit 操作完成。");
} catch (Exception e) {
e.printStackTrace();
}
}
public void push() {
try (Git git = Git.open(new File(this.workDir))) {
PushCommand pushCommand = git.push();
pushCommand.setRemote(remoteName);
pushCommand.setRefSpecs(new RefSpec(this.branch));
// 用户密码验证
CredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(this.username, this.password);
pushCommand.setCredentialsProvider(credentialsProvider);
pushCommand.call();
System.out.println("Git push 操作完成。");
} catch (Exception e) {
e.printStackTrace();
}
}
private List<File>uploadHelper(List<File> files) {
List<File>failedFile = new ArrayList<>();
for (File f : files) {
if (!this.add(f.getName()))
failedFile.add(f);
}
return failedFile;
}
private String getCommitMessage() {
return DateUtils.getCurTime(ApplicationConstants.MIN_PATTERN) + "提交";
}
public static void testGitUploader() throws IOException, NullPropertiesException {
Uploader uploader = new GitUploader();
List<File> fileList = new ArrayList<>();
String[] filePathList = {"D:\\projects\\java\\projects\\autoCommit\\test3.txt", "D:\\projects\\java\\projects\\autoCommit\\test4.txt"};
for (String filePath : filePathList) {
fileList.add(new File(filePath));
}
List<File>failedFiles = uploader.upload(fileList);
if (failedFiles.size() != 0) {
System.out.println("上传失败的文件有:");
for (File file : failedFiles) {
System.out.println(file.getName());
}
}
}
public static void main(String[] args) throws NullPropertiesException , IOException{
// new GitUploader().push();
GitUploader uploader = new GitUploader();
uploader.add("test3.txt");
}
}
工具文件
package autoSendFiles.utils;
import autoSendFiles.constants.UploadConstants;
import autoSendFiles.exception.NullPropertiesException;
import org.apache.commons.lang3.StringUtils;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* @author: Zekun Fu
* @date: 2024/3/17 21:50
* @Description:
*/
public class AppPropertiesUtils {
private static Properties properties = new Properties();
static {
// 读取参数,如果没有配置,抛出异常
try (InputStream input = new FileInputStream(UploadConstants.APP_PROPERTIES_PATH)) {
// 加载 properties 文件
properties.load(input);
} catch (IOException e) {
System.out.println("系统异常!请联系管理员");
e.printStackTrace();
}
}
public static String getProperty(String key) {
return properties.getProperty(key);
}
}
package autoSendFiles.utils;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* @author: Zekun Fu
* @date: 2024/3/17 21:43
* @Description:
*/
public class DateUtils {
public static String getCurTime(String pattern) {
LocalDateTime currentDateTime = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
return currentDateTime.format(formatter);
}
}
配置文件
# gitee配置
gitee.accessToken=你的口令
gitee.branch=本地分支
gitee.username=用户名
gitee.password=密码
gitee.repository=仓库
gitee.remoteName=远程分成
# 监听的git路径
work.dir=D:/projects/java/projects/autoCommit
# 监听的时间间隔10min
listen.time=10
# 程序运行时间100min
run.time=100
常量文件
package autoSendFiles.constants;
/**
* @author: Zekun Fu
* @date: 2024/3/17 21:45
* @Description:
*/
public interface ApplicationConstants {
String MIN_PATTERN = "yyyy/MM/dd HH:mm";
int TEN_MINUTES = 1000 * 60 * 10;
int TWENTY_MINUTES = 1000 * 60 * 20;
int TO_SECONDS = 1000;
int TO_MIMUTES = 1000 * 60;
}
package autoSendFiles.constants;
/**
* @author: Zekun Fu
* @date: 2024/3/17 22:39
* @Description: 配置Key的常量
*/
public interface PropertyConstants {
String GITEE_BRANCH = "gitee.branch";
String GITEE_REMOTE_NAME="gitee.remoteName";
String WORK_DIR = "work.dir";
String GITEE_PASSWORD = "gitee.password";
String GITEE_USERNAME = "gitee.username";
String LISTEN_TIME = "listen.time";
String RUN_TIME = "run.time";
}
package autoSendFiles.constants;
/**
* @author: Zekun Fu
* @date: 2024/3/17 20:01
* @Description:
*/
public interface UploadConstants {
String UPLOAT_URL = "https://gitee.com/api/v5/repos/%s/%s/contents/%s";
String ACCESS_TOKEN = "access_token";
String CONTENT = "content";
String MESSAGE = "message";
String BRANCH = "branch";
String ROOT_DIR = ".git";
String DIR_SPLIT = "/";
String APP_PROPERTIES_PATH = "src/main/resources/application.properties";
}
异常类
package autoSendFiles.exception;
/**
* @author: Zekun Fu
* @date: 2024/3/17 18:18
* @Description:
*/
public class NullPropertiesException extends Exception{
public NullPropertiesException(String msg) {
super(msg);
}
}
maven依赖
<dependencies>
<!-- Apache HttpClient Core -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<!-- Apache HttpClient Mime -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.5.13</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.3</version>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit</artifactId>
<version>5.13.0.202109080827-r</version>
</dependency>
</dependencies>
总结
- 没有实现事件监听器,可以通过listenner进行扩展
- 文件上传器,接口设计的不好,应该单一职责,这里两个职责了。一个是上传文件,一个是上传所有变化的文件
- 没有实现图像化结面
下一步
- 实现
git pull
的功能,进行文件的覆盖 - 实现事件监听功能