Sfoglia il codice sorgente

自动更新jar包

hfy 3 giorni fa
parent
commit
033341db0e

+ 9 - 3
src/com/mes/ui/MesClient.java

@@ -4,6 +4,7 @@ import com.alibaba.fastjson2.JSONObject;
 import com.mes.component.MesRadio;
 import com.mes.component.MesWebView;
 import com.mes.device.DeviceStateReporter;
+import com.mes.update.ClientAutoUpdater;
 import com.mes.util.DateLocalUtils;
 import com.mes.util.HttpUtils;
 import com.mes.util.JdbcUtils;
@@ -89,8 +90,12 @@ public class MesClient extends JFrame {
                 @Override
                 public void run() {
                     try{
-                        //读文件配置
-                        readProperty();
+                        Properties config = readProperty();
+                        // 程序启动时先检查是否有当前工位的新 jar;如需升级,会退出并交给外部 bat 替换 jar。
+                        if (ClientAutoUpdater.checkAndUpdate(config)) {
+                            return;
+                        }
+
 
                         // 显示界面
                         mesClientFrame = new MesClient();
@@ -112,7 +117,7 @@ public class MesClient extends JFrame {
     }
 
     //读配置文件
-    private static void readProperty() throws IOException{
+    private static Properties readProperty() throws IOException{
         String enconding = "UTF-8";
         InputStream is = ClassLoader.getSystemResourceAsStream("config/config.properties");
         Properties pro = new Properties();
@@ -131,6 +136,7 @@ public class MesClient extends JFrame {
             mes_device_heartbeat_minutes = 5;
         }
         log.info(mes_gw + ";" + mes_gw_des + ";" + mes_server_ip + ";");
+        return pro;
     }
 
     //启动心跳包程序(界面图标闪烁)

+ 218 - 0
src/com/mes/update/ClientAutoUpdater.java

@@ -0,0 +1,218 @@
+package com.mes.update;
+
+import com.alibaba.fastjson2.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.util.Properties;
+
+public class ClientAutoUpdater {
+    private static final Logger log = LoggerFactory.getLogger(ClientAutoUpdater.class);
+
+    private ClientAutoUpdater() {
+    }
+
+    public static boolean checkAndUpdate(Properties config) {
+        try {
+            // 开关配置:现场调试时可在 config.properties 中设为 false,临时关闭自动升级。
+            if (!Boolean.parseBoolean(config.getProperty("mes.update.enabled", "true"))) {
+                return false;
+            }
+
+            // 从客户端配置读取服务器 IP、产线编号和工位号,服务端按这些参数匹配对应版本包。
+            String serverIp = trim(config.getProperty("mes.server_ip"));
+            String lineSn = trim(config.getProperty("mes.line_sn"));
+            String oprno = trim(config.getProperty("mes.gw"));
+            long currentVersion = getCurrentVersion(config);
+            if (serverIp.length() == 0 || lineSn.length() == 0 || oprno.length() == 0) {
+                return false;
+            }
+
+            // 调用服务端新建的“客户端版本上传”接口,返回是否有新版本、下载地址和版本号。
+            String apiUrl = "http://" + serverIp + ":8980/js/a/mes/mesClientUpgrade/checkUpdate"
+                    + "?currentVersion=" + currentVersion
+                    + "&lineSn=" + encode(lineSn)
+                    + "&oprno=" + encode(oprno);
+            String body = httpGet(apiUrl);
+            if (body == null || body.trim().length() == 0) {
+                return false;
+            }
+
+            JSONObject resp = JSONObject.parseObject(body);
+            if (!"true".equalsIgnoreCase(resp.getString("result"))) {
+                log.info("client update skipped: {}", resp.getString("message"));
+                return false;
+            }
+
+            JSONObject data = resp.getJSONObject("data");
+            if (data == null || !data.getBooleanValue("hasUpdate")) {
+                return false;
+            }
+
+            String downloadUrl = data.getString("downloadUrl");
+            long latestVersion = data.getLongValue("latestVersion");
+            if (downloadUrl == null || downloadUrl.length() == 0 || latestVersion <= currentVersion) {
+                return false;
+            }
+
+            // jarName 是要被替换的业务 jar,exeName 是更新后要重新启动的 exe 壳。
+            String jarName = config.getProperty("mes.update.jar", "mesclient-sd.jar");
+            String exeName = config.getProperty("mes.update.exe", "mesclient-sd.exe");
+            File appDir = resolveAppDir(jarName, exeName);
+            File updateDir = new File(appDir, ".update");
+            if (!updateDir.exists() && !updateDir.mkdirs()) {
+                log.warn("create update dir failed: {}", updateDir.getAbsolutePath());
+                return false;
+            }
+
+            // 先下载到 .update 临时目录,避免下载失败时破坏当前可运行的 jar。
+            File newJar = new File(updateDir, jarName + ".new");
+            download(downloadUrl, newJar);
+
+            File targetJar = new File(appDir, jarName);
+            File backupJar = new File(appDir, jarName + ".bak");
+            File launcherExe = new File(appDir, exeName);
+            File versionFile = new File(appDir, "client-version.properties");
+            File script = new File(updateDir, "update_mesclient.bat");
+            writeUpdateScript(script, newJar, targetJar, backupJar, launcherExe, versionFile, latestVersion);
+
+            // 当前 Java 进程不能稳定覆盖正在使用的 jar,所以启动 bat 后退出,由 bat 完成替换和重启。
+            Runtime.getRuntime().exec(new String[]{"cmd", "/c", "start", "\"MES Client Update\"", script.getAbsolutePath()});
+            log.info("client update started: {} -> {}", currentVersion, latestVersion);
+            System.exit(0);
+            return true;
+        } catch (Exception e) {
+            log.warn("client update check failed: {}", e.getMessage());
+            return false;
+        }
+    }
+
+    private static long getCurrentVersion(Properties config) {
+        // 升级成功后会在 exe 同目录写入 client-version.properties,优先读取这个实际安装版本。
+        File versionFile = new File(System.getProperty("user.dir"), "client-version.properties");
+        if (versionFile.exists()) {
+            try (InputStream in = new java.io.FileInputStream(versionFile)) {
+                Properties versionProps = new Properties();
+                versionProps.load(in);
+                return Long.parseLong(versionProps.getProperty("mes.client.version", "0").trim());
+            } catch (Exception ignored) {
+            }
+        }
+        try {
+            // 第一次运行或没有版本文件时,使用 jar 内 config.properties 的初始版本号。
+            return Long.parseLong(config.getProperty("mes.client.version", "0").trim());
+        } catch (Exception e) {
+            return 0L;
+        }
+    }
+
+    private static File resolveAppDir(String jarName, String exeName) {
+        // 优先使用工作目录;如果快捷方式启动导致工作目录不对,再回退到当前 jar 所在目录。
+        File userDir = new File(System.getProperty("user.dir")).getAbsoluteFile();
+        if (new File(userDir, jarName).exists() || new File(userDir, exeName).exists()) {
+            return userDir;
+        }
+        try {
+            File codeFile = new File(ClientAutoUpdater.class.getProtectionDomain()
+                    .getCodeSource().getLocation().toURI()).getAbsoluteFile();
+            if (codeFile.isFile()) {
+                return codeFile.getParentFile();
+            }
+            return codeFile;
+        } catch (Exception e) {
+            return userDir;
+        }
+    }
+
+    private static String httpGet(String apiUrl) throws Exception {
+        // 简单 GET 请求,用于获取服务端版本检查 JSON。
+        HttpURLConnection connection = null;
+        try {
+            URL url = new URL(apiUrl);
+            connection = (HttpURLConnection) url.openConnection();
+            connection.setRequestMethod("GET");
+            connection.setConnectTimeout(5000);
+            connection.setReadTimeout(15000);
+            if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
+                return null;
+            }
+            try (InputStream in = connection.getInputStream()) {
+                return readAll(in);
+            }
+        } finally {
+            if (connection != null) {
+                connection.disconnect();
+            }
+        }
+    }
+
+    private static void download(String downloadUrl, File target) throws Exception {
+        // 下载新版 jar 到临时文件,下载完成后再由 bat 脚本替换正式 jar。
+        HttpURLConnection connection = null;
+        try {
+            URL url = new URL(downloadUrl);
+            connection = (HttpURLConnection) url.openConnection();
+            connection.setConnectTimeout(10000);
+            connection.setReadTimeout(120000);
+            if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
+                throw new IllegalStateException("download failed, responseCode=" + connection.getResponseCode());
+            }
+            try (InputStream in = new BufferedInputStream(connection.getInputStream());
+                 BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(target))) {
+                byte[] buffer = new byte[8192];
+                int len;
+                while ((len = in.read(buffer)) != -1) {
+                    out.write(buffer, 0, len);
+                }
+            }
+        } finally {
+            if (connection != null) {
+                connection.disconnect();
+            }
+        }
+    }
+
+    private static String readAll(InputStream in) throws Exception {
+        java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream();
+        byte[] buffer = new byte[4096];
+        int len;
+        while ((len = in.read(buffer)) != -1) {
+            out.write(buffer, 0, len);
+        }
+        return new String(out.toByteArray(), "UTF-8");
+    }
+
+    private static void writeUpdateScript(File script, File newJar, File targetJar, File backupJar,
+                                          File launcherExe, File versionFile, long latestVersion) throws Exception {
+        try (BufferedWriter writer = new BufferedWriter(new FileWriter(script))) {
+            // bat 等待当前程序退出后,备份旧 jar、复制新 jar、写入版本号,并重新启动 exe。
+            writer.write("@echo off\r\n");
+            writer.write("chcp 65001 >nul\r\n");
+            writer.write("timeout /t 2 /nobreak >nul\r\n");
+            writer.write("if exist \"" + targetJar.getAbsolutePath() + "\" copy /Y \"" + targetJar.getAbsolutePath() + "\" \"" + backupJar.getAbsolutePath() + "\" >nul\r\n");
+            writer.write("copy /Y \"" + newJar.getAbsolutePath() + "\" \"" + targetJar.getAbsolutePath() + "\" >nul\r\n");
+            writer.write("if errorlevel 1 (\r\n");
+            writer.write("  if exist \"" + backupJar.getAbsolutePath() + "\" copy /Y \"" + backupJar.getAbsolutePath() + "\" \"" + targetJar.getAbsolutePath() + "\" >nul\r\n");
+            writer.write("  exit /b 1\r\n");
+            writer.write(")\r\n");
+            writer.write("echo mes.client.version=" + latestVersion + ">\"" + versionFile.getAbsolutePath() + "\"\r\n");
+            writer.write("if exist \"" + launcherExe.getAbsolutePath() + "\" (\r\n");
+            writer.write("  start \"\" \"" + launcherExe.getAbsolutePath() + "\"\r\n");
+            writer.write(") else (\r\n");
+            writer.write("  start \"\" javaw -jar \"" + targetJar.getAbsolutePath() + "\"\r\n");
+            writer.write(")\r\n");
+        }
+    }
+
+    private static String encode(String value) throws Exception {
+        return URLEncoder.encode(value, "UTF-8");
+    }
+
+    private static String trim(String value) {
+        return value == null ? "" : value.trim();
+    }
+}

+ 11 - 3
src/resources/config/config.properties

@@ -1,6 +1,14 @@
 mes.gw=OP030A
-mes.server_ip=127.0.0.1
-#mes.server_ip=192.168.9.180
+#mes.server_ip=127.0.0.1
+mes.server_ip=192.168.9.180
 mes.line_sn=115XT
 # 设备状态定时上报间隔(分钟)
-mes.device.heartbeat.minutes=5
+mes.device.heartbeat.minutes=5
+# ???????????????????????? client-version.properties ???????
+mes.client.version=100
+# ??????????????????????? false?
+mes.update.enabled=true
+# ??????????? jar ?????? exe4j ?? classpath ? jar ?????
+mes.update.jar=mesclient-sd.jar
+# ????????????? exe ?????? exe4j ??? exe ??????
+mes.update.exe=mesclient-sd.exe