(从零写到生成exe文件)Java 将Git作为文件云盘?我的世界MOD同步器?全代码从新建项目开始写

开源 0

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

也许您对下面的内容还感兴趣: