Hunter0x7c7
2024-09-12 60a3f2bc64b7a5f502e4133ced31f0b25c88d3f1
版本v1.0.2(3)发布。
7个文件已修改
318 ■■■■■ 已修改文件
pom.xml 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
readme.md 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/github/hunter0x7c7/sync/Main.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/github/hunter0x7c7/sync/ctrls/Controller.java 214 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/github/hunter0x7c7/sync/model/global/Parameters.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/github/hunter0x7c7/sync/utils/TrayUtil.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/forxml/sample.fxml 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml
@@ -6,7 +6,7 @@
    <groupId>com.github.hunter0x7c7</groupId>
    <artifactId>SyncTools</artifactId>
    <version>1.0.1.RELEASE</version>
    <version>1.0.2.RELEASE</version>
    <packaging>war</packaging>
    <name>SyncTools</name>
@@ -56,7 +56,7 @@
    </repositories>
    <!--将项目打包成exe安装包-->
    <!--将项目打包成exe安装包 mvn package-->
    <build>
        <plugins>
            <plugin>
@@ -80,8 +80,8 @@
                            <!-- 精简版jre -->
                            <bundleJre>true</bundleJre>
                            <!-- 绑定自定义JRE路径 -->
                            <!--<jrePath>D:\Develop\Java\jdk-9.0.1\jre</jrePath>-->
                            <jrePath>/Library/Java/JavaVirtualMachines/jre-9.0.1.jre/Contents/Home</jrePath>
                            <jrePath>D:\Develop\Java\jdk-9.0.1\jre</jrePath>
                            <!--<jrePath>/Library/Java/JavaVirtualMachines/jre-9.0.1.jre/Contents/Home</jrePath>-->
                            <!-- 生成安装包 -->
                            <generateInstaller>false</generateInstaller>
                            <!-- 不使用管理员身份打开程序 -->
@@ -89,11 +89,11 @@
                            <!-- 将指定文件夹包含到生成的程序路径 -->
                            <!-- 手动引入额外资源-->
                            <additionalResources>
                                <!--<additionalResource>F:\workspace_java\JavaFx\SyncTools\libs</additionalResource>-->
                                <additionalResource>/Users/hunter/workspace/JavaFx/SyncTools/lib</additionalResource>
                                <additionalResource>F:\workspace_java\JavaFx\SyncTools\libs</additionalResource>
                                <!--<additionalResource>/Users/hunter/workspace/JavaFx/SyncTools/lib</additionalResource>-->
                            </additionalResources>
                            <!-- 当前打包的平台 -->
                            <platform>mac</platform>
                            <platform>windows</platform>
                            <name>SyncTools</name>
                            <!-- exe 名称,通过引用的方式 -->
                            <displayName>${project.name}</displayName>
@@ -103,16 +103,16 @@
                                <vmArg>-Dfile.encoding=GBK</vmArg><!-- 解决打包之后托盘乱码 -->
                            </vmArgs>
                            <!-- 特定平台参数 -->
                            <!--<winConfig>
                            <winConfig>
                                <icoFile>
                                    F:\workspace_java\JavaFx\SyncTools\src\main\deploy\package\windows\SyncTools.ico
                                </icoFile>
                                <generateSetup>false</generateSetup>
                                <generateMsi>false</generateMsi>
                                <generateMsm>false</generateMsm>
                            </winConfig>-->
                            </winConfig>
                            <!--<linuxConfig></linuxConfig>-->
                            <macConfig>
                            <!--<macConfig>
                                <appId>com.github.hunter0x7c7.SyncTools</appId>
                                <icnsFile>
                                    /Users/hunter/workspace/JavaFx/SyncTools/src/main/deploy/package/mac/SyncTools.icns
@@ -120,7 +120,7 @@
                                <generateDmg>false</generateDmg>
                                <generatePkg>false</generatePkg>
                                <relocateJar>false</relocateJar>
                            </macConfig>
                            </macConfig>-->
                        </configuration>
                    </execution>
                </executions>
@@ -140,8 +140,8 @@
                <configuration>
                    <verbose>true</verbose>
                    <fork>true</fork>
                    <!--<executable>D:\Develop\Java\jdk-9.0.1\bin\javac</executable>-->
                    <executable>/Library/Java/JavaVirtualMachines/jdk-9.0.1.jdk/Contents/Home/bin/javac</executable>
                    <executable>D:\Develop\Java\jdk-9.0.1\bin\javac</executable>
                    <!--<executable>/Library/Java/JavaVirtualMachines/jdk-9.0.1.jdk/Contents/Home/bin/javac</executable>-->
                </configuration>
            </plugin>
        </plugins>
readme.md
@@ -3,7 +3,9 @@
根据目标数据库中的传感器数据列表,到总表中查找最新的数据值
 - 将idea中的JavaFX项目打包成可执行的exe应用
https://blog.csdn.net/qq_29428909/article/details/122103131
   https://blog.csdn.net/qq_29428909/article/details/122103131
   执行maven指令打包:```mvn package```
 - JavaFx 自定义系统托盘
https://www.jianshu.com/p/6058e7a48f0b
@@ -19,3 +21,25 @@
## 解决系统托盘乱码问题
1. 运行乱码,Run->Edit Configurations->Modify options->Add VM options->输入“-Dfile.encoding=GBK”
2. 打包乱码,JavaPackager打包乱码,打开pom.xml文件,在<vmArgs>标签中插入一行<vmArg>-Dfile.encoding=GBK</vmArg>
## Release Logs
 + Tag v1.0.2
    - 20230921
    - 优化最小化和恢复显示功能;
    - 增加了多个传感器类型的网关;
    - 解决了正在同步时从菜单栏退出失败的问题。
 + Tag v1.0.1
    - 20230818
    - 优化打包后系统托盘中文乱码的问题。
 + Tag v1.0.0
    - 20230804
    - 实现了定时同步功能;
    - 支持设置同步时间的单位;
    - 支持设置是否启动即开启同步功能;
    - 支持保存配置信息到同目录;
    - 配置信息中的密码加密后再保存;
    - 只查找XPH物联网关。
src/main/java/com/github/hunter0x7c7/sync/Main.java
@@ -12,6 +12,20 @@
public class Main extends Application {
    /*    {
  "sh": "db.tyuniot.com:20235",
  "sn": "sa",
  "sp": "uQo/ij8nAbTmhCv/BKAMxQ\u003d\u003d",
  "sdn": "TY_SPDB",
  "th": "220.203.22.91:1466",
  "tn": "sa",
  "tp": "UpVahnS5ZlI/uNaNUNdmuQ\u003d\u003d",
  "tdn": "proj_hbzgfy",
  "fv": "10",
  "fu": 2,
  "ss": true
}     */
    @Override
    public void start(Stage stage) throws Exception {
        Session.getInstance().setPrimaryStage(stage);
src/main/java/com/github/hunter0x7c7/sync/ctrls/Controller.java
@@ -16,6 +16,8 @@
import io.reactivex.functions.Predicate;
import io.reactivex.schedulers.Schedulers;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
@@ -48,21 +50,37 @@
import static com.github.hunter0x7c7.sync.model.global.Parameters.*;
public class Controller {
    public static final String SQL1_QUERY = "SELECT * FROM IOT_Equipment_Info WHERE IE_Type = 'XPH物联网关'; ";
    public static final String SQL2_QUERY = "SELECT SD_Addr + '|' + SD_Code IE_Param, * " +
            "FROM TY_SensorData WHERE SD_Key IN ( '%s' );";
    public static final String SQL3_UPDATE = "UPDATE IOT_Equipment_Info " +
    //1. 在目标库,把采集类网关设备先找出来
    public static final String SQL_QUERY_TARGET = "SELECT * " +
            "FROM IOT_Equipment_Info " +
            "WHERE IE_Type = '物联网关' " +
            "OR  IE_Type = 'XPH网关' " +
            "OR IE_Type = 'XPH物联网关' " +
            "OR IE_Type = '气象站网关' " +
            "OR IE_Type = '采集物联网关' " +//温室采集机柜
            "OR IE_Type = '虫情监测网关' " +
            "OR IE_Type = '墒情网关' " +
            "OR IE_Type = '远传水表'; ";
    //2. 在源数据库,把相关网关设备的子行查找出来,拼接好Param参数00|01
    public static final String SQL_QUERY_SRC = "SELECT SD_Addr + '|' + SD_Code IE_Param, * " +
            "FROM TY_SensorData " +
            "WHERE SD_Key IN ( '%s' ); ";
    //3. 在目标库,把匹配的最新数据更新到相关的行中
    public static final String SQL_UPDATE_TARGET = "UPDATE IOT_Equipment_Info " +
            "SET IE_Realtime_Data = ?, IE_Realtime_Time = ?, Edit_User = ?, Edit_Time = ? " +
            "WHERE IE_Param = ? AND IE_Parent = ( " +
            "SELECT IE_ID FROM IOT_Equipment_Info WHERE IE_Param = ? " +
            ") ;";
            "WHERE IE_Param = ? " +
            "AND IE_Parent IN ( " +
            "SELECT IE_ID " +
            "FROM IOT_Equipment_Info " +
            "WHERE IE_Param = ? ) ;";
    @FXML
    private TextField tvInputSrcHost;
    @FXML
    private TextField tvInputSrcName;
    @FXML
    private PasswordField tvInputSrcPwd;
    private TextField tvInputSrcPwd;
    @FXML
    private TextField tvInputSrcDbName;
@@ -71,7 +89,7 @@
    @FXML
    private TextField tvInputTargetName;
    @FXML
    private PasswordField tvInputTargetPwd;
    private TextField tvInputTargetPwd;
    @FXML
    private TextField tvInputTargetDbName;
    @FXML
@@ -100,7 +118,12 @@
    private CompositeDisposable mCompositeDisposable;
    //同步频率单位列表
    private static final TimeUnit[] mFreqUnitList = {TimeUnit.MILLISECONDS, TimeUnit.SECONDS, TimeUnit.MINUTES, TimeUnit.HOURS, TimeUnit.DAYS};
    private static final TimeUnit[] mFreqUnitList = {
            TimeUnit.MILLISECONDS,
            TimeUnit.SECONDS,
            TimeUnit.MINUTES,
            TimeUnit.HOURS,
            TimeUnit.DAYS};
    //同步频率单位名称列表
    private static final String[] mFreqNameList = {"毫秒", "秒", "分钟", "小时", "天"};
    //同步次数
@@ -186,7 +209,13 @@
                        show.addActionListener(actionListener -> {
                            Platform.runLater(() -> {
                                if (primaryStage != null) {
                                    primaryStage.show();
                                    if (primaryStage.isIconified()) {//最小化
                                        primaryStage.setIconified(false);
                                    }
                                    if (!primaryStage.isShowing()) {
                                        primaryStage.show();
                                    }
                                    primaryStage.toFront();
                                }
                            });
                        });
@@ -200,6 +229,21 @@
                        };
                        //添加到系统托盘
                        TrayUtil.getInstance().addSystemTray(primaryStage, AppName_zh_rCN, mipmap, callback, show);
                        //最小化之后,关闭窗口
                        primaryStage.iconifiedProperty().addListener(new ChangeListener<Boolean>() {
                            @Override
                            public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
                                //如果你执行了setIconified(true)之后再执行hide(),
                                // stage会被销毁,因此需要在窗口关闭前将Iconified设为false
                                if (!oldValue && newValue) {
                                    //最小化之后,关闭窗口
                                    if (primaryStage != null) {
                                        primaryStage.close();
                                    }
                                }
                            }
                        });
                    }
                }).concatMap(new Function<Object, Observable<ConfigBean>>() {//准备初始化的配置数据
                    @Override
@@ -217,6 +261,9 @@
                                }).map(new Function<Boolean, ConfigBean>() {
                                    @Override
                                    public ConfigBean apply(Boolean aBoolean) throws Exception {
                                        assert configBean != null;
                                        configBean.setSrcPwd(getDecryptData(configBean.getSrcPwd()));
                                        configBean.setTargetPwd(getDecryptData(configBean.getTargetPwd()));
                                        return configBean;
                                    }
                                }).switchIfEmpty(new Observable<ConfigBean>() {
@@ -270,11 +317,11 @@
                                        //更新界面数据
                                        setInputText(tvInputSrcHost, cb.getSrcHost());
                                        setInputText(tvInputSrcName, cb.getSrcName());
                                        setInputText(tvInputSrcPwd, getDecryptData(cb.getSrcPwd()));
                                        setInputText(tvInputSrcPwd, cb.getSrcPwd());
                                        setInputText(tvInputSrcDbName, cb.getSrcDbName());
                                        setInputText(tvInputTargetHost, cb.getTargetHost());
                                        setInputText(tvInputTargetName, cb.getTargetName());
                                        setInputText(tvInputTargetPwd, getDecryptData(cb.getTargetPwd()));
                                        setInputText(tvInputTargetPwd, cb.getTargetPwd());
                                        setInputText(tvInputTargetDbName, cb.getTargetDbName());
                                        setInputText(tvInputFreq, cb.getFreqValue());
                                        cbSelectFreqUnit.getSelectionModel().select(cb.getFreqUnit());
@@ -372,42 +419,54 @@
    //点击:退出
    private void clickExit() {
        addSubscribe(Observable.just(true)
        Disposable d = Observable.just(true)
                .subscribeOn(Schedulers.newThread())
                .observeOn(Schedulers.io())
                .doOnNext(new Consumer<Object>() {
                    @Override
                    public void accept(Object o) throws Exception {
                        //1. 停止同步
                        if (isSyncing()) {
                            stopSync();//停止同步
                        }
                    }
                })
                .doOnNext(new Consumer<Object>() {
                .concatMap(new Function<Object, ObservableSource<Object>>() {
                    @Override
                    public void accept(Object o) throws Exception {
                        //检查系统是否支持托盘
                        if (SystemTray.isSupported()) {
                            //系统托盘
                            SystemTray tray = SystemTray.getSystemTray();
                            if (tray != null) {
                                TrayIcon[] icons = tray.getTrayIcons();
                                for (TrayIcon icon : icons) {
                                    if (icon == null) continue;
                    public ObservableSource<Object> apply(Object o) throws Exception {
                        //2. 移除系统托盘
                        return Observable.create(new ObservableOnSubscribe<Object>() {
                            @Override
                            public void subscribe(ObservableEmitter<Object> emitter) throws Exception {
                                //检查系统是否支持托盘
                                if (SystemTray.isSupported()) {
                                    //退出之前先移除系统托盘图标
                                    if (APPID.equals(icon.getActionCommand())) {
                                        tray.remove(icon);
                                    }
                                    final SystemTray tray = SystemTray.getSystemTray();//系统托盘
                                    final TrayIcon icon = getTrayIcon(tray, APPID);
                                    Platform.runLater(new Runnable() {
                                        @Override
                                        public void run() {
                                            try {
                                                //移除系统托盘
                                                tray.remove(icon);
                                            } catch (Exception e) {
                                                e.printStackTrace();
                                            }
                                            if (emitter != null) {
                                                emitter.onNext(o);
                                                emitter.onComplete();
                                            }
                                        }
                                    });
                                }
                            }
                        }
                        });
                    }
                })
                .subscribe(new Consumer<Boolean>() {
                .subscribe(new Consumer<Object>() {
                    @Override
                    public void accept(Boolean aBoolean) throws Exception {
                    public void accept(Object o) throws Exception {
                        //3. 停止同步和移除系统托盘后,退出程序
                        TrayUtil.getInstance().exitApp();//退出
                    }
                }, new Consumer<Throwable>() {
@@ -417,7 +476,7 @@
                        TrayUtil.getInstance().exitApp();//退出
                    }
                }));
                });
    }
@@ -495,11 +554,21 @@
    //点击:停止同步
    private void clickStopSync() {
        if (isSyncing()) {
            stopSync();//停止同步
        } else {
            updateResult("未启动同步功能!");
        stopSync();//停止同步
    }
    public TrayIcon getTrayIcon(SystemTray tray, String key) {
        if (tray != null && key != null) {
            TrayIcon[] icons = tray.getTrayIcons();
            for (TrayIcon icon : icons) {
                if (icon == null) continue;
                //找到指定的托盘图标
                if (key.equals(icon.getActionCommand())) {
                    return icon;
                }
            }
        }
        return null;
    }
    //解密
@@ -745,7 +814,10 @@
    //启动同步功能
    private void startSync() {
        mSyncCount = 0;
        setInputText(txSyncStatus, "启动中");
        setInputText(txSyncCount, String.valueOf(mSyncCount));
        setInputText(txStartSyncTime, "-");
        setInputText(txLastSyncTime, "-");
        final String srcHost = getInputText(tvInputSrcHost);
        final String srcName = getInputText(tvInputSrcName);
@@ -800,14 +872,18 @@
                .doOnNext(new Consumer<Object>() {
                    @Override
                    public void accept(Object o) throws Exception {
                        //UI线程
                        setInputText(txStartSyncTime, DateUtils.formatDate(new Date()));
                        updateResult("连接中...");
                        btnStartSync.setDisable(true);
                        btnStopSync.setDisable(false);
                    }
                })
                .observeOn(Schedulers.newThread())
                .concatMap(new Function<String, ObservableSource<String>>() {
                    @Override
                    public ObservableSource<String> apply(String targetFreq) throws Exception {
                        mSyncCount = 0;
                        setSyncing(true);
                        return getStartSyncObservable(srcHost, srcName, srcPwd, srcDbName, targetHost, targetName, targetPwd, targetDbName)
                                .map(new Function<String, String>() {
                                    @Override
@@ -821,20 +897,18 @@
                    @Override
                    public void accept(Object o) throws Exception {
                        //启动成功
                        setSyncing(true);
                        mSyncCount++;
                        if (mSyncCount >= Integer.MAX_VALUE) {
                            mSyncCount = 0;
                        if (isSyncing()) {
                            mSyncCount++;
                            if (mSyncCount >= Integer.MAX_VALUE) {
                                mSyncCount = 0;
                            }
                            setInputText(txLastSyncTime, DateUtils.formatDate(new Date()));
                            setInputText(txSyncCount, String.valueOf(mSyncCount));
                            setInputText(txSyncStatus, "同步中");
                            updateResult("启动成功!");
                            btnStartSync.setDisable(true);
                            btnStopSync.setDisable(false);
                        }
                        setInputText(txLastSyncTime, DateUtils.formatDate(new Date()));
                        setInputText(txSyncCount, String.valueOf(mSyncCount));
                        btnStartSync.setDisable(true);
                        btnStopSync.setDisable(false);
                        setInputText(txSyncStatus, "同步中");
                        setInputText(txStartSyncTime, DateUtils.formatDate(new Date()));
                        updateResult("启动成功!");
                    }
                })
                .map(new Function<String, Integer>() {
@@ -866,14 +940,17 @@
                                                    @Override
                                                    public void accept(Object o) throws Exception {
                                                        //同步成功!
                                                        setSyncing(true);
                                                        mSyncCount++;
                                                        if (mSyncCount >= Integer.MAX_VALUE) {
                                                            mSyncCount = 0;
                                                        if (isSyncing()) {
                                                            mSyncCount++;
                                                            if (mSyncCount >= Integer.MAX_VALUE) {
                                                                mSyncCount = 0;
                                                            }
                                                            setInputText(txLastSyncTime, DateUtils.formatDate(new Date()));
                                                            setInputText(txSyncCount, String.valueOf(mSyncCount));
                                                            updateResult("同步成功!");
                                                            btnStartSync.setDisable(true);
                                                            btnStopSync.setDisable(false);
                                                        }
                                                        setInputText(txLastSyncTime, DateUtils.formatDate(new Date()));
                                                        setInputText(txSyncCount, String.valueOf(mSyncCount));
                                                        updateResult("同步成功!");
                                                    }
                                                }, new Consumer<Throwable>() {
                                                    @Override
@@ -910,16 +987,16 @@
    //停止同步
    private void stopSync() {
        setSyncing(false);
        mSyncCount = 0;
        if (mCompositeDisposable != null) {
            mCompositeDisposable.clear();
            mCompositeDisposable.clear();//注意:会把所有操作都取消
        }
        setInputText(txSyncStatus, "已停止");
        updateResult("同步停止!");
        btnStartSync.setDisable(false);
        btnStopSync.setDisable(true);
        setSyncing(false);
        setInputText(txSyncStatus, "已停止");
        updateResult("停止成功!");
    }
    //保存 在启动时”启动“同步功能
@@ -975,7 +1052,8 @@
                .doOnNext(new Consumer<Object>() {
                    @Override
                    public void accept(Object o) throws Exception {
                        String targetUrl = "jdbc:sqlserver://" + targetHost + ";databaseName=" + targetDbName + ";integratedSecurity=false;";
                        String urlString = "jdbc:sqlserver://%s;databaseName=%s;integratedSecurity=false;";
                        String targetUrl = String.format(urlString, targetHost, targetDbName);
                        List<String> keyList = new ArrayList<>();
                        //1.从目标库查出来有多少传感器需要查找
@@ -983,7 +1061,7 @@
                             Statement stmt = con.createStatement()
                        ) {
                            ResultSet rs = stmt.executeQuery(SQL1_QUERY);
                            ResultSet rs = stmt.executeQuery(SQL_QUERY_TARGET);
                            while (rs.next()) {
                                keyList.add(rs.getString("IE_Param"));
                            }
@@ -1000,9 +1078,9 @@
                        for (String key : keyList) {
                            StringUtil.append(sb, key, "','");
                        }
                        String sql = String.format(SQL2_QUERY, sb);
                        String sql = String.format(SQL_QUERY_SRC, sb);
                        String srcUrl = "jdbc:sqlserver://" + srcHost + ";databaseName=" + srcDbName + ";integratedSecurity=false;";
                        String srcUrl = String.format(urlString, srcHost, srcDbName);
                        //System.out.println("sql2:" + sql);
                        Map<String, List<TargetBean>> map = new HashMap<>();
@@ -1052,7 +1130,7 @@
                                    for (TargetBean tb : list) {
                                        if (tb == null) continue;
                                        PreparedStatement ps = con3.prepareStatement(SQL3_UPDATE);
                                        PreparedStatement ps = con3.prepareStatement(SQL_UPDATE_TARGET);
                                        ps.setString(1, tb.getData());
                                        ps.setTimestamp(2, tb.getTime());
                                        ps.setString(3, version);
src/main/java/com/github/hunter0x7c7/sync/model/global/Parameters.java
@@ -5,9 +5,9 @@
//参数设置
public class Parameters {
    public static final int VersionCode = 2;
    public static final String VersionName = "1.0.1";
    public static final String BuildDate = "230818";
    public static final int VersionCode = 3;
    public static final String VersionName = "1.0.2";
    public static final String BuildDate = "230921";
    public static final BuildTypeEnum BuildType = BuildTypeEnum.RELEASE;
src/main/java/com/github/hunter0x7c7/sync/utils/TrayUtil.java
@@ -8,6 +8,7 @@
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import static com.github.hunter0x7c7.sync.model.global.Parameters.APPID;
@@ -60,26 +61,31 @@
        Image img = imageIcon.getImage();
        TrayIcon trayIcon = new TrayIcon(img, tooltip, popup);
        //设置图标尺寸自动适应
        //trayIcon.setImageAutoSize(true);
        final TrayIcon trayIcon = new TrayIcon(img, tooltip, popup);
        //trayIcon.setImageAutoSize(true);//设置图标尺寸自动适应
        trayIcon.setActionCommand(APPID);
        trayIcon.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(java.awt.event.ActionEvent e) {
            public void actionPerformed(ActionEvent e) {
                //鼠标双击系统托盘图标
                Platform.runLater(new Runnable() {
                    @Override
                    public void run() {
                        if (stage != null) {
                            stage.show();
                            if (stage.isIconified()) {//最小化
                                stage.setIconified(false);
                            }
                            if (!stage.isShowing()) {
                                stage.show();
                            }
                            stage.toFront();
                        }
                    }
                });
            }
        });
        //系统托盘
        SystemTray tray = SystemTray.getSystemTray();
        final SystemTray tray = SystemTray.getSystemTray();
        Platform.runLater(new Runnable() {
            @Override
            public void run() {
src/main/resources/forxml/sample.fxml
@@ -221,7 +221,7 @@
                                    </font>
                                </Text>
                                <Text fill="#000000b3" layoutX="27.0" layoutY="118.0" strokeType="OUTSIDE"
                                      strokeWidth="0.0" text="开始同步时间">
                                      strokeWidth="0.0" text="启动同步时间">
                                    <font>
                                        <Font size="16.0"/>
                                    </font>
@@ -266,9 +266,9 @@
                                        <Font size="16.0"/>
                                    </font>
                                </Text>
                                <Button fx:id="btnSaveConfig" layoutX="44.0" layoutY="37.0" mnemonicParsing="false"
                                        onAction="#onClickSaveConfig" prefHeight="34.0" prefWidth="200.0"
                                        text="保存配置"/>
                                <Button fx:id="btnSaveConfig" cancelButton="true" layoutX="44.0" layoutY="37.0"
                                        mnemonicParsing="false" onAction="#onClickSaveConfig" prefHeight="34.0"
                                        prefWidth="200.0" text="保存配置"/>
                                <Button fx:id="btnStartSync" defaultButton="true" layoutX="44.0" layoutY="78.0"
                                        mnemonicParsing="false" onAction="#onClickStartSync" prefHeight="62.0"
                                        prefWidth="122.0" text="启动">
@@ -276,9 +276,9 @@
                                        <Font size="18.0"/>
                                    </font>
                                </Button>
                                <Button fx:id="btnStopSync" disable="true" layoutX="172.0" layoutY="78.0"
                                        mnemonicParsing="false" onAction="#onClickStopSync" prefHeight="62.0"
                                        prefWidth="72.0" text="停止"/>
                                <Button fx:id="btnStopSync" cancelButton="true" disable="true" layoutX="172.0"
                                        layoutY="78.0" mnemonicParsing="false" onAction="#onClickStopSync"
                                        prefHeight="62.0" prefWidth="72.0" text="停止"/>
                            </children>
                        </Pane>
                    </children>