升级任务

This commit is contained in:
waner 2026-04-08 11:10:26 +08:00
parent 57639e78a1
commit 2f81beb0d8
8 changed files with 115 additions and 10 deletions

View File

@ -1,6 +1,10 @@
spring:
application:
name: tms-framework
servlet:
multipart:
max-file-size: 104857600
max-request-size: 104857600
server:
port: 8080

View File

@ -11,6 +11,7 @@ import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ -45,6 +46,12 @@ public class GlobalExceptionHandler {
return ApiResponse.fail(ErrorCode.VALIDATE_FAILED.getCode(), ex.getMessage()).withPath(request.getRequestURI());
}
@ExceptionHandler(MaxUploadSizeExceededException.class)
public ApiResponse<Void> handleMaxUploadSizeExceeded(MaxUploadSizeExceededException ex, HttpServletRequest request) {
return ApiResponse.fail(ErrorCode.VALIDATE_FAILED.getCode(), "upload file size exceeds limit")
.withPath(request.getRequestURI());
}
@ExceptionHandler(PcieCryptoException.class)
public ApiResponse<Void> handleCryptoCardException(PcieCryptoException ex, HttpServletRequest request) {
return ApiResponse.fail(ErrorCode.CRYPTO_CARD_ERROR.getCode(), ex.getMessage())

View File

@ -15,6 +15,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
@ -26,17 +27,13 @@ import org.springframework.web.multipart.MultipartFile;
@RestController
@RequestMapping("/api/v1")
@RequiredArgsConstructor
@Tag(name = "离线升级管理", description = "离线升级包上传、预检、任务创建、执行和升级记录查询接口")
public class UpgradeController {
private final UpgradeService upgradeService;
private final FileService fileService;
public UpgradeController(UpgradeService upgradeService, FileService fileService) {
this.upgradeService = upgradeService;
this.fileService = fileService;
}
@PostMapping("/upgrade-packages")
@Operation(summary = "上传离线升级包", description = "上传离线升级包,返回 fileId 供升级预检和任务创建使用。")
public ApiResponse<FileUploadResponse> uploadPackage(

View File

@ -98,8 +98,9 @@ public class UpgradeTaskRunner {
}
DeviceSoftwareVersionEntity versionEntity = new DeviceSoftwareVersionEntity();
versionEntity.setComponentCode(task.getTaskType());
versionEntity.setComponentName(componentName(task.getTaskType()));
String componentCode = componentCode(task.getTaskType());
versionEntity.setComponentCode(componentCode);
versionEntity.setComponentName(componentName(componentCode));
versionEntity.setCurrentVersion(task.getTargetVersion());
versionEntity.setSourceType("UPGRADE");
versionEntity.setDetectedAt(LocalDateTime.now());
@ -211,12 +212,18 @@ public class UpgradeTaskRunner {
private String componentName(String componentCode) {
return switch (trim(componentCode)) {
case "TMS" -> "设备管理软件";
case "RECEIVER" -> "标准收发器";
case "FIRMWARE" -> "固件";
case "APP" -> "标准收发器";
default -> componentCode;
};
}
private String componentCode(String taskType) {
return switch (trim(taskType)) {
case "RECEIVER" -> "APP";
default -> trim(taskType);
};
}
private String trim(String value) {
return value == null ? "" : value.trim();
}

View File

@ -2,6 +2,11 @@ spring:
application:
# Spring 应用名;用于日志、注册中心标识等。
name: tms-framework
servlet:
multipart:
# Spring/Tomcat multipart 解析上限;必须不小于业务上传上限,否则请求会在进入 Controller 前被拒绝。
max-file-size: ${TMS_FILE_STORAGE_MAX_FILE_SIZE_BYTES:104857600}
max-request-size: ${TMS_FILE_STORAGE_MAX_FILE_SIZE_BYTES:104857600}
profiles:
# 默认激活环境;可通过 --spring.profiles.active=prod 覆盖。
active: dev
@ -173,7 +178,7 @@ tms:
# 升级任务统一日志目录。
log-dir: ${TMS_UPGRADE_LOG_DIR:/home/tms/tmp/tms-upgrade-logs}
# 升级包验签使用的公钥 PEM 文件路径;为空时升级预检会拒绝通过。
signature-public-key-pem-path: ${TMS_UPGRADE_SIGNATURE_PUBLIC_KEY_PEM_PATH:}
signature-public-key-pem-path: ${TMS_UPGRADE_SIGNATURE_PUBLIC_KEY_PEM_PATH:/home/tms/cert/public_key.pem}
mk:
init-identify:
# SDFE_InitIdentify 旧 PINBase64 编码);主密钥初始化接口会读取该值。

View File

@ -0,0 +1,27 @@
package com.cisd.tms.common.exception;
import com.cisd.tms.common.api.ApiResponse;
import com.cisd.tms.common.enums.ErrorCode;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
class GlobalExceptionHandlerTest {
@Test
void shouldHandleMultipartMaxUploadSizeExceededAsValidationFailure() {
GlobalExceptionHandler handler = new GlobalExceptionHandler();
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/api/v1/upgrade-packages");
ApiResponse<Void> response = handler.handleMaxUploadSizeExceeded(
new MaxUploadSizeExceededException(104857600),
request
);
Assertions.assertFalse(response.isSuccess());
Assertions.assertEquals(ErrorCode.VALIDATE_FAILED.getCode(), response.getCode());
Assertions.assertEquals("upload file size exceeds limit", response.getMsg());
Assertions.assertEquals("/api/v1/upgrade-packages", response.getPath());
}
}

View File

@ -0,0 +1,32 @@
package com.cisd.tms.config;
import java.nio.file.Path;
import java.util.List;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.FileSystemResource;
class MultipartUploadConfigurationTest {
@Test
void shouldConfigureSpringMultipartLimitsForLargeUpgradePackages() throws Exception {
FileSystemResource resource = new FileSystemResource(Path.of("src/main/resources/application.yml"));
List<PropertySource<?>> propertySources = new YamlPropertySourceLoader().load("application", resource);
Object maxFileSize = findProperty(propertySources, "spring.servlet.multipart.max-file-size");
Object maxRequestSize = findProperty(propertySources, "spring.servlet.multipart.max-request-size");
Assertions.assertEquals("${TMS_FILE_STORAGE_MAX_FILE_SIZE_BYTES:104857600}", maxFileSize);
Assertions.assertEquals("${TMS_FILE_STORAGE_MAX_FILE_SIZE_BYTES:104857600}", maxRequestSize);
}
private Object findProperty(List<PropertySource<?>> propertySources, String name) {
return propertySources.stream()
.filter(source -> source.containsProperty(name))
.findFirst()
.map(source -> source.getProperty(name))
.orElse(null);
}
}

View File

@ -56,6 +56,32 @@ class UpgradeTaskRunnerTest {
Assertions.assertTrue(log.indexOf("execute") < log.indexOf("verify"));
}
@Test
void shouldUpdateAppSoftwareVersionWhenReceiverUpgradeSucceeds() throws Exception {
Path tempDir = Files.createTempDirectory("upgrade-runner-test");
FileRecordEntity fileRecord = buildPackage(tempDir, false, "#!/bin/sh\necho execute\nexit 0\n", false);
InMemoryFileRecordRepository fileRepository = new InMemoryFileRecordRepository();
fileRepository.save(fileRecord);
InMemoryUpgradeTaskRepository taskRepository = new InMemoryUpgradeTaskRepository();
InMemoryDeviceSoftwareVersionRepository versionRepository = new InMemoryDeviceSoftwareVersionRepository();
UpgradeTaskRunner runner = new UpgradeTaskRunner(
taskRepository,
fileRepository,
versionRepository,
stagingService(tempDir.resolve("staging"), tempDir.resolve("logs")),
properties(tempDir.resolve("staging"), tempDir.resolve("logs")),
objectMapper
);
runner.run(task("UPG-001-R", fileRecord.getFileId(), "RECEIVER", "V1.4.0"));
Assertions.assertEquals("SUCCESS", taskRepository.status);
Assertions.assertNull(versionRepository.store.get("RECEIVER"));
Assertions.assertEquals("V1.4.0", versionRepository.store.get("APP").getCurrentVersion());
Assertions.assertEquals("标准收发器", versionRepository.store.get("APP").getComponentName());
}
@Test
void shouldMarkTaskFailedWhenExecuteScriptFails() throws Exception {
Path tempDir = Files.createTempDirectory("upgrade-runner-test");