This commit is contained in:
2025-09-26 16:27:27 +08:00
parent 1d0bab7804
commit fcc5755dad
11 changed files with 403 additions and 745 deletions

View File

@@ -1,39 +1,21 @@
package com.tashow.erp.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
/**
* Web版本更新控制器
* Web版本信息控制器
*
* @author Claude
*/
@RestController
@RequestMapping("/api/update")
public class UpdateController {
@Value("${project.version:2.3.6}")
@Value("${project.version:2.3.6}")
private String currentVersion;
@Value("${project.build.time:}")
private String buildTime;
// 简化:移除与鉴权/版本转发/SQLite 存储相关的依赖
// 下载进度跟踪
private volatile int downloadProgress = 0;
private volatile long downloadedBytes = 0;
private volatile long totalBytes = 0;
private volatile String downloadStatus = "ready"; // ready, downloading, completed, failed, cancelled
private volatile String downloadSpeed = "0 KB/s";
private volatile long downloadStartTime = 0;
private volatile boolean downloadCancelled = false;
/**
* 获取当前版本号
@@ -48,415 +30,4 @@ public class UpdateController {
result.put("buildTime", buildTime);
return result;
}
/**
* 获取下载进度
*
* @return 下载进度信息
*/
@GetMapping("/progress")
public Map<String, Object> getDownloadProgress() {
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("progress", downloadProgress);
result.put("status", downloadStatus);
result.put("downloadedBytes", downloadedBytes);
result.put("totalBytes", totalBytes);
result.put("downloadSpeed", downloadSpeed);
result.put("downloadedMB", String.format("%.1f", downloadedBytes / 1024.0 / 1024.0));
result.put("totalMB", String.format("%.1f", totalBytes / 1024.0 / 1024.0));
return result;
}
/**
* 重置下载状态
*
* @return 重置结果
*/
@PostMapping("/reset")
public Map<String, Object> resetDownloadStatus() {
Map<String, Object> result = new HashMap<>();
downloadProgress = 0;
downloadedBytes = 0;
totalBytes = 0;
downloadStatus = "ready";
downloadSpeed = "0 KB/s";
downloadStartTime = 0;
downloadCancelled = false;
tempUpdateFilePath = null;
result.put("success", true);
result.put("message", "下载状态已重置");
return result;
}
/**
* 取消下载
*
* @return 取消结果
*/
@PostMapping("/cancel")
public Map<String, Object> cancelDownload() {
Map<String, Object> result = new HashMap<>();
if ("downloading".equals(downloadStatus)) {
downloadCancelled = true;
downloadStatus = "cancelled";
downloadSpeed = "已取消";
result.put("success", true);
result.put("message", "下载已取消");
} else if ("completed".equals(downloadStatus)) {
// 下载完成后“稍后更新”:仅关闭弹窗,不触发安装,不改变文件
result.put("success", true);
result.put("message", "已设置为稍后更新");
} else {
result.put("success", false);
result.put("message", "无效的操作状态");
}
return result;
}
/**
* 完全清除更新文件和状态
*
* @return 清除结果
*/
@PostMapping("/clear")
public Map<String, Object> clearUpdateFiles() {
Map<String, Object> result = new HashMap<>();
if (tempUpdateFilePath != null) {
try {
java.io.File tempFile = new java.io.File(tempUpdateFilePath);
if (tempFile.exists()) {
tempFile.delete();
System.out.println("已删除更新文件: " + tempUpdateFilePath);
}
} catch (Exception e) {
System.err.println("删除临时文件失败: " + e.getMessage());
}
}
// 重置状态
downloadProgress = 0;
downloadedBytes = 0;
totalBytes = 0;
downloadStatus = "ready";
downloadSpeed = "0 KB/s";
downloadCancelled = false;
tempUpdateFilePath = null;
result.put("success", true);
result.put("message", "更新文件和状态已清除");
return result;
}
/**
* 验证更新文件是否存在
*
* @return 验证结果
*/
@GetMapping("/verify-file")
public Map<String, Object> verifyUpdateFile() {
Map<String, Object> result = new HashMap<>();
try {
boolean fileExists = false;
String filePath = "";
if (tempUpdateFilePath != null) {
java.io.File updateFile = new java.io.File(tempUpdateFilePath);
fileExists = updateFile.exists();
filePath = tempUpdateFilePath;
if (fileExists) {
result.put("fileSize", updateFile.length());
result.put("lastModified", new java.util.Date(updateFile.lastModified()));
}
}
result.put("success", true);
result.put("fileExists", fileExists);
result.put("filePath", filePath);
result.put("downloadStatus", downloadStatus);
System.out.println("验证更新文件: " + filePath + ", 存在: " + fileExists);
} catch (Exception e) {
result.put("success", false);
result.put("message", "验证文件失败:" + e.getMessage());
result.put("fileExists", false);
}
return result;
}
/**
* 用户确认后执行安装
*
* @return 安装结果
*/
@PostMapping("/install")
public Map<String, Object> installUpdate() {
Map<String, Object> result = new HashMap<>();
if (tempUpdateFilePath == null || !new java.io.File(tempUpdateFilePath).exists()) {
result.put("success", false);
result.put("message", "更新文件不存在,请重新下载");
return result;
}
try {
result.put("success", true);
result.put("message", "开始安装更新...");
// 异步执行安装,避免阻塞响应
new Thread(() -> {
String updateScript = createUpdateScript(null, tempUpdateFilePath);
if (updateScript != null) {
executeUpdateAndExit(updateScript);
}
}).start();
} catch (Exception e) {
result.put("success", false);
result.put("message", "启动安装失败:" + e.getMessage());
}
return result;
}
/**
* 自动更新:下载、替换、重启
*
* @return 更新结果
*/
@PostMapping("/auto-update")
public Map<String, Object> autoUpdate(@RequestBody Map<String, String> request) {
Map<String, Object> result = new HashMap<>();
try {
String downloadUrl = request.get("downloadUrl");
result.put("success", true);
result.put("message", "开始自动更新...");
result.put("downloadUrl", downloadUrl);
String finalDownloadUrl = downloadUrl;
new Thread(() -> {
performUpdate(finalDownloadUrl);
}).start();
} catch (Exception e) {
result.put("success", false);
result.put("message", "启动更新失败:" + e.getMessage());
}
return result;
}
/**
* 执行更新过程
*/
private void performUpdate(String downloadUrl) {
String tempUpdateFile = downloadUpdate(downloadUrl);
if (tempUpdateFile == null) {
downloadStatus = "failed";
return;
}
// 下载完成后不自动重启,等待用户确认
downloadStatus = "completed";
System.out.println("下载完成,等待用户确认安装...");
// 将下载文件路径保存,供后续安装使用
this.tempUpdateFilePath = tempUpdateFile;
}
private String tempUpdateFilePath = null;
/**
* 下载更新文件
*/
private String downloadUpdate(String downloadUrl) {
try {
downloadStatus = "downloading";
downloadProgress = 0;
downloadedBytes = 0;
downloadStartTime = System.currentTimeMillis();
URL url = new URL(downloadUrl);
URLConnection connection = url.openConnection();
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
connection.setConnectTimeout(10000);
connection.setReadTimeout(30000);
// 获取文件大小
totalBytes = connection.getContentLength();
if (totalBytes <= 0) {
totalBytes = 70 * 1024 * 1024; // 默认70MB
}
String tempDir = System.getProperty("java.io.tmpdir");
String updateFileName = "erp_update_" + System.currentTimeMillis() + ".exe";
String tempUpdatePath = Paths.get(tempDir, updateFileName).toString();
System.out.println("开始下载更新文件,大小: " + (totalBytes / 1024 / 1024) + "MB");
try (InputStream inputStream = connection.getInputStream();
FileOutputStream outputStream = new FileOutputStream(tempUpdatePath)) {
byte[] buffer = new byte[8192];
int bytesRead;
long lastSpeedUpdate = System.currentTimeMillis();
long lastDownloadedBytes = 0;
while ((bytesRead = inputStream.read(buffer)) != -1) {
if (downloadCancelled) {
System.out.println("下载已取消,停止下载进程");
downloadStatus = "cancelled";
downloadSpeed = "已取消";
outputStream.close();
try {
java.io.File partialFile = new java.io.File(tempUpdatePath);
if (partialFile.exists()) {
partialFile.delete();
}
} catch (Exception e) {
System.err.println("删除部分下载文件失败: " + e.getMessage());
}
return null;
}
outputStream.write(buffer, 0, bytesRead);
downloadedBytes += bytesRead;
// 计算下载进度
downloadProgress = (int) ((downloadedBytes * 100) / totalBytes);
// 每秒计算一次下载速度
long currentTime = System.currentTimeMillis();
if (currentTime - lastSpeedUpdate >= 1000) {
long speedBytes = downloadedBytes - lastDownloadedBytes;
double speedKB = speedBytes / 1024.0;
if (speedKB >= 1024) {
downloadSpeed = String.format("%.1f MB/s", speedKB / 1024.0);
} else {
downloadSpeed = String.format("%.1f KB/s", speedKB);
}
lastSpeedUpdate = currentTime;
lastDownloadedBytes = downloadedBytes;
System.out.println(String.format("下载进度: %d%% (%d/%d MB) 速度: %s",
downloadProgress,
downloadedBytes / 1024 / 1024,
totalBytes / 1024 / 1024,
downloadSpeed));
}
// 防止阻塞UI线程
if (downloadedBytes % (1024 * 1024) == 0) { // 每MB休息一下
Thread.sleep(10);
}
}
downloadStatus = "completed";
downloadProgress = 100;
downloadSpeed = "完成";
System.out.println("下载完成: " + (downloadedBytes / 1024 / 1024) + "MB");
}
return tempUpdatePath;
} catch (Exception e) {
downloadStatus = "failed";
downloadSpeed = "失败";
System.err.println("下载失败: " + e.getMessage());
e.printStackTrace();
return null;
}
}
/**
* 创建更新脚本
*/
private String createUpdateScript(String currentExePath, String tempUpdateFile) {
try {
String tempDir = System.getProperty("java.io.tmpdir");
String scriptPath = Paths.get(tempDir, "update_erp.bat").toString();
File currentDir = new File(System.getProperty("user.dir"));
File targetExe = new File(currentDir, "erpClient.exe");
String targetPath = targetExe.getAbsolutePath();
StringBuilder script = new StringBuilder();
script.append("@echo off\r\n");
script.append("chcp 65001 > nul\r\n");
script.append("echo 正在等待程序完全退出...\r\n");
script.append("timeout /t 1 /nobreak > nul\r\n");
script.append("echo 开始更新程序文件...\r\n");
script.append("if exist \"").append(targetPath).append("\" (");
script.append(" move \"").append(targetPath).append("\" \"").append(targetPath).append(".backup\"");
script.append(" )\r\n");
script.append("move \"").append(tempUpdateFile).append("\" \"").append(targetPath).append("\"\r\n");
script.append("if exist \"").append(targetPath).append("\" (\r\n");
script.append(" echo 更新成功程序将在1秒后重新启动...\r\n");
script.append(" timeout /t 1 /nobreak > nul\r\n");
script.append(" start \"\" \"").append(targetPath).append("\"\r\n");
script.append(" if exist \"").append(targetPath).append(".backup\" del \"").append(targetPath).append(".backup\"\r\n");
script.append(") else (\r\n");
script.append(" echo 更新失败!正在恢复原版本...\r\n");
script.append(" if exist \"").append(targetPath).append(".backup\" (\r\n");
script.append(" move \"").append(targetPath).append(".backup\" \"").append(targetPath).append("\"\r\n");
script.append(" echo 已恢复原版本程序将在1秒后重新启动...\r\n");
script.append(" timeout /t 1 /nobreak > nul\r\n");
script.append(" start \"\" \"").append(targetPath).append("\"\r\n");
script.append(" )\r\n");
script.append(")\r\n");
script.append("echo 更新操作完成!\r\n");
script.append("timeout /t 1 /nobreak > nul\r\n");
script.append("(goto) 2>nul & del \"%~f0\" & exit\r\n");
Files.write(Paths.get(scriptPath), script.toString().getBytes("GBK"));
return scriptPath;
} catch (Exception e) {
return null;
}
}
/**
* 执行更新脚本并退出程序
*/
private void executeUpdateAndExit(String scriptPath) {
try {
System.out.println("下载完成!正在准备更新...");
ProcessBuilder pb = new ProcessBuilder("cmd", "/c", scriptPath);
pb.redirectOutput(ProcessBuilder.Redirect.DISCARD);
pb.redirectError(ProcessBuilder.Redirect.DISCARD);
pb.start();
System.out.println("更新程序已启动,当前程序即将退出...");
Thread.sleep(1000);
Runtime.getRuntime().halt(0);
} catch (Exception e) {
Runtime.getRuntime().halt(1);
}
}
// /**
// * 保存跳过的版本
// */
// @PostMapping("/skip-version")
// public Map<String, Object> saveSkippedVersion(@RequestBody Map<String, String> request) {
// Map<String, Object> result = new HashMap<>();
// try {
// saveUpdateInfo("skippedUpdateVersion", request.get("version"));
// result.put("success", true);
// } catch (Exception e) {
// result.put("success", false);
// }
// return result;
// }
}