diff --git a/src/main/java/com/cisd/tms/modules/log/aspect/OperationAuditAspect.java b/src/main/java/com/cisd/tms/modules/log/aspect/OperationAuditAspect.java index 2de551c..43d2298 100644 --- a/src/main/java/com/cisd/tms/modules/log/aspect/OperationAuditAspect.java +++ b/src/main/java/com/cisd/tms/modules/log/aspect/OperationAuditAspect.java @@ -6,6 +6,7 @@ import com.cisd.tms.modules.log.enums.AuthLevel; import com.cisd.tms.modules.log.enums.OperationResult; import com.cisd.tms.modules.log.enums.OperatorRoleCode; import com.cisd.tms.modules.log.service.OperationAuditService; +import com.cisd.tms.security.internal.InternalApiAuthInterceptor; import jakarta.servlet.http.HttpServletRequest; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; @@ -21,10 +22,6 @@ public class OperationAuditAspect { public static final String ATTR_ROLE_CODE = "CURRENT_ROLE_CODE"; public static final String ATTR_AUTH_LEVEL = "CURRENT_AUTH_LEVEL"; - 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; @Autowired private OperationAuditService auditService; @@ -66,14 +63,15 @@ public class OperationAuditAspect { */ private void getContextInfo(OperationAuditCommand command) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + //todo 是否考虑为null的情况 if (attributes != null) { HttpServletRequest request = attributes.getRequest(); - //todo 是否考虑未知情况 - Object roleObj = request.getAttribute(ATTR_ROLE_CODE); + + Object roleObj = request.getAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE); command.setOperatorRoleCode(OperatorRoleCode.valueOf(roleObj.toString())); - Object levelObj = request.getAttribute(ATTR_AUTH_LEVEL); + Object levelObj = request.getAttribute(InternalApiAuthInterceptor.ATTR_AUTH_LEVEL); command.setOperatorAuthLevel(AuthLevel.valueOf(levelObj.toString())); command.setRemoteIp(request.getRemoteAddr()); diff --git a/src/main/java/com/cisd/tms/modules/log/controller/AuditLogRestoreController.java b/src/main/java/com/cisd/tms/modules/log/controller/AuditLogRestoreController.java new file mode 100644 index 0000000..5744664 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/log/controller/AuditLogRestoreController.java @@ -0,0 +1,39 @@ +package com.cisd.tms.modules.log.controller; + + +import com.cisd.tms.common.api.ApiResponse; +import com.cisd.tms.common.enums.ErrorCode; +import com.cisd.tms.common.exception.BizException; +import com.cisd.tms.modules.log.service.AuditLogRestoreService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@RequestMapping("/api/v1/audit-logs/restore") +@RequiredArgsConstructor +@Tag(name = "审计日志恢复接口", description = "将本地备份的 ZIP 文件还原并解析导入到系统中") +public class AuditLogRestoreController { + + private final AuditLogRestoreService restoreService; + + @PostMapping(consumes = {"multipart/form-data"}) + @Operation( + summary = "导入 ZIP 恢复审计日志", + description = "上传审计日志的备份压缩包(.zip),系统将进行解密、验签并落库。" + ) + public ApiResponse restoreFromZip(@RequestParam("file") MultipartFile file) { + if (file.isEmpty() || !file.getOriginalFilename().endsWith(".zip")) { + throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "上传有效的 ZIP 格式备份文件"); + } + + String result = restoreService.auditLogRestore(file); + return ApiResponse.success(result); + } + +} \ No newline at end of file diff --git a/src/main/java/com/cisd/tms/modules/log/controller/BackupController.java b/src/main/java/com/cisd/tms/modules/log/controller/BackupController.java new file mode 100644 index 0000000..59f1774 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/log/controller/BackupController.java @@ -0,0 +1,69 @@ +package com.cisd.tms.modules.log.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.cisd.tms.common.api.ApiResponse; +import com.cisd.tms.common.enums.ErrorCode; +import com.cisd.tms.common.exception.BizException; +import com.cisd.tms.modules.log.dto.BackupRecordPageRequest; +import com.cisd.tms.modules.log.dto.BackupRecordResponse; +import com.cisd.tms.modules.log.entity.BackupRecordEntity; +import com.cisd.tms.modules.log.repository.BackupRecordRepository; +import com.cisd.tms.modules.log.service.AuditBackupService; +import com.cisd.tms.modules.log.service.BackupRecordService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.io.File; + +@RestController +@RequestMapping("/api/v1/audit-logs/backup") +@RequiredArgsConstructor +@Tag(name = "审计日志备份接口", description = "提供审计日志的手动触发备份、备份记录分页查询以及备份文件下载功能") +public class BackupController { + + private final AuditBackupService auditBackupService; + private final BackupRecordService backupRecordService; + private final BackupRecordRepository backupRecordRepository; + + @PostMapping() + @Operation(summary = "触发手动备份", description = "执行一次审计日志的备份任务,生成 ZIP 备份包") + public ApiResponse backup() { + auditBackupService.manualBackup(); + return ApiResponse.success(); + } + + @PostMapping("/record/page") + @Operation(summary = "分页查询备份记录", description = "根据条件(例如:备份类型、操作日期)分页获取历史备份记录列表") + public ApiResponse> queryPage(@RequestBody BackupRecordPageRequest req) { + IPage pageResult = backupRecordService.queryPage(req); + return ApiResponse.success(pageResult); + } + + + @GetMapping("/record/download/{recordId}") + @Operation(summary = "下载备份文件", description = "根据记录的唯一编码 (recordId) 下载对应的 ZIP 备份压缩包文件") + public ResponseEntity downloadBackupFile(@PathVariable("recordId") String recordId) { + + BackupRecordEntity record = backupRecordRepository.findByRecordId(recordId).orElseThrow(() -> new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "备份记录不存在")); + + File file = new File(record.getFilePath()); + if (!file.exists()) { + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "物理备份文件已被删除或丢失"); + } + + Resource resource = new FileSystemResource(file); + + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + record.getFileName() + "\"") + .contentType(MediaType.parseMediaType("application/zip")) + .contentLength(file.length()) + .body(resource); + } +} \ No newline at end of file diff --git a/src/main/java/com/cisd/tms/modules/log/controller/OperationAuditController.java b/src/main/java/com/cisd/tms/modules/log/controller/OperationAuditController.java index 78182c4..2df0d73 100644 --- a/src/main/java/com/cisd/tms/modules/log/controller/OperationAuditController.java +++ b/src/main/java/com/cisd/tms/modules/log/controller/OperationAuditController.java @@ -33,7 +33,7 @@ public class OperationAuditController { return ApiResponse.success(pageResult); } - @Operation(summary = "获取审计日志详情", description = "根据日志的唯一标识 ID 获取单条日志的完整信息") + @Operation(summary = "获取审计日志详情", description = "根据日志的唯一标识 LogId 获取单条日志的完整信息") @GetMapping("/{logId}") public ApiResponse getDetail(@PathVariable String logId) { OperationAuditLogResponse detail = auditService.getDetail(logId); diff --git a/src/main/java/com/cisd/tms/modules/log/dto/BackupRecordPageRequest.java b/src/main/java/com/cisd/tms/modules/log/dto/BackupRecordPageRequest.java new file mode 100644 index 0000000..bf26aba --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/log/dto/BackupRecordPageRequest.java @@ -0,0 +1,23 @@ +package com.cisd.tms.modules.log.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDate; + +@Data +@Schema(description = "备份记录分页查询请求参数") +public class BackupRecordPageRequest { + + @Schema(description = "备份类型 (AUTO:自动 / MANUAL:手动)", allowableValues = {"AUTO", "MANUAL"}, example = "AUTO") + private String backupType; + + @Schema(description = "操作日期", type = "string", format = "date", example = "2026-04-21") + private LocalDate operationDate; + + @Schema(description = "当前页码", example = "1", defaultValue = "1") + private int pageNum = 1; + + @Schema(description = "每页数量", example = "10", defaultValue = "10") + private int pageSize = 10; +} \ No newline at end of file diff --git a/src/main/java/com/cisd/tms/modules/log/dto/BackupRecordResponse.java b/src/main/java/com/cisd/tms/modules/log/dto/BackupRecordResponse.java new file mode 100644 index 0000000..658bc18 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/log/dto/BackupRecordResponse.java @@ -0,0 +1,29 @@ +package com.cisd.tms.modules.log.dto; + + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + + +@Data +public class BackupRecordResponse { + + @Schema(description = "备份记录唯一标识", example = "1bc6e347692e7b396666e78e9e491cd7") + private String recordId; + + @Schema(description = "备份时间", type = "string", example = "2026-04-21 11:00:00") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime backupTime; + + @Schema(description = "备份类型 (AUTO:自动 / MANUAL:手动)", example = "AUTO") + private String backupType; + + @Schema(description = "备份结果 (SUCCESS:成功 / FAIL:失败)", example = "SUCCESS") + private String backupResult; + +// private String fileName; + +} diff --git a/src/main/java/com/cisd/tms/modules/log/dto/OperationAuditLogResponse.java b/src/main/java/com/cisd/tms/modules/log/dto/OperationAuditLogResponse.java index 448048e..c750108 100644 --- a/src/main/java/com/cisd/tms/modules/log/dto/OperationAuditLogResponse.java +++ b/src/main/java/com/cisd/tms/modules/log/dto/OperationAuditLogResponse.java @@ -17,7 +17,7 @@ public class OperationAuditLogResponse { @Schema(description = "操作人认证等级", example = "FULL") private AuthLevel operatorAuthLevel; - @Schema(description = "系统模块代码", example = "LOG_MANAGEMENT") + @Schema(description = "系统模块代码", example = "DEVICE") private ModuleCode moduleCode; @Schema(description = "操作类型", example = "UPDATE") diff --git a/src/main/java/com/cisd/tms/modules/log/entity/BackupConfigEntity.java b/src/main/java/com/cisd/tms/modules/log/entity/BackupConfigEntity.java index 296f529..303cbb5 100644 --- a/src/main/java/com/cisd/tms/modules/log/entity/BackupConfigEntity.java +++ b/src/main/java/com/cisd/tms/modules/log/entity/BackupConfigEntity.java @@ -14,33 +14,15 @@ import java.time.LocalDateTime; @TableName("tms_backup_config") public class BackupConfigEntity{ - /** - * 主键 - */ private Integer id; - /** - * 启用状态 1:启用 0:禁用 (非空, 默认 0) - */ private Integer enable; - /** - * Cron表达式 - */ private String cronExp; - /** - * 保留份数 (默认 1) - */ - private Integer retentionCount; + private Integer retentionCount = 1; - /** - * 上次执行时间 - */ private LocalDateTime lastBackupTime; - /** - * 其他字段的签名值 (防篡改) - */ private String signData; } diff --git a/src/main/java/com/cisd/tms/modules/log/entity/BackupRecordEntity.java b/src/main/java/com/cisd/tms/modules/log/entity/BackupRecordEntity.java index 8fc1a09..4fb25c5 100644 --- a/src/main/java/com/cisd/tms/modules/log/entity/BackupRecordEntity.java +++ b/src/main/java/com/cisd/tms/modules/log/entity/BackupRecordEntity.java @@ -6,48 +6,28 @@ import lombok.experimental.Accessors; import java.time.LocalDateTime; -@TableName("tms_log_backup") +@TableName("tms_log_backup_record") @Data @Accessors(chain = true) public class BackupRecordEntity { - /** - * 主键 - */ private Long id; - /** - * 文件名 - */ + private String recordId; + private String fileName; - /** - * 存储路径 - */ + private String backupType; + private String filePath; - /** - * 文件哈希 - */ private String fileHash; - /** - * 文件签名 - */ private String fileSign; - /** - * 状态 SUCCESS/DELETED - */ - private String status; + private String BackupResult; - /** - * 创建时间 - */ private LocalDateTime createTime; - /** - * 其他字段签名值 - */ private String signData; } diff --git a/src/main/java/com/cisd/tms/modules/log/enums/ModuleCode.java b/src/main/java/com/cisd/tms/modules/log/enums/ModuleCode.java index 23da9bd..3ac24e3 100644 --- a/src/main/java/com/cisd/tms/modules/log/enums/ModuleCode.java +++ b/src/main/java/com/cisd/tms/modules/log/enums/ModuleCode.java @@ -1,5 +1,5 @@ package com.cisd.tms.modules.log.enums; public enum ModuleCode { - AUTH, INIT, UPGRADE, DEVICE, NETWORK, KEY, SYSTEM, SECURITY + AUTH, INIT, UPGRADE, DEVICE, NETWORK, KEY, SYSTEM, SECURITY, LOG } diff --git a/src/main/java/com/cisd/tms/modules/log/mapper/OperationAuditLogMapper.java b/src/main/java/com/cisd/tms/modules/log/mapper/OperationAuditLogMapper.java index 5e275cf..d0b4c9b 100644 --- a/src/main/java/com/cisd/tms/modules/log/mapper/OperationAuditLogMapper.java +++ b/src/main/java/com/cisd/tms/modules/log/mapper/OperationAuditLogMapper.java @@ -3,7 +3,12 @@ package com.cisd.tms.modules.log.mapper; import com.cisd.tms.infrastructure.persistence.mapper.BaseMapperX; import com.cisd.tms.modules.log.entity.OperationAuditLogEntity; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; @Mapper public interface OperationAuditLogMapper extends BaseMapperX { + int addBatch(@Param("list") List entityList); + } diff --git a/src/main/java/com/cisd/tms/modules/log/repository/BackupRecordRepository.java b/src/main/java/com/cisd/tms/modules/log/repository/BackupRecordRepository.java index 9d61ce4..1e78aba 100644 --- a/src/main/java/com/cisd/tms/modules/log/repository/BackupRecordRepository.java +++ b/src/main/java/com/cisd/tms/modules/log/repository/BackupRecordRepository.java @@ -1,7 +1,13 @@ package com.cisd.tms.modules.log.repository; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.cisd.tms.modules.log.dto.BackupRecordPageRequest; import com.cisd.tms.modules.log.entity.BackupRecordEntity; +import java.util.Optional; + public interface BackupRecordRepository { void add(BackupRecordEntity backupRecordEntity); + IPage findPage(BackupRecordPageRequest req); + Optional findByRecordId(String recordId); } diff --git a/src/main/java/com/cisd/tms/modules/log/repository/OperationAuditLogRepository.java b/src/main/java/com/cisd/tms/modules/log/repository/OperationAuditLogRepository.java index 6b6565b..6acdecc 100644 --- a/src/main/java/com/cisd/tms/modules/log/repository/OperationAuditLogRepository.java +++ b/src/main/java/com/cisd/tms/modules/log/repository/OperationAuditLogRepository.java @@ -13,4 +13,6 @@ public interface OperationAuditLogRepository { Optional findByLogId(String logId); void updateByLogId(OperationAuditLogEntity entity); Optional> findAll (); + Optional> findExistSignValues(List incomingSignValues); + int addBatch(List auditLogEntity); } diff --git a/src/main/java/com/cisd/tms/modules/log/repository/impl/BackupRecordRepositoryImpl.java b/src/main/java/com/cisd/tms/modules/log/repository/impl/BackupRecordRepositoryImpl.java index 20c66d6..4bb3d7f 100644 --- a/src/main/java/com/cisd/tms/modules/log/repository/impl/BackupRecordRepositoryImpl.java +++ b/src/main/java/com/cisd/tms/modules/log/repository/impl/BackupRecordRepositoryImpl.java @@ -1,10 +1,19 @@ package com.cisd.tms.modules.log.repository.impl; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cisd.tms.modules.log.dto.BackupRecordPageRequest; import com.cisd.tms.modules.log.entity.BackupRecordEntity; import com.cisd.tms.modules.log.mapper.BackupRecordMapper; import com.cisd.tms.modules.log.repository.BackupRecordRepository; import org.springframework.stereotype.Repository; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Optional; + @Repository public class BackupRecordRepositoryImpl implements BackupRecordRepository { @@ -18,4 +27,32 @@ public class BackupRecordRepositoryImpl implements BackupRecordRepository { public void add(BackupRecordEntity backupRecordEntity){ backupRecordMapper.insert(backupRecordEntity); } + + + @Override + public IPage findPage(BackupRecordPageRequest req){ + IPage page = new Page<>(req.getPageNum(), req.getPageSize()); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(StringUtils.isNotBlank(req.getBackupType()), + BackupRecordEntity::getBackupType, req.getBackupType()); + + if (req.getOperationDate() != null) { + LocalDateTime startOfDay = req.getOperationDate().atStartOfDay(); + LocalDateTime endOfDay = req.getOperationDate().atTime(LocalTime.MAX); + + wrapper.ge(BackupRecordEntity::getCreateTime, startOfDay) + .le(BackupRecordEntity::getCreateTime, endOfDay); + } + + IPage entityPage = backupRecordMapper.selectPage(page, wrapper); + + return entityPage; + } + + @Override + public Optional findByRecordId(String recordId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(BackupRecordEntity::getRecordId, recordId).last("LIMIT 1");; + return Optional.ofNullable(backupRecordMapper.selectOne(wrapper)); + } } diff --git a/src/main/java/com/cisd/tms/modules/log/repository/impl/OperationAuditLogRepositoryImpl.java b/src/main/java/com/cisd/tms/modules/log/repository/impl/OperationAuditLogRepositoryImpl.java index 01300d7..69f7f53 100644 --- a/src/main/java/com/cisd/tms/modules/log/repository/impl/OperationAuditLogRepositoryImpl.java +++ b/src/main/java/com/cisd/tms/modules/log/repository/impl/OperationAuditLogRepositoryImpl.java @@ -10,8 +10,10 @@ import com.cisd.tms.modules.log.mapper.OperationAuditLogMapper; import com.cisd.tms.modules.log.repository.OperationAuditLogRepository; import org.springframework.stereotype.Repository; +import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; @Repository @@ -83,4 +85,35 @@ public class OperationAuditLogRepositoryImpl implements OperationAuditLogReposit List logList = operationAuditLogMapper.selectList(wrapper); return Optional.ofNullable(logList); } + + @Override + public Optional> findExistSignValues(List incomingSignValues){ + + List existList = new ArrayList<>(); + + int batchSize = 1000; + + for (int i = 0; i < incomingSignValues.size(); i += batchSize) { + int end = Math.min(i + batchSize, incomingSignValues.size()); + List subList = incomingSignValues.subList(i, end); + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.select(OperationAuditLogEntity::getSignValue) + .in(OperationAuditLogEntity::getSignValue, subList); + + List batchResult = operationAuditLogMapper.selectList(wrapper); + + existList.addAll(batchResult.stream() + .map(OperationAuditLogEntity::getSignValue) + .collect(Collectors.toList())); + } + return Optional.ofNullable(existList); + } + + + @Override + public int addBatch(List entityList){ + int actualInserted = operationAuditLogMapper.addBatch(entityList); + return actualInserted; + } } diff --git a/src/main/java/com/cisd/tms/modules/log/runner/BackupTaskStartupRunner.java b/src/main/java/com/cisd/tms/modules/log/runner/BackupTaskStartupRunner.java index 5eae805..c293051 100644 --- a/src/main/java/com/cisd/tms/modules/log/runner/BackupTaskStartupRunner.java +++ b/src/main/java/com/cisd/tms/modules/log/runner/BackupTaskStartupRunner.java @@ -1,64 +1,50 @@ -//package com.cisd.tms.modules.log.runner; -// -//import com.cisd.tms.modules.log.dto.BackupConfigUpdateRequest; -//import com.cisd.tms.modules.log.entity.BackupConfigEntity; -//import com.cisd.tms.modules.log.repository.BackupConfigRepository; -//import com.cisd.tms.modules.log.service.AuditBackupService; -//import org.slf4j.Logger; -//import org.slf4j.LoggerFactory; -//import org.springframework.beans.factory.annotation.Autowired; -//import org.springframework.boot.CommandLineRunner; -//import org.springframework.stereotype.Component; -// -//import java.util.ArrayList; -//import java.util.List; -// -// -//@Component -//public class BackupTaskStartupRunner implements CommandLineRunner { -// -// private static final Logger log = LoggerFactory.getLogger(BackupTaskStartupRunner.class); -// -// @Autowired -// private BackupConfigRepository backupConfigRepository; -// -// @Autowired -// private AuditBackupService auditBackupService; -// -// @Override -// public void run(String... args) throws Exception { -// log.info("============= 系统启动:开始初始化审计日志备份定时任务 ============="); -// -// try { -// List configList = backupConfigRepository.selectAll().orElseGet(ArrayList::new); -// -// if (configList.isEmpty()) { -// log.info("未在数据库中找到备份策略配置,跳过任务初始化。"); -// return; -// } -// -// for (BackupConfigEntity config : configList) { -// // 仅当状态为启用 (1) 且 cron 表达式不为空时,才去开启任务 -// if (config.getEnable() != null && config.getEnable() == 1 -// && config.getCronExp() != null && !config.getCronExp().isEmpty()) { -// -// BackupConfigUpdateRequest req = new BackupConfigUpdateRequest(); -// req.setCronExp(config.getCronExp()); -// req.setEnable(config.getEnable()); -// req.setRetentionCount(config.getRetentionCount()); -// auditBackupService.updateAndRestartBackupTask( -// req -// ); -// log.info("成功加载并开启备份任务,配置ID: {}, Cron: {}", config.getId(), config.getCronExp()); -// } else { -// log.info("配置ID: {} 未启用或 Cron 为空,跳过。", config.getId()); -// } -// } -// -// log.info("============= 审计日志备份定时任务初始化完成 ============="); -// -// } catch (Exception e) { -// log.error("初始化审计日志备份任务发生异常: {}", e.getMessage(), e); -// } -// } -//} \ No newline at end of file +package com.cisd.tms.modules.log.runner; + +import com.cisd.tms.modules.log.entity.BackupConfigEntity; +import com.cisd.tms.modules.log.repository.BackupConfigRepository; +import com.cisd.tms.modules.log.service.AuditBackupService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + + +@Component +public class BackupTaskStartupRunner implements CommandLineRunner { + + private static final Logger log = LoggerFactory.getLogger(BackupTaskStartupRunner.class); + + @Autowired + private BackupConfigRepository backupConfigRepository; + + @Autowired + private AuditBackupService auditBackupService; + + @Override + public void run(String... args) throws Exception { + log.info("============= 系统启动:开始初始化审计日志备份定时任务 ============="); + + try { + BackupConfigEntity config = backupConfigRepository.find().orElseGet(BackupConfigEntity :: new); + if (config.getId() == null) { + log.info("未在数据库中找到备份策略配置,跳过任务初始化。"); + return; + } + + // 仅当状态为启用 (1) 且 cron 表达式不为空时,才去开启任务 + if (config.getEnable() != null && config.getEnable() == 1 + && config.getCronExp() != null && !config.getCronExp().isEmpty()) { + auditBackupService.restartBackupTask(config); + log.info("成功加载并开启备份任务,配置ID: {}, Cron: {}", config.getId(), config.getCronExp()); + } else { + log.info("配置ID: {} 未启用或 Cron 为空,跳过。", config.getId()); + } + + log.info("审计日志备份定时任务初始化完成"); + + } catch (Exception e) { + log.error("初始化审计日志备份任务发生异常: {}", e.getMessage(), e); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/cisd/tms/modules/log/service/AuditBackupService.java b/src/main/java/com/cisd/tms/modules/log/service/AuditBackupService.java index 42596ba..23b7763 100644 --- a/src/main/java/com/cisd/tms/modules/log/service/AuditBackupService.java +++ b/src/main/java/com/cisd/tms/modules/log/service/AuditBackupService.java @@ -1,248 +1,434 @@ -//package com.cisd.tms.modules.log.service; -// -//import com.cisd.tms.integration.crypto.pcie.model.BackupDataResult; -//import com.cisd.tms.integration.crypto.pcie.model.EccInternalEncryptRequest; -//import com.cisd.tms.integration.crypto.pcie.service.PcieCryptoService; -//import com.cisd.tms.modules.log.dto.BackupConfigRequest; -//import com.cisd.tms.modules.log.entity.BackupConfigEntity; -//import com.cisd.tms.modules.log.entity.BackupRecordEntity; -//import com.cisd.tms.modules.log.entity.OperationAuditLogEntity; -//import com.cisd.tms.modules.log.enums.ActionType; -//import com.cisd.tms.modules.log.enums.OperationResult; -//import com.cisd.tms.modules.log.repository.BackupConfigRepository; -//import com.cisd.tms.modules.log.repository.BackupRecordRepository; -//import com.cisd.tms.modules.log.repository.OperationAuditLogRepository; -//import org.slf4j.Logger; -//import org.slf4j.LoggerFactory; -//import org.springframework.beans.factory.annotation.Autowired; -//import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; -//import org.springframework.scheduling.support.CronExpression; -//import org.springframework.scheduling.support.CronTrigger; -//import org.springframework.stereotype.Service; -//import org.springframework.transaction.annotation.Transactional; -// -//import javax.crypto.Cipher; -//import javax.crypto.spec.IvParameterSpec; -//import javax.crypto.spec.SecretKeySpec; -//import java.io.File; -//import java.io.FileOutputStream; -//import java.io.IOException; -//import java.nio.charset.StandardCharsets; -//import java.text.SimpleDateFormat; -//import java.time.LocalDateTime; -//import java.time.format.DateTimeFormatter; -//import java.util.*; -//import java.util.concurrent.ScheduledFuture; -//import java.util.zip.ZipEntry; -//import java.util.zip.ZipOutputStream; -// -//@Service -//public class AuditBackupService { -// -// private static final Logger log = LoggerFactory.getLogger(AuditBackupService.class); -// -// @Autowired -// private BackupConfigRepository backupConfigRepository; -// -// @Autowired -// private ThreadPoolTaskScheduler taskScheduler; -// -// @Autowired -// private OperationAuditLogRepository operationAuditLogRepository; -// -// @Autowired -// private BackupRecordRepository backupRecordRepository; -// -// @Autowired -// private PcieCryptoService pcieCryptoService; -// private ScheduledFuture currentTaskFuture; -// private static final String PROVIDER = "BC"; -// -// @Transactional(rollbackFor = Exception.class) -// public void updateAndRestartBackupTask(BackupConfigRequest req) { -// String cronExp = trim(req.getCronExp()); -// if (!CronExpression.isValidExpression(req.getCronExp())) { -// throw new IllegalArgumentException("Cron表达式格式不正确,请重新输入"); -// } -// -// BackupConfigEntity updateEntity = new BackupConfigEntity(); -// updateEntity.setEnable(req.getEnable()); -// updateEntity.setCronExp(req.getCronExp()); -// if (req.getRetentionCount() != null) { -// updateEntity.setRetentionCount(req.getRetentionCount()); -// } -// -// backupConfigRepository.update(updateEntity); -// -// -// if (currentTaskFuture != null && !currentTaskFuture.isCancelled()) { -// currentTaskFuture.cancel(true); -// log.info("已关闭旧的备份定时任务"); -// } -// -// -// if (req.getEnable() == 1) { -// currentTaskFuture = taskScheduler.schedule( -// () -> executeBackup(), // 指向下面的备份方法 -// new CronTrigger(cronExp) -// ); -// log.info("已开启新的备份定时任务,Cron表达式: {}", cronExp); -// } -// } -// -// private void executeBackup() { -// log.info("开始执行审计日志定期备份任务"); -// -// String timeStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")); -// // 定义压缩包的名称和路径 -// String zipFileName = "audit_backup_" + timeStr + ".zip"; -// String zipFilePath = "./data/backup/" + zipFileName; -// -// File zipFile = new File(zipFilePath); -// zipFile.getParentFile().mkdirs(); // 确保目录存在 -// -// boolean isBackupSuccess = false; -// try (FileOutputStream fos = new FileOutputStream(zipFile); -// ZipOutputStream zos = new ZipOutputStream(fos)) { -// -// StringBuilder csvContent = new StringBuilder(); -// -// csvContent.append("日志ID,发生时间,操作角色,认证等级,业务模块,操作动作,操作结果,来源IP,操作摘要,失败原因,审计状态,审计结果,审计人,审计时间,防篡改摘要(SM3),签名值(SM2)\n"); -// -// List logList = operationAuditLogRepository.findAll().orElseGet(ArrayList::new); -// SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); -// -// for (OperationAuditLogEntity auditLog : logList) { -// String summary = escapeCSV(auditLog.getSummary()); -// String errorMsg = escapeCSV(auditLog.getErrorMessage()); -// ActionType actionType = auditLog.getActionType(); -// OperationResult result = auditLog.getOperationResult(); -// -// String occurredTime = auditLog.getOccurredAt() != null ? sdf.format(auditLog.getOccurredAt()) : ""; -// String auditedTime = auditLog.getAuditedAt() != null ? sdf.format(auditLog.getAuditedAt()) : ""; -// -// String line = String.format("%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n", -// auditLog.getLogId(), -// occurredTime, -// auditLog.getOperatorRoleCode(), -// auditLog.getOperatorAuthLevel(), -// auditLog.getModuleCode(), -// actionType, -// result, -// auditLog.getRemoteIp(), -// summary, -// errorMsg, -// auditLog.getAuditStatus(), -// auditLog.getAuditResult(), -// auditLog.getAuditedBy(), -// auditedTime, -// auditLog.getPayloadHash(), -// auditLog.getSignValue() -// ); -// csvContent.append(line); -// } -// -// byte[] orgDataBytes = csvContent.toString().getBytes(StandardCharsets.UTF_8); -// -// byte[] iv = randomBytes(16); -// byte[] sm4Key = randomBytes(16); -// -// Cipher cipher = Cipher.getInstance("SM4/CBC/PKCS7Padding", PROVIDER); -// cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(sm4Key, "SM4"), new IvParameterSpec(iv)); -// -// EccInternalEncryptRequest encryptRequest = new EccInternalEncryptRequest(); -// encryptRequest.setKeyIndex(1); -// encryptRequest.setData(sm4Key); -// BackupDataResult wrappedDek = pcieCryptoService.sdfeInternalEncryptEcc(encryptRequest); -// -// Map envelope = new LinkedHashMap<>(); -// envelope.put("version", 1); -// envelope.put("payloadAlg", "SM4-CBC"); -// envelope.put("keyWrapAlg", "SDFE_InternalEncrypt_ECC"); -// envelope.put("ivBase64", Base64.getEncoder().encodeToString(iv)); -// envelope.put("wrappedDekBase64", Base64.getEncoder().encodeToString(normalize(wrappedDek))); -// envelope.put("dekLength", sm4Key.length); -// -// -// byte[] encryptedData = cipher.doFinal(orgDataBytes); -// -// ZipEntry csvEntry = new ZipEntry("data.csv.enc"); -// zos.putNextEntry(csvEntry); -// -// zos.write(encryptedData); -// zos.closeEntry(); -// -// log.info("审计日志备份完成,共备份 {} 条", logList.size()); -// -// // 更新备份更新时间 -// BackupConfigEntity config = new BackupConfigEntity(); -// config.setLastBackupTime(LocalDateTime.now()); -// backupConfigRepository.saveOrUpdate(config); -// -// isBackupSuccess = true; -// -// } catch (IOException e) { -// log.error("写入 ZIP 压缩包发生异常: {}", e.getMessage(), e); -// } catch (Exception e) { -// log.error("执行备份任务发生异常: {}", e.getMessage(), e); -// } -// -// -// if (isBackupSuccess) { -// try { -// BackupRecordEntity record = new BackupRecordEntity() -// .setFileName(zipFileName) -// .setFilePath(zipFilePath) -// .setStatus("SUCCESS"); -// -// backupRecordRepository.add(record); -// -// log.info("备份记录已成功存入 tms_log_backup 表。"); -// } catch (Exception e) { -// log.error("保存备份记录到数据库失败: {}", e.getMessage(), e); -// } -// } -// } -// -// private String escapeCSV(String str) { -// if (str == null) { -// return ""; -// } -// -// boolean containSpecialChar = str.contains(",") -// || str.contains("\"") -// || str.contains("\n") -// || str.contains("\r"); -// -// if (containSpecialChar) { -// str = str.replace("\"", "\"\""); -// str = "\"" + str + "\""; -// } -// -// return str; -// } -// -// -// private static byte[] normalize(BackupDataResult result) { -// if (result == null || result.getData() == null) { -// return new byte[0]; -// } -// int safeLength = result.getLength() > 0 && result.getLength() <= result.getData().length -// ? result.getLength() -// : result.getData().length; -// byte[] normalized = new byte[safeLength]; -// System.arraycopy(result.getData(), 0, normalized, 0, safeLength); -// return normalized; -// } -// -// -// private static byte[] randomBytes(int size) { -// byte[] bytes = new byte[size]; -// new java.security.SecureRandom().nextBytes(bytes); -// return bytes; -// } -// -// private String trim(String str) { -// return str == null ? "" : str.trim(); -// } -//} \ No newline at end of file +package com.cisd.tms.modules.log.service; + +import com.cisd.tms.common.enums.ErrorCode; +import com.cisd.tms.common.exception.BizException; +import com.cisd.tms.integration.crypto.pcie.model.BackupDataResult; +import com.cisd.tms.integration.crypto.pcie.model.UserKeyEncryptRequest; +import com.cisd.tms.integration.crypto.pcie.model.UserKeySignRequest; +import com.cisd.tms.integration.crypto.pcie.service.PcieCryptoService; +import com.cisd.tms.modules.log.entity.BackupConfigEntity; +import com.cisd.tms.modules.log.entity.BackupRecordEntity; +import com.cisd.tms.modules.log.entity.OperationAuditLogEntity; +import com.cisd.tms.modules.log.repository.BackupConfigRepository; +import com.cisd.tms.modules.log.repository.BackupRecordRepository; +import com.cisd.tms.modules.log.repository.OperationAuditLogRepository; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import org.bouncycastle.crypto.digests.SM3Digest; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.scheduling.support.CronTrigger; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.security.Security; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.concurrent.ScheduledFuture; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +@Service +@RequiredArgsConstructor +public class AuditBackupService { + + private static final Logger log = LoggerFactory.getLogger(AuditBackupService.class); + + private final BackupConfigRepository backupConfigRepository; + private final ThreadPoolTaskScheduler taskScheduler; + private final OperationAuditLogRepository operationAuditLogRepository; + private final BackupRecordRepository backupRecordRepository; + private final PcieCryptoService pcieCryptoService; + private final ObjectMapper objectMapper; + private ScheduledFuture currentTaskFuture; + + private static final String ZIPFILEPATH = "/home/tms/audit-logs/backup/"; + private static final String PROVIDER = "BC"; + static { + if (Security.getProvider(PROVIDER) == null) { + Security.addProvider(new BouncyCastleProvider()); + } + } + + @Transactional(rollbackFor = Exception.class) + public void restartBackupTask(BackupConfigEntity entity) { + String cronExp = trim(entity.getCronExp()); + int enable = entity.getEnable(); + if (currentTaskFuture != null && !currentTaskFuture.isCancelled()) { + currentTaskFuture.cancel(true); + log.info("已关闭旧的备份定时任务"); + } + + if (enable == 1) { + currentTaskFuture = taskScheduler.schedule( + () -> executeBackup(), // 指向下面的备份方法 + new CronTrigger(cronExp) + ); + log.info("已开启新的备份定时任务,Cron表达式: {}", cronExp); + } + } + + private void executeBackup() { + log.info("开始执行审计日志定期备份任务"); + String timeStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")); + String zipFileName = "audit_backup_" + timeStr + ".zip"; + String zipFilePath = ZIPFILEPATH + zipFileName; + + File zipFile = new File(zipFilePath); + zipFile.getParentFile().mkdirs(); + + boolean isBackupSuccess = false; + + try (FileOutputStream fos = new FileOutputStream(zipFile); + ZipOutputStream zos = new ZipOutputStream(fos)) { + + StringBuilder csvContent = new StringBuilder(); + csvContent.append("日志ID,发生时间,操作角色,认证等级,业务模块,操作动作,操作结果,来源IP,操作摘要,失败原因,审计状态,审计结果,审计人,审计时间,防篡改摘要(SM3),签名值(SM2),创建时间\n"); + List logList = operationAuditLogRepository.findAll().orElseGet(ArrayList::new); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + for (OperationAuditLogEntity auditLog : logList) { + String summary = escapeCSV(auditLog.getSummary()); + String errorMsg = escapeCSV(auditLog.getErrorMessage()); + + String actionType = auditLog.getActionType() != null ? auditLog.getActionType().name() : ""; + String result = auditLog.getOperationResult() != null ? auditLog.getOperationResult().name() : ""; + + String occurredTime = auditLog.getOccurredAt() != null ? auditLog.getOccurredAt().format(formatter) : ""; + String auditedTime = auditLog.getAuditedAt() != null ? auditLog.getAuditedAt().format(formatter) : ""; + String createdTime = auditLog.getCreateTime() != null ? auditLog.getCreateTime().format(formatter) : ""; + String line = String.format("%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n", + auditLog.getLogId(), + occurredTime, + auditLog.getOperatorRoleCode() != null ? auditLog.getOperatorRoleCode() : "", + auditLog.getOperatorAuthLevel() != null ? auditLog.getOperatorAuthLevel() : "", + auditLog.getModuleCode(), + actionType, + result, + auditLog.getRemoteIp(), + summary, + errorMsg, + auditLog.getAuditStatus() != null ? auditLog.getAuditStatus() : "", + auditLog.getAuditResult() != null ? auditLog.getAuditResult() : "", + auditLog.getAuditedBy()!= null ? auditLog.getAuditedBy() : "", + auditedTime, + auditLog.getPayloadHash(), + auditLog.getSignValue(), + createdTime + ); + csvContent.append(line); + } + + byte[] orgDataBytes = csvContent.toString().getBytes(StandardCharsets.UTF_8); + + byte[] iv = randomBytes(16); + byte[] sm4Key = randomBytes(16); + + + Cipher cipher = Cipher.getInstance("SM4/CBC/PKCS7Padding", "BC"); // 假设使用 BouncyCastle + cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(sm4Key, "SM4"), new IvParameterSpec(iv)); + byte[] encryptedData = cipher.doFinal(orgDataBytes); + + UserKeyEncryptRequest encryptRequest = new UserKeyEncryptRequest(); + encryptRequest.setKeyIndex(1); + encryptRequest.setData(sm4Key); + BackupDataResult wrappedDekResult = pcieCryptoService.userKeyEncrypt(encryptRequest); + + Map envelope = new LinkedHashMap<>(); + envelope.put("version", 1); + envelope.put("payloadAlg", "SM4-CBC"); + envelope.put("keyWrapAlg", "SDFE_InternalEncrypt_ECC"); + envelope.put("ivBase64", Base64.getEncoder().encodeToString(iv)); + + envelope.put("wrappedDekBase64", Base64.getEncoder().encodeToString(normalize(wrappedDekResult))); + envelope.put("dekLength", sm4Key.length); + byte[] envelopeBytes = objectMapper.writeValueAsBytes(envelope); + + byte[] signatureBytes = sign(buildSignatureSource(envelopeBytes, encryptedData)); + + zos.putNextEntry(new ZipEntry("data.csv.enc")); + zos.write(encryptedData); + zos.closeEntry(); + + zos.putNextEntry(new ZipEntry("envelope.json")); + zos.write(envelopeBytes); + zos.closeEntry(); + + zos.putNextEntry(new ZipEntry("signature.sig")); + zos.write(signatureBytes); + zos.closeEntry(); + + log.info("审计日志备份完成,共备份 {} 条", logList.size()); + + BackupConfigEntity config = backupConfigRepository.find().orElseThrow(() -> new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "backupConfig not found")); + config.setLastBackupTime(LocalDateTime.now()); + backupConfigRepository.saveOrUpdate(config); + + isBackupSuccess = true; + } catch (IOException e) { + log.error("写入 ZIP 压缩包发生异常: {}", e.getMessage(), e); + } catch (Exception e) { + log.error("执行加密或备份任务发生异常: {}", e.getMessage(), e); + } + + try { + BackupRecordEntity record = new BackupRecordEntity(); + String recordId = UUID.randomUUID().toString().replace("-", ""); + record.setRecordId(recordId); + record.setFileName(zipFileName); + record.setFilePath(zipFilePath); + record.setBackupType("AUTO"); + if (isBackupSuccess) { + record.setBackupResult("SUCCESS"); + } else { + record.setBackupResult("FAIL"); + } + record.setCreateTime(LocalDateTime.now()); + + // 对生成zip文件进行哈希和签名 + byte[] zipFileBytes = Files.readAllBytes(zipFile.toPath()); + byte[] zipFileHash = sm3(zipFileBytes); + + byte[] zipFileSign = sign(zipFileHash); + + record.setFileHash(Base64.getEncoder().encodeToString(zipFileHash)); + record.setFileSign(Base64.getEncoder().encodeToString(zipFileSign)); + + backupRecordRepository.add(record); + + log.info("备份记录已成功存入 tms_log_backup 表。"); + } catch (Exception e) { + log.error("保存备份记录到数据库失败: {}", e.getMessage(), e); + } + + } + + //手动备份 + public void manualBackup() { + log.info("开始执行审计日志手动备份任务"); + + String timeStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")); + String zipFileName = "audit_backup_" + timeStr + ".zip"; + String zipFilePath = ZIPFILEPATH + zipFileName; + + File zipFile = new File(zipFilePath); + zipFile.getParentFile().mkdirs(); + + boolean isBackupSuccess = false; + + try (FileOutputStream fos = new FileOutputStream(zipFile); + ZipOutputStream zos = new ZipOutputStream(fos)) { + + StringBuilder csvContent = new StringBuilder(); + csvContent.append("日志ID,发生时间,操作角色,认证等级,业务模块,操作动作,操作结果,来源IP,操作摘要,失败原因,审计状态,审计结果,审计人,审计时间,防篡改摘要(SM3),签名值(SM2),创建时间\n"); + List logList = operationAuditLogRepository.findAll().orElseGet(ArrayList::new); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + for (OperationAuditLogEntity auditLog : logList) { + String summary = escapeCSV(auditLog.getSummary()); + String errorMsg = escapeCSV(auditLog.getErrorMessage()); + + String actionType = auditLog.getActionType() != null ? auditLog.getActionType().name() : ""; + String result = auditLog.getOperationResult() != null ? auditLog.getOperationResult().name() : ""; + + String occurredTime = auditLog.getOccurredAt() != null ? auditLog.getOccurredAt().format(formatter) : ""; + String auditedTime = auditLog.getAuditedAt() != null ? auditLog.getAuditedAt().format(formatter) : ""; + String createdTime = auditLog.getCreateTime() != null ? auditLog.getCreateTime().format(formatter) : ""; + String line = String.format("%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n", + auditLog.getLogId(), + occurredTime, + auditLog.getOperatorRoleCode() != null ? auditLog.getOperatorRoleCode() : "", + auditLog.getOperatorAuthLevel() != null ? auditLog.getOperatorAuthLevel() : "", + auditLog.getModuleCode(), + actionType, + result, + auditLog.getRemoteIp(), + summary, + errorMsg, + auditLog.getAuditStatus() != null ? auditLog.getAuditStatus() : "", + auditLog.getAuditResult() != null ? auditLog.getAuditResult() : "", + auditLog.getAuditedBy() != null ? auditLog.getAuditedBy() : "", + auditedTime, + auditLog.getPayloadHash(), + auditLog.getSignValue(), + createdTime + ); + csvContent.append(line); + } + + // 加密 + byte[] orgDataBytes = csvContent.toString().getBytes(StandardCharsets.UTF_8); + byte[] iv = randomBytes(16); + byte[] sm4Key = randomBytes(16); + + + Cipher cipher = Cipher.getInstance("SM4/CBC/PKCS7Padding", "BC"); // 假设使用 BouncyCastle + cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(sm4Key, "SM4"), new IvParameterSpec(iv)); + byte[] encryptedData = cipher.doFinal(orgDataBytes); + + UserKeyEncryptRequest encryptRequest = new UserKeyEncryptRequest(); + encryptRequest.setKeyIndex(1); + encryptRequest.setData(sm4Key); + BackupDataResult wrappedDekResult = pcieCryptoService.userKeyEncrypt(encryptRequest); + + Map envelope = new LinkedHashMap<>(); + envelope.put("version", 1); + envelope.put("payloadAlg", "SM4-CBC"); + envelope.put("keyWrapAlg", "SDFE_InternalEncrypt_ECC"); + envelope.put("ivBase64", Base64.getEncoder().encodeToString(iv)); + + envelope.put("wrappedDekBase64", Base64.getEncoder().encodeToString(normalize(wrappedDekResult))); + envelope.put("dekLength", sm4Key.length); + byte[] envelopeBytes = objectMapper.writeValueAsBytes(envelope); + + byte[] signatureBytes = sign(buildSignatureSource(envelopeBytes, encryptedData)); + + zos.putNextEntry(new ZipEntry("data.csv.enc")); + zos.write(encryptedData); + zos.closeEntry(); + + zos.putNextEntry(new ZipEntry("envelope.json")); + zos.write(envelopeBytes); + zos.closeEntry(); + + zos.putNextEntry(new ZipEntry("signature.sig")); + zos.write(signatureBytes); + zos.closeEntry(); + + log.info("审计日志备份完成,共备份 {} 条", logList.size()); + + BackupConfigEntity config = backupConfigRepository.find().orElseThrow(() -> new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "backupConfig not found")); + config.setLastBackupTime(LocalDateTime.now()); + backupConfigRepository.saveOrUpdate(config); + + isBackupSuccess = true; + + } catch (IOException e) { + log.error("写入 ZIP 压缩包发生异常: {}", e.getMessage(), e); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), e.getMessage()); + } catch (Exception e) { + log.error("执行加密或备份任务发生异常: {}", e.getMessage(), e); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "执行加密或备份任务发生异常"); + } + + + try { + BackupRecordEntity record = new BackupRecordEntity(); + String recordId = UUID.randomUUID().toString().replace("-", ""); + record.setRecordId(recordId); + record.setFileName(zipFileName); + record.setFilePath(zipFilePath); + record.setBackupType("MANUAL"); + if (isBackupSuccess) { + record.setBackupResult("SUCCESS"); + } else { + record.setBackupResult("FAIL"); + } + record.setCreateTime(LocalDateTime.now()); + + // 对生成zip文件进行哈希和签名 + byte[] zipFileBytes = Files.readAllBytes(zipFile.toPath()); + byte[] zipFileHash = sm3(zipFileBytes); + + byte[] zipFileSign = sign(zipFileHash); + + record.setFileHash(Base64.getEncoder().encodeToString(zipFileHash)); + record.setFileSign(Base64.getEncoder().encodeToString(zipFileSign)); + + backupRecordRepository.add(record); + + log.info("备份记录已成功存入 tms_log_backup 表。"); + } catch (Exception e) { + log.error("保存备份记录到数据库失败: {}", e.getMessage(), e); + } + + } + + static byte[] buildSignatureSource(byte[] envelopeBytes, byte[] cipherBytes) { + try { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + writePart(outputStream, envelopeBytes); + writePart(outputStream, cipherBytes); + return outputStream.toByteArray(); + } catch (IOException ex) { + throw new IllegalStateException("failed to build package signature source", ex); + } + } + + private static void writePart(ByteArrayOutputStream outputStream, byte[] data) throws IOException { + byte[] safeData = data == null ? new byte[0] : data; + outputStream.write(ByteBuffer.allocate(4).putInt(safeData.length).array()); + outputStream.write(safeData); + } + + private byte[] sign(byte[] dataToSign){ + if (dataToSign == null) { + throw new IllegalArgumentException("dataToSign is required"); + } + UserKeySignRequest request = new UserKeySignRequest(); + request.setKeyIndex(1); + request.setData(sm3(dataToSign)); + return normalize(pcieCryptoService.userKeySignWithSm3(request)); + } + + private String escapeCSV(String str) { + if (str == null) { + return ""; + } + + boolean containSpecialChar = str.contains(",") + || str.contains("\"") + || str.contains("\n") + || str.contains("\r"); + + if (containSpecialChar) { + str = str.replace("\"", "\"\""); + str = "\"" + str + "\""; + } + + return str; + } + + + private static byte[] normalize(BackupDataResult result) { + if (result == null || result.getData() == null) { + return new byte[0]; + } + int safeLength = result.getLength() > 0 && result.getLength() <= result.getData().length + ? result.getLength() + : result.getData().length; + byte[] normalized = new byte[safeLength]; + System.arraycopy(result.getData(), 0, normalized, 0, safeLength); + return normalized; + } + + private static byte[] sm3(byte[] data) { + SM3Digest digest = new SM3Digest(); + digest.update(data, 0, data.length); + byte[] out = new byte[digest.getDigestSize()]; + digest.doFinal(out, 0); + return out; + } + + + private static byte[] randomBytes(int size) { + byte[] bytes = new byte[size]; + new java.security.SecureRandom().nextBytes(bytes); + return bytes; + } + + private String trim(String str) { + return str == null ? "" : str.trim(); + } +} \ No newline at end of file diff --git a/src/main/java/com/cisd/tms/modules/log/service/AuditLogRestoreService.java b/src/main/java/com/cisd/tms/modules/log/service/AuditLogRestoreService.java new file mode 100644 index 0000000..08b0648 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/log/service/AuditLogRestoreService.java @@ -0,0 +1,327 @@ +package com.cisd.tms.modules.log.service; + +import com.cisd.tms.common.enums.ErrorCode; +import com.cisd.tms.common.exception.BizException; +import com.cisd.tms.integration.crypto.pcie.model.BackupDataResult; +import com.cisd.tms.integration.crypto.pcie.model.UserKeyDecryptRequest; +import com.cisd.tms.integration.crypto.pcie.model.UserKeyVerifyRequest; +import com.cisd.tms.integration.crypto.pcie.service.PcieCryptoService; +import com.cisd.tms.modules.log.dto.OperationAuditCommand; +import com.cisd.tms.modules.log.entity.OperationAuditLogEntity; +import com.cisd.tms.modules.log.enums.*; +import com.cisd.tms.modules.log.repository.OperationAuditLogRepository; +import com.cisd.tms.security.internal.InternalApiAuthInterceptor; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.bouncycastle.crypto.digests.SM3Digest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.FileSystemUtils; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.multipart.MultipartFile; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +@Slf4j +@Service +@RequiredArgsConstructor +public class AuditLogRestoreService { + + private final PcieCryptoService pcieCryptoService; + private final OperationAuditLogRepository auditLogRepository; + private final OperationAuditService operationAuditService; + private final ObjectMapper objectMapper; + + private static final String TEMP_RESTORE_DIR = "/home/tms/audit-logs/restore/"; + + @Transactional(rollbackFor = Exception.class) + public String auditLogRestore(MultipartFile file) { + String batchId = UUID.randomUUID().toString().replace("-", ""); + Path extractDir = Paths.get(TEMP_RESTORE_DIR, batchId); + + int totalCount = 0; + int skippedCount = 0; + int successCount = 0; + boolean restoreSuccess = false; + try { + log.info("开始审计日志恢复任务, 批次号: {}", batchId); + Files.createDirectories(extractDir); + + byte[] encryptedData = null; + byte[] envelopeBytes = null; + byte[] signatureBytes = null; + + try (ZipInputStream zis = new ZipInputStream(file.getInputStream())) { + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + zis.transferTo(baos); + if ("data.csv.enc".equals(entry.getName())) encryptedData = baos.toByteArray(); + if ("envelope.json".equals(entry.getName())) envelopeBytes = baos.toByteArray(); + if ("signature.sig".equals(entry.getName())) signatureBytes = baos.toByteArray(); + zis.closeEntry(); + } + } + + if (encryptedData == null || envelopeBytes == null || signatureBytes == null) { + throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "备份包文件不完整"); + } + + byte[] signatureSource = buildSignatureSource(envelopeBytes, encryptedData); +// EccInternalVerifyRequest verifyReq = new EccInternalVerifyRequest(); +// verifyReq.setKeyIndex(1); +// verifyReq.setData(signatureSource); +// verifyReq.setSignature(signatureBytes); + // //todo 验签 + UserKeyVerifyRequest userKeyVerifyRequest = new UserKeyVerifyRequest(); + userKeyVerifyRequest.setKeyIndex(1); + userKeyVerifyRequest.setData(sm3(signatureSource)); + userKeyVerifyRequest.setSignature(signatureBytes); +// try { +// pcieCryptoService.userKeyVerifyWithSm3(userKeyVerifyRequest); +// } catch (BizException ex) { +// throw ex; +// } catch (RuntimeException ex) { +// throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "signature verification failed"); +// } + + try { + pcieCryptoService.userKeyVerifyWithSm3(userKeyVerifyRequest); + log.info("文件验签成功"); + } catch (Exception e) { + log.error("文件验签失败", e); + + try { + FileSystemUtils.deleteRecursively(extractDir); + log.info("临时解压目录已清理: {}", extractDir); + } catch (IOException ioException) { + log.error("清理临时目录失败: {}", extractDir, ioException); + } + throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "文件验签失败, 数据可能被篡改"); + } + + Map envelope = objectMapper.readValue(envelopeBytes, new TypeReference>(){}); + byte[] wrappedDek = Base64.getDecoder().decode(String.valueOf(envelope.get("wrappedDekBase64"))); + + byte[] iv = Base64.getDecoder().decode(String.valueOf(envelope.get("ivBase64"))); + int dekLength = parseInt(envelope.get("dekLength"), 16); + UserKeyDecryptRequest decryptReq = new UserKeyDecryptRequest(); + decryptReq.setKeyIndex(1); + decryptReq.setCipherBlob(wrappedDek); +// decryptReq.setOutBufferSize(dekLength); + + byte[] sm4Key = normalize(pcieCryptoService.userKeyDecrypt(decryptReq)); + + + Cipher cipher = Cipher.getInstance("SM4/CBC/PKCS7Padding", "BC"); + cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(sm4Key, "SM4"), new IvParameterSpec(iv)); + byte[] decryptedCsvBytes = cipher.doFinal(encryptedData); + String csvContent = new String(decryptedCsvBytes, StandardCharsets.UTF_8); + + + List pendingInsertList = new ArrayList<>(); + + String[] lines = csvContent.split("\n"); + totalCount = lines.length - 1; + + List parsedLogs = parseCsvToEntities(lines); + List incomingSignValues = parsedLogs.stream() + .map(OperationAuditLogEntity::getSignValue) + .collect(Collectors.toList()); + + + List existSignValues = auditLogRepository.findExistSignValues(incomingSignValues).orElseGet(ArrayList::new); + Set existSignSet = new HashSet<>(existSignValues); + + for (OperationAuditLogEntity logEntity : parsedLogs) { + if (existSignSet.contains(logEntity.getSignValue())) { + skippedCount ++; // 若已存在:跳过 + } else { + pendingInsertList.add(logEntity); // 若不存在:加入待插入队列 + } + } + + // 执行批量插入 + if (!pendingInsertList.isEmpty()) { + successCount = auditLogRepository.addBatch(pendingInsertList); + } + +// RestoreResultResponse result = new RestoreResultResponse(); +// result.setTotalCount(totalCount); +// result.setSkippedCount(skippedCount); +// result.setSuccessCount(successCount); + restoreSuccess = true; + return String.format("恢复完成。共解析 %d 条,跳过已存在 %d 条,成功恢复 %d 条", + totalCount, skippedCount, successCount); + + } catch (Exception e) { + log.error("恢复审计日志失败", e); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), e.getMessage()); + } finally { + try { + FileSystemUtils.deleteRecursively(extractDir); + log.info("临时解压目录已清理: {}", extractDir); + } catch (IOException e) { + log.error("清理临时目录失败: {}", extractDir, e); + } + + recordRestoreAuditLog(file.getOriginalFilename(), totalCount, skippedCount, successCount, restoreSuccess); + } + } + + + private static int parseInt(Object value, int defaultValue) { + if (value == null) { + return defaultValue; + } + if (value instanceof Number number) { + return number.intValue(); + } + try { + return Integer.parseInt(String.valueOf(value)); + } catch (NumberFormatException ex) { + return defaultValue; + } + } + + private static byte[] normalize(BackupDataResult result) { + if (result == null || result.getData() == null) { + return new byte[0]; + } + int safeLength = result.getLength() > 0 && result.getLength() <= result.getData().length + ? result.getLength() + : result.getData().length; + byte[] normalized = new byte[safeLength]; + System.arraycopy(result.getData(), 0, normalized, 0, safeLength); + return normalized; + } + + static byte[] buildSignatureSource(byte[] envelopeBytes, byte[] cipherBytes) { + try { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + writePart(outputStream, envelopeBytes); + writePart(outputStream, cipherBytes); + return outputStream.toByteArray(); + } catch (IOException ex) { + throw new IllegalStateException("failed to build package signature source", ex); + } + } + + private static void writePart(ByteArrayOutputStream outputStream, byte[] data) throws IOException { + byte[] safeData = data == null ? new byte[0] : data; + outputStream.write(ByteBuffer.allocate(4).putInt(safeData.length).array()); + outputStream.write(safeData); + } + + //todo后续改转义 + private List parseCsvToEntities(String[] lines) { + List list = new ArrayList<>(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + for (int i = 1; i < lines.length; i++) { + if (lines[i].trim().isEmpty()) continue; + String[] cols = lines[i].split(",", -1); + OperationAuditLogEntity entity = new OperationAuditLogEntity(); + + entity.setLogId(cols[0]); + + if (!cols[1].isEmpty()) { + entity.setOccurredAt(LocalDateTime.parse(cols[1], formatter)); + } + + if (!cols[2].isEmpty()) entity.setOperatorRoleCode(OperatorRoleCode.valueOf(cols[2])); + + if (!cols[3].isEmpty()) entity.setOperatorAuthLevel(AuthLevel.valueOf(cols[3])); + + if (!cols[4].isEmpty()) entity.setModuleCode(ModuleCode.valueOf(cols[4])); + + if (!cols[5].isEmpty()) entity.setActionType(ActionType.valueOf(cols[5])); + + if (!cols[6].isEmpty()) entity.setOperationResult(OperationResult.valueOf(cols[6])); + + entity.setRemoteIp(cols[7]); + + entity.setSummary(cols[8].replace("\"\"", "\"").replaceAll("^\"|\"$", "")); + + entity.setErrorMessage(cols[9].replace("\"\"", "\"").replaceAll("^\"|\"$", "")); + + if (!cols[10].isEmpty()) entity.setAuditStatus(AuditStatus.valueOf(cols[10])); + + if (!cols[11].isEmpty()) entity.setAuditResult(AuditResult.valueOf(cols[11])); + + entity.setAuditedBy(cols[12]); + + if (!cols[13].isEmpty()) { + entity.setAuditedAt(LocalDateTime.parse(cols[13], formatter)); + } + + entity.setPayloadHash(cols[14]); + + entity.setSignValue(cols[15]); + + if (!cols[16].isEmpty()) { + entity.setCreateTime(LocalDateTime.parse(cols[16], formatter)); + } + + list.add(entity); + } + return list; + } + + + private void recordRestoreAuditLog(String fileName, int total, int skipped, int success, boolean restoreSuccess) { + + OperationAuditCommand command = new OperationAuditCommand(); + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + //todo 是否考虑为null的情况 + if (attributes != null) { + HttpServletRequest request = attributes.getRequest(); + + Object roleObj = request.getAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE); + command.setOperatorRoleCode(OperatorRoleCode.valueOf(roleObj.toString())); + + Object levelObj = request.getAttribute(InternalApiAuthInterceptor.ATTR_AUTH_LEVEL); + command.setOperatorAuthLevel(AuthLevel.valueOf(levelObj.toString())); + + command.setRemoteIp(request.getRemoteAddr()); + } + + command.setModuleCode(ModuleCode.LOG); + command.setActionType(ActionType.EXECUTE); + command.setSummary("执行审计日志恢复"); + + if (restoreSuccess){ + command.setOperationResult(OperationResult.SUCCESS); + } else{ + command.setOperationResult(OperationResult.FAILED); + } + + operationAuditService.record(command, false); + } + + private static byte[] sm3(byte[] data) { + SM3Digest digest = new SM3Digest(); + digest.update(data, 0, data.length); + byte[] out = new byte[digest.getDigestSize()]; + digest.doFinal(out, 0); + return out; + } +} \ No newline at end of file diff --git a/src/main/java/com/cisd/tms/modules/log/service/BackupConfigService.java b/src/main/java/com/cisd/tms/modules/log/service/BackupConfigService.java index 18ce7d9..5f1fbef 100644 --- a/src/main/java/com/cisd/tms/modules/log/service/BackupConfigService.java +++ b/src/main/java/com/cisd/tms/modules/log/service/BackupConfigService.java @@ -14,6 +14,7 @@ import org.springframework.stereotype.Service; public class BackupConfigService { private final BackupConfigRepository backupConfigRepository; + private final AuditBackupService auditBackupService; public BackupConfigResponse getConfig(){ BackupConfigEntity entity = backupConfigRepository.find().orElse(null); @@ -22,6 +23,7 @@ public class BackupConfigService { response.setId(entity.getId()); response.setEnable(entity.getEnable()); response.setCronExp(entity.getCronExp()); + response.setRetentionCount(entity.getRetentionCount()); response.setLastBackupTime(entity.getLastBackupTime()); } return response; @@ -32,17 +34,16 @@ public class BackupConfigService { if (!CronExpression.isValidExpression(cronExp)) { throw new IllegalArgumentException("Cron表达式格式不正确,请重新输入"); } - BackupConfigEntity entity = new BackupConfigEntity(); - BackupConfigResponse resp = getConfig(); - if (resp != null){ - resp.setId(entity.getId()); - resp.setLastBackupTime(entity.getLastBackupTime()); - } + BackupConfigEntity entity = backupConfigRepository.find().orElseGet(BackupConfigEntity::new); entity.setEnable(req.getEnable()); entity.setCronExp(req.getCronExp()); entity.setRetentionCount(req.getRetentionCount()); + backupConfigRepository.saveOrUpdate(entity); + + //更新后重启备份服务 + auditBackupService.restartBackupTask(entity); } private String trim(String str) { diff --git a/src/main/java/com/cisd/tms/modules/log/service/BackupRecordService.java b/src/main/java/com/cisd/tms/modules/log/service/BackupRecordService.java new file mode 100644 index 0000000..edcce92 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/log/service/BackupRecordService.java @@ -0,0 +1,30 @@ +package com.cisd.tms.modules.log.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.cisd.tms.modules.log.dto.BackupRecordPageRequest; +import com.cisd.tms.modules.log.dto.BackupRecordResponse; +import com.cisd.tms.modules.log.entity.BackupRecordEntity; +import com.cisd.tms.modules.log.repository.BackupRecordRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; +import org.springframework.stereotype.Service; + +@Repository +@Service +@RequiredArgsConstructor +public class BackupRecordService { + + private final BackupRecordRepository backupRecordRepository; + + public IPage queryPage(BackupRecordPageRequest req) { + IPage entityPage = backupRecordRepository.findPage(req); + return entityPage.convert(entity -> { + BackupRecordResponse resp = new BackupRecordResponse(); + resp.setRecordId(entity.getRecordId()); + resp.setBackupTime(entity.getCreateTime()); + resp.setBackupType(entity.getBackupType()); + resp.setBackupResult(entity.getBackupResult()); + return resp; + }); + } +} diff --git a/src/main/java/com/cisd/tms/modules/log/service/OperationAuditService.java b/src/main/java/com/cisd/tms/modules/log/service/OperationAuditService.java index 5b996e7..4942113 100644 --- a/src/main/java/com/cisd/tms/modules/log/service/OperationAuditService.java +++ b/src/main/java/com/cisd/tms/modules/log/service/OperationAuditService.java @@ -137,9 +137,10 @@ public class OperationAuditService { resp.setAuditComment(entity.getAuditComment()); resp.setAuditedBy(entity.getAuditedBy()); resp.setRemoteIp(entity.getRemoteIp()); - resp.setErrorMessage(entity.getErrorMessage()); resp.setSignValue(entity.getSignValue()); resp.setOccurredAt(entity.getOccurredAt()); + resp.setAuditedAt(entity.getAuditedAt()); + resp.setSummary(entity.getSummary()); return resp; }); diff --git a/src/main/resources/mapper/log/OperationAuditLogMapper.xml b/src/main/resources/mapper/log/OperationAuditLogMapper.xml new file mode 100644 index 0000000..3d31728 --- /dev/null +++ b/src/main/resources/mapper/log/OperationAuditLogMapper.xml @@ -0,0 +1,55 @@ + + + + + + INSERT INTO tms_operation_audit_log ( + id, + log_id, + operator_role_code, + operator_auth_level, + module_code, + action_type, + remote_ip, + operation_result, + summary, + error_message, + audit_status, + audit_result, + audit_comment, + audited_by, + audited_at, + payload_hash, + sign_value, + occurred_at, + create_time + ) + VALUES + + ( + #{item.id}, + #{item.logId}, + #{item.operatorRoleCode}, + #{item.operatorAuthLevel}, + #{item.moduleCode}, + #{item.actionType}, + #{item.remoteIp}, + #{item.operationResult}, + #{item.summary}, + #{item.errorMessage}, + #{item.auditStatus}, + #{item.auditResult}, + #{item.auditComment}, + #{item.auditedBy}, + #{item.auditedAt}, + #{item.payloadHash}, + #{item.signValue}, + #{item.occurredAt}, + #{item.createTime} + ) + + + + \ No newline at end of file diff --git a/src/test/java/com/cisd/tms/modules/log/service/AuditBackupServiceTest.java b/src/test/java/com/cisd/tms/modules/log/service/AuditBackupServiceTest.java index 3ce7893..2d1f163 100644 --- a/src/test/java/com/cisd/tms/modules/log/service/AuditBackupServiceTest.java +++ b/src/test/java/com/cisd/tms/modules/log/service/AuditBackupServiceTest.java @@ -5,12 +5,19 @@ import org.junit.jupiter.api.Test; import java.io.File; public class AuditBackupServiceTest { + + + private static final String TEMP_RESTORE_DIR = "./tmp/audit_restore/"; + @Test public void FileTest(){ - String filePath = "./data/backup/audit_log_" + ".csv"; + String filePath = "/data/backup/audit_log_" + ".csv"; File file = new File(filePath); System.out.print(file.getParentFile()); + +// Path extractDir = Paths.get(TEMP_RESTORE_DIR, String.valueOf(1)); +// System.out.print(extractDir); } }