fix:资源备份和恢复
This commit is contained in:
+18
-1
@@ -4,6 +4,7 @@ import com.cisd.tms.common.api.ApiResponse;
|
||||
import com.cisd.tms.common.enums.ErrorCode;
|
||||
import com.cisd.tms.common.util.HttpResponseUtil;
|
||||
import com.cisd.tms.modules.auth.enums.AuthLevel;
|
||||
import com.cisd.tms.modules.auth.enums.RoleCode;
|
||||
import com.cisd.tms.modules.log.service.OperationAuditService;
|
||||
import com.cisd.tms.security.internal.InternalApiAuthInterceptor;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -36,7 +37,7 @@ public class InternalAuthorizationInterceptor implements HandlerInterceptor {
|
||||
RequireInternalAuth requireInternalAuth = findAnnotation(handlerMethod, RequireInternalAuth.class);
|
||||
if (requireInternalAuth != null) {
|
||||
String currentRole = (String) request.getAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE);
|
||||
if (!requireInternalAuth.role().getCode().equals(currentRole)) {
|
||||
if (!roleAllowed(currentRole, requireInternalAuth)) {
|
||||
writeForbidden(response, "角色无权限访问");
|
||||
operationAuditService.recordDeniedLog(request, handlerMethod, "角色无权限访问");
|
||||
return false;
|
||||
@@ -52,6 +53,22 @@ public class InternalAuthorizationInterceptor implements HandlerInterceptor {
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean roleAllowed(String currentRole, RequireInternalAuth requireInternalAuth) {
|
||||
if (currentRole == null || currentRole.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
RoleCode[] anyRole = requireInternalAuth.anyRole();
|
||||
if (anyRole.length == 0) {
|
||||
return requireInternalAuth.role().getCode().equals(currentRole);
|
||||
}
|
||||
for (RoleCode roleCode : anyRole) {
|
||||
if (roleCode.getCode().equals(currentRole)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean authLevelAllowed(String currentAuthLevel, AuthLevel requiredAuthLevel) {
|
||||
if (currentAuthLevel == null) {
|
||||
return false;
|
||||
|
||||
@@ -11,7 +11,9 @@ import java.lang.annotation.Target;
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface RequireInternalAuth {
|
||||
|
||||
RoleCode role();
|
||||
RoleCode role() default RoleCode.SUPER_ADMIN;
|
||||
|
||||
RoleCode[] anyRole() default {};
|
||||
|
||||
AuthLevel authLevel();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package com.cisd.tms.modules.backup.controller;
|
||||
|
||||
import com.cisd.tms.common.api.ApiResponse;
|
||||
import com.cisd.tms.modules.auth.enums.AuthLevel;
|
||||
import com.cisd.tms.modules.auth.enums.RoleCode;
|
||||
import com.cisd.tms.modules.auth.security.RequireInternalAuth;
|
||||
import com.cisd.tms.modules.backup.dto.request.CreateResourceBackupRequest;
|
||||
import com.cisd.tms.modules.backup.dto.response.CreateResourceBackupResponse;
|
||||
import com.cisd.tms.modules.backup.dto.response.ResourceBackupTaskDetailResponse;
|
||||
@@ -8,6 +11,9 @@ import com.cisd.tms.modules.backup.dto.response.ResourceTaskStepItemResponse;
|
||||
import com.cisd.tms.modules.backup.dto.response.ResourceTaskStepLogResponse;
|
||||
import com.cisd.tms.modules.backup.service.ResourceBackupService;
|
||||
import com.cisd.tms.modules.backup.support.ResourceBackupDownloadResult;
|
||||
import com.cisd.tms.modules.log.annotation.AuditedOperation;
|
||||
import com.cisd.tms.modules.log.enums.ActionType;
|
||||
import com.cisd.tms.modules.log.enums.ModuleCode;
|
||||
import com.cisd.tms.security.internal.ReplayProtected;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
@@ -40,12 +46,15 @@ public class ResourceBackupController {
|
||||
|
||||
@PostMapping
|
||||
@ReplayProtected
|
||||
@RequireInternalAuth(anyRole = {RoleCode.OPS_ADMIN}, authLevel = AuthLevel.FULL)
|
||||
@AuditedOperation(module = ModuleCode.BACKUP, action = ActionType.BACKUP, summary = "创建资源备份任务")
|
||||
@Operation(summary = "创建资源备份任务", description = "受理资源备份请求并返回备份任务号。")
|
||||
public ApiResponse<CreateResourceBackupResponse> createBackupTask(@Valid @RequestBody CreateResourceBackupRequest request) {
|
||||
return ApiResponse.success(resourceBackupService.createBackupTask(request));
|
||||
}
|
||||
|
||||
@GetMapping("/{taskId}")
|
||||
@RequireInternalAuth(anyRole = {RoleCode.OPS_ADMIN}, authLevel = AuthLevel.FULL)
|
||||
@Operation(summary = "查询资源备份任务详情", description = "返回资源备份任务详情。")
|
||||
public ApiResponse<ResourceBackupTaskDetailResponse> taskDetail(
|
||||
@Parameter(description = "备份任务号")
|
||||
@@ -55,6 +64,8 @@ public class ResourceBackupController {
|
||||
}
|
||||
|
||||
@GetMapping("/{taskId}/download")
|
||||
@RequireInternalAuth(anyRole = {RoleCode.OPS_ADMIN}, authLevel = AuthLevel.FULL)
|
||||
@AuditedOperation(module = ModuleCode.BACKUP, action = ActionType.EXPORT, summary = "下载资源备份包")
|
||||
@Operation(summary = "下载资源备份包", description = "以浏览器附件方式下载资源备份包。")
|
||||
public ResponseEntity<Resource> downloadPackage(
|
||||
@Parameter(description = "备份任务号")
|
||||
@@ -70,6 +81,7 @@ public class ResourceBackupController {
|
||||
}
|
||||
|
||||
@GetMapping("/{taskId}/steps")
|
||||
@RequireInternalAuth(anyRole = {RoleCode.OPS_ADMIN}, authLevel = AuthLevel.FULL)
|
||||
@Operation(summary = "查询资源备份任务步骤", description = "返回资源备份任务步骤列表。")
|
||||
public ApiResponse<List<ResourceTaskStepItemResponse>> taskSteps(
|
||||
@Parameter(description = "备份任务号")
|
||||
@@ -79,6 +91,7 @@ public class ResourceBackupController {
|
||||
}
|
||||
|
||||
@GetMapping("/{taskId}/steps/{stepNo}/log")
|
||||
@RequireInternalAuth(anyRole = {RoleCode.OPS_ADMIN}, authLevel = AuthLevel.FULL)
|
||||
@Operation(summary = "查询资源备份步骤日志", description = "返回资源备份任务步骤日志。")
|
||||
public ApiResponse<ResourceTaskStepLogResponse> taskStepLog(
|
||||
@Parameter(description = "备份任务号")
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package com.cisd.tms.modules.backup.controller;
|
||||
|
||||
import com.cisd.tms.common.api.ApiResponse;
|
||||
import com.cisd.tms.modules.auth.enums.AuthLevel;
|
||||
import com.cisd.tms.modules.auth.enums.RoleCode;
|
||||
import com.cisd.tms.modules.auth.security.RequireInternalAuth;
|
||||
import com.cisd.tms.modules.backup.dto.request.CreateResourceRestoreRequest;
|
||||
import com.cisd.tms.modules.backup.dto.request.ResourceRestorePrecheckRequest;
|
||||
import com.cisd.tms.modules.backup.dto.response.CreateResourceRestoreResponse;
|
||||
@@ -11,6 +14,9 @@ import com.cisd.tms.modules.backup.dto.response.ResourceTaskStepLogResponse;
|
||||
import com.cisd.tms.modules.backup.service.ResourcePrecheckService;
|
||||
import com.cisd.tms.modules.backup.service.ResourceRestoreService;
|
||||
import com.cisd.tms.modules.backup.service.ResourceTaskQueryService;
|
||||
import com.cisd.tms.modules.log.annotation.AuditedOperation;
|
||||
import com.cisd.tms.modules.log.enums.ActionType;
|
||||
import com.cisd.tms.modules.log.enums.ModuleCode;
|
||||
import com.cisd.tms.security.internal.ReplayProtected;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
@@ -37,6 +43,8 @@ public class ResourceRestoreController {
|
||||
private final ResourceTaskQueryService resourceTaskQueryService;
|
||||
|
||||
@PostMapping("/precheck")
|
||||
// @RequireInternalAuth(anyRole = {RoleCode.OPS_ADMIN, RoleCode.SUPER_ADMIN}, authLevel = AuthLevel.FULL)
|
||||
@AuditedOperation(module = ModuleCode.BACKUP, action = ActionType.ACCESS, summary = "预检资源恢复包")
|
||||
@Operation(summary = "预检资源恢复请求", description = "根据 fileId 校验备份包头、签名和恢复范围摘要。")
|
||||
public ApiResponse<ResourceRestorePrecheckResponse> precheck(@Valid @RequestBody ResourceRestorePrecheckRequest request) {
|
||||
return ApiResponse.success(resourcePrecheckService.precheck(request));
|
||||
@@ -44,12 +52,15 @@ public class ResourceRestoreController {
|
||||
|
||||
@PostMapping
|
||||
@ReplayProtected
|
||||
// @RequireInternalAuth(anyRole = {RoleCode.OPS_ADMIN, RoleCode.SUPER_ADMIN}, authLevel = AuthLevel.FULL)
|
||||
@AuditedOperation(module = ModuleCode.BACKUP, action = ActionType.RECOVER, summary = "创建资源恢复任务")
|
||||
@Operation(summary = "创建资源恢复任务", description = "根据预检号创建资源恢复任务。")
|
||||
public ApiResponse<CreateResourceRestoreResponse> createRestoreTask(@Valid @RequestBody CreateResourceRestoreRequest request) {
|
||||
return ApiResponse.success(resourceRestoreService.createRestoreTask(request));
|
||||
}
|
||||
|
||||
@GetMapping("/{taskId}")
|
||||
// @RequireInternalAuth(anyRole = {RoleCode.OPS_ADMIN, RoleCode.SUPER_ADMIN}, authLevel = AuthLevel.FULL)
|
||||
@Operation(summary = "查询资源恢复任务详情", description = "返回资源恢复任务详情和当前控制阶段。")
|
||||
public ApiResponse<ResourceRestoreTaskDetailResponse> taskDetail(
|
||||
@Parameter(description = "恢复任务号")
|
||||
@@ -59,6 +70,7 @@ public class ResourceRestoreController {
|
||||
}
|
||||
|
||||
@GetMapping("/{taskId}/steps")
|
||||
// @RequireInternalAuth(anyRole = {RoleCode.OPS_ADMIN, RoleCode.SUPER_ADMIN}, authLevel = AuthLevel.FULL)
|
||||
@Operation(summary = "查询资源恢复任务步骤", description = "返回资源恢复任务步骤列表。")
|
||||
public ApiResponse<List<ResourceTaskStepItemResponse>> taskSteps(
|
||||
@Parameter(description = "恢复任务号")
|
||||
@@ -68,6 +80,7 @@ public class ResourceRestoreController {
|
||||
}
|
||||
|
||||
@GetMapping("/{taskId}/steps/{stepNo}/log")
|
||||
// @RequireInternalAuth(anyRole = {RoleCode.OPS_ADMIN, RoleCode.SUPER_ADMIN}, authLevel = AuthLevel.FULL)
|
||||
@Operation(summary = "查询资源恢复步骤日志", description = "返回资源恢复步骤日志。")
|
||||
public ApiResponse<ResourceTaskStepLogResponse> taskStepLog(
|
||||
@Parameter(description = "恢复任务号")
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
package com.cisd.tms.modules.backup.repository;
|
||||
|
||||
import com.cisd.tms.modules.backup.entity.ResourceBackupTaskEntity;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface ResourceBackupTaskRepository {
|
||||
|
||||
void save(ResourceBackupTaskEntity entity);
|
||||
|
||||
void update(ResourceBackupTaskEntity entity);
|
||||
|
||||
Optional<ResourceBackupTaskEntity> findByTaskId(String taskId);
|
||||
|
||||
List<ResourceBackupTaskEntity> findAll();
|
||||
}
|
||||
|
||||
+12
@@ -3,6 +3,7 @@ package com.cisd.tms.modules.backup.repository.impl;
|
||||
import com.cisd.tms.modules.backup.entity.ResourceBackupTaskEntity;
|
||||
import com.cisd.tms.modules.backup.mapper.ResourceBackupTaskMapper;
|
||||
import com.cisd.tms.modules.backup.repository.ResourceBackupTaskRepository;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@@ -20,8 +21,19 @@ public class ResourceBackupTaskRepositoryImpl implements ResourceBackupTaskRepos
|
||||
resourceBackupTaskMapper.insert(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(ResourceBackupTaskEntity entity) {
|
||||
resourceBackupTaskMapper.updateById(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ResourceBackupTaskEntity> findByTaskId(String taskId) {
|
||||
return Optional.ofNullable(resourceBackupTaskMapper.selectByTaskId(taskId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ResourceBackupTaskEntity> findAll() {
|
||||
List<ResourceBackupTaskEntity> tasks = resourceBackupTaskMapper.selectList(null);
|
||||
return tasks == null ? List.of() : tasks;
|
||||
}
|
||||
}
|
||||
|
||||
+5
-1
@@ -40,6 +40,7 @@ public class ResourceBackupServiceImpl implements ResourceBackupService {
|
||||
private final ResourceBackupTaskRepository resourceBackupTaskRepository;
|
||||
private final ResourceTaskIdGenerator resourceTaskIdGenerator;
|
||||
private final com.cisd.tms.modules.backup.config.ResourceBackupProperties resourceBackupProperties;
|
||||
private final ResourceTaskAdmissionGuard resourceTaskAdmissionGuard;
|
||||
|
||||
@Override
|
||||
public ResourceKeysetStatusResponse getKeysetStatus() {
|
||||
@@ -57,6 +58,7 @@ public class ResourceBackupServiceImpl implements ResourceBackupService {
|
||||
|
||||
@Override
|
||||
public CreateResourceBackupResponse createBackupTask(CreateResourceBackupRequest request) {
|
||||
resourceTaskAdmissionGuard.assertCanStartBackup();
|
||||
// 资源备份必须基于“最近一次成功初始化”快照收集上下文,避免前端重新传一套可能失真的参数。
|
||||
InitTaskEntity initTask = initTaskRepository.findLatestByTaskTypeAndStatus("INIT", "SUCCESS")
|
||||
.orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "成功ful init task is missing"));
|
||||
@@ -77,6 +79,7 @@ public class ResourceBackupServiceImpl implements ResourceBackupService {
|
||||
entity.setCreateTime(now);
|
||||
entity.setUpdateTime(now);
|
||||
entity.setStartTime(now);
|
||||
resourceBackupTaskRepository.save(entity);
|
||||
|
||||
try {
|
||||
// 先根据初始化快照收敛资源范围,再交给打包服务生成本地可见的 .tmsbak 文件。
|
||||
@@ -106,7 +109,8 @@ public class ResourceBackupServiceImpl implements ResourceBackupService {
|
||||
entity.setErrorMessage(ex.getMessage());
|
||||
}
|
||||
entity.setFinishTime(LocalDateTime.now());
|
||||
resourceBackupTaskRepository.save(entity);
|
||||
entity.setUpdateTime(entity.getFinishTime());
|
||||
resourceBackupTaskRepository.update(entity);
|
||||
|
||||
CreateResourceBackupResponse response = new CreateResourceBackupResponse();
|
||||
response.setTaskId(taskId);
|
||||
|
||||
@@ -36,10 +36,12 @@ public class ResourceRestoreServiceImpl implements ResourceRestoreService {
|
||||
private final ResourceBackupProperties resourceBackupProperties;
|
||||
private final ResourcePayloadExtractor resourcePayloadExtractor;
|
||||
private final LightweightRestoreApplierLauncher lightweightRestoreApplierLauncher;
|
||||
private final ResourceTaskAdmissionGuard resourceTaskAdmissionGuard;
|
||||
|
||||
|
||||
@Override
|
||||
public CreateResourceRestoreResponse createRestoreTask(CreateResourceRestoreRequest request) {
|
||||
resourceTaskAdmissionGuard.assertCanStartRestore();
|
||||
if (!Boolean.TRUE.equals(request.getConfirmRiskAccepted())) {
|
||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "必须确认恢复风险");
|
||||
}
|
||||
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
package com.cisd.tms.modules.backup.service.impl;
|
||||
|
||||
import com.cisd.tms.common.enums.ErrorCode;
|
||||
import com.cisd.tms.common.exception.BizException;
|
||||
import com.cisd.tms.modules.backup.entity.ResourceBackupTaskEntity;
|
||||
import com.cisd.tms.modules.backup.entity.ResourceRestoreTaskEntity;
|
||||
import com.cisd.tms.modules.backup.repository.ResourceBackupTaskRepository;
|
||||
import com.cisd.tms.modules.backup.repository.ResourceRestoreTaskRepository;
|
||||
import com.cisd.tms.modules.backup.restore.RestoreStatusFileReader;
|
||||
import com.cisd.tms.modules.init.repository.InitTaskRepository;
|
||||
import com.cisd.tms.modules.upgrade.repository.UpgradeTaskRepository;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class ResourceTaskAdmissionGuard {
|
||||
|
||||
private static final Set<String> ACTIVE_BACKUP_STATUSES = Set.of("RUNNING");
|
||||
private static final Set<String> ACTIVE_RESTORE_STATUSES = Set.of("APPLYING", "RUNNING", "HANDOFF", "PENDING");
|
||||
|
||||
private final ResourceBackupTaskRepository resourceBackupTaskRepository;
|
||||
private final ResourceRestoreTaskRepository resourceRestoreTaskRepository;
|
||||
private final InitTaskRepository initTaskRepository;
|
||||
private final UpgradeTaskRepository upgradeTaskRepository;
|
||||
private final RestoreStatusFileReader restoreStatusFileReader;
|
||||
|
||||
public ResourceTaskAdmissionGuard(
|
||||
ResourceBackupTaskRepository resourceBackupTaskRepository,
|
||||
ResourceRestoreTaskRepository resourceRestoreTaskRepository,
|
||||
InitTaskRepository initTaskRepository,
|
||||
UpgradeTaskRepository upgradeTaskRepository,
|
||||
RestoreStatusFileReader restoreStatusFileReader
|
||||
) {
|
||||
this.resourceBackupTaskRepository = resourceBackupTaskRepository;
|
||||
this.resourceRestoreTaskRepository = resourceRestoreTaskRepository;
|
||||
this.initTaskRepository = initTaskRepository;
|
||||
this.upgradeTaskRepository = upgradeTaskRepository;
|
||||
this.restoreStatusFileReader = restoreStatusFileReader;
|
||||
}
|
||||
|
||||
public synchronized void assertCanStartBackup() {
|
||||
assertNoActiveBackup();
|
||||
assertNoActiveRestore();
|
||||
assertNoRunningInitOrReset();
|
||||
assertNoRunningUpgrade();
|
||||
}
|
||||
|
||||
public synchronized void assertCanStartRestore() {
|
||||
assertNoActiveBackup();
|
||||
assertNoActiveRestore();
|
||||
assertNoRunningInitOrReset();
|
||||
assertNoRunningUpgrade();
|
||||
}
|
||||
|
||||
private void assertNoActiveBackup() {
|
||||
for (ResourceBackupTaskEntity task : safeList(resourceBackupTaskRepository.findAll())) {
|
||||
if (ACTIVE_BACKUP_STATUSES.contains(trim(task.getStatus()))) {
|
||||
throw new BizException(ErrorCode.CONFLICT.getCode(), "资源备份任务正在执行:" + trim(task.getTaskId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void assertNoActiveRestore() {
|
||||
for (ResourceRestoreTaskEntity task : safeList(resourceRestoreTaskRepository.findAll())) {
|
||||
if (!ACTIVE_RESTORE_STATUSES.contains(trim(task.getStatus()))) {
|
||||
continue;
|
||||
}
|
||||
RestoreStatusFileReader.RestoreStatusSnapshot snapshot = restoreStatusFileReader.read(resolveTaskDir(task));
|
||||
if ("SUCCESS".equalsIgnoreCase(trim(snapshot.status())) || "FAILED".equalsIgnoreCase(trim(snapshot.status()))) {
|
||||
continue;
|
||||
}
|
||||
throw new BizException(ErrorCode.CONFLICT.getCode(), "资源恢复任务正在执行:" + trim(task.getTaskId()));
|
||||
}
|
||||
}
|
||||
|
||||
private void assertNoRunningInitOrReset() {
|
||||
initTaskRepository.findLatestByStatus("RUNNING").ifPresent(task -> {
|
||||
throw new BizException(ErrorCode.CONFLICT.getCode(), "初始化或重置任务正在执行:" + trim(task.getTaskId()));
|
||||
});
|
||||
}
|
||||
|
||||
private void assertNoRunningUpgrade() {
|
||||
upgradeTaskRepository.findRunningTask().ifPresent(task -> {
|
||||
throw new BizException(ErrorCode.CONFLICT.getCode(), "升级任务正在执行:" + trim(task.getTaskId()));
|
||||
});
|
||||
}
|
||||
|
||||
private static Path resolveTaskDir(ResourceRestoreTaskEntity task) {
|
||||
String runnerStatePath = trim(task.getRunnerStatePath());
|
||||
if (runnerStatePath.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Path statePath = Path.of(runnerStatePath).normalize();
|
||||
return statePath.getParent();
|
||||
}
|
||||
|
||||
private static String trim(String value) {
|
||||
return value == null ? "" : value.trim();
|
||||
}
|
||||
|
||||
private static <T> List<T> safeList(List<T> items) {
|
||||
return items == null ? List.of() : items;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
package com.cisd.tms.modules.log.enums;
|
||||
|
||||
public enum ModuleCode {
|
||||
AUTH, INIT, UPGRADE, DEVICE, NETWORK, KEY, SYSTEM, SECURITY, LOG
|
||||
AUTH, INIT, UPGRADE, DEVICE, NETWORK, KEY, SYSTEM, SECURITY, LOG, BACKUP
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user