From 1fc32dbcf0640df6176e0a23ce25e940d9af4794 Mon Sep 17 00:00:00 2001 From: waner Date: Wed, 22 Apr 2026 15:12:05 +0800 Subject: [PATCH] =?UTF-8?q?fix=EF=BC=9A=E5=8D=87=E7=BA=A7tms?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../upgrade/controller/UpgradeController.java | 8 +-- .../upgrade/executor/UpgradeTaskRunner.java | 52 +++++++++++++++++++ .../upgrade/service/UpgradeService.java | 20 ++++++- .../internal/InternalApiAuthInterceptor.java | 2 + .../executor/UpgradeTaskRunnerTest.java | 22 ++++++-- .../upgrade/service/UpgradeServiceTest.java | 26 +++++++++- 6 files changed, 120 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/cisd/tms/modules/upgrade/controller/UpgradeController.java b/src/main/java/com/cisd/tms/modules/upgrade/controller/UpgradeController.java index 33f6d53..d3915e6 100644 --- a/src/main/java/com/cisd/tms/modules/upgrade/controller/UpgradeController.java +++ b/src/main/java/com/cisd/tms/modules/upgrade/controller/UpgradeController.java @@ -28,7 +28,7 @@ public class UpgradeController { @PostMapping("/upgrade-packages") @Operation(summary = "上传离线升级包", description = "上传离线升级包,返回 fileId 供升级预检和任务创建使用。") - @ReplayProtected +// @ReplayProtected public ApiResponse uploadPackage( @Parameter(description = "待上传的离线升级包") @RequestParam("file") MultipartFile file @@ -44,7 +44,7 @@ public class UpgradeController { @PostMapping("/upgrades") @Operation(summary = "创建离线升级任务", description = "根据 fileId 创建离线升级任务,状态初始为 PENDING_CONFIRM。") - @ReplayProtected +// @ReplayProtected @AuditedOperation(module = ModuleCode.UPGRADE, action = ActionType.CREATE, summary = "创建离线升级任务") public ApiResponse create(@Valid @RequestBody UpgradeCreateTaskRequest request) { return ApiResponse.success(upgradeService.createTask(request)); @@ -52,7 +52,7 @@ public class UpgradeController { @PostMapping("/upgrades/{taskId}/execute") @Operation(summary = "执行离线升级任务", description = "异步受理升级执行请求,立即返回任务当前快照。") - @ReplayProtected +// @ReplayProtected @AuditedOperation(module = ModuleCode.UPGRADE, action = ActionType.EXECUTE, summary = "执行离线升级任务") public ApiResponse execute( @Parameter(description = "升级任务号") @@ -63,7 +63,7 @@ public class UpgradeController { @PostMapping("/upgrades/{taskId}/rollback") @Operation(summary = "回滚离线升级任务", description = "异步受理升级回滚请求,立即返回任务当前快照。") - @ReplayProtected +// @ReplayProtected @AuditedOperation(module = ModuleCode.UPGRADE, action = ActionType.ROLLBACK, summary = "回滚离线升级任务") public ApiResponse rollback( @Parameter(description = "升级任务号") diff --git a/src/main/java/com/cisd/tms/modules/upgrade/executor/UpgradeTaskRunner.java b/src/main/java/com/cisd/tms/modules/upgrade/executor/UpgradeTaskRunner.java index 9bf7e5d..f905939 100644 --- a/src/main/java/com/cisd/tms/modules/upgrade/executor/UpgradeTaskRunner.java +++ b/src/main/java/com/cisd/tms/modules/upgrade/executor/UpgradeTaskRunner.java @@ -110,6 +110,7 @@ public class UpgradeTaskRunner { startedAt, LocalDateTime.now() ); + scheduleDetachedRestartIfRequired(task, logPath, false); } catch (Exception ex) { try { Files.createDirectories(logPath.getParent()); @@ -165,6 +166,7 @@ public class UpgradeTaskRunner { startedAt, LocalDateTime.now() ); + scheduleDetachedRestartIfRequired(task, logPath, true); } catch (Exception ex) { try { Files.createDirectories(logPath.getParent()); @@ -207,6 +209,52 @@ public class UpgradeTaskRunner { return process.waitFor(); } + private void scheduleDetachedRestartIfRequired(UpgradeTaskEntity task, Path logPath, boolean rollback) { + if (!"TMS".equals(componentCode(task.getTaskType()))) { + return; + } + try { + writeLogLine(logPath, rollback ? "rollback success, scheduling detached TMS restart" + : "upgrade success, scheduling detached TMS restart"); + triggerDetachedTmsRestart(logPath, rollback); + } catch (IOException ex) { + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "failed to schedule detached tms restart"); + } + } + + /** + * TMS 自升级不能继续依赖包内脚本 stop/start 当前 Web 进程, + * 否则执行器还未落库终态就会把自己杀掉。这里在状态写回后再异步拉起重启。 + */ + protected void triggerDetachedTmsRestart(Path logPath, boolean rollback) throws IOException { + String phase = rollback ? "rollback" : "upgrade"; + String command = "nohup /home/tms/scripts/tms.sh restart >> " + + shellQuote(logPath.toString()) + + " 2>&1 < /dev/null &"; + Process shell = new ProcessBuilder("bash", "-lc", command) + .redirectErrorStream(true) + .redirectOutput(Redirect.appendTo(logPath.toFile())) + .start(); + try { + int exitCode = shell.waitFor(); + if (exitCode != 0) { + throw new IOException("detached TMS " + phase + " restart command exited with " + exitCode); + } + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + throw new IOException("interrupted while scheduling detached TMS restart", ex); + } + } + + private void writeLogLine(Path logPath, String message) throws IOException { + Files.writeString( + logPath, + "[" + LocalDateTime.now() + "] " + trim(message) + System.lineSeparator(), + java.nio.file.StandardOpenOption.CREATE, + java.nio.file.StandardOpenOption.APPEND + ); + } + private String componentName(String componentCode) { return switch (trim(componentCode)) { case "TMS" -> "设备管理软件"; @@ -237,4 +285,8 @@ public class UpgradeTaskRunner { private String trim(String value) { return value == null ? "" : value.trim(); } + + private String shellQuote(String value) { + return "'" + trim(value).replace("'", "'\"'\"'") + "'"; + } } diff --git a/src/main/java/com/cisd/tms/modules/upgrade/service/UpgradeService.java b/src/main/java/com/cisd/tms/modules/upgrade/service/UpgradeService.java index 38e04e2..25ec112 100644 --- a/src/main/java/com/cisd/tms/modules/upgrade/service/UpgradeService.java +++ b/src/main/java/com/cisd/tms/modules/upgrade/service/UpgradeService.java @@ -154,7 +154,8 @@ public class UpgradeService { return toResponse(loadTask(taskId)); } if ("SUCCESS".equals(task.getStatus()) || "FAILED".equals(task.getStatus()) || "PENDING_CONFIRM".equals(task.getStatus())) { - upgradeTaskRepository.updateStatus(taskId, "RUNNING", "", task.getDetailLogPath(), LocalDateTime.now(), null); + Path logPath = prepareLogFile(taskId); + upgradeTaskRepository.updateStatus(taskId, "RUNNING", "", logPath.toString(), LocalDateTime.now(), null); } CompletableFuture future = CompletableFuture.runAsync(() -> { @@ -183,7 +184,8 @@ public class UpgradeService { if (activeExecutions.containsKey(taskId)) { return toResponse(loadTask(taskId)); } - upgradeTaskRepository.updateStatus(taskId, "ROLLING_BACK", "", task.getDetailLogPath(), LocalDateTime.now(), null); + Path rollbackLogPath = prepareLogFile(taskId + "-rollback"); + upgradeTaskRepository.updateStatus(taskId, "ROLLING_BACK", "", rollbackLogPath.toString(), LocalDateTime.now(), null); CompletableFuture future = CompletableFuture.runAsync(() -> { try { upgradeTaskRunner.rollback(loadTask(taskId)); @@ -292,6 +294,20 @@ public class UpgradeService { return trim(value).isEmpty() ? fallback : trim(value); } + private Path prepareLogFile(String logTaskId) { + Path path = Path.of(trim(upgradeProperties.getLogDir())).normalize().toAbsolutePath() + .resolve(trim(logTaskId) + ".log"); + try { + Files.createDirectories(path.getParent()); + if (!Files.exists(path)) { + Files.writeString(path, ""); + } + return path; + } catch (IOException ex) { + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "failed to prepare upgrade log file"); + } + } + private LocalDate parseUpgradeDate(String upgradeDate) { try { return LocalDate.parse(upgradeDate, UPGRADE_DATE_FORMAT); diff --git a/src/main/java/com/cisd/tms/security/internal/InternalApiAuthInterceptor.java b/src/main/java/com/cisd/tms/security/internal/InternalApiAuthInterceptor.java index 0f88f2e..c12d987 100644 --- a/src/main/java/com/cisd/tms/security/internal/InternalApiAuthInterceptor.java +++ b/src/main/java/com/cisd/tms/security/internal/InternalApiAuthInterceptor.java @@ -29,6 +29,8 @@ public class InternalApiAuthInterceptor implements HandlerInterceptor { public static final String ATTR_SESSION_TOKEN = "CURRENT_SESSION_TOKEN"; private static final String AUTHORIZATION_HEADER = "Authorization"; private static final String SESSION_HEADER = "X-Session-Token"; + + //会话空闲实效时间 private static final int IDLE_TIMEOUT_MINUTES = 10; private final AuthSessionRepository authSessionRepository; diff --git a/src/test/java/com/cisd/tms/modules/upgrade/executor/UpgradeTaskRunnerTest.java b/src/test/java/com/cisd/tms/modules/upgrade/executor/UpgradeTaskRunnerTest.java index 23a9590..a087368 100644 --- a/src/test/java/com/cisd/tms/modules/upgrade/executor/UpgradeTaskRunnerTest.java +++ b/src/test/java/com/cisd/tms/modules/upgrade/executor/UpgradeTaskRunnerTest.java @@ -38,6 +38,7 @@ class UpgradeTaskRunnerTest { fileRepository.save(fileRecord); InMemoryUpgradeTaskRepository taskRepository = new InMemoryUpgradeTaskRepository(); InMemoryDeviceSoftwareVersionRepository versionRepository = new InMemoryDeviceSoftwareVersionRepository(); + List restartRequests = new ArrayList<>(); UpgradeTaskRunner runner = new UpgradeTaskRunner( taskRepository, @@ -46,18 +47,25 @@ class UpgradeTaskRunnerTest { stagingService(tempDir.resolve("staging"), tempDir.resolve("logs")), properties(tempDir.resolve("staging"), tempDir.resolve("logs")), objectMapper - ); + ) { + @Override + protected void triggerDetachedTmsRestart(Path logPath, boolean rollback) { + restartRequests.add(logPath.getFileName() + ":" + rollback); + } + }; runner.run(task("UPG-001", fileRecord.getFileId(), "TMS", "V1.0.1")); Assertions.assertEquals("SUCCESS", taskRepository.status); Assertions.assertEquals("V1.0.1", versionRepository.store.get("TMS").getCurrentVersion()); + Assertions.assertEquals(List.of("UPG-001.log:false"), restartRequests); String log = Files.readString(Path.of(taskRepository.logPath)); Assertions.assertTrue(log.contains("precheck")); Assertions.assertTrue(log.contains("execute")); Assertions.assertTrue(log.contains("verify")); Assertions.assertTrue(log.indexOf("precheck") < log.indexOf("execute")); Assertions.assertTrue(log.indexOf("execute") < log.indexOf("verify")); + Assertions.assertTrue(log.contains("scheduling detached TMS restart")); } @Test @@ -140,6 +148,7 @@ class UpgradeTaskRunnerTest { InMemoryFileRecordRepository fileRepository = new InMemoryFileRecordRepository(); fileRepository.save(fileRecord); InMemoryUpgradeTaskRepository taskRepository = new InMemoryUpgradeTaskRepository(); + List restartRequests = new ArrayList<>(); UpgradeTaskRunner runner = new UpgradeTaskRunner( taskRepository, @@ -148,13 +157,20 @@ class UpgradeTaskRunnerTest { stagingService(tempDir.resolve("staging"), tempDir.resolve("logs")), properties(tempDir.resolve("staging"), tempDir.resolve("logs")), objectMapper - ); + ) { + @Override + protected void triggerDetachedTmsRestart(Path logPath, boolean rollback) { + restartRequests.add(logPath.getFileName() + ":" + rollback); + } + }; - runner.rollback(task("UPG-003", fileRecord.getFileId(), "FIRMWARE", "V3.0.0")); + runner.rollback(task("UPG-003", fileRecord.getFileId(), "TMS", "V3.0.0")); Assertions.assertEquals("ROLLBACK_SUCCESS", taskRepository.status); + Assertions.assertEquals(List.of("UPG-003-rollback.log:true"), restartRequests); String log = Files.readString(Path.of(taskRepository.logPath)); Assertions.assertTrue(log.contains("rollback")); + Assertions.assertTrue(log.contains("scheduling detached TMS restart")); } private UpgradeProperties properties(Path stagingRoot, Path logDir) { diff --git a/src/test/java/com/cisd/tms/modules/upgrade/service/UpgradeServiceTest.java b/src/test/java/com/cisd/tms/modules/upgrade/service/UpgradeServiceTest.java index 626ac34..0cf012b 100644 --- a/src/test/java/com/cisd/tms/modules/upgrade/service/UpgradeServiceTest.java +++ b/src/test/java/com/cisd/tms/modules/upgrade/service/UpgradeServiceTest.java @@ -13,6 +13,7 @@ import com.cisd.tms.modules.upgrade.dto.UpgradeTaskResponse; import com.cisd.tms.modules.upgrade.entity.UpgradeTaskEntity; import com.cisd.tms.modules.upgrade.executor.UpgradeTaskRunner; import com.cisd.tms.modules.upgrade.repository.UpgradeTaskRepository; +import java.nio.file.Path; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Comparator; @@ -131,6 +132,29 @@ class UpgradeServiceTest { Assertions.assertEquals("another upgrade task is running", exception.getMessage()); } + @Test + void shouldPrepareLogPathWhenExecuteStarts() { + InMemoryUpgradeTaskRepository repository = new InMemoryUpgradeTaskRepository(); + UpgradeTaskEntity task = entity("UPG-001", "PENDING_CONFIRM"); + repository.save(task); + + UpgradeService service = new UpgradeService( + repository, + Mockito.mock(FileRecordRepository.class), + Mockito.mock(UpgradePackageService.class), + Mockito.mock(UpgradeTaskRunner.class), + properties(), + Runnable::run, + new ConcurrentHashMap<>() + ); + + UpgradeTaskResponse response = service.executeTask("UPG-001"); + + Assertions.assertEquals("RUNNING", response.getStatus()); + Assertions.assertTrue(response.getDetailLogPath().endsWith("/UPG-001.log")); + Assertions.assertTrue(task.getStartedAt() != null); + } + @Test void shouldPageUpgradeTasks() { InMemoryUpgradeTaskRepository repository = new InMemoryUpgradeTaskRepository(); @@ -235,7 +259,7 @@ class UpgradeServiceTest { private static UpgradeProperties properties() { UpgradeProperties properties = new UpgradeProperties(); - properties.setLogDir("/home/tms/tmp/tms-upgrade-logs"); + properties.setLogDir(Path.of(System.getProperty("java.io.tmpdir"), "upgrade-service-test-logs").toString()); return properties; }