|
|
@@ -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();
|
|
|
+ }
|
|
|
+}
|