使用场景:跟朋友玩我的世界联机的时候朋友是个啥子不会装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(); } } }}