使用场景:跟朋友玩我的世界联机的时候朋友是个啥子不会装Mod,故闲着没事做的时候写了这个项目,只需配置好Git文件下载地址及保存文件路径就可以一键同步了
软件体验下载及使用教程地址:https://blog.csdn.net/u012930947/article/details/139595128
开发软件:idea、jdk11、exe4j
tips:之前写了一版使用JGit去clone或者pull去同步github的文件,但是经常会因为网络问题获取不到而失效,故变成下载zip
一、创建GIt项目并拿到下载zip文件路径和zip文件内目录名,记录及保存
这个看我这篇文章,翻到下面有教程
https://blog.csdn.net/u012930947/article/details/139595128
1、打开https://github.com/,注册账号或登录成功后面进入此界面,点击New
2、创建Git项目
3、点击uploading an existing 跳转到上传Mod文件界面后点击 choose your files 上传Mod文件
4、获取Git压缩包文件地址,先点击左上角你的Git名称,回到主页后再点击你创建的Git项目
5、先点右上角Code打开下拉,点Download Zip
6、出现这样的界面,复制zip文件下载链接,保存下来
7、下载下来后,打开压缩包,将压缩包内目录名保存下来
注:重要配置参数
二、创建Java项目及iml配置
1、打开Idea,点击左上角的File->New->Project->New Proect
命名随便
2、在src下创建java、resources文件
3、打开Project Structure 更新及设置iml文件
4、设置完成后data_pull_exe.iml文件里应该是这样
5、添加jar包 commons-io-2.13.0 (这个jar就做了个强制删除,视情况可以不加)在resources下增加libs包
6、再次打开Project Structure->Libraries,点+号选择Java,选中resources下的libs
7、选择确认后.iml文件内应该是这样的
三、项目结构介绍
四、项目开发
1、新建常量类、异常处理类及字符处理类
新建com.common.constant包,创建SyncConstant类
新建com.common.exception包,创建ServiceException类
新建com.common.utils包,创建StringUtils类
SyncContent 类
LOCAL_TEMP :缓存文件地址,下载的zip文件保存及解压地址,窗口关闭时会删除这个文件
DOWNLOAD_PATH、IN_PACK_FILE_NAME、MOVE_LOCAL_PATH: 用于初始化.config文件配置参数的默认赋值
CONFIG_LOCAL_PATH:.config文件夹生成到的路径,默认当前文件夹 CONFIG_NAME :.config文件夹的文件名
package com.common.constant;
public class SyncContent {
/** 下载缓存文件地址 */
public static String LOCAL_TEMP = ".temp\\";
/** 远程Zip地址下载地址 */
public static String DOWNLOAD_PATH = ""; // 用于替换初始化.config配置文件里的配置
public static String COL_DOWNLOAD_PATH = "download_path";
/** 远程下载的压缩包内文件名 */
public static String IN_PACK_FILE_NAME = ""; // 用于替换初始化.config配置文件里的配置
public static String COL_IN_PACK_FILE_NAME = "in_pack_file_name";
/** 默认解压的文件地址 */
public static String MOVE_LOCAL_PATH = ""; // 用于替换初始化.config配置文件里的配置
public static String COL_MOVE_LOCAL_PATH = "move_local_path";
/** 配置文件保存地址 - 当前文件夹 */
public static final String CONFIG_LOCAL_PATH = "./";
/** 配置名称 */
public static final String CONFIG_NAME = ".config";
}
ServiceException类
package com.common.exception;
public class ServiceException extends RuntimeException {
private String message;
public ServiceException() {
}
public ServiceException(String message) {
this.message = message;
}
public ServiceException(Exception exception) {
if (exception instanceof ServiceException) {
this.message = ((ServiceException) exception).getMessage();
} else {
this.message = "系统异常";
}
}
@Override
public String getMessage() {
return message;
}
}
StringUtils类
package com.common.utils;
public class StringUtils {
/** 空字符串 */
private static final String NULLSTR = "";
public static boolean isEmpty(String str) {
return str == null || NULLSTR.equals(str.trim());
}
}
2、创建config.template模板文件
a、在resources下先创建一个txt文件
b、将模板格式复制进去
# 远程zip文件下载地址
download_path={download_path}
# zip文件内的目录名
in_pack_file_name={in_pack_file_name}
# 将zip目录in_pack_file_name下的文件转移到的本地路径
move_local_path={move_local_path}
c、再点击 文件->另存为->config.template
改成ANSI编码使用用exe软件生成.config文件时中文才不会乱码
3、读取.config配置文件,新建配置类,在com.properties下创建ConfigProperties
package com.properties;
import com.common.constant.SyncContent;
import com.global.SyncGlobal;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.StringReader;
import java.nio.charset.Charset;
import java.util.Properties;
public class ConfigProperties {
// 远程zip文件下载地址
private String download_path;
// zip文件内的目录名
private String in_pack_file_name;
// 将zip目录in_pack_file_name下的文件转移到的本地路径
private String move_local_path;
public ConfigProperties () {
Properties properties = new Properties();
try {
if (SyncGlobal.configFile.exists()) {
FileInputStream fileInputStream = new FileInputStream(SyncContent.CONFIG_NAME);
// 替换单斜杠 - 为什么需要替换:在弹窗中选择文件路径时获取到的文件地址是单斜杠,会被自动转意导致路径不正确
String content = new String(fileInputStream.readAllBytes(), Charset.forName("GBK"))
.replace("\\", "\\\\");
properties.load(new StringReader(content));
fileInputStream.close();
download_path = properties.getProperty(SyncContent.COL_DOWNLOAD_PATH);
in_pack_file_name = properties.getProperty(SyncContent.COL_IN_PACK_FILE_NAME);
move_local_path = properties.getProperty(SyncContent.COL_MOVE_LOCAL_PATH);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public String getDownload_path() {
return download_path;
}
public void setDownload_path(String download_path) {
this.download_path = download_path;
}
public String getIn_pack_file_name() {
return in_pack_file_name;
}
public void setIn_pack_file_name(String in_pack_file_name) {
this.in_pack_file_name = in_pack_file_name;
}
public String getMove_local_path() {
return move_local_path;
}
public void setMove_local_path(String move_local_path) {
this.move_local_path = move_local_path;
}
}
4、新建全局变量类,在com.global下新建SyncGlobal类
package com.global;
import com.common.constant.SyncContent;
import com.properties.ConfigProperties;
import com.window.SyncWindows;
import org.apache.commons.io.FileUtils;
import javax.net.ssl.HttpsURLConnection;
import java.awt.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.ZipFile;
/**
* 全局变量
*/
public class SyncGlobal {
/** 主窗口 */
public static SyncWindows jFrame;
/** 下载输入流 **/
public static InputStream dowInputStream = null;
/** 下载输出流 **/
public static FileOutputStream dowOutputStream = null;
/** zip文件类 **/
public static ZipFile zipFile = null;
/** 解压输入流 **/
public static InputStream zipInputStream = null;
/** 解压输出流 **/
public static FileOutputStream zipOutputStream = null;
/** https网络连接 **/
public static HttpsURLConnection connection = null;
/** 配置文件 **/
public static ConfigProperties configProperties;
/** 配置文件路径 */
public static File configFile = new File(SyncContent.CONFIG_LOCAL_PATH + SyncContent.CONFIG_NAME);
// 图标
public static Image imageIcon = null;
/**
* 全局关闭时-清空缓存文件.temp
* @throws IOException
*/
public static void clearTemp () throws IOException {
if (dowInputStream != null) {
dowInputStream.close();
}
if (dowOutputStream != null) {
dowOutputStream.close();
}
if (zipInputStream != null) {
zipInputStream.close();
}
if (zipOutputStream != null) {
zipOutputStream.close();
}
if (zipFile != null) {
zipFile.close();
}
if (connection != null) {
connection.disconnect();
}
// 强制删除
File tempFile = new File(SyncContent.LOCAL_TEMP);
if (tempFile.isDirectory()) {
FileUtils.forceDelete(tempFile);
}
}
}
5、初始化配置文件.config,在com.properties下新建ConfigReader
可用于根据config.template初始化.config文件,设置全局配置变量
package com.properties;
import com.Main;
import com.common.constant.SyncContent;
import com.common.exception.ServiceException;
import com.common.utils.StringUtils;
import com.global.SyncGlobal;
import java.io.BufferedReader;
import java.io.FileWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.nio.charset.Charset;
public class ConfigReader {
/**
* 初始化
*/
public static void init () {
// 初始化配置文件
SyncGlobal.configProperties = new ConfigProperties();
// 配置文件不存在即初始化
if (!SyncGlobal.configFile.exists()) {
rebuildConfig();
}
// 配置文件重要配置为空抛异常
if (StringUtils.isEmpty(SyncGlobal.configProperties.getDownload_path())) {
throw new ServiceException("远程zip文件下载地址未配置");
} else if (StringUtils.isEmpty(SyncGlobal.configProperties.getIn_pack_file_name())) {
throw new ServiceException("zip文件内目录名未配置");
}
}
/**
* 重建配置
*/
public static void rebuildConfig () {
// 获取配置模板,读取Resource下配置模板文件流
try (InputStream inputStream = Main.class.getClassLoader().getResourceAsStream("config.template");
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
String template;
String configContent = "";
while ((template = reader.readLine()) != null) {
configContent += template + "\r\n";
}
// 通过反射设置初始化配置默认值
for (Field field : SyncGlobal.configProperties.getClass().getDeclaredFields()) {
field.setAccessible(true);
String key = field.getName();
String value = (String) field.get(SyncGlobal.configProperties);
// 设置默认值
if (value == null || "".equals(value.trim())) {
if (key.equals(SyncContent.COL_DOWNLOAD_PATH)) {
value = SyncContent.DOWNLOAD_PATH;
}
if (key.equals(SyncContent.COL_IN_PACK_FILE_NAME)) {
value = SyncContent.IN_PACK_FILE_NAME;
}
if (key.equals(SyncContent.COL_MOVE_LOCAL_PATH)) {
value = SyncContent.MOVE_LOCAL_PATH;
}
}
field.set(SyncGlobal.configProperties, value);
configContent = configContent.replace("{" + key + "}", value);
}
// 输出.confg文件 - GBK编码加上模板文件格式为ANSI编码才能使软件生成配置文件时中文不会乱码
//(直接idea运行可能会中文乱码)
FileWriter writer = new FileWriter(SyncGlobal.configFile, Charset.forName("GBK"));
writer.write(configContent);
writer.close();
} catch (Exception e) {
e.printStackTrace();
throw new ServiceException(e);
}
}
}
6、创建主弹窗,在com.window下新建SyncWindows类
icon.png是弹窗打开的图标,可随便找一张放在resource下
package com.window;
import com.global.SyncGlobal;
import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.net.URL;
public class SyncWindows extends JFrame {
JLabel label;
Container container;
JProgressBar progressBar;
JFrame jFrame = this;
JScrollPane scrollPane;
public Integer width = 300;
public Integer height = 100;
/**
* 初始化窗口
*/
public SyncWindows () {
// 从文件路径加载图标
URL imageUrl = SyncWindows.class.getClassLoader().getResource("icon.png");
SyncGlobal.imageIcon = Toolkit.getDefaultToolkit().getImage(imageUrl);
jFrame.setIconImage(SyncGlobal.imageIcon);
// 设置窗口大小
jFrame.setSize(width, height);
// 窗口名称
jFrame.setTitle("Mincraft Mod 同步");
// 屏幕居中显示
jFrame.setLocationRelativeTo(null);
// 禁止用户调整窗口的大小
jFrame.setResizable(false);
// 面板
container = jFrame.getContentPane();
// 文字显示
label = new JLabel();
// 文字默认显示
label.setText("...");
//使标签上的文字居中
label.setHorizontalAlignment(SwingConstants.CENTER);
// 进度条
progressBar = new JProgressBar(JProgressBar.HORIZONTAL, 0, 100);
progressBar.setStringPainted(true);
// 设置初始进度值(下载或解压进度条)
progressBar.setValue(0);
scrollPane = new JScrollPane(label);
// 将文字添加到JFrame
container.add(scrollPane);
// 将进度条添加到JFrame
container.add(progressBar, BorderLayout.SOUTH);
// 监听窗口关闭
jFrame.addWindowListener(windowsClose());
// 显示窗口
this.setVisible(true);
}
/**
* 设置窗口大小
* @param width
* @param height
*/
public void updateSize (int width, int height) {
SwingUtilities.invokeLater(() -> {
this.setSize(width, height);
});
}
/**
* 设置主要文字信息
* @param text
*/
public void setLabel (String text) {
SwingUtilities.invokeLater(() -> {
label.setText(text);
});
}
/**
* 设置进度条
* @param progress
*/
public void setProgress (int progress) {
SwingUtilities.invokeLater(() -> {
progressBar.setValue(progress);
});
}
/**
* 窗口关闭
*/
public WindowAdapter windowsClose () {
return new WindowAdapter() {
//窗口被关闭时的监听
public void windowClosed(java.awt.event.WindowEvent e) {
systemClose ();
}
//点击窗口关闭按钮监听
public void windowClosing(java.awt.event.WindowEvent e) {
jFrame.dispose();
}
};
}
/**
* 系统关闭
*/
public static void systemClose () {
try {
SyncGlobal.clearTemp ();
} catch (Exception e) {
e.printStackTrace();
} finally {
System.exit(0);
}
}
public Container getContainer() {
return container;
}
public JProgressBar getProgressBar() {
return progressBar;
}
public void setProgressBar(JProgressBar progressBar) {
this.progressBar = progressBar;
}
}
7、文件选择弹窗(包含覆盖.config配置文件参数),在com.window下创建SelectFileJDialog类
package com.window;
import com.common.exception.ServiceException;
import com.global.SyncGlobal;
import com.properties.ConfigReader;
import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
public class SelectFileJDialog extends JDialog {
JDialog jDialog = this;
private Integer width = 300;
private Integer height = 120;
public final static SelectFileJDialog init () {
return new SelectFileJDialog();
}
public SelectFileJDialog() {
// 从文件路径加载图标
jDialog.setIconImage(SyncGlobal.imageIcon);
// 文本框
final JTextField fileTextField = new JTextField(20);
fileTextField.setText(SyncGlobal.configProperties.getMove_local_path());
// 选择按钮
JButton select = new JButton("选择文件");
select.addActionListener(e -> {
// 选择文件夹
JFileChooser fileChooser = new JFileChooser(new File(SyncGlobal.configProperties.getMove_local_path()).isDirectory() ? SyncGlobal.configProperties.getMove_local_path() : "./");
fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
int result = fileChooser.showOpenDialog(jDialog);
if (result == JFileChooser.APPROVE_OPTION) {
File selectedFile = fileChooser.getSelectedFile();
fileTextField.setText(selectedFile.getAbsolutePath());
}
});
// 为对话框添加窗口关闭事件监听器
jDialog.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
// 窗口关闭关闭整个程序
jDialog.dispose();
SyncGlobal.jFrame.systemClose();
}
});
// 选择按钮
JButton confirm = new JButton("确认");
confirm.addActionListener(e -> {
boolean isStart = false;
File selectFile = new File(fileTextField.getText());
if (!selectFile.isDirectory()) {
if (JOptionPane.showConfirmDialog(null, "文件目录不存在,是否创建?", "提示",
JOptionPane.YES_NO_OPTION) == 0) {
isStart = true;
selectFile.mkdirs();
}
} else if (JOptionPane.showConfirmDialog(null, "程序将会清空选择文件夹中所有文件,确认?", "提示",
JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE) == 0){
isStart = true;
}
if (isStart) {
try {
// 设置全局变量 - 文件同步地址
SyncGlobal.configProperties.setMove_local_path(fileTextField.getText());
// 重写.config文件
ConfigReader.rebuildConfig();
} catch (Exception ex) {
ex.printStackTrace();
throw new ServiceException(ex);
}
jDialog.dispose();
}
});
// 设置对话框的布局和内容
jDialog.setLayout(new FlowLayout());
jDialog.add(fileTextField);
jDialog.add(select);
jDialog.add(confirm);
// 设置标题
jDialog.setTitle("确认路径");
// 禁止用户调整窗口的大小
jDialog.setResizable(false);
// 设置对话框的大小和可见性
jDialog.setSize(width, height);
// 这个配置等于弹窗等待,这个弹窗开启时后面的代码不会执行
jDialog.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
// 使对话框居中于主窗口
jDialog.setLocationRelativeTo(SyncGlobal.jFrame);
jDialog.setVisible(true);
}
}
8、编写文件下载、zip解压、文件迁移类FileUtils(包含下载解压进度条调整),在com.common.utils下新建FileUtils类
package com.common.utils;
import com.common.exception.ServiceException;
import com.global.SyncGlobal;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.DosFileAttributeView;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
public class FileUtils {
/**
* 远程下载zip
* @param downloadPath 远程文件地址
* @param downLocalPath 本地保存地址
* @return
* @throws Exception
*/
public static String remoteDownloadZip(String downloadPath, String downLocalPath) throws Exception {
// 更改文字显示
SyncGlobal.jFrame.setLabel("正在下载..");
File downLocalFile = new File(downLocalPath);
if (!downLocalFile.isDirectory()) {
downLocalFile.mkdirs();
}
// 隐藏文件
isHidden(downLocalPath);
downLocalFile.setExecutable(true);
URL url = new URL(downloadPath);
SyncGlobal.connection = (HttpsURLConnection) url.openConnection();
// 创建信任所有服务器的TrustManager
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
}
}
};
// 生成的exe使https请求不报错
System.setProperty("javax.net.debug", "all");
// 初始化SSLContext并设置TrustManager
SSLContext sc = SSLContext.getInstance("TLSv1.2");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
// 设置允许输入流输入数据到本地
SyncGlobal.connection.setDoInput(true);
// 设置允许输出流输出到服务器
SyncGlobal.connection.setDoOutput(true);
// 从SSLContext获取SSLSocketFactory并设置到HttpsURLConnection中
SyncGlobal.connection.setSSLSocketFactory(sc.getSocketFactory());
// 设置通用的请求属性
SyncGlobal.connection.setRequestProperty("Accept-Encoding", "identity");
// 建立实际的连接
SyncGlobal.connection.connect();
int responseCode = SyncGlobal.connection.getResponseCode();
if (responseCode != HttpURLConnection.HTTP_OK) {
throw new ServiceException("下载失败!");
}
// 引用形式的描述信息:打开远程文件的输入流
SyncGlobal.dowInputStream = SyncGlobal.connection.getInputStream();
// 创建本地文件输出流
String downFilePath = downLocalPath + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".zip";
SyncGlobal.dowOutputStream = new FileOutputStream(downFilePath);
// 按字节读取写入文件
byte[] buffer = new byte[1024];
int length;
// 文件大小
BigDecimal totalSize = new BigDecimal(SyncGlobal.connection.getContentLength());
BigDecimal downloadedSize = BigDecimal.ZERO;
// 计数
int num = 0;
while ((length = SyncGlobal.dowInputStream.read(buffer)) != -1) {
SyncGlobal.dowOutputStream.write(buffer, 0, length);
downloadedSize = downloadedSize.add(new BigDecimal(length));
// 请求下载时文件大小可能会返回-1,即做个假进度条
if (SyncGlobal.connection.getContentLength() > 0) {
BigDecimal progress = (downloadedSize.multiply(new BigDecimal(100))).divide(totalSize, BigDecimal.ROUND_HALF_DOWN);
SyncGlobal.jFrame.setProgress(progress.intValue());
} else {
// 假进度条
num++;
SyncGlobal.jFrame.setProgress(num > 100000 ? 80 : 50);
}
}
// 关闭流
SyncGlobal.dowInputStream.close();
SyncGlobal.dowOutputStream.close();
SyncGlobal.connection.disconnect();
if (!new File(downFilePath).exists()) {
throw new ServiceException("同步失败,文件下载失败");
}
return downFilePath;
}
/**
* 解压zip
* @param zipPath
* @param toPath
* @return
*/
public static void decZipFile (String zipPath, String toPath) throws Exception{
// 更改文字显示
SyncGlobal.jFrame.setLabel("正在解压...");
// 获取Zip文件 - 编码设置gbk,在生成exe文件后启动后解压的文件名显示才正常
SyncGlobal.zipFile = new ZipFile(zipPath, Charset.forName("gbk"));
// 遍历压缩文件中的所有条目
Enumeration<? extends ZipEntry> entries = SyncGlobal.zipFile.entries();
int totalEntries = SyncGlobal.zipFile.size();
int currentEntry = 0;
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
// 解压缩条目到目标文件夹
String entryName = entry.getName();
entryName = new String(entryName.getBytes(Charset.forName("gbk")));
File entryFile = new File(toPath, entryName);
if (entry.isDirectory()) {
entryFile.mkdirs();
} else {
entryFile.getParentFile().mkdirs();
SyncGlobal.zipInputStream = SyncGlobal.zipFile.getInputStream(entry);
SyncGlobal.zipOutputStream = new FileOutputStream(entryFile);
byte[] buffer = new byte[1024];
int length;
while ((length = SyncGlobal.zipInputStream.read(buffer)) > 0) {
SyncGlobal.zipOutputStream.write(buffer, 0, length);
}
SyncGlobal.zipOutputStream.close();
SyncGlobal.zipInputStream.close();
}
// 进度条赋值
currentEntry++;
int progress = (int) ((currentEntry / (double) totalEntries) * 100);
SyncGlobal.jFrame.setProgress(progress);
}
// 关闭压缩文件
SyncGlobal.zipFile.close();
}
/**
* 移动zip文件
* @param zipDecFilePath
* @param moveLocalPath
* @throws Exception
*/
public static void moveZipFile (String zipDecFilePath, String moveLocalPath) throws Exception {
// 删除目标目录所有文件
deleteFiles (moveLocalPath);
SyncGlobal.jFrame.setLabel("正在移动文件..");
File sourceFolder = new File(zipDecFilePath);
File destinationFolder = new File(moveLocalPath);
if (!sourceFolder.exists()) {
throw new ServiceException("同步失败,解压文件不存在!");
}
if (!destinationFolder.isDirectory()) {
destinationFolder.mkdirs();
}
File[] files = sourceFolder.listFiles();
if (files != null && files.length > 0) {
StringBuilder fileMsgs = new StringBuilder();
for (File file : files) {
try {
Files.move(file.toPath(), new File(destinationFolder.getAbsolutePath() + File.separator + file.getName()).toPath());
fileMsgs.append("文件" + file.getName() + "同步完成<br/>");
} catch (IOException e) {
fileMsgs.append("文件" + file.getName() + "同步失败<br/>");
e.printStackTrace();
}
SyncGlobal.jFrame.setLabel("<html><body>" + fileMsgs + "</body></html>");
}
SyncGlobal.jFrame.updateSize(500, 300);
// 屏幕居中显示
SyncGlobal.jFrame.setLocationRelativeTo(null);
} else {
throw new ServiceException("文件不存在!");
}
}
/**
* 删除指定文件夹下的文件
* @param path
*/
public static void deleteFiles(String path) {
File file = new File(path);
if (file.exists()) {
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File f : files) {
if (f.isFile()) {
f.delete();
} else if (f.isDirectory()) {
deleteFiles(f.getAbsolutePath());
}
}
file.delete();
} else {
file.delete();
}
}
}
/**
* 隐藏文件
* @param filePath
* @return
* @throws IOException
*/
public static boolean isHidden (String filePath) throws IOException {
Path path = Path.of(filePath);
DosFileAttributeView view = Files.getFileAttributeView(path, DosFileAttributeView.class);
view.setHidden(true);
return view.readAttributes().isHidden();
}
}
9、同步开始方法,用于调用下载、解压、迁移文件方法,在com.service下新建SyncService类
package com.service;
import com.common.constant.SyncContent;
import com.global.SyncGlobal;
import com.common.utils.FileUtils;
import javax.swing.*;
public class SyncService {
/**
* 开始同步
* @throws Exception
*/
public static void start () throws Exception {
// 隐藏文件 - 会使config禁止访问
// FileUtils.isHidden(SyncGlobal.configFile.getAbsolutePath());
// 远程下载
String zipPath = FileUtils.remoteDownloadZip(SyncGlobal.configProperties.getDownload_path(), SyncContent.LOCAL_TEMP);
// 解压压缩包文件
FileUtils.decZipFile(zipPath, SyncContent.LOCAL_TEMP);
// 移动文件
FileUtils.moveZipFile(SyncContent.LOCAL_TEMP + SyncGlobal.configProperties.getIn_pack_file_name(), SyncGlobal.configProperties.getMove_local_path());
// 同步完成
JOptionPane.showMessageDialog(SyncGlobal.jFrame, "同步完成");
}
}
10、启动类,在com包下新建Main.java
用于调用窗口启动、初始化配置、文件选择弹窗、开始同步文件及异常窗口处理
package com;
import com.common.exception.ServiceException;
import com.global.SyncGlobal;
import com.properties.ConfigReader;
import com.service.SyncService;
import com.window.SelectFileJDialog;
import com.window.SyncWindows;
import javax.swing.*;
public class Main {
public static void main(String[] args) throws InterruptedException {
// 开启窗口
SyncGlobal.jFrame = new SyncWindows();
try {
// 初始化配置
ConfigReader.init();
// 文件选择
SelectFileJDialog.init();
// 开始同步文件
SyncService.start();
} catch (Exception e) {
e.printStackTrace();
if (e instanceof ServiceException) {
JOptionPane.showMessageDialog(SyncGlobal.jFrame, e.getMessage(), "确认", 0);
SyncGlobal.jFrame.systemClose();
} else {
JOptionPane.showMessageDialog(SyncGlobal.jFrame, "同步失败", "确认", 0);
SyncGlobal.jFrame.setLabel(e.getMessage());
SyncGlobal.jFrame.setProgressBar(null);
}
} finally {
// 停30秒
Thread.sleep(30000);
SyncGlobal.jFrame.systemClose();
}
}
}
五、项目打成jar包
1、打开Project Structure
2、选择Artifacts,点击+号,按图片点击
3、选择你的Main方法,然后点OK
4、再次点击OK,之后应该会在项目中生成一个META-INF文件
5、打包成jar包
六、生成exe文件
1、先将我们的jar包复制到桌面
2、打开exe4j,百度搜索有下载
3、激活exe4j,不然打开软件会有exe4j带的弹窗
通用激活码:L-g782dn2d-1f1yqxx1rv1sqd 名称和公司名称随意
4、点击下一步,选择JAR IN EXE
5、下一步
6、下一步,设置应用图标
随便找张图片就行
百度搜在线ico转换器
例:https://www.xunjietupian.com/image-to-icon/
7、下一步,选择Jar包及设置启动类
8、下一步、配置jre(重要)
a.填写完最小最大java版本后,点击Search sequence
b、将这三个删除
c、然后点击+,按图片步骤完成后确定
9、往后直接下一步到完成,exe文件就生成了
10、生成jre文件,这个必须和生成的exe文件放在同目录
a、找到jdk安装目录,例如我的:C:\Program Files\Java\jdk-11.0.6
b、用管理员身份打开cmd,输入cd C:\Program Files\Java\jdk-11.0.6到jdk目录地址,假如在D盘那就先输入 D: 回车再输入,cd D:\Program Files\Java\jdk-11.0.6定位到jdk目录地址
c、将 bin\jlink.exe --module-path jmods --add-modules java.desktop --output jre 复制到cmd里执行,将在jdk目录生成jre文件
d、将jre文件复制到我们生成的exe文件目录
七、软件使用及下载地址
https://blog.csdn.net/u012930947/article/details/139595128
八、其他:使用JGit同步
简单写的一个弹窗应用,使用exe执行大概不可用
需要引用的包org.eclipse.jgit.jar及sl4fj(jgit需要引)
package com.mcworld;
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 javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws Exception {
// 创建一个简单的弹窗
JDialog dialog = new JDialog();
dialog.setTitle("同步Mod");
dialog.setSize(300, 100);
dialog.setLocationRelativeTo(null);
dialog.setLayout(new FlowLayout());
dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
// 添加窗口监听器来处理关闭事件
dialog.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
// 这里编写你关闭窗口时想要执行的代码
System.out.println("进程结束");
// 确保窗口关闭
dialog.dispose();
System.exit(0);
}
});
// 本地仓库路径:你的我的世界mods文件路径
String localRepoPath = "";
File filePath = new File(localRepoPath);
if (!filePath.exists()) {
filePath.mkdirs();
}
JLabel label = null;
String gitignorePath = localRepoPath + ".git";
try {
if (!new File(gitignorePath).isDirectory()) {
label = new JLabel("首次执行稍长..请耐心等待");
dialog.add(label);
deleteFiles(localRepoPath);
// 设置弹窗可见
dialog.setVisible(true);
// git仓库地址
String repoUrl = "https://github.com/xxx/MyMincraft.git";
// 使用克隆命令
CloneCommand cloneCommand = Git.cloneRepository();
// 设置仓库的URL
cloneCommand.setURI(repoUrl);
// 设置克隆到的目录
cloneCommand.setDirectory(new File(localRepoPath));
// 执行克隆操作
Git git = cloneCommand.call();
git.close();
} else {
label = new JLabel("正在更新");
dialog.add(label);
// 设置弹窗可见
dialog.setVisible(true);
}
// 打开本地仓库
Repository localRepo = new FileRepositoryBuilder()
.setGitDir(new File(localRepoPath + "/.git"))
.build();
// 创建Git对象
Git git = new Git(localRepo);
// 创建Pull命令
git.reset().setMode(ResetCommand.ResetType.HARD).call();
PullCommand pullCommand = git.pull();
// 执行Pull操作
pullCommand.call();
} catch (IOException | GitAPIException e) {
e.printStackTrace();
label.setText(e.getMessage());
Thread.sleep(5000);
} finally {
dialog.setVisible(false); // 设置弹窗不可见
System.exit(0);
}
}
/**
* 删除指定文件夹下的文件
* @param path
*/
public static void deleteFiles(String path) {
File file = new File(path);
if (file.exists()) {
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File f : files) {
if (f.isFile()) {
f.delete();
} else if (f.isDirectory()) {
deleteFiles(f.getAbsolutePath());
}
}
file.delete();
} else {
file.delete();
}
}
}
}