| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244 |
- package com.mes.update;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import javax.swing.JDialog;
- import javax.swing.JLabel;
- import javax.swing.JOptionPane;
- import javax.swing.JPanel;
- import javax.swing.JProgressBar;
- import javax.swing.SwingUtilities;
- import javax.swing.WindowConstants;
- import java.awt.BorderLayout;
- import java.awt.Component;
- import java.awt.Dimension;
- import java.awt.Frame;
- import java.io.File;
- import java.io.FileOutputStream;
- import java.io.OutputStreamWriter;
- import java.nio.charset.StandardCharsets;
- import java.util.concurrent.atomic.AtomicBoolean;
- /**
- * 启动时自动升级编排:检查 → 弹窗 → 下载 → 替换脚本 → 退出。
- */
- public final class ClientUpdater {
- private static final Logger log = LoggerFactory.getLogger(ClientUpdater.class);
- private ClientUpdater() {
- }
- /**
- * @return true 表示继续正常启动;false 表示应退出进程(已安排升级或强制更新失败)
- */
- public static boolean checkAndMaybeUpdate(String serverIp,
- String localVersionFromConfig,
- boolean enabled,
- String updatePath) {
- if (!enabled) {
- log.info("自动升级已关闭");
- return true;
- }
- File installDir = UpdateInstaller.resolveInstallDir();
- String localVersion = VersionUtil.resolveLocalVersion(installDir, localVersionFromConfig);
- log.info("本地版本={},安装目录={}", localVersion, installDir.getAbsolutePath());
- UpdateInfo info;
- try {
- info = new UpdateChecker().fetch(serverIp, updatePath);
- } catch (Exception e) {
- log.warn("升级检查失败,继续启动:{}", e.getMessage());
- return true;
- }
- if (info == null) {
- return true;
- }
- if (!VersionUtil.isNewer(info.getVersion(), localVersion)) {
- log.info("已是最新版本:local={} remote={}", localVersion, info.getVersion());
- return true;
- }
- log.info("发现新版本:{} -> {},force={}", localVersion, info.getVersion(), info.isForce());
- final boolean[] proceed = new boolean[]{false};
- try {
- runOnEdtWait(() -> proceed[0] = askUser(null, localVersion, info));
- } catch (Exception e) {
- log.warn("升级确认弹窗失败:{}", e.getMessage());
- return true;
- }
- if (!proceed[0]) {
- if (info.isForce()) {
- try {
- runOnEdtWait(() -> JOptionPane.showMessageDialog(null,
- "当前版本必须升级后才能使用。",
- "强制更新",
- JOptionPane.WARNING_MESSAGE));
- } catch (Exception ignored) {
- }
- return false;
- }
- return true;
- }
- boolean applied = downloadAndApply(installDir, info);
- if (!applied) {
- try {
- runOnEdtWait(() -> {
- if (info.isForce()) {
- JOptionPane.showMessageDialog(null,
- "强制更新失败,程序将退出。请检查网络或联系管理员。",
- "更新失败",
- JOptionPane.ERROR_MESSAGE);
- } else {
- JOptionPane.showMessageDialog(null,
- "更新失败,将继续使用当前版本。",
- "更新失败",
- JOptionPane.WARNING_MESSAGE);
- }
- });
- } catch (Exception ignored) {
- }
- return !info.isForce();
- }
- return false;
- }
- /** 已在 EDT 时直接执行,避免 invokeAndWait 死锁 */
- private static void runOnEdtWait(Runnable action) throws Exception {
- if (SwingUtilities.isEventDispatchThread()) {
- action.run();
- } else {
- SwingUtilities.invokeAndWait(action);
- }
- }
- private static boolean askUser(Component parent, String localVersion, UpdateInfo info) {
- String notes = info.getNotes() == null ? "" : info.getNotes().trim();
- StringBuilder msg = new StringBuilder();
- msg.append("发现新版本 ").append(info.getVersion())
- .append("(当前 ").append(localVersion).append(")\n\n");
- if (!notes.isEmpty()) {
- msg.append(notes).append("\n\n");
- }
- if (info.isForce()) {
- msg.append("本次为强制更新,必须升级后才能继续使用。");
- JOptionPane.showMessageDialog(parent, msg.toString(), "强制更新", JOptionPane.INFORMATION_MESSAGE);
- return true;
- }
- msg.append("是否立即更新?");
- int choice = JOptionPane.showConfirmDialog(parent, msg.toString(), "发现新版本",
- JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
- return choice == JOptionPane.YES_OPTION;
- }
- private static boolean downloadAndApply(File installDir, UpdateInfo info) {
- File tempRoot = new File(System.getProperty("java.io.tmpdir"),
- "mesclient-update-" + System.currentTimeMillis());
- File zipFile = new File(tempRoot, "package.zip");
- File extractDir = new File(tempRoot, "payload");
- if (!tempRoot.mkdirs()) {
- log.warn("无法创建临时目录:{}", tempRoot.getAbsolutePath());
- return false;
- }
- JProgressBar bar = new JProgressBar(0, 100);
- bar.setStringPainted(true);
- bar.setPreferredSize(new Dimension(360, 24));
- JLabel label = new JLabel("正在下载更新包...");
- JPanel panel = new JPanel(new BorderLayout(8, 8));
- panel.add(label, BorderLayout.NORTH);
- panel.add(bar, BorderLayout.CENTER);
- JDialog dialog = new JDialog((Frame) null, "正在更新", true);
- dialog.setContentPane(panel);
- dialog.pack();
- dialog.setLocationRelativeTo(null);
- dialog.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
- AtomicBoolean ok = new AtomicBoolean(false);
- Thread worker = new Thread(() -> {
- try {
- UpdateDownloader downloader = new UpdateDownloader();
- File downloaded = downloader.download(info.getPackageUrl(), zipFile, (downloadedBytes, total) -> {
- final int percent;
- if (total > 0) {
- percent = (int) Math.min(100, (downloadedBytes * 100) / total);
- } else {
- percent = -1;
- }
- SwingUtilities.invokeLater(() -> {
- if (percent < 0) {
- bar.setIndeterminate(true);
- bar.setString(formatSize(downloadedBytes));
- } else {
- bar.setIndeterminate(false);
- bar.setValue(percent);
- bar.setString(percent + "%");
- }
- });
- });
- if (downloaded == null) {
- return;
- }
- if (!downloader.verifySha256(downloaded, info.getSha256())) {
- SwingUtilities.invokeLater(() -> label.setText("校验失败"));
- return;
- }
- SwingUtilities.invokeLater(() -> {
- label.setText("正在解压...");
- bar.setIndeterminate(true);
- bar.setString("解压中");
- });
- UpdateInstaller installer = new UpdateInstaller();
- File payload = installer.unzip(downloaded, extractDir);
- File versionFile = new File(payload, "app.version");
- if (!versionFile.isFile()) {
- try (OutputStreamWriter w = new OutputStreamWriter(
- new FileOutputStream(versionFile), StandardCharsets.UTF_8)) {
- w.write(info.getVersion());
- }
- }
- String exeName = UpdateInstaller.resolveExeName(installDir);
- File[] payloadFiles = payload.listFiles();
- if (payloadFiles != null) {
- for (File f : payloadFiles) {
- if (f.isFile() && f.getName().toLowerCase().endsWith(".exe")) {
- exeName = f.getName();
- break;
- }
- }
- }
- SwingUtilities.invokeLater(() -> label.setText("准备替换并重启..."));
- installer.launchReplaceScript(installDir, payload, exeName);
- ok.set(true);
- } catch (Exception e) {
- log.warn("执行升级失败:{}", e.getMessage(), e);
- } finally {
- SwingUtilities.invokeLater(dialog::dispose);
- }
- }, "mes-client-updater");
- worker.setDaemon(true);
- worker.start();
- dialog.setVisible(true);
- try {
- worker.join(5 * 60 * 1000L);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- if (!ok.get()) {
- UpdateInstaller.deleteRecursively(tempRoot);
- }
- return ok.get();
- }
- private static String formatSize(long bytes) {
- if (bytes < 1024) {
- return bytes + " B";
- }
- if (bytes < 1024 * 1024) {
- return String.format("%.1f KB", bytes / 1024.0);
- }
- return String.format("%.1f MB", bytes / (1024.0 * 1024.0));
- }
- }
|