fix:app映射
This commit is contained in:
parent
2f81beb0d8
commit
3d28f84115
@ -83,9 +83,9 @@ public class DeviceProfileServiceImpl implements DeviceProfileService {
|
|||||||
if ("ENTERPRISE".equals(productType) || "INDIRECT".equals(productType)) {
|
if ("ENTERPRISE".equals(productType) || "INDIRECT".equals(productType)) {
|
||||||
addSoftwareItem(items, "TIDB", "数据库", cached("TIDB"));
|
addSoftwareItem(items, "TIDB", "数据库", cached("TIDB"));
|
||||||
addSoftwareItem(items, "NGINX", "Nginx", cached("NGINX"));
|
addSoftwareItem(items, "NGINX", "Nginx", cached("NGINX"));
|
||||||
addSoftwareItem(items, "APP", appName(productType), firstNonBlank(cached("APP"), cisdPresetProperties.getVersion()));
|
addSoftwareItem(items, "RECEIVER", appName(productType), firstNonBlank(cached("RECEIVER"), cisdPresetProperties.getVersion()));
|
||||||
} else if ("DIRECT".equals(productType)) {
|
} else if ("DIRECT".equals(productType)) {
|
||||||
addSoftwareItem(items, "APP", appName(productType), firstNonBlank(cached("APP"), cisdPresetProperties.getVersion()));
|
addSoftwareItem(items, "RECEIVER", appName(productType), firstNonBlank(cached("RECEIVER"), cisdPresetProperties.getVersion()));
|
||||||
}
|
}
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -114,17 +114,17 @@ public class DeviceRuntimeStatusServiceImpl implements DeviceRuntimeStatusServic
|
|||||||
items.add(buildSoftwareItem("TLQ", "消息队列", cached("TLQ"), initialized && isTlqEnabled(mqType), softwareRuntimeProbe::isTlqRunning));
|
items.add(buildSoftwareItem("TLQ", "消息队列", cached("TLQ"), initialized && isTlqEnabled(mqType), softwareRuntimeProbe::isTlqRunning));
|
||||||
items.add(buildSoftwareItem("TIDB", "数据库", cached("TIDB"), initialized && isTidbEnabled(productType), softwareRuntimeProbe::isTidbRunning));
|
items.add(buildSoftwareItem("TIDB", "数据库", cached("TIDB"), initialized && isTidbEnabled(productType), softwareRuntimeProbe::isTidbRunning));
|
||||||
items.add(buildSoftwareItem("NGINX", "Nginx", cached("NGINX"), initialized && isNginxEnabled(productType), softwareRuntimeProbe::isNginxRunning));
|
items.add(buildSoftwareItem("NGINX", "Nginx", cached("NGINX"), initialized && isNginxEnabled(productType), softwareRuntimeProbe::isNginxRunning));
|
||||||
items.add(buildAppItem(productType, initialized));
|
items.add(buildReceiverItem(productType, initialized));
|
||||||
items.add(buildSoftwareItem("TMS", "设备管理软件", firstNonBlank(cached("TMS"), systemSoftwareVersionProbe.tmsVersion(), cisdPresetProperties.getVersion()), true, softwareRuntimeProbe::isTmsRunning));
|
items.add(buildSoftwareItem("TMS", "设备管理软件", firstNonBlank(cached("TMS"), systemSoftwareVersionProbe.tmsVersion(), cisdPresetProperties.getVersion()), true, softwareRuntimeProbe::isTmsRunning));
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SoftwareRuntimeStatusItem buildAppItem(String productType, boolean initialized) {
|
private SoftwareRuntimeStatusItem buildReceiverItem(String productType, boolean initialized) {
|
||||||
boolean standard = "ENTERPRISE".equals(productType) || "INDIRECT".equals(productType);
|
boolean standard = "ENTERPRISE".equals(productType) || "INDIRECT".equals(productType);
|
||||||
BooleanSupplier supplier = standard ? softwareRuntimeProbe::isStandardAppRunning : softwareRuntimeProbe::isDirectAppRunning;
|
BooleanSupplier supplier = standard ? softwareRuntimeProbe::isStandardAppRunning : softwareRuntimeProbe::isDirectAppRunning;
|
||||||
String name = "应用软件";
|
String name = "标准收发器";
|
||||||
String displayName = firstNonBlank(cached("APP"), appName(productType));
|
String displayName = firstNonBlank(cached("RECEIVER"), appName(productType));
|
||||||
return buildSoftwareItem("APP", name, displayName, initialized, supplier);
|
return buildSoftwareItem("RECEIVER", name, displayName, initialized, supplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
private SoftwareRuntimeStatusItem buildSoftwareItem(
|
private SoftwareRuntimeStatusItem buildSoftwareItem(
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
package com.cisd.tms.modules.upgrade.enums;
|
package com.cisd.tms.modules.upgrade.enums;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 升级任务类型。具体应用名称由升级包脚本处理,后端不按应用名拆分。
|
||||||
|
*/
|
||||||
public enum UpgradeTargetComponent {
|
public enum UpgradeTargetComponent {
|
||||||
TMS,
|
TMS,
|
||||||
RECEIVER,
|
RECEIVER,
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
package com.cisd.tms.modules.upgrade.enums;
|
package com.cisd.tms.modules.upgrade.enums;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 升级任务主状态。脚本级细分状态当前通过日志体现,尚未独立落表。
|
||||||
|
*/
|
||||||
public enum UpgradeTaskStatus {
|
public enum UpgradeTaskStatus {
|
||||||
PENDING_CONFIRM,
|
PENDING_CONFIRM,
|
||||||
RUNNING,
|
RUNNING,
|
||||||
|
|||||||
@ -58,6 +58,7 @@ public class UpgradeTaskRunner {
|
|||||||
UpgradeManifest manifest = objectMapper.readValue(Files.readString(stagedDir.resolve("manifest.json")), UpgradeManifest.class);
|
UpgradeManifest manifest = objectMapper.readValue(Files.readString(stagedDir.resolve("manifest.json")), UpgradeManifest.class);
|
||||||
Path root = stagedDir.normalize().toAbsolutePath();
|
Path root = stagedDir.normalize().toAbsolutePath();
|
||||||
|
|
||||||
|
// 包内脚本按固定阶段执行;可选阶段未声明时跳过,execute 阶段必须存在。
|
||||||
int precheckCode = runOptionalScript("precheck", root, manifest.getEntrypoints() == null ? "" : manifest.getEntrypoints().getPrecheck(), task, logPath);
|
int precheckCode = runOptionalScript("precheck", root, manifest.getEntrypoints() == null ? "" : manifest.getEntrypoints().getPrecheck(), task, logPath);
|
||||||
if (precheckCode != 0) {
|
if (precheckCode != 0) {
|
||||||
upgradeTaskRepository.updateStatus(
|
upgradeTaskRepository.updateStatus(
|
||||||
@ -97,6 +98,7 @@ public class UpgradeTaskRunner {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 只有 execute 和 verify 都成功后,才认为升级成功并回写设备软件版本表。
|
||||||
DeviceSoftwareVersionEntity versionEntity = new DeviceSoftwareVersionEntity();
|
DeviceSoftwareVersionEntity versionEntity = new DeviceSoftwareVersionEntity();
|
||||||
String componentCode = componentCode(task.getTaskType());
|
String componentCode = componentCode(task.getTaskType());
|
||||||
versionEntity.setComponentCode(componentCode);
|
versionEntity.setComponentCode(componentCode);
|
||||||
@ -147,6 +149,7 @@ public class UpgradeTaskRunner {
|
|||||||
if (rollbackPath.isEmpty()) {
|
if (rollbackPath.isEmpty()) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade rollback script not found");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade rollback script not found");
|
||||||
}
|
}
|
||||||
|
// 回滚不自动触发,只有外部调用 rollback 接口后才执行包内 rollback 脚本。
|
||||||
int exitCode = runRequiredScript("rollback", stagedDir.normalize().toAbsolutePath(), rollbackPath, task, logPath);
|
int exitCode = runRequiredScript("rollback", stagedDir.normalize().toAbsolutePath(), rollbackPath, task, logPath);
|
||||||
if (exitCode != 0) {
|
if (exitCode != 0) {
|
||||||
upgradeTaskRepository.updateStatus(
|
upgradeTaskRepository.updateStatus(
|
||||||
@ -198,6 +201,7 @@ public class UpgradeTaskRunner {
|
|||||||
|
|
||||||
private int runRequiredScript(String phase, Path stagedDir, String scriptPath, UpgradeTaskEntity task, Path logPath) throws IOException, InterruptedException {
|
private int runRequiredScript(String phase, Path stagedDir, String scriptPath, UpgradeTaskEntity task, Path logPath) throws IOException, InterruptedException {
|
||||||
Path resolved = stagedDir.resolve(trim(scriptPath)).normalize().toAbsolutePath();
|
Path resolved = stagedDir.resolve(trim(scriptPath)).normalize().toAbsolutePath();
|
||||||
|
// 防止脚本路径通过 ../ 跳出升级包解压目录。
|
||||||
if (!resolved.startsWith(stagedDir) || !Files.exists(resolved)) {
|
if (!resolved.startsWith(stagedDir) || !Files.exists(resolved)) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade " + phase + " script not found");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade " + phase + " script not found");
|
||||||
}
|
}
|
||||||
@ -212,16 +216,13 @@ public class UpgradeTaskRunner {
|
|||||||
private String componentName(String componentCode) {
|
private String componentName(String componentCode) {
|
||||||
return switch (trim(componentCode)) {
|
return switch (trim(componentCode)) {
|
||||||
case "TMS" -> "设备管理软件";
|
case "TMS" -> "设备管理软件";
|
||||||
case "APP" -> "标准收发器";
|
case "RECEIVER" -> "标准收发器";
|
||||||
default -> componentCode;
|
default -> componentCode;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private String componentCode(String taskType) {
|
private String componentCode(String taskType) {
|
||||||
return switch (trim(taskType)) {
|
return trim(taskType);
|
||||||
case "RECEIVER" -> "APP";
|
|
||||||
default -> trim(taskType);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String trim(String value) {
|
private String trim(String value) {
|
||||||
|
|||||||
@ -51,11 +51,14 @@ public class UpgradePackageService {
|
|||||||
public UpgradePreviewResponse preview(String fileId) {
|
public UpgradePreviewResponse preview(String fileId) {
|
||||||
StagedUpgradePackage stagedPackage = stagePackage(fileId);
|
StagedUpgradePackage stagedPackage = stagePackage(fileId);
|
||||||
UpgradeManifest manifest = stagedPackage.manifest();
|
UpgradeManifest manifest = stagedPackage.manifest();
|
||||||
|
// 预检顺序保持“结构/产品/签名/脚本/版本”,先挡住不属于当前设备或不可信的包。
|
||||||
validateManifest(manifest);
|
validateManifest(manifest);
|
||||||
validateProductType(manifest.getProductType());
|
validateProductType(manifest.getProductType());
|
||||||
|
// 签名对象固定为 manifest.json 原始字节,具体算法由 UpgradePackageSignatureVerifier 实现。
|
||||||
signatureVerifier.verify(manifest, stagedPackage.manifestPath(), stagedPackage.signaturePath());
|
signatureVerifier.verify(manifest, stagedPackage.manifestPath(), stagedPackage.signaturePath());
|
||||||
validateExecuteScript(stagedPackage.stagedDir(), manifest);
|
validateExecuteScript(stagedPackage.stagedDir(), manifest);
|
||||||
|
|
||||||
|
// 当前版本来自设备软件版本表;空版本允许继续预检,由前端展示“未记录版本”。
|
||||||
String currentVersion = currentVersion(manifest.getTaskType());
|
String currentVersion = currentVersion(manifest.getTaskType());
|
||||||
String targetVersion = normalize(manifest.getVersion());
|
String targetVersion = normalize(manifest.getVersion());
|
||||||
|
|
||||||
@ -101,6 +104,7 @@ public class UpgradePackageService {
|
|||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade package file not found on disk");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade package file not found on disk");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 同一个 fileId 每次预检都会重新解压,确保读取的是当前上传文件内容。
|
||||||
Path stagedDir = upgradePackageStagingService.stage(record.getFileId(), packagePath);
|
Path stagedDir = upgradePackageStagingService.stage(record.getFileId(), packagePath);
|
||||||
Path manifestPath = stagedDir.resolve("manifest.json");
|
Path manifestPath = stagedDir.resolve("manifest.json");
|
||||||
Path signaturePath = stagedDir.resolve("signature.sig");
|
Path signaturePath = stagedDir.resolve("signature.sig");
|
||||||
@ -142,6 +146,7 @@ public class UpgradePackageService {
|
|||||||
|
|
||||||
private void validateExecuteScript(Path stagedDir, UpgradeManifest manifest) {
|
private void validateExecuteScript(Path stagedDir, UpgradeManifest manifest) {
|
||||||
Path executePath = stagedDir.resolve(normalize(manifest.getEntrypoints().getExecute())).normalize().toAbsolutePath();
|
Path executePath = stagedDir.resolve(normalize(manifest.getEntrypoints().getExecute())).normalize().toAbsolutePath();
|
||||||
|
// execute 路径必须在 staging 目录内,避免 manifest 声明外部系统脚本。
|
||||||
if (!executePath.startsWith(stagedDir.normalize().toAbsolutePath()) || !Files.exists(executePath)) {
|
if (!executePath.startsWith(stagedDir.normalize().toAbsolutePath()) || !Files.exists(executePath)) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade execute script not found");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade execute script not found");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,6 +43,10 @@ public class UpgradeService {
|
|||||||
private final UpgradeTaskRunner upgradeTaskRunner;
|
private final UpgradeTaskRunner upgradeTaskRunner;
|
||||||
private final UpgradeProperties upgradeProperties;
|
private final UpgradeProperties upgradeProperties;
|
||||||
private final Executor backgroundExecutor;
|
private final Executor backgroundExecutor;
|
||||||
|
/**
|
||||||
|
* 进程内执行互斥表:避免同一任务被前端重复点击后并发启动。
|
||||||
|
* 跨进程/重启后的互斥仍以数据库中的 RUNNING 任务为准。
|
||||||
|
*/
|
||||||
private final ConcurrentMap<String, CompletableFuture<Void>> activeExecutions;
|
private final ConcurrentMap<String, CompletableFuture<Void>> activeExecutions;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@ -93,6 +97,7 @@ public class UpgradeService {
|
|||||||
if (request == null || request.getFileId() == null || request.getFileId().isBlank()) {
|
if (request == null || request.getFileId() == null || request.getFileId().isBlank()) {
|
||||||
throw new IllegalArgumentException("fileId must not be blank");
|
throw new IllegalArgumentException("fileId must not be blank");
|
||||||
}
|
}
|
||||||
|
// 创建任务前重新预检一次,避免用户上传后替换文件或设备版本状态发生变化。
|
||||||
UpgradePreviewResponse preview = upgradePackageService.preview(request.getFileId());
|
UpgradePreviewResponse preview = upgradePackageService.preview(request.getFileId());
|
||||||
if (!preview.isAllowed()) {
|
if (!preview.isAllowed()) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), blankAs(preview.getBlockReason(), "upgrade preview not allowed"));
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), blankAs(preview.getBlockReason(), "upgrade preview not allowed"));
|
||||||
@ -131,6 +136,7 @@ public class UpgradeService {
|
|||||||
|
|
||||||
public UpgradeTaskResponse executeTask(String taskId) {
|
public UpgradeTaskResponse executeTask(String taskId) {
|
||||||
UpgradeTaskEntity task = loadTask(taskId);
|
UpgradeTaskEntity task = loadTask(taskId);
|
||||||
|
// 第一版按一体机单机串行升级处理,避免同时升级 TMS、收发器或固件造成状态不可控。
|
||||||
UpgradeTaskEntity runningTask = upgradeTaskRepository.findRunningTask().orElse(null);
|
UpgradeTaskEntity runningTask = upgradeTaskRepository.findRunningTask().orElse(null);
|
||||||
if (runningTask != null && !runningTask.getTaskId().equals(taskId)) {
|
if (runningTask != null && !runningTask.getTaskId().equals(taskId)) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "another upgrade task is running");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "another upgrade task is running");
|
||||||
@ -144,6 +150,7 @@ public class UpgradeService {
|
|||||||
|
|
||||||
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
|
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
|
||||||
try {
|
try {
|
||||||
|
// 后台执行重新加载任务,确保使用已落库的最新状态和日志路径。
|
||||||
upgradeTaskRunner.run(loadTask(taskId));
|
upgradeTaskRunner.run(loadTask(taskId));
|
||||||
} finally {
|
} finally {
|
||||||
activeExecutions.remove(taskId);
|
activeExecutions.remove(taskId);
|
||||||
@ -155,6 +162,7 @@ public class UpgradeService {
|
|||||||
|
|
||||||
public UpgradeTaskResponse rollbackTask(String taskId) {
|
public UpgradeTaskResponse rollbackTask(String taskId) {
|
||||||
UpgradeTaskEntity task = loadTask(taskId);
|
UpgradeTaskEntity task = loadTask(taskId);
|
||||||
|
// 回滚能力由升级包声明;TMS 不假定所有包都能安全回滚。
|
||||||
UpgradeManifest manifest = upgradePackageService.loadManifest(task.getPackageFileId());
|
UpgradeManifest manifest = upgradePackageService.loadManifest(task.getPackageFileId());
|
||||||
if (manifest.getEntrypoints() == null || trim(manifest.getEntrypoints().getRollback()).isEmpty()) {
|
if (manifest.getEntrypoints() == null || trim(manifest.getEntrypoints().getRollback()).isEmpty()) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade rollback script not found");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade rollback script not found");
|
||||||
@ -205,6 +213,7 @@ public class UpgradeService {
|
|||||||
}
|
}
|
||||||
Path path = Path.of(logPath).normalize().toAbsolutePath();
|
Path path = Path.of(logPath).normalize().toAbsolutePath();
|
||||||
Path allowedRoot = Path.of(trim(upgradeProperties.getLogDir())).normalize().toAbsolutePath();
|
Path allowedRoot = Path.of(trim(upgradeProperties.getLogDir())).normalize().toAbsolutePath();
|
||||||
|
// 日志路径必须落在升级日志根目录下,防止通过篡改任务记录读取任意文件。
|
||||||
if (!path.startsWith(allowedRoot)) {
|
if (!path.startsWith(allowedRoot)) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade log path is out of allowed directory");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade log path is out of allowed directory");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -44,6 +44,7 @@ public class SoftUpgradePackageSignatureVerifier implements UpgradePackageSignat
|
|||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade package signature file not found");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade package signature file not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 第一版升级包仅对 manifest.json 做离线软验签,避免在包内脚本执行前信任未授权包。
|
||||||
PublicKey publicKey = loadPublicKey();
|
PublicKey publicKey = loadPublicKey();
|
||||||
byte[] manifestBytes = readBytes(manifestPath, "failed to read manifest for signature verification");
|
byte[] manifestBytes = readBytes(manifestPath, "failed to read manifest for signature verification");
|
||||||
byte[] signatureBytes = loadSignature(signaturePath);
|
byte[] signatureBytes = loadSignature(signaturePath);
|
||||||
@ -74,6 +75,7 @@ public class SoftUpgradePackageSignatureVerifier implements UpgradePackageSignat
|
|||||||
String pemContent = Files.readString(pemPath, StandardCharsets.UTF_8);
|
String pemContent = Files.readString(pemPath, StandardCharsets.UTF_8);
|
||||||
try (Reader reader = new StringReader(pemContent); PEMParser parser = new PEMParser(reader)) {
|
try (Reader reader = new StringReader(pemContent); PEMParser parser = new PEMParser(reader)) {
|
||||||
Object parsed = parser.readObject();
|
Object parsed = parser.readObject();
|
||||||
|
// 约定使用 X.509 SubjectPublicKeyInfo 格式的 PUBLIC KEY PEM。
|
||||||
if (!(parsed instanceof SubjectPublicKeyInfo publicKeyInfo)) {
|
if (!(parsed instanceof SubjectPublicKeyInfo publicKeyInfo)) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade signature public key pem is invalid");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade signature public key pem is invalid");
|
||||||
}
|
}
|
||||||
@ -97,12 +99,13 @@ public class SoftUpgradePackageSignatureVerifier implements UpgradePackageSignat
|
|||||||
String text = new String(raw, StandardCharsets.UTF_8).trim();
|
String text = new String(raw, StandardCharsets.UTF_8).trim();
|
||||||
if (!text.isEmpty()) {
|
if (!text.isEmpty()) {
|
||||||
try {
|
try {
|
||||||
|
// 兼容联调阶段生成的 Base64 文本签名;生产包可直接使用二进制签名。
|
||||||
byte[] decoded = Base64.getDecoder().decode(text);
|
byte[] decoded = Base64.getDecoder().decode(text);
|
||||||
if (decoded.length > 0) {
|
if (decoded.length > 0) {
|
||||||
return decoded;
|
return decoded;
|
||||||
}
|
}
|
||||||
} catch (IllegalArgumentException ignored) {
|
} catch (IllegalArgumentException ignored) {
|
||||||
// Keep compatibility with raw binary signature files.
|
// 非 Base64 文本时按原始二进制签名处理。
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return raw;
|
return raw;
|
||||||
|
|||||||
@ -39,6 +39,7 @@ public class UpgradePackageStagingService {
|
|||||||
ZipEntry entry;
|
ZipEntry entry;
|
||||||
while ((entry = zipInputStream.getNextEntry()) != null) {
|
while ((entry = zipInputStream.getNextEntry()) != null) {
|
||||||
Path target = targetDir.resolve(entry.getName()).normalize().toAbsolutePath();
|
Path target = targetDir.resolve(entry.getName()).normalize().toAbsolutePath();
|
||||||
|
// 防止恶意 zip 条目通过 ../ 写出 staging 目录。
|
||||||
if (!target.startsWith(targetDir)) {
|
if (!target.startsWith(targetDir)) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "invalid upgrade package entry");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "invalid upgrade package entry");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,6 +25,7 @@ public class UpgradeVersionComparator {
|
|||||||
String value = version == null ? "" : version.trim();
|
String value = version == null ? "" : version.trim();
|
||||||
List<Integer> parts = new ArrayList<>();
|
List<Integer> parts = new ArrayList<>();
|
||||||
StringBuilder current = new StringBuilder();
|
StringBuilder current = new StringBuilder();
|
||||||
|
// 只提取版本号中的数字段,兼容 V1.2.3、1.2.3-build4 等简单格式。
|
||||||
for (int i = 0; i < value.length(); i++) {
|
for (int i = 0; i < value.length(); i++) {
|
||||||
char ch = value.charAt(i);
|
char ch = value.charAt(i);
|
||||||
if (Character.isDigit(ch)) {
|
if (Character.isDigit(ch)) {
|
||||||
|
|||||||
@ -110,7 +110,7 @@ VALUES
|
|||||||
(1002, 'TLQ', '消息队列', 'V8.1.11', 'MANUAL', NOW(), '出厂预置'),
|
(1002, 'TLQ', '消息队列', 'V8.1.11', 'MANUAL', NOW(), '出厂预置'),
|
||||||
(1003, 'TIDB', '数据库', 'V8.1.0', 'MANUAL', NOW(), '出厂预置'),
|
(1003, 'TIDB', '数据库', 'V8.1.0', 'MANUAL', NOW(), '出厂预置'),
|
||||||
(1004, 'NGINX', 'Nginx', 'V1.20', 'MANUAL', NOW(), '出厂预置'),
|
(1004, 'NGINX', 'Nginx', 'V1.20', 'MANUAL', NOW(), '出厂预置'),
|
||||||
(1005, 'APP', '标准收发器企业版', 'V1.3.2', 'MANUAL', NOW(), '出厂预置'),
|
(1005, 'RECEIVER', '标准收发器', 'V1.3.2', 'MANUAL', NOW(), '出厂预置'),
|
||||||
(1006, 'TMS', 'CISD终端管理服务', 'V1.0.0', 'MANUAL', NOW(), '出厂预置')
|
(1006, 'TMS', 'CISD终端管理服务', 'V1.0.0', 'MANUAL', NOW(), '出厂预置')
|
||||||
ON DUPLICATE KEY UPDATE
|
ON DUPLICATE KEY UPDATE
|
||||||
component_name = VALUES(component_name),
|
component_name = VALUES(component_name),
|
||||||
|
|||||||
@ -0,0 +1,19 @@
|
|||||||
|
-- 组件版本表统一使用 RECEIVER 表示标准收发器,废弃旧编码 APP。
|
||||||
|
-- 若环境中已存在 RECEIVER 行,则保留 RECEIVER 并删除旧 APP,避免唯一键冲突。
|
||||||
|
DELETE FROM tms_device_software_version
|
||||||
|
WHERE component_code = 'APP'
|
||||||
|
AND EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM (SELECT id FROM tms_device_software_version WHERE component_code = 'RECEIVER') receiver_row
|
||||||
|
);
|
||||||
|
|
||||||
|
UPDATE tms_device_software_version
|
||||||
|
SET component_code = 'RECEIVER',
|
||||||
|
component_name = '标准收发器',
|
||||||
|
update_time = CURRENT_TIMESTAMP
|
||||||
|
WHERE component_code = 'APP';
|
||||||
|
|
||||||
|
UPDATE tms_device_software_version
|
||||||
|
SET component_name = '标准收发器',
|
||||||
|
update_time = CURRENT_TIMESTAMP
|
||||||
|
WHERE component_code = 'RECEIVER';
|
||||||
@ -47,7 +47,7 @@ class DeviceProfileServiceTest {
|
|||||||
versionRepository.save(component("TLQ", "消息队列", "tonglink/Q V8.1.11"));
|
versionRepository.save(component("TLQ", "消息队列", "tonglink/Q V8.1.11"));
|
||||||
versionRepository.save(component("TIDB", "数据库", "TiDB V8.1.0"));
|
versionRepository.save(component("TIDB", "数据库", "TiDB V8.1.0"));
|
||||||
versionRepository.save(component("NGINX", "Nginx", "Nginx V1.20"));
|
versionRepository.save(component("NGINX", "Nginx", "Nginx V1.20"));
|
||||||
versionRepository.save(component("APP", "标准收发器间参版", "标准收发器间参版 V1.3.2"));
|
versionRepository.save(component("RECEIVER", "标准收发器间参版", "标准收发器间参版 V1.3.2"));
|
||||||
|
|
||||||
DeviceProfileService service = new DeviceProfileServiceImpl(
|
DeviceProfileService service = new DeviceProfileServiceImpl(
|
||||||
initService,
|
initService,
|
||||||
@ -73,7 +73,7 @@ class DeviceProfileServiceTest {
|
|||||||
Assertions.assertEquals("32GB", response.getHardware().getMemoryTotal());
|
Assertions.assertEquals("32GB", response.getHardware().getMemoryTotal());
|
||||||
Assertions.assertEquals("953.87GB", response.getHardware().getDiskTotal());
|
Assertions.assertEquals("953.87GB", response.getHardware().getDiskTotal());
|
||||||
Assertions.assertEquals(
|
Assertions.assertEquals(
|
||||||
List.of("OS", "TMS", "RABBITMQ", "TLQ", "TIDB", "NGINX", "APP"),
|
List.of("OS", "TMS", "RABBITMQ", "TLQ", "TIDB", "NGINX", "RECEIVER"),
|
||||||
response.getSoftware().stream().map(DeviceProfileSoftwareItem::getComponentCode).toList()
|
response.getSoftware().stream().map(DeviceProfileSoftwareItem::getComponentCode).toList()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -156,7 +156,7 @@ class DeviceProfileServiceTest {
|
|||||||
DeviceProfileResponse response = service.profile();
|
DeviceProfileResponse response = service.profile();
|
||||||
|
|
||||||
Assertions.assertEquals(
|
Assertions.assertEquals(
|
||||||
List.of("OS", "TMS", "RABBITMQ", "TIDB", "NGINX", "APP"),
|
List.of("OS", "TMS", "RABBITMQ", "TIDB", "NGINX", "RECEIVER"),
|
||||||
response.getSoftware().stream().map(DeviceProfileSoftwareItem::getComponentCode).toList()
|
response.getSoftware().stream().map(DeviceProfileSoftwareItem::getComponentCode).toList()
|
||||||
);
|
);
|
||||||
DeviceProfileSoftwareItem rabbitmq = response.getSoftware().stream()
|
DeviceProfileSoftwareItem rabbitmq = response.getSoftware().stream()
|
||||||
|
|||||||
@ -48,7 +48,7 @@ class DeviceRuntimeStatusServiceTest {
|
|||||||
versions.save(component("TLQ", "消息队列", "tonglink/Q V8.1.11"));
|
versions.save(component("TLQ", "消息队列", "tonglink/Q V8.1.11"));
|
||||||
versions.save(component("TIDB", "数据库", "TiDB V8.1.0"));
|
versions.save(component("TIDB", "数据库", "TiDB V8.1.0"));
|
||||||
versions.save(component("NGINX", "Nginx", "Nginx V1.20"));
|
versions.save(component("NGINX", "Nginx", "Nginx V1.20"));
|
||||||
versions.save(component("APP", "标准收发器企业版", "标准收发器企业版 V1.3.2"));
|
versions.save(component("RECEIVER", "标准收发器企业版", "标准收发器企业版 V1.3.2"));
|
||||||
|
|
||||||
DeviceRuntimeStatusService service = new DeviceRuntimeStatusServiceImpl(
|
DeviceRuntimeStatusService service = new DeviceRuntimeStatusServiceImpl(
|
||||||
initService,
|
initService,
|
||||||
@ -78,7 +78,7 @@ class DeviceRuntimeStatusServiceTest {
|
|||||||
assertSoftwareStatus(response.getSoftware(), "TLQ", "NOT_INSTALLED");
|
assertSoftwareStatus(response.getSoftware(), "TLQ", "NOT_INSTALLED");
|
||||||
assertSoftwareStatus(response.getSoftware(), "TIDB", "NORMAL");
|
assertSoftwareStatus(response.getSoftware(), "TIDB", "NORMAL");
|
||||||
assertSoftwareStatus(response.getSoftware(), "NGINX", "ABNORMAL");
|
assertSoftwareStatus(response.getSoftware(), "NGINX", "ABNORMAL");
|
||||||
assertSoftwareStatus(response.getSoftware(), "APP", "NORMAL");
|
assertSoftwareStatus(response.getSoftware(), "RECEIVER", "NORMAL");
|
||||||
assertSoftwareStatus(response.getSoftware(), "TMS", "NORMAL");
|
assertSoftwareStatus(response.getSoftware(), "TMS", "NORMAL");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,7 +100,7 @@ class DeviceRuntimeStatusServiceTest {
|
|||||||
assertSoftwareStatus(response.getSoftware(), "TLQ", "NOT_INSTALLED");
|
assertSoftwareStatus(response.getSoftware(), "TLQ", "NOT_INSTALLED");
|
||||||
assertSoftwareStatus(response.getSoftware(), "TIDB", "NOT_INSTALLED");
|
assertSoftwareStatus(response.getSoftware(), "TIDB", "NOT_INSTALLED");
|
||||||
assertSoftwareStatus(response.getSoftware(), "NGINX", "NOT_INSTALLED");
|
assertSoftwareStatus(response.getSoftware(), "NGINX", "NOT_INSTALLED");
|
||||||
assertSoftwareStatus(response.getSoftware(), "APP", "NOT_INSTALLED");
|
assertSoftwareStatus(response.getSoftware(), "RECEIVER", "NOT_INSTALLED");
|
||||||
assertSoftwareStatus(response.getSoftware(), "TMS", "NORMAL");
|
assertSoftwareStatus(response.getSoftware(), "TMS", "NORMAL");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,7 +130,7 @@ class DeviceRuntimeStatusServiceTest {
|
|||||||
assertSoftwareStatus(response.getSoftware(), "TLQ", "UNKNOWN");
|
assertSoftwareStatus(response.getSoftware(), "TLQ", "UNKNOWN");
|
||||||
assertSoftwareStatus(response.getSoftware(), "TIDB", "UNKNOWN");
|
assertSoftwareStatus(response.getSoftware(), "TIDB", "UNKNOWN");
|
||||||
assertSoftwareStatus(response.getSoftware(), "NGINX", "UNKNOWN");
|
assertSoftwareStatus(response.getSoftware(), "NGINX", "UNKNOWN");
|
||||||
assertSoftwareStatus(response.getSoftware(), "APP", "UNKNOWN");
|
assertSoftwareStatus(response.getSoftware(), "RECEIVER", "UNKNOWN");
|
||||||
assertSoftwareStatus(response.getSoftware(), "TMS", "UNKNOWN");
|
assertSoftwareStatus(response.getSoftware(), "TMS", "UNKNOWN");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -57,7 +57,7 @@ class UpgradeTaskRunnerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldUpdateAppSoftwareVersionWhenReceiverUpgradeSucceeds() throws Exception {
|
void shouldUpdateReceiverSoftwareVersionWhenReceiverUpgradeSucceeds() throws Exception {
|
||||||
Path tempDir = Files.createTempDirectory("upgrade-runner-test");
|
Path tempDir = Files.createTempDirectory("upgrade-runner-test");
|
||||||
FileRecordEntity fileRecord = buildPackage(tempDir, false, "#!/bin/sh\necho execute\nexit 0\n", false);
|
FileRecordEntity fileRecord = buildPackage(tempDir, false, "#!/bin/sh\necho execute\nexit 0\n", false);
|
||||||
InMemoryFileRecordRepository fileRepository = new InMemoryFileRecordRepository();
|
InMemoryFileRecordRepository fileRepository = new InMemoryFileRecordRepository();
|
||||||
@ -77,9 +77,9 @@ class UpgradeTaskRunnerTest {
|
|||||||
runner.run(task("UPG-001-R", fileRecord.getFileId(), "RECEIVER", "V1.4.0"));
|
runner.run(task("UPG-001-R", fileRecord.getFileId(), "RECEIVER", "V1.4.0"));
|
||||||
|
|
||||||
Assertions.assertEquals("SUCCESS", taskRepository.status);
|
Assertions.assertEquals("SUCCESS", taskRepository.status);
|
||||||
Assertions.assertNull(versionRepository.store.get("RECEIVER"));
|
Assertions.assertNull(versionRepository.store.get("APP"));
|
||||||
Assertions.assertEquals("V1.4.0", versionRepository.store.get("APP").getCurrentVersion());
|
Assertions.assertEquals("V1.4.0", versionRepository.store.get("RECEIVER").getCurrentVersion());
|
||||||
Assertions.assertEquals("标准收发器", versionRepository.store.get("APP").getComponentName());
|
Assertions.assertEquals("标准收发器", versionRepository.store.get("RECEIVER").getComponentName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user