feat(add-ledger): 新增线路类型和设备单位管理功能
- 添加线路类型常量定义(主网0,配网1)及验证逻辑 - 新增设备单位查询和保存接口及实现 - 新增监测点限值查询接口 - 扩展AddLedgerDetailVO和AddLedgerLinePO实体类以支持线路类型字段 - 在测点保存时自动计算并保存限值信息 - 添加设备单位默认配置初始化逻辑 - 新增COverlimitUtil工具类用于限值计算 - 完善相关单元测试用例
This commit is contained in:
@@ -2,13 +2,88 @@
|
||||
|
||||
## 模块定位
|
||||
|
||||
`dbms` 是 `system-ops` 下的数据库监控模块,当前先提供数据库监控菜单对应的后端基础入口。
|
||||
`dbms` 是 `system-ops` 下的数据库运维模块,当前面向 Oracle 数据库提供连接配置、连接测试、表列表查询、备份、恢复、任务状态查询和删除接口。
|
||||
|
||||
## 当前接口
|
||||
|
||||
- `GET /database/overview`
|
||||
- 查询数据库监控基础信息。
|
||||
- 查询数据库运维概览信息。
|
||||
- `POST /database/connections/list`
|
||||
- 查询数据库连接配置。
|
||||
- `POST /database/connections/add`
|
||||
- 新增 Oracle 数据库连接配置。
|
||||
- `POST /database/connections/update`
|
||||
- 修改 Oracle 数据库连接配置。
|
||||
- `POST /database/connections/delete`
|
||||
- 删除数据库连接配置。
|
||||
- `POST /database/connections/test`
|
||||
- 测试 Oracle 数据库连接。
|
||||
- `POST /database/connections/tables`
|
||||
- 查询 Oracle 表列表。
|
||||
- `POST /database/backups/create`
|
||||
- 创建备份任务,默认使用 `DATA_PUMP`,可选 `JDBC_EXPORT`。
|
||||
- `POST /database/backups/tasks/list`
|
||||
- 查询备份任务列表。
|
||||
- `GET /database/backups/tasks/status`
|
||||
- 查询任务状态。
|
||||
- `POST /database/backups/files/list`
|
||||
- 查询备份文件记录。
|
||||
- `POST /database/restores/create`
|
||||
- 创建恢复任务。
|
||||
- `GET /database/restores/tasks/status`
|
||||
- 查询恢复任务状态。
|
||||
- `POST /database/delete/backup-file`
|
||||
- 删除备份文件,要求 `confirmText=确认删除`。
|
||||
- `POST /database/delete/task`
|
||||
- 删除任务记录,要求 `confirmText=确认删除`。
|
||||
|
||||
## 数据脚本
|
||||
|
||||
- `src/main/resources/sql/system-ops/system-ops-init.sql`
|
||||
- 系统运维菜单初始化脚本。
|
||||
- `src/main/resources/sql/system-ops/dbms-database-ops-init.sql`
|
||||
- 数据库运维连接、任务、备份文件和恢复记录表结构。
|
||||
|
||||
## 配置项
|
||||
|
||||
建议通过环境配置覆盖:
|
||||
|
||||
```yaml
|
||||
dbms:
|
||||
backup:
|
||||
storage-path: D:/dbms-backup
|
||||
default-max-file-size-mb: 512
|
||||
tools:
|
||||
expdp-path:
|
||||
impdp-path:
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `backup.storage-path`
|
||||
- `JDBC_EXPORT` 生成的 CSV 和元数据 JSON 的受管根目录。
|
||||
- `tools.expdp-path`、`tools.impdp-path`
|
||||
- Oracle Data Pump 工具路径;为空时尝试走系统 `PATH`。
|
||||
|
||||
## 当前行为
|
||||
|
||||
- 一期仅支持 `ORACLE`。
|
||||
- 连接密码支持两种运行方式:
|
||||
- 前端每次传 `temporaryPassword`。
|
||||
- 连接已保存密文,且公共 `Sm4Utils` 提供 `decryptData_ECB` 时由后端自动解密复用。
|
||||
- 新增连接前的测试接口允许只传 `temporaryPassword`,不强制把密码写进 `connection.password`。
|
||||
- 备份任务异步执行,只有实际文件生成成功后才会写入 `dbms_backup_file` 记录。
|
||||
- `JDBC_EXPORT` 当前会生成两类文件:
|
||||
- 主数据文件:`*.csv`
|
||||
- 元数据文件:`*_metadata_yyyyMMdd_<taskNo>.json`
|
||||
- `JDBC_EXPORT` 恢复依赖元数据文件,不再允许缺少元数据直接发起恢复。
|
||||
- 删除备份文件时,会校验目标路径必须位于受管备份目录下,避免误删非备份文件。
|
||||
|
||||
## 当前限制
|
||||
|
||||
- 当前只完成后端基础入口,不包含真实数据库连接状态、容量或性能指标探测逻辑。
|
||||
- `DATA_PUMP` 仍依赖部署机器可执行 `expdp`、`impdp`,并且 Oracle 侧已准备好 `directory` 对象和权限。
|
||||
- 当前代码要求 `DATA_PUMP` 连接配置里补齐可管理的 `directoryPath`,否则虽然 Oracle 端可能已导出成功,后端无法安全管理文件记录与删除。
|
||||
- `JDBC_EXPORT` 恢复当前仅覆盖表数据,不承诺恢复索引、约束、触发器、序列、存储过程、权限等 Oracle 对象。
|
||||
- `TIME_RANGE` 模式当前只在 `JDBC_EXPORT` 场景真正参与查询过滤;`DATA_PUMP` 尚未接入 Oracle `QUERY` 参数。
|
||||
- `SIZE_SPLIT` 参数目前已做入参校验,但尚未实现真正的导出分片。
|
||||
- 本轮仅完成代码路径和文档收口,未执行 `mvn` 编译、测试或真实库联调。
|
||||
|
||||
@@ -26,5 +26,14 @@
|
||||
<artifactId>spingboot2.3.12</artifactId>
|
||||
<version>2.3.12</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.njcn</groupId>
|
||||
<artifactId>mybatis-plus</artifactId>
|
||||
<version>0.0.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-tx</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
package com.njcn.gather.systemops.database.component;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.njcn.gather.systemops.database.config.DbmsProperties;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseConnection;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Oracle Data Pump 命令执行组件。
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class DataPumpCommandExecutor {
|
||||
|
||||
private final DbmsProperties dbmsProperties;
|
||||
|
||||
public CommandResult expdp(DatabaseConnection connection, String password, String directoryName,
|
||||
String dumpFileName, String logFileName, List<String> tableNames) {
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add(resolveTool(dbmsProperties.getTools().getExpdpPath(), "expdp"));
|
||||
fillCommonArgs(command, connection, password, directoryName, dumpFileName, logFileName);
|
||||
if (tableNames != null && !tableNames.isEmpty()) {
|
||||
command.add("tables=" + connection.getSchemaName() + "." + String.join("," + connection.getSchemaName() + ".", tableNames));
|
||||
}
|
||||
return execute(command);
|
||||
}
|
||||
|
||||
public CommandResult impdp(DatabaseConnection connection, String password, String directoryName,
|
||||
String dumpFileName, String logFileName, String tableExistsAction) {
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add(resolveTool(dbmsProperties.getTools().getImpdpPath(), "impdp"));
|
||||
fillCommonArgs(command, connection, password, directoryName, dumpFileName, logFileName);
|
||||
if (StrUtil.isNotBlank(tableExistsAction)) {
|
||||
command.add("table_exists_action=" + tableExistsAction);
|
||||
}
|
||||
return execute(command);
|
||||
}
|
||||
|
||||
private void fillCommonArgs(List<String> command, DatabaseConnection connection, String password,
|
||||
String directoryName, String dumpFileName, String logFileName) {
|
||||
command.add(connection.getUsername() + "/" + password + "@" + buildConnectIdentifier(connection));
|
||||
command.add("directory=" + directoryName);
|
||||
command.add("dumpfile=" + dumpFileName);
|
||||
command.add("logfile=" + logFileName);
|
||||
}
|
||||
|
||||
private String buildConnectIdentifier(DatabaseConnection connection) {
|
||||
if (StrUtil.isNotBlank(connection.getServiceName())) {
|
||||
return "//" + connection.getHost() + ":" + connection.getPort() + "/" + connection.getServiceName();
|
||||
}
|
||||
return connection.getHost() + ":" + connection.getPort() + ":" + connection.getSid();
|
||||
}
|
||||
|
||||
private String resolveTool(String configuredPath, String defaultName) {
|
||||
return StrUtil.blankToDefault(configuredPath, defaultName);
|
||||
}
|
||||
|
||||
private CommandResult execute(List<String> command) {
|
||||
CommandResult result = new CommandResult();
|
||||
result.setCommand(maskPassword(command));
|
||||
try {
|
||||
Process process = new ProcessBuilder(command).redirectErrorStream(true).start();
|
||||
StringBuilder output = new StringBuilder();
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), Charset.defaultCharset()))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
output.append(line).append(System.lineSeparator());
|
||||
}
|
||||
}
|
||||
int exitCode = process.waitFor();
|
||||
result.setExitCode(exitCode);
|
||||
result.setOutput(output.toString());
|
||||
result.setSuccess(exitCode == 0);
|
||||
} catch (Exception exception) {
|
||||
result.setExitCode(-1);
|
||||
result.setOutput(exception.getMessage());
|
||||
result.setSuccess(false);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String maskPassword(List<String> command) {
|
||||
if (command.size() < 2) {
|
||||
return String.join(" ", command);
|
||||
}
|
||||
List<String> masked = new ArrayList<>(command);
|
||||
String credential = masked.get(1);
|
||||
int slashIndex = credential.indexOf('/');
|
||||
int atIndex = credential.indexOf('@');
|
||||
if (slashIndex > 0 && atIndex > slashIndex) {
|
||||
masked.set(1, credential.substring(0, slashIndex + 1) + "******" + credential.substring(atIndex));
|
||||
}
|
||||
return String.join(" ", masked);
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class CommandResult {
|
||||
private Boolean success;
|
||||
private Integer exitCode;
|
||||
private String command;
|
||||
private String output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.njcn.gather.systemops.database.component;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.njcn.common.utils.sm.Sm4Utils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 数据库连接密码处理组件。
|
||||
*/
|
||||
@Component
|
||||
public class DatabasePasswordComponent {
|
||||
|
||||
public String encrypt(String plainText) {
|
||||
if (StrUtil.isBlank(plainText)) {
|
||||
return null;
|
||||
}
|
||||
return new Sm4Utils(Sm4Utils.globalSecretKey).encryptData_ECB(plainText);
|
||||
}
|
||||
|
||||
/**
|
||||
* 优先使用本次请求传入的临时密码;如果公共 SM4 工具存在解密能力,则复用已保存密文。
|
||||
*/
|
||||
public String resolveRuntimePassword(String passwordCipher, String temporaryPassword) {
|
||||
if (StrUtil.isNotBlank(temporaryPassword)) {
|
||||
return temporaryPassword;
|
||||
}
|
||||
if (StrUtil.isBlank(passwordCipher)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
Sm4Utils sm4Utils = new Sm4Utils(Sm4Utils.globalSecretKey);
|
||||
Method decryptMethod = Sm4Utils.class.getMethod("decryptData_ECB", String.class);
|
||||
Object plainText = decryptMethod.invoke(sm4Utils, passwordCipher);
|
||||
if (plainText instanceof String && StrUtil.isNotBlank((String) plainText)) {
|
||||
return (String) plainText;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
// 兼容公共工具不同版本,未找到解密方法时继续走统一失败提示。
|
||||
}
|
||||
throw new IllegalArgumentException("当前环境未确认密码解密方法,请传入临时密码执行本次操作");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.njcn.gather.systemops.database.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* 数据库运维后台任务线程池。
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class DbmsExecutorConfig {
|
||||
|
||||
@Bean(name = "dbmsTaskExecutorService", destroyMethod = "shutdown")
|
||||
public ExecutorService dbmsTaskExecutorService() {
|
||||
AtomicInteger threadIndex = new AtomicInteger(1);
|
||||
return new ThreadPoolExecutor(
|
||||
1,
|
||||
1,
|
||||
30,
|
||||
TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<Runnable>(8),
|
||||
runnable -> {
|
||||
Thread thread = new Thread(runnable);
|
||||
thread.setName("dbms-task-" + threadIndex.getAndIncrement());
|
||||
return thread;
|
||||
},
|
||||
(runnable, executor) -> log.warn("数据库运维任务线程池已满,拒绝新的任务")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.njcn.gather.systemops.database.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 数据库运维配置。
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "dbms")
|
||||
public class DbmsProperties {
|
||||
|
||||
private Backup backup = new Backup();
|
||||
private Tools tools = new Tools();
|
||||
|
||||
@Data
|
||||
public static class Backup {
|
||||
private String storagePath = "D:/dbms-backup";
|
||||
private Integer defaultMaxFileSizeMb = 512;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Tools {
|
||||
private String expdpPath;
|
||||
private String impdpPath;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.njcn.gather.systemops.database.constant;
|
||||
|
||||
/**
|
||||
* 数据库运维常量。
|
||||
*/
|
||||
public final class DatabaseOpsConst {
|
||||
|
||||
public static final String DB_TYPE_ORACLE = "ORACLE";
|
||||
public static final String CONNECT_TYPE_SERVICE_NAME = "SERVICE_NAME";
|
||||
public static final String CONNECT_TYPE_SID = "SID";
|
||||
public static final String CONFIRM_DELETE = "确认删除";
|
||||
public static final String CONFIRM_OVERWRITE = "确认覆盖";
|
||||
public static final int STATE_DELETED = 0;
|
||||
public static final int STATE_ENABLED = 1;
|
||||
public static final int SAVE_PASSWORD_YES = 1;
|
||||
public static final int SAVE_PASSWORD_NO = 0;
|
||||
|
||||
private DatabaseOpsConst() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.njcn.gather.systemops.database.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.njcn.common.pojo.annotation.OperateInfo;
|
||||
import com.njcn.common.pojo.constant.OperateType;
|
||||
import com.njcn.common.pojo.enums.common.LogEnum;
|
||||
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||
import com.njcn.common.pojo.response.HttpResult;
|
||||
import com.njcn.gather.systemops.database.pojo.param.DatabaseBackupParam;
|
||||
import com.njcn.gather.systemops.database.pojo.vo.DatabaseBackupFileVO;
|
||||
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTaskCreateVO;
|
||||
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTaskVO;
|
||||
import com.njcn.gather.systemops.database.service.DatabaseBackupFileService;
|
||||
import com.njcn.gather.systemops.database.service.DatabaseOperationTaskService;
|
||||
import com.njcn.web.controller.BaseController;
|
||||
import com.njcn.web.utils.HttpResultUtil;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 数据库备份接口。
|
||||
*/
|
||||
@Api(tags = "数据库备份")
|
||||
@RestController
|
||||
@RequestMapping("/database/backups")
|
||||
@RequiredArgsConstructor
|
||||
public class DatabaseBackupController extends BaseController {
|
||||
|
||||
private final DatabaseOperationTaskService databaseOperationTaskService;
|
||||
private final DatabaseBackupFileService databaseBackupFileService;
|
||||
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.ADD)
|
||||
@ApiOperation("创建备份任务")
|
||||
@PostMapping("/create")
|
||||
public HttpResult<DatabaseTaskCreateVO> create(@RequestBody @Validated DatabaseBackupParam.CreateParam param) {
|
||||
String methodDescribe = getMethodDescribe("create");
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, databaseOperationTaskService.createBackupTask(param), methodDescribe);
|
||||
}
|
||||
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||
@ApiOperation("查询备份任务")
|
||||
@PostMapping("/tasks/list")
|
||||
public HttpResult<Page<DatabaseTaskVO>> listTasks(@RequestBody @Validated DatabaseBackupParam.TaskQueryParam param) {
|
||||
String methodDescribe = getMethodDescribe("listTasks");
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, databaseOperationTaskService.listBackupTasks(param), methodDescribe);
|
||||
}
|
||||
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||
@ApiOperation("查询任务状态")
|
||||
@GetMapping("/tasks/status")
|
||||
public HttpResult<DatabaseTaskVO> status(@RequestParam("taskId") String taskId) {
|
||||
String methodDescribe = getMethodDescribe("status");
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, databaseOperationTaskService.getStatus(taskId), methodDescribe);
|
||||
}
|
||||
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||
@ApiOperation("查询备份文件")
|
||||
@PostMapping("/files/list")
|
||||
public HttpResult<Page<DatabaseBackupFileVO>> listFiles(@RequestBody @Validated DatabaseBackupParam.FileQueryParam param) {
|
||||
String methodDescribe = getMethodDescribe("listFiles");
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, databaseBackupFileService.listFiles(param), methodDescribe);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.njcn.gather.systemops.database.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.njcn.common.pojo.annotation.OperateInfo;
|
||||
import com.njcn.common.pojo.constant.OperateType;
|
||||
import com.njcn.common.pojo.enums.common.LogEnum;
|
||||
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||
import com.njcn.common.pojo.response.HttpResult;
|
||||
import com.njcn.gather.systemops.database.pojo.param.DatabaseConnectionParam;
|
||||
import com.njcn.gather.systemops.database.pojo.vo.DatabaseConnectionVO;
|
||||
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTableVO;
|
||||
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTestResultVO;
|
||||
import com.njcn.gather.systemops.database.service.DatabaseConnectionService;
|
||||
import com.njcn.web.controller.BaseController;
|
||||
import com.njcn.web.utils.HttpResultUtil;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 数据库连接配置接口。
|
||||
*/
|
||||
@Api(tags = "数据库连接配置")
|
||||
@RestController
|
||||
@RequestMapping("/database/connections")
|
||||
@RequiredArgsConstructor
|
||||
public class DatabaseConnectionController extends BaseController {
|
||||
|
||||
private final DatabaseConnectionService databaseConnectionService;
|
||||
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||
@ApiOperation("查询数据库连接配置")
|
||||
@PostMapping("/list")
|
||||
public HttpResult<Page<DatabaseConnectionVO>> list(@RequestBody @Validated DatabaseConnectionParam.QueryParam param) {
|
||||
String methodDescribe = getMethodDescribe("list");
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, databaseConnectionService.listConnections(param), methodDescribe);
|
||||
}
|
||||
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.ADD)
|
||||
@ApiOperation("新增数据库连接配置")
|
||||
@PostMapping("/add")
|
||||
public HttpResult<Boolean> add(@RequestBody @Validated DatabaseConnectionParam param) {
|
||||
String methodDescribe = getMethodDescribe("add");
|
||||
boolean result = databaseConnectionService.addConnection(param);
|
||||
return HttpResultUtil.assembleCommonResponseResult(result ? CommonResponseEnum.SUCCESS : CommonResponseEnum.FAIL, result, methodDescribe);
|
||||
}
|
||||
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.UPDATE)
|
||||
@ApiOperation("修改数据库连接配置")
|
||||
@PostMapping("/update")
|
||||
public HttpResult<Boolean> update(@RequestBody @Validated DatabaseConnectionParam.UpdateParam param) {
|
||||
String methodDescribe = getMethodDescribe("update");
|
||||
boolean result = databaseConnectionService.updateConnection(param);
|
||||
return HttpResultUtil.assembleCommonResponseResult(result ? CommonResponseEnum.SUCCESS : CommonResponseEnum.FAIL, result, methodDescribe);
|
||||
}
|
||||
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.DELETE)
|
||||
@ApiOperation("删除数据库连接配置")
|
||||
@PostMapping("/delete")
|
||||
public HttpResult<Boolean> delete(@RequestBody @Validated DatabaseConnectionParam.DeleteParam param) {
|
||||
String methodDescribe = getMethodDescribe("delete");
|
||||
boolean result = databaseConnectionService.deleteConnection(param);
|
||||
return HttpResultUtil.assembleCommonResponseResult(result ? CommonResponseEnum.SUCCESS : CommonResponseEnum.FAIL, result, methodDescribe);
|
||||
}
|
||||
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||
@ApiOperation("测试数据库连接")
|
||||
@PostMapping("/test")
|
||||
public HttpResult<DatabaseTestResultVO> test(@RequestBody @Validated DatabaseConnectionParam.TestParam param) {
|
||||
String methodDescribe = getMethodDescribe("test");
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, databaseConnectionService.testConnection(param), methodDescribe);
|
||||
}
|
||||
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||
@ApiOperation("查询 Oracle 表列表")
|
||||
@PostMapping("/tables")
|
||||
public HttpResult<List<DatabaseTableVO>> tables(@RequestBody @Validated DatabaseConnectionParam.TablesParam param) {
|
||||
String methodDescribe = getMethodDescribe("tables");
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, databaseConnectionService.listTables(param), methodDescribe);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.njcn.gather.systemops.database.controller;
|
||||
|
||||
import com.njcn.common.pojo.annotation.OperateInfo;
|
||||
import com.njcn.common.pojo.constant.OperateType;
|
||||
import com.njcn.common.pojo.enums.common.LogEnum;
|
||||
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||
import com.njcn.common.pojo.response.HttpResult;
|
||||
import com.njcn.gather.systemops.database.pojo.param.DatabaseDeleteParam;
|
||||
import com.njcn.gather.systemops.database.service.DatabaseBackupFileService;
|
||||
import com.njcn.gather.systemops.database.service.DatabaseOperationTaskService;
|
||||
import com.njcn.web.controller.BaseController;
|
||||
import com.njcn.web.utils.HttpResultUtil;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 数据库运维删除接口。
|
||||
*/
|
||||
@Api(tags = "数据库运维删除")
|
||||
@RestController
|
||||
@RequestMapping("/database/delete")
|
||||
@RequiredArgsConstructor
|
||||
public class DatabaseDeleteController extends BaseController {
|
||||
|
||||
private final DatabaseBackupFileService databaseBackupFileService;
|
||||
private final DatabaseOperationTaskService databaseOperationTaskService;
|
||||
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.DELETE)
|
||||
@ApiOperation("删除备份文件")
|
||||
@PostMapping("/backup-file")
|
||||
public HttpResult<Boolean> deleteBackupFile(@RequestBody @Validated DatabaseDeleteParam.BackupFileParam param) {
|
||||
String methodDescribe = getMethodDescribe("deleteBackupFile");
|
||||
boolean result = databaseBackupFileService.deleteBackupFile(param.getBackupFileId(), param.getConfirmText());
|
||||
return HttpResultUtil.assembleCommonResponseResult(result ? CommonResponseEnum.SUCCESS : CommonResponseEnum.FAIL, result, methodDescribe);
|
||||
}
|
||||
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.DELETE)
|
||||
@ApiOperation("删除任务记录")
|
||||
@PostMapping("/task")
|
||||
public HttpResult<Boolean> deleteTask(@RequestBody @Validated DatabaseDeleteParam.TaskParam param) {
|
||||
String methodDescribe = getMethodDescribe("deleteTask");
|
||||
boolean result = databaseOperationTaskService.deleteTask(param.getTaskId(), param.getConfirmText());
|
||||
return HttpResultUtil.assembleCommonResponseResult(result ? CommonResponseEnum.SUCCESS : CommonResponseEnum.FAIL, result, methodDescribe);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.njcn.gather.systemops.database.controller;
|
||||
|
||||
import com.njcn.common.pojo.annotation.OperateInfo;
|
||||
import com.njcn.common.pojo.constant.OperateType;
|
||||
import com.njcn.common.pojo.enums.common.LogEnum;
|
||||
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||
import com.njcn.common.pojo.response.HttpResult;
|
||||
import com.njcn.gather.systemops.database.pojo.param.DatabaseRestoreParam;
|
||||
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTaskCreateVO;
|
||||
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTaskVO;
|
||||
import com.njcn.gather.systemops.database.service.DatabaseOperationTaskService;
|
||||
import com.njcn.gather.systemops.database.service.DatabaseRestoreService;
|
||||
import com.njcn.web.controller.BaseController;
|
||||
import com.njcn.web.utils.HttpResultUtil;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 数据库恢复接口。
|
||||
*/
|
||||
@Api(tags = "数据库恢复")
|
||||
@RestController
|
||||
@RequestMapping("/database/restores")
|
||||
@RequiredArgsConstructor
|
||||
public class DatabaseRestoreController extends BaseController {
|
||||
|
||||
private final DatabaseRestoreService databaseRestoreService;
|
||||
private final DatabaseOperationTaskService databaseOperationTaskService;
|
||||
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.ADD)
|
||||
@ApiOperation("创建恢复任务")
|
||||
@PostMapping("/create")
|
||||
public HttpResult<DatabaseTaskCreateVO> create(@RequestBody @Validated DatabaseRestoreParam.CreateParam param) {
|
||||
String methodDescribe = getMethodDescribe("create");
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, databaseRestoreService.createRestoreTask(param), methodDescribe);
|
||||
}
|
||||
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||
@ApiOperation("查询恢复任务状态")
|
||||
@GetMapping("/tasks/status")
|
||||
public HttpResult<DatabaseTaskVO> status(@RequestParam("taskId") String taskId) {
|
||||
String methodDescribe = getMethodDescribe("status");
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, databaseOperationTaskService.getStatus(taskId), methodDescribe);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.njcn.gather.systemops.database.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseBackupFile;
|
||||
|
||||
/**
|
||||
* 数据库备份文件 Mapper。
|
||||
*/
|
||||
public interface DatabaseBackupFileMapper extends BaseMapper<DatabaseBackupFile> {
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.njcn.gather.systemops.database.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseConnection;
|
||||
|
||||
/**
|
||||
* 数据库连接配置 Mapper。
|
||||
*/
|
||||
public interface DatabaseConnectionMapper extends BaseMapper<DatabaseConnection> {
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.njcn.gather.systemops.database.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseOperationTask;
|
||||
|
||||
/**
|
||||
* 数据库运维任务 Mapper。
|
||||
*/
|
||||
public interface DatabaseOperationTaskMapper extends BaseMapper<DatabaseOperationTask> {
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.njcn.gather.systemops.database.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseRestoreRecord;
|
||||
|
||||
/**
|
||||
* 数据库恢复记录 Mapper。
|
||||
*/
|
||||
public interface DatabaseRestoreRecordMapper extends BaseMapper<DatabaseRestoreRecord> {
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.njcn.gather.systemops.database.pojo.enums;
|
||||
|
||||
/**
|
||||
* 备份模式。
|
||||
*/
|
||||
public enum BackupModeEnum {
|
||||
FULL_TABLE,
|
||||
TIME_RANGE,
|
||||
SIZE_SPLIT
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.njcn.gather.systemops.database.pojo.enums;
|
||||
|
||||
/**
|
||||
* 备份策略。
|
||||
*/
|
||||
public enum BackupStrategyEnum {
|
||||
DATA_PUMP,
|
||||
JDBC_EXPORT
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.njcn.gather.systemops.database.pojo.enums;
|
||||
|
||||
/**
|
||||
* 备份文件格式。
|
||||
*/
|
||||
public enum FileFormatEnum {
|
||||
DMP,
|
||||
SQL,
|
||||
CSV
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.njcn.gather.systemops.database.pojo.enums;
|
||||
|
||||
/**
|
||||
* 运维任务状态。
|
||||
*/
|
||||
public enum TaskStatusEnum {
|
||||
WAITING,
|
||||
RUNNING,
|
||||
SUCCESS,
|
||||
FAIL,
|
||||
CANCELLED
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.njcn.gather.systemops.database.pojo.param;
|
||||
|
||||
import com.njcn.web.pojo.param.BaseParam;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 数据库备份参数。
|
||||
*/
|
||||
public class DatabaseBackupParam {
|
||||
|
||||
@Data
|
||||
@ApiModel("创建备份任务参数")
|
||||
public static class CreateParam {
|
||||
@ApiModelProperty("连接 ID")
|
||||
@NotBlank(message = "连接 ID 不能为空")
|
||||
private String connectionId;
|
||||
@ApiModelProperty("备份策略:DATA_PUMP、JDBC_EXPORT,默认 DATA_PUMP")
|
||||
private String backupStrategy;
|
||||
@ApiModelProperty("Schema")
|
||||
private String schemaName;
|
||||
@ApiModelProperty("表名列表")
|
||||
private List<String> targetNames;
|
||||
@ApiModelProperty("备份模式:FULL_TABLE、TIME_RANGE、SIZE_SPLIT")
|
||||
private String backupMode;
|
||||
@ApiModelProperty("时间字段")
|
||||
private String timeColumn;
|
||||
@ApiModelProperty("开始时间")
|
||||
private LocalDateTime startTime;
|
||||
@ApiModelProperty("结束时间")
|
||||
private LocalDateTime endTime;
|
||||
@ApiModelProperty("最大文件大小 MB")
|
||||
private Integer maxFileSizeMb;
|
||||
@ApiModelProperty("Oracle Directory 名称")
|
||||
private String directoryName;
|
||||
@ApiModelProperty("临时密码,不保存密码时传入")
|
||||
private String temporaryPassword;
|
||||
}
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ApiModel("备份任务查询参数")
|
||||
public static class TaskQueryParam extends BaseParam {
|
||||
@ApiModelProperty("连接 ID")
|
||||
private String connectionId;
|
||||
@ApiModelProperty("任务状态")
|
||||
private String taskStatus;
|
||||
}
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ApiModel("备份文件查询参数")
|
||||
public static class FileQueryParam extends BaseParam {
|
||||
@ApiModelProperty("连接 ID")
|
||||
private String connectionId;
|
||||
@ApiModelProperty("任务 ID")
|
||||
private String taskId;
|
||||
@ApiModelProperty("备份策略")
|
||||
private String backupStrategy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package com.njcn.gather.systemops.database.pojo.param;
|
||||
|
||||
import com.njcn.web.pojo.param.BaseParam;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* 数据库连接配置参数。
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("数据库连接配置参数")
|
||||
public class DatabaseConnectionParam {
|
||||
|
||||
@ApiModelProperty("连接名称")
|
||||
@NotBlank(message = "连接名称不能为空")
|
||||
private String connectionName;
|
||||
|
||||
@ApiModelProperty("数据库类型,一期固定 ORACLE")
|
||||
private String dbType;
|
||||
|
||||
@ApiModelProperty("数据库主机地址")
|
||||
@NotBlank(message = "数据库主机地址不能为空")
|
||||
private String host;
|
||||
|
||||
@ApiModelProperty("数据库端口")
|
||||
@NotNull(message = "数据库端口不能为空")
|
||||
private Integer port;
|
||||
|
||||
@ApiModelProperty("连接类型:SERVICE_NAME、SID")
|
||||
private String connectType;
|
||||
|
||||
@ApiModelProperty("服务名")
|
||||
private String serviceName;
|
||||
|
||||
@ApiModelProperty("SID")
|
||||
private String sid;
|
||||
|
||||
@ApiModelProperty("Schema")
|
||||
private String schemaName;
|
||||
|
||||
@ApiModelProperty("用户名")
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
private String username;
|
||||
|
||||
@ApiModelProperty("密码")
|
||||
private String password;
|
||||
|
||||
@ApiModelProperty("是否保存密码:0-否,1-是")
|
||||
private Integer savePassword;
|
||||
|
||||
@ApiModelProperty("Oracle Directory 名称")
|
||||
private String directoryName;
|
||||
|
||||
@ApiModelProperty("Oracle Directory 物理路径")
|
||||
private String directoryPath;
|
||||
|
||||
@ApiModelProperty("扩展配置 JSON")
|
||||
private String extraConfigJson;
|
||||
|
||||
@ApiModelProperty("备注")
|
||||
private String remark;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ApiModel("数据库连接更新参数")
|
||||
public static class UpdateParam extends DatabaseConnectionParam {
|
||||
@ApiModelProperty("连接 ID")
|
||||
@NotBlank(message = "连接 ID 不能为空")
|
||||
private String id;
|
||||
}
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ApiModel("数据库连接查询参数")
|
||||
public static class QueryParam extends BaseParam {
|
||||
@ApiModelProperty("连接名称")
|
||||
private String connectionName;
|
||||
@ApiModelProperty("数据库类型")
|
||||
private String dbType;
|
||||
@ApiModelProperty("Schema")
|
||||
private String schemaName;
|
||||
}
|
||||
|
||||
@Data
|
||||
@ApiModel("数据库连接删除参数")
|
||||
public static class DeleteParam {
|
||||
@ApiModelProperty("连接 ID")
|
||||
@NotBlank(message = "连接 ID 不能为空")
|
||||
private String id;
|
||||
}
|
||||
|
||||
@Data
|
||||
@ApiModel("数据库连接测试参数")
|
||||
public static class TestParam {
|
||||
@ApiModelProperty("连接 ID,已有连接测试时传入")
|
||||
private String connectionId;
|
||||
@ApiModelProperty("临时连接参数,新增前测试时传入")
|
||||
private DatabaseConnectionParam connection;
|
||||
@ApiModelProperty("临时密码,测试时允许只传该字段而不写入 connection.password")
|
||||
private String temporaryPassword;
|
||||
}
|
||||
|
||||
@Data
|
||||
@ApiModel("数据库表查询参数")
|
||||
public static class TablesParam {
|
||||
@ApiModelProperty("连接 ID")
|
||||
@NotBlank(message = "连接 ID 不能为空")
|
||||
private String connectionId;
|
||||
@ApiModelProperty("临时密码,不保存密码时传入")
|
||||
private String temporaryPassword;
|
||||
@ApiModelProperty("Schema,不传则使用连接默认 Schema")
|
||||
private String schemaName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.njcn.gather.systemops.database.pojo.param;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* 数据库运维删除参数。
|
||||
*/
|
||||
public class DatabaseDeleteParam {
|
||||
|
||||
@Data
|
||||
@ApiModel("删除备份文件参数")
|
||||
public static class BackupFileParam {
|
||||
@ApiModelProperty("备份文件 ID")
|
||||
@NotBlank(message = "备份文件 ID 不能为空")
|
||||
private String backupFileId;
|
||||
@ApiModelProperty("确认文案")
|
||||
private String confirmText;
|
||||
}
|
||||
|
||||
@Data
|
||||
@ApiModel("删除任务参数")
|
||||
public static class TaskParam {
|
||||
@ApiModelProperty("任务 ID")
|
||||
@NotBlank(message = "任务 ID 不能为空")
|
||||
private String taskId;
|
||||
@ApiModelProperty("确认文案")
|
||||
private String confirmText;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.njcn.gather.systemops.database.pojo.param;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* 数据库恢复参数。
|
||||
*/
|
||||
public class DatabaseRestoreParam {
|
||||
|
||||
@Data
|
||||
@ApiModel("创建恢复任务参数")
|
||||
public static class CreateParam {
|
||||
@ApiModelProperty("目标连接 ID")
|
||||
@NotBlank(message = "连接 ID 不能为空")
|
||||
private String connectionId;
|
||||
@ApiModelProperty("备份文件 ID")
|
||||
@NotBlank(message = "备份文件 ID 不能为空")
|
||||
private String backupFileId;
|
||||
@ApiModelProperty("恢复模式:SKIP、APPEND、TRUNCATE、REPLACE")
|
||||
private String restoreMode;
|
||||
@ApiModelProperty("目标 Schema")
|
||||
private String targetSchemaName;
|
||||
@ApiModelProperty("临时密码,不保存密码时传入")
|
||||
private String temporaryPassword;
|
||||
@ApiModelProperty("覆盖确认文案")
|
||||
private String overwriteConfirmText;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.njcn.gather.systemops.database.pojo.po;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 数据库备份文件记录。
|
||||
*/
|
||||
@Data
|
||||
@TableName("dbms_backup_file")
|
||||
public class DatabaseBackupFile implements Serializable {
|
||||
private static final long serialVersionUID = 3119981982091873277L;
|
||||
|
||||
@TableId("id")
|
||||
private String id;
|
||||
@TableField("task_id")
|
||||
private String taskId;
|
||||
@TableField("connection_id")
|
||||
private String connectionId;
|
||||
@TableField("db_type")
|
||||
private String dbType;
|
||||
@TableField("backup_strategy")
|
||||
private String backupStrategy;
|
||||
@TableField("file_format")
|
||||
private String fileFormat;
|
||||
@TableField("schema_name")
|
||||
private String schemaName;
|
||||
@TableField("target_names_json")
|
||||
private String targetNamesJson;
|
||||
@TableField("backup_mode")
|
||||
private String backupMode;
|
||||
@TableField("backup_start_time")
|
||||
private LocalDateTime backupStartTime;
|
||||
@TableField("backup_end_time")
|
||||
private LocalDateTime backupEndTime;
|
||||
@TableField("time_column")
|
||||
private String timeColumn;
|
||||
@TableField("directory_name")
|
||||
private String directoryName;
|
||||
@TableField("dump_file_name")
|
||||
private String dumpFileName;
|
||||
@TableField("log_file_name")
|
||||
private String logFileName;
|
||||
@TableField("file_name")
|
||||
private String fileName;
|
||||
@TableField("file_path")
|
||||
private String filePath;
|
||||
@TableField("log_file_path")
|
||||
private String logFilePath;
|
||||
@TableField("metadata_file_path")
|
||||
private String metadataFilePath;
|
||||
@TableField("file_size")
|
||||
private Long fileSize;
|
||||
@TableField("checksum")
|
||||
private String checksum;
|
||||
@TableField("state")
|
||||
private Integer state;
|
||||
@TableField("create_by")
|
||||
private String createBy;
|
||||
@TableField("create_time")
|
||||
private LocalDateTime createTime;
|
||||
@TableField("update_by")
|
||||
private String updateBy;
|
||||
@TableField("update_time")
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.njcn.gather.systemops.database.pojo.po;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 数据库连接配置。
|
||||
*/
|
||||
@Data
|
||||
@TableName("dbms_connection")
|
||||
public class DatabaseConnection implements Serializable {
|
||||
private static final long serialVersionUID = -5821519248914313778L;
|
||||
|
||||
@TableId("id")
|
||||
private String id;
|
||||
@TableField("connection_name")
|
||||
private String connectionName;
|
||||
@TableField("db_type")
|
||||
private String dbType;
|
||||
@TableField("host")
|
||||
private String host;
|
||||
@TableField("port")
|
||||
private Integer port;
|
||||
@TableField("connect_type")
|
||||
private String connectType;
|
||||
@TableField("service_name")
|
||||
private String serviceName;
|
||||
@TableField("sid")
|
||||
private String sid;
|
||||
@TableField("database_name")
|
||||
private String databaseName;
|
||||
@TableField("schema_name")
|
||||
private String schemaName;
|
||||
@TableField("username")
|
||||
private String username;
|
||||
@TableField("password_cipher")
|
||||
private String passwordCipher;
|
||||
@TableField("save_password")
|
||||
private Integer savePassword;
|
||||
@TableField("directory_name")
|
||||
private String directoryName;
|
||||
@TableField("directory_path")
|
||||
private String directoryPath;
|
||||
@TableField("extra_config_json")
|
||||
private String extraConfigJson;
|
||||
@TableField("remark")
|
||||
private String remark;
|
||||
@TableField("last_test_status")
|
||||
private String lastTestStatus;
|
||||
@TableField("last_test_message")
|
||||
private String lastTestMessage;
|
||||
@TableField("last_test_time")
|
||||
private LocalDateTime lastTestTime;
|
||||
@TableField("state")
|
||||
private Integer state;
|
||||
@TableField("create_by")
|
||||
private String createBy;
|
||||
@TableField("create_time")
|
||||
private LocalDateTime createTime;
|
||||
@TableField("update_by")
|
||||
private String updateBy;
|
||||
@TableField("update_time")
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.njcn.gather.systemops.database.pojo.po;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 数据库运维任务。
|
||||
*/
|
||||
@Data
|
||||
@TableName("dbms_operation_task")
|
||||
public class DatabaseOperationTask implements Serializable {
|
||||
private static final long serialVersionUID = 1831235987236858769L;
|
||||
|
||||
@TableId("id")
|
||||
private String id;
|
||||
@TableField("task_no")
|
||||
private String taskNo;
|
||||
@TableField("connection_id")
|
||||
private String connectionId;
|
||||
@TableField("db_type")
|
||||
private String dbType;
|
||||
@TableField("operation_type")
|
||||
private String operationType;
|
||||
@TableField("backup_strategy")
|
||||
private String backupStrategy;
|
||||
@TableField("task_status")
|
||||
private String taskStatus;
|
||||
@TableField("schema_name")
|
||||
private String schemaName;
|
||||
@TableField("target_names_json")
|
||||
private String targetNamesJson;
|
||||
@TableField("request_param_json")
|
||||
private String requestParamJson;
|
||||
@TableField("result_message")
|
||||
private String resultMessage;
|
||||
@TableField("progress_percent")
|
||||
private BigDecimal progressPercent;
|
||||
@TableField("started_at")
|
||||
private LocalDateTime startedAt;
|
||||
@TableField("finished_at")
|
||||
private LocalDateTime finishedAt;
|
||||
@TableField("state")
|
||||
private Integer state;
|
||||
@TableField("create_by")
|
||||
private String createBy;
|
||||
@TableField("create_time")
|
||||
private LocalDateTime createTime;
|
||||
@TableField("update_by")
|
||||
private String updateBy;
|
||||
@TableField("update_time")
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.njcn.gather.systemops.database.pojo.po;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 数据库恢复记录。
|
||||
*/
|
||||
@Data
|
||||
@TableName("dbms_restore_record")
|
||||
public class DatabaseRestoreRecord implements Serializable {
|
||||
private static final long serialVersionUID = -5638979151924581277L;
|
||||
|
||||
@TableId("id")
|
||||
private String id;
|
||||
@TableField("task_id")
|
||||
private String taskId;
|
||||
@TableField("backup_file_id")
|
||||
private String backupFileId;
|
||||
@TableField("connection_id")
|
||||
private String connectionId;
|
||||
@TableField("db_type")
|
||||
private String dbType;
|
||||
@TableField("restore_mode")
|
||||
private String restoreMode;
|
||||
@TableField("target_schema_name")
|
||||
private String targetSchemaName;
|
||||
@TableField("target_names_json")
|
||||
private String targetNamesJson;
|
||||
@TableField("table_exists_action")
|
||||
private String tableExistsAction;
|
||||
@TableField("overwrite_confirmed")
|
||||
private Integer overwriteConfirmed;
|
||||
@TableField("result_message")
|
||||
private String resultMessage;
|
||||
@TableField("state")
|
||||
private Integer state;
|
||||
@TableField("create_by")
|
||||
private String createBy;
|
||||
@TableField("create_time")
|
||||
private LocalDateTime createTime;
|
||||
@TableField("update_by")
|
||||
private String updateBy;
|
||||
@TableField("update_time")
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.njcn.gather.systemops.database.pojo.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 数据库备份文件响应。
|
||||
*/
|
||||
@Data
|
||||
public class DatabaseBackupFileVO {
|
||||
private String id;
|
||||
private String taskId;
|
||||
private String connectionId;
|
||||
private String dbType;
|
||||
private String backupStrategy;
|
||||
private String fileFormat;
|
||||
private String schemaName;
|
||||
private String targetNamesJson;
|
||||
private String backupMode;
|
||||
private String fileName;
|
||||
private String filePath;
|
||||
private String logFileName;
|
||||
private String logFilePath;
|
||||
private Long fileSize;
|
||||
private String checksum;
|
||||
private Integer state;
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.njcn.gather.systemops.database.pojo.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 数据库连接配置响应。
|
||||
*/
|
||||
@Data
|
||||
public class DatabaseConnectionVO {
|
||||
private String id;
|
||||
private String connectionName;
|
||||
private String dbType;
|
||||
private String host;
|
||||
private Integer port;
|
||||
private String connectType;
|
||||
private String serviceName;
|
||||
private String sid;
|
||||
private String schemaName;
|
||||
private String username;
|
||||
private Integer savePassword;
|
||||
private String directoryName;
|
||||
private String directoryPath;
|
||||
private String extraConfigJson;
|
||||
private String remark;
|
||||
private String lastTestStatus;
|
||||
private String lastTestMessage;
|
||||
private LocalDateTime lastTestTime;
|
||||
private Integer state;
|
||||
private LocalDateTime createTime;
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.njcn.gather.systemops.database.pojo.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 数据库表信息。
|
||||
*/
|
||||
@Data
|
||||
public class DatabaseTableVO {
|
||||
private String owner;
|
||||
private String tableName;
|
||||
private String comments;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.njcn.gather.systemops.database.pojo.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 运维任务创建结果。
|
||||
*/
|
||||
@Data
|
||||
public class DatabaseTaskCreateVO {
|
||||
private String taskId;
|
||||
private String taskNo;
|
||||
private String taskStatus;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.njcn.gather.systemops.database.pojo.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 数据库运维任务响应。
|
||||
*/
|
||||
@Data
|
||||
public class DatabaseTaskVO {
|
||||
private String id;
|
||||
private String taskNo;
|
||||
private String connectionId;
|
||||
private String dbType;
|
||||
private String operationType;
|
||||
private String backupStrategy;
|
||||
private String taskStatus;
|
||||
private String schemaName;
|
||||
private String targetNamesJson;
|
||||
private String resultMessage;
|
||||
private BigDecimal progressPercent;
|
||||
private LocalDateTime startedAt;
|
||||
private LocalDateTime finishedAt;
|
||||
private LocalDateTime createTime;
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.njcn.gather.systemops.database.pojo.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 数据库连接测试结果。
|
||||
*/
|
||||
@Data
|
||||
public class DatabaseTestResultVO {
|
||||
private Boolean success;
|
||||
private String message;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.njcn.gather.systemops.database.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.njcn.gather.systemops.database.pojo.param.DatabaseBackupParam;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseBackupFile;
|
||||
import com.njcn.gather.systemops.database.pojo.vo.DatabaseBackupFileVO;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* 数据库备份文件服务。
|
||||
*/
|
||||
public interface DatabaseBackupFileService extends IService<DatabaseBackupFile> {
|
||||
|
||||
Page<DatabaseBackupFileVO> listFiles(DatabaseBackupParam.FileQueryParam param);
|
||||
|
||||
boolean deleteBackupFile(String backupFileId, String confirmText);
|
||||
|
||||
void validateBackupFileReadable(DatabaseBackupFile backupFile);
|
||||
|
||||
Path resolveManagedPath(DatabaseBackupFile backupFile, String filePath);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.njcn.gather.systemops.database.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.njcn.gather.systemops.database.pojo.param.DatabaseConnectionParam;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseConnection;
|
||||
import com.njcn.gather.systemops.database.pojo.vo.DatabaseConnectionVO;
|
||||
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTableVO;
|
||||
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTestResultVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 数据库连接配置服务。
|
||||
*/
|
||||
public interface DatabaseConnectionService extends IService<DatabaseConnection> {
|
||||
|
||||
Page<DatabaseConnectionVO> listConnections(DatabaseConnectionParam.QueryParam queryParam);
|
||||
|
||||
boolean addConnection(DatabaseConnectionParam param);
|
||||
|
||||
boolean updateConnection(DatabaseConnectionParam.UpdateParam param);
|
||||
|
||||
boolean deleteConnection(DatabaseConnectionParam.DeleteParam param);
|
||||
|
||||
DatabaseTestResultVO testConnection(DatabaseConnectionParam.TestParam param);
|
||||
|
||||
List<DatabaseTableVO> listTables(DatabaseConnectionParam.TablesParam param);
|
||||
|
||||
DatabaseConnection requireEnabled(String connectionId);
|
||||
|
||||
String resolvePassword(DatabaseConnection connection, String temporaryPassword);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.njcn.gather.systemops.database.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.njcn.gather.systemops.database.pojo.param.DatabaseBackupParam;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseOperationTask;
|
||||
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTaskCreateVO;
|
||||
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTaskVO;
|
||||
|
||||
/**
|
||||
* 数据库运维任务服务。
|
||||
*/
|
||||
public interface DatabaseOperationTaskService extends IService<DatabaseOperationTask> {
|
||||
|
||||
DatabaseTaskCreateVO createBackupTask(DatabaseBackupParam.CreateParam param);
|
||||
|
||||
Page<DatabaseTaskVO> listBackupTasks(DatabaseBackupParam.TaskQueryParam param);
|
||||
|
||||
DatabaseTaskVO getStatus(String taskId);
|
||||
|
||||
boolean deleteTask(String taskId, String confirmText);
|
||||
|
||||
boolean existsRunningTask(String connectionId);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.njcn.gather.systemops.database.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.njcn.gather.systemops.database.pojo.param.DatabaseRestoreParam;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseRestoreRecord;
|
||||
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTaskCreateVO;
|
||||
|
||||
/**
|
||||
* 数据库恢复服务。
|
||||
*/
|
||||
public interface DatabaseRestoreService extends IService<DatabaseRestoreRecord> {
|
||||
|
||||
DatabaseTaskCreateVO createRestoreTask(DatabaseRestoreParam.CreateParam param);
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package com.njcn.gather.systemops.database.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||
import com.njcn.common.pojo.exception.BusinessException;
|
||||
import com.njcn.gather.systemops.database.config.DbmsProperties;
|
||||
import com.njcn.gather.systemops.database.constant.DatabaseOpsConst;
|
||||
import com.njcn.gather.systemops.database.mapper.DatabaseBackupFileMapper;
|
||||
import com.njcn.gather.systemops.database.pojo.param.DatabaseBackupParam;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseBackupFile;
|
||||
import com.njcn.gather.systemops.database.pojo.vo.DatabaseBackupFileVO;
|
||||
import com.njcn.gather.systemops.database.service.DatabaseBackupFileService;
|
||||
import com.njcn.gather.systemops.database.util.DatabaseChecksumUtil;
|
||||
import com.njcn.gather.systemops.database.util.DatabasePathUtil;
|
||||
import com.njcn.web.factory.PageFactory;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 数据库备份文件服务实现。
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DatabaseBackupFileServiceImpl extends ServiceImpl<DatabaseBackupFileMapper, DatabaseBackupFile> implements DatabaseBackupFileService {
|
||||
|
||||
private final DbmsProperties dbmsProperties;
|
||||
|
||||
@Override
|
||||
public Page<DatabaseBackupFileVO> listFiles(DatabaseBackupParam.FileQueryParam param) {
|
||||
DatabaseBackupParam.FileQueryParam query = param == null ? new DatabaseBackupParam.FileQueryParam() : param;
|
||||
LambdaQueryWrapper<DatabaseBackupFile> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(DatabaseBackupFile::getState, DatabaseOpsConst.STATE_ENABLED)
|
||||
.eq(StrUtil.isNotBlank(query.getConnectionId()), DatabaseBackupFile::getConnectionId, query.getConnectionId())
|
||||
.eq(StrUtil.isNotBlank(query.getTaskId()), DatabaseBackupFile::getTaskId, query.getTaskId())
|
||||
.eq(StrUtil.isNotBlank(query.getBackupStrategy()), DatabaseBackupFile::getBackupStrategy, query.getBackupStrategy())
|
||||
.orderByDesc(DatabaseBackupFile::getCreateTime);
|
||||
Page<DatabaseBackupFile> page = this.page(new Page<>(PageFactory.getPageNum(query), PageFactory.getPageSize(query)), wrapper);
|
||||
Page<DatabaseBackupFileVO> result = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
|
||||
result.setRecords(page.getRecords().stream().map(this::toVO).collect(Collectors.toList()));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean deleteBackupFile(String backupFileId, String confirmText) {
|
||||
if (!DatabaseOpsConst.CONFIRM_DELETE.equals(confirmText)) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "确认文案不正确");
|
||||
}
|
||||
DatabaseBackupFile file = this.lambdaQuery()
|
||||
.eq(DatabaseBackupFile::getId, backupFileId)
|
||||
.eq(DatabaseBackupFile::getState, DatabaseOpsConst.STATE_ENABLED)
|
||||
.one();
|
||||
if (file == null) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "备份文件不存在或已删除");
|
||||
}
|
||||
deletePhysicalFile(file, file.getFilePath());
|
||||
deletePhysicalFile(file, file.getLogFilePath());
|
||||
deletePhysicalFile(file, file.getMetadataFilePath());
|
||||
file.setState(DatabaseOpsConst.STATE_DELETED);
|
||||
file.setUpdateTime(LocalDateTime.now());
|
||||
return this.updateById(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateBackupFileReadable(DatabaseBackupFile backupFile) {
|
||||
validateReadablePath(backupFile, backupFile.getFilePath(), "备份文件", false);
|
||||
validateReadablePath(backupFile, backupFile.getMetadataFilePath(), "备份元数据文件",
|
||||
StrUtil.isBlank(backupFile.getMetadataFilePath()));
|
||||
if (StrUtil.isBlank(backupFile.getChecksum())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "备份文件缺少校验值");
|
||||
}
|
||||
Path filePath = resolveManagedPath(backupFile, backupFile.getFilePath());
|
||||
String actualChecksum = DatabaseChecksumUtil.sha256(filePath);
|
||||
if (!backupFile.getChecksum().equalsIgnoreCase(actualChecksum)) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "备份文件校验失败");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path resolveManagedPath(DatabaseBackupFile backupFile, String filePath) {
|
||||
if (StrUtil.isBlank(filePath)) {
|
||||
return null;
|
||||
}
|
||||
Path path = DatabasePathUtil.normalize(filePath);
|
||||
if (path == null) {
|
||||
return null;
|
||||
}
|
||||
Path storageRoot = DatabasePathUtil.normalize(dbmsProperties.getBackup().getStoragePath());
|
||||
if (DatabasePathUtil.isUnder(path, storageRoot)) {
|
||||
return path;
|
||||
}
|
||||
Path primaryFilePath = DatabasePathUtil.normalize(backupFile.getFilePath());
|
||||
if (primaryFilePath != null && primaryFilePath.getParent() != null
|
||||
&& DatabasePathUtil.isUnder(path, primaryFilePath.getParent())) {
|
||||
return path;
|
||||
}
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "文件路径不在允许的备份目录内");
|
||||
}
|
||||
|
||||
private void deletePhysicalFile(DatabaseBackupFile backupFile, String filePath) {
|
||||
if (StrUtil.isBlank(filePath)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Path path = resolveManagedPath(backupFile, filePath);
|
||||
if (path != null && Files.exists(path) && !Files.isDirectory(path)) {
|
||||
Files.delete(path);
|
||||
}
|
||||
} catch (BusinessException exception) {
|
||||
throw exception;
|
||||
} catch (Exception exception) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "删除物理文件失败:" + exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void validateReadablePath(DatabaseBackupFile backupFile, String filePath, String fileType, boolean allowBlank) {
|
||||
if (StrUtil.isBlank(filePath)) {
|
||||
if (allowBlank) {
|
||||
return;
|
||||
}
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, fileType + "路径不能为空");
|
||||
}
|
||||
Path path = resolveManagedPath(backupFile, filePath);
|
||||
if (path == null || !Files.exists(path) || Files.isDirectory(path)) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, fileType + "不存在");
|
||||
}
|
||||
}
|
||||
|
||||
private DatabaseBackupFileVO toVO(DatabaseBackupFile file) {
|
||||
DatabaseBackupFileVO vo = new DatabaseBackupFileVO();
|
||||
BeanUtil.copyProperties(file, vo);
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
package com.njcn.gather.systemops.database.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||
import com.njcn.common.pojo.exception.BusinessException;
|
||||
import com.njcn.gather.systemops.database.component.DatabasePasswordComponent;
|
||||
import com.njcn.gather.systemops.database.component.OracleJdbcComponent;
|
||||
import com.njcn.gather.systemops.database.constant.DatabaseOpsConst;
|
||||
import com.njcn.gather.systemops.database.mapper.DatabaseConnectionMapper;
|
||||
import com.njcn.gather.systemops.database.pojo.param.DatabaseConnectionParam;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseConnection;
|
||||
import com.njcn.gather.systemops.database.pojo.vo.DatabaseConnectionVO;
|
||||
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTableVO;
|
||||
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTestResultVO;
|
||||
import com.njcn.gather.systemops.database.service.DatabaseConnectionService;
|
||||
import com.njcn.gather.systemops.database.service.DatabaseOperationTaskService;
|
||||
import com.njcn.gather.systemops.database.util.DatabaseOpsIdUtil;
|
||||
import com.njcn.web.factory.PageFactory;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 数据库连接配置服务实现。
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DatabaseConnectionServiceImpl extends ServiceImpl<DatabaseConnectionMapper, DatabaseConnection> implements DatabaseConnectionService {
|
||||
|
||||
private final DatabasePasswordComponent databasePasswordComponent;
|
||||
private final OracleJdbcComponent oracleJdbcComponent;
|
||||
private final ObjectProvider<DatabaseOperationTaskService> databaseOperationTaskServiceProvider;
|
||||
|
||||
@Override
|
||||
public Page<DatabaseConnectionVO> listConnections(DatabaseConnectionParam.QueryParam queryParam) {
|
||||
DatabaseConnectionParam.QueryParam query = queryParam == null ? new DatabaseConnectionParam.QueryParam() : queryParam;
|
||||
LambdaQueryWrapper<DatabaseConnection> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(DatabaseConnection::getState, DatabaseOpsConst.STATE_ENABLED)
|
||||
.like(StrUtil.isNotBlank(query.getConnectionName()), DatabaseConnection::getConnectionName, query.getConnectionName())
|
||||
.eq(StrUtil.isNotBlank(query.getDbType()), DatabaseConnection::getDbType, query.getDbType())
|
||||
.like(StrUtil.isNotBlank(query.getSchemaName()), DatabaseConnection::getSchemaName, query.getSchemaName())
|
||||
.orderByDesc(DatabaseConnection::getUpdateTime);
|
||||
Page<DatabaseConnection> page = this.page(new Page<>(PageFactory.getPageNum(query), PageFactory.getPageSize(query)), wrapper);
|
||||
Page<DatabaseConnectionVO> result = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
|
||||
result.setRecords(page.getRecords().stream().map(this::toVO).collect(Collectors.toList()));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean addConnection(DatabaseConnectionParam param) {
|
||||
DatabaseConnection connection = new DatabaseConnection();
|
||||
fillConnection(connection, param, true);
|
||||
connection.setId(DatabaseOpsIdUtil.uuid());
|
||||
connection.setState(DatabaseOpsConst.STATE_ENABLED);
|
||||
connection.setCreateTime(LocalDateTime.now());
|
||||
connection.setUpdateTime(LocalDateTime.now());
|
||||
return this.save(connection);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean updateConnection(DatabaseConnectionParam.UpdateParam param) {
|
||||
DatabaseConnection connection = requireEnabled(param.getId());
|
||||
fillConnection(connection, param, false);
|
||||
connection.setUpdateTime(LocalDateTime.now());
|
||||
return this.updateById(connection);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean deleteConnection(DatabaseConnectionParam.DeleteParam param) {
|
||||
requireEnabled(param.getId());
|
||||
if (databaseOperationTaskServiceProvider.getObject().existsRunningTask(param.getId())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "存在运行中的任务,不能删除连接");
|
||||
}
|
||||
return this.lambdaUpdate()
|
||||
.set(DatabaseConnection::getState, DatabaseOpsConst.STATE_DELETED)
|
||||
.set(DatabaseConnection::getUpdateTime, LocalDateTime.now())
|
||||
.eq(DatabaseConnection::getId, param.getId())
|
||||
.update();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public DatabaseTestResultVO testConnection(DatabaseConnectionParam.TestParam param) {
|
||||
DatabaseConnection connection = resolveTestConnection(param);
|
||||
DatabaseTestResultVO result = oracleJdbcComponent.test(connection, resolvePassword(connection, param.getTemporaryPassword()));
|
||||
if (StrUtil.isNotBlank(connection.getId())) {
|
||||
connection.setLastTestStatus(Boolean.TRUE.equals(result.getSuccess()) ? "SUCCESS" : "FAIL");
|
||||
connection.setLastTestMessage(result.getMessage());
|
||||
connection.setLastTestTime(LocalDateTime.now());
|
||||
this.updateById(connection);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DatabaseTableVO> listTables(DatabaseConnectionParam.TablesParam param) {
|
||||
DatabaseConnection connection = requireEnabled(param.getConnectionId());
|
||||
try {
|
||||
return oracleJdbcComponent.listTables(connection, resolvePassword(connection, param.getTemporaryPassword()), param.getSchemaName());
|
||||
} catch (Exception exception) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DatabaseConnection requireEnabled(String connectionId) {
|
||||
if (StrUtil.isBlank(connectionId)) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "连接 ID 不能为空");
|
||||
}
|
||||
DatabaseConnection connection = this.lambdaQuery()
|
||||
.eq(DatabaseConnection::getId, connectionId)
|
||||
.eq(DatabaseConnection::getState, DatabaseOpsConst.STATE_ENABLED)
|
||||
.one();
|
||||
if (connection == null) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "数据库连接不存在或已删除");
|
||||
}
|
||||
return connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resolvePassword(DatabaseConnection connection, String temporaryPassword) {
|
||||
try {
|
||||
return databasePasswordComponent.resolveRuntimePassword(connection.getPasswordCipher(), temporaryPassword);
|
||||
} catch (IllegalArgumentException exception) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private DatabaseConnection resolveTestConnection(DatabaseConnectionParam.TestParam param) {
|
||||
if (StrUtil.isNotBlank(param.getConnectionId())) {
|
||||
return requireEnabled(param.getConnectionId());
|
||||
}
|
||||
if (param.getConnection() == null) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "连接测试参数不能为空");
|
||||
}
|
||||
DatabaseConnection connection = new DatabaseConnection();
|
||||
fillConnection(connection, param.getConnection(), true, StrUtil.isNotBlank(param.getTemporaryPassword()));
|
||||
return connection;
|
||||
}
|
||||
|
||||
private void fillConnection(DatabaseConnection connection, DatabaseConnectionParam param, boolean create) {
|
||||
fillConnection(connection, param, create, false);
|
||||
}
|
||||
|
||||
private void fillConnection(DatabaseConnection connection, DatabaseConnectionParam param, boolean create,
|
||||
boolean allowTemporaryPasswordOnly) {
|
||||
validateConnectionParam(param);
|
||||
connection.setConnectionName(param.getConnectionName().trim());
|
||||
connection.setDbType(DatabaseOpsConst.DB_TYPE_ORACLE);
|
||||
connection.setHost(param.getHost().trim());
|
||||
connection.setPort(param.getPort());
|
||||
connection.setConnectType(resolveConnectType(param.getConnectType()));
|
||||
connection.setServiceName(trimToNull(param.getServiceName()));
|
||||
connection.setSid(trimToNull(param.getSid()));
|
||||
connection.setSchemaName(trimToNull(param.getSchemaName()));
|
||||
connection.setUsername(param.getUsername().trim());
|
||||
connection.setSavePassword(param.getSavePassword() == null ? DatabaseOpsConst.SAVE_PASSWORD_YES : param.getSavePassword());
|
||||
if (connection.getSavePassword() != DatabaseOpsConst.SAVE_PASSWORD_YES
|
||||
&& connection.getSavePassword() != DatabaseOpsConst.SAVE_PASSWORD_NO) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "savePassword 只能是 0 或 1");
|
||||
}
|
||||
if (DatabaseOpsConst.SAVE_PASSWORD_YES == connection.getSavePassword() && StrUtil.isNotBlank(param.getPassword())) {
|
||||
connection.setPasswordCipher(databasePasswordComponent.encrypt(param.getPassword()));
|
||||
}
|
||||
if (DatabaseOpsConst.SAVE_PASSWORD_NO == connection.getSavePassword()) {
|
||||
connection.setPasswordCipher(null);
|
||||
} else if (create && StrUtil.isBlank(param.getPassword()) && !allowTemporaryPasswordOnly) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "保存密码时密码不能为空");
|
||||
}
|
||||
connection.setDirectoryName(trimToNull(param.getDirectoryName()));
|
||||
connection.setDirectoryPath(trimToNull(param.getDirectoryPath()));
|
||||
connection.setExtraConfigJson(trimToNull(param.getExtraConfigJson()));
|
||||
connection.setRemark(param.getRemark());
|
||||
}
|
||||
|
||||
private void validateConnectionParam(DatabaseConnectionParam param) {
|
||||
String connectType = resolveConnectType(param.getConnectType());
|
||||
if (DatabaseOpsConst.CONNECT_TYPE_SERVICE_NAME.equals(connectType) && StrUtil.isBlank(param.getServiceName())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "SERVICE_NAME 连接方式下服务名不能为空");
|
||||
}
|
||||
if (DatabaseOpsConst.CONNECT_TYPE_SID.equals(connectType) && StrUtil.isBlank(param.getSid())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "SID 连接方式下 SID 不能为空");
|
||||
}
|
||||
}
|
||||
|
||||
private String resolveConnectType(String connectType) {
|
||||
return StrUtil.blankToDefault(connectType, DatabaseOpsConst.CONNECT_TYPE_SERVICE_NAME).trim().toUpperCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
private String trimToNull(String value) {
|
||||
return StrUtil.isBlank(value) ? null : value.trim();
|
||||
}
|
||||
|
||||
private DatabaseConnectionVO toVO(DatabaseConnection connection) {
|
||||
DatabaseConnectionVO vo = new DatabaseConnectionVO();
|
||||
BeanUtil.copyProperties(connection, vo);
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
package com.njcn.gather.systemops.database.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||
import com.njcn.common.pojo.exception.BusinessException;
|
||||
import com.njcn.gather.systemops.database.component.DataPumpCommandExecutor;
|
||||
import com.njcn.gather.systemops.database.component.JdbcExportComponent;
|
||||
import com.njcn.gather.systemops.database.config.DbmsProperties;
|
||||
import com.njcn.gather.systemops.database.constant.DatabaseOpsConst;
|
||||
import com.njcn.gather.systemops.database.mapper.DatabaseOperationTaskMapper;
|
||||
import com.njcn.gather.systemops.database.pojo.enums.BackupModeEnum;
|
||||
import com.njcn.gather.systemops.database.pojo.enums.BackupStrategyEnum;
|
||||
import com.njcn.gather.systemops.database.pojo.enums.FileFormatEnum;
|
||||
import com.njcn.gather.systemops.database.pojo.enums.OperationTypeEnum;
|
||||
import com.njcn.gather.systemops.database.pojo.enums.TaskStatusEnum;
|
||||
import com.njcn.gather.systemops.database.pojo.param.DatabaseBackupParam;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseBackupFile;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseConnection;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseOperationTask;
|
||||
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTaskCreateVO;
|
||||
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTaskVO;
|
||||
import com.njcn.gather.systemops.database.service.DatabaseBackupFileService;
|
||||
import com.njcn.gather.systemops.database.service.DatabaseConnectionService;
|
||||
import com.njcn.gather.systemops.database.service.DatabaseOperationTaskService;
|
||||
import com.njcn.gather.systemops.database.util.DatabaseChecksumUtil;
|
||||
import com.njcn.gather.systemops.database.util.DatabaseFileNameUtil;
|
||||
import com.njcn.gather.systemops.database.util.DatabaseOpsIdUtil;
|
||||
import com.njcn.gather.systemops.database.util.DatabasePathUtil;
|
||||
import com.njcn.web.factory.PageFactory;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 数据库运维任务服务实现。
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DatabaseOperationTaskServiceImpl extends ServiceImpl<DatabaseOperationTaskMapper, DatabaseOperationTask> implements DatabaseOperationTaskService {
|
||||
|
||||
private final DatabaseConnectionService databaseConnectionService;
|
||||
private final DatabaseBackupFileService databaseBackupFileService;
|
||||
private final DataPumpCommandExecutor dataPumpCommandExecutor;
|
||||
private final JdbcExportComponent jdbcExportComponent;
|
||||
private final DbmsProperties dbmsProperties;
|
||||
private final ObjectMapper objectMapper;
|
||||
@Resource(name = "dbmsTaskExecutorService")
|
||||
private ExecutorService dbmsTaskExecutorService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public DatabaseTaskCreateVO createBackupTask(DatabaseBackupParam.CreateParam param) {
|
||||
DatabaseConnection connection = databaseConnectionService.requireEnabled(param.getConnectionId());
|
||||
validateBackupParam(param, connection);
|
||||
if (existsRunningTask(connection.getId())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "当前连接存在运行中的任务");
|
||||
}
|
||||
DatabaseOperationTask task = buildBackupTask(param, connection);
|
||||
this.save(task);
|
||||
dbmsTaskExecutorService.submit(() -> executeBackupTask(task.getId(), param));
|
||||
return toCreateVO(task);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<DatabaseTaskVO> listBackupTasks(DatabaseBackupParam.TaskQueryParam param) {
|
||||
DatabaseBackupParam.TaskQueryParam query = param == null ? new DatabaseBackupParam.TaskQueryParam() : param;
|
||||
LambdaQueryWrapper<DatabaseOperationTask> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(DatabaseOperationTask::getState, DatabaseOpsConst.STATE_ENABLED)
|
||||
.eq(DatabaseOperationTask::getOperationType, OperationTypeEnum.BACKUP.name())
|
||||
.eq(StrUtil.isNotBlank(query.getConnectionId()), DatabaseOperationTask::getConnectionId, query.getConnectionId())
|
||||
.eq(StrUtil.isNotBlank(query.getTaskStatus()), DatabaseOperationTask::getTaskStatus, query.getTaskStatus())
|
||||
.orderByDesc(DatabaseOperationTask::getCreateTime);
|
||||
Page<DatabaseOperationTask> page = this.page(new Page<>(PageFactory.getPageNum(query), PageFactory.getPageSize(query)), wrapper);
|
||||
Page<DatabaseTaskVO> result = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
|
||||
result.setRecords(page.getRecords().stream().map(this::toVO).collect(Collectors.toList()));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DatabaseTaskVO getStatus(String taskId) {
|
||||
DatabaseOperationTask task = this.getById(taskId);
|
||||
if (task == null || !Integer.valueOf(DatabaseOpsConst.STATE_ENABLED).equals(task.getState())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "任务不存在或已删除");
|
||||
}
|
||||
return toVO(task);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean deleteTask(String taskId, String confirmText) {
|
||||
if (!DatabaseOpsConst.CONFIRM_DELETE.equals(confirmText)) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "确认文案不正确");
|
||||
}
|
||||
DatabaseOperationTask task = this.getById(taskId);
|
||||
if (task == null || !Integer.valueOf(DatabaseOpsConst.STATE_ENABLED).equals(task.getState())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "任务不存在或已删除");
|
||||
}
|
||||
if (TaskStatusEnum.RUNNING.name().equals(task.getTaskStatus()) || TaskStatusEnum.WAITING.name().equals(task.getTaskStatus())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "运行中的任务不能删除");
|
||||
}
|
||||
task.setState(DatabaseOpsConst.STATE_DELETED);
|
||||
task.setUpdateTime(LocalDateTime.now());
|
||||
return this.updateById(task);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsRunningTask(String connectionId) {
|
||||
return this.lambdaQuery()
|
||||
.eq(DatabaseOperationTask::getConnectionId, connectionId)
|
||||
.eq(DatabaseOperationTask::getState, DatabaseOpsConst.STATE_ENABLED)
|
||||
.in(DatabaseOperationTask::getTaskStatus, Arrays.asList(TaskStatusEnum.WAITING.name(), TaskStatusEnum.RUNNING.name()))
|
||||
.count() > 0;
|
||||
}
|
||||
|
||||
private void executeBackupTask(String taskId, DatabaseBackupParam.CreateParam param) {
|
||||
DatabaseOperationTask task = this.getById(taskId);
|
||||
try {
|
||||
markRunning(task);
|
||||
DatabaseConnection connection = databaseConnectionService.requireEnabled(task.getConnectionId());
|
||||
connection.setSchemaName(task.getSchemaName());
|
||||
String password = databaseConnectionService.resolvePassword(connection, param.getTemporaryPassword());
|
||||
DatabaseBackupFile backupFile;
|
||||
if (BackupStrategyEnum.DATA_PUMP.name().equals(task.getBackupStrategy())) {
|
||||
backupFile = executeDataPumpBackup(task, connection, password, param);
|
||||
} else {
|
||||
backupFile = executeJdbcExportBackup(task, connection, password, param);
|
||||
}
|
||||
databaseBackupFileService.save(backupFile);
|
||||
markSuccess(task, "备份任务执行成功");
|
||||
} catch (Exception exception) {
|
||||
log.error("数据库备份任务失败,taskId={}", taskId, exception);
|
||||
markFail(task, exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private DatabaseBackupFile executeDataPumpBackup(DatabaseOperationTask task, DatabaseConnection connection, String password,
|
||||
DatabaseBackupParam.CreateParam param) {
|
||||
String directoryName = StrUtil.blankToDefault(param.getDirectoryName(), connection.getDirectoryName());
|
||||
if (StrUtil.isBlank(directoryName)) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "DATA_PUMP 备份需要 Oracle Directory 名称");
|
||||
}
|
||||
String baseName = buildBaseFileName(connection, task);
|
||||
String dumpFileName = DatabaseFileNameUtil.appendTodayWithTask(baseName + ".dmp", task.getTaskNo());
|
||||
String logFileName = DatabaseFileNameUtil.appendTodayWithTask(baseName + ".log", task.getTaskNo());
|
||||
DataPumpCommandExecutor.CommandResult commandResult = dataPumpCommandExecutor.expdp(connection, password, directoryName, dumpFileName, logFileName, param.getTargetNames());
|
||||
if (!Boolean.TRUE.equals(commandResult.getSuccess())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "Data Pump 执行失败:" + commandResult.getOutput());
|
||||
}
|
||||
if (StrUtil.isBlank(connection.getDirectoryPath())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "Data Pump 备份需要配置可管理的 directoryPath");
|
||||
}
|
||||
Path dumpPath = buildManagedPath(connection.getDirectoryPath(), dumpFileName);
|
||||
Path logPath = buildManagedPath(connection.getDirectoryPath(), logFileName);
|
||||
return buildBackupFile(task, connection, param, FileFormatEnum.DMP.name(), dumpFileName, dumpPath, logFileName, logPath, null);
|
||||
}
|
||||
|
||||
private DatabaseBackupFile executeJdbcExportBackup(DatabaseOperationTask task, DatabaseConnection connection, String password,
|
||||
DatabaseBackupParam.CreateParam param) throws Exception {
|
||||
String baseName = buildBaseFileName(connection, task);
|
||||
String fileName = DatabaseFileNameUtil.appendTodayWithTask(baseName + ".csv", task.getTaskNo());
|
||||
String metadataFileName = DatabaseFileNameUtil.appendTodayWithTask(baseName + "_metadata.json", task.getTaskNo());
|
||||
Path dataFilePath = buildManagedPath(dbmsProperties.getBackup().getStoragePath(), fileName);
|
||||
Path metadataFilePath = buildManagedPath(dbmsProperties.getBackup().getStoragePath(), metadataFileName);
|
||||
jdbcExportComponent.exportCsv(connection, password, param, dataFilePath, metadataFilePath);
|
||||
return buildBackupFile(task, connection, param, FileFormatEnum.CSV.name(), fileName, dataFilePath, null, null, metadataFilePath);
|
||||
}
|
||||
|
||||
private DatabaseBackupFile buildBackupFile(DatabaseOperationTask task, DatabaseConnection connection, DatabaseBackupParam.CreateParam param,
|
||||
String fileFormat, String fileName, Path filePath, String logFileName, Path logFilePath,
|
||||
Path metadataFilePath) {
|
||||
if (filePath == null || !Files.exists(filePath)) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "备份文件未生成");
|
||||
}
|
||||
DatabaseBackupFile file = new DatabaseBackupFile();
|
||||
file.setId(DatabaseOpsIdUtil.uuid());
|
||||
file.setTaskId(task.getId());
|
||||
file.setConnectionId(connection.getId());
|
||||
file.setDbType(connection.getDbType());
|
||||
file.setBackupStrategy(task.getBackupStrategy());
|
||||
file.setFileFormat(fileFormat);
|
||||
file.setSchemaName(task.getSchemaName());
|
||||
file.setTargetNamesJson(task.getTargetNamesJson());
|
||||
file.setBackupMode(StrUtil.blankToDefault(param.getBackupMode(), BackupModeEnum.FULL_TABLE.name()).toUpperCase(Locale.ROOT));
|
||||
file.setBackupStartTime(param.getStartTime());
|
||||
file.setBackupEndTime(param.getEndTime());
|
||||
file.setTimeColumn(param.getTimeColumn());
|
||||
file.setDirectoryName(StrUtil.blankToDefault(param.getDirectoryName(), connection.getDirectoryName()));
|
||||
file.setDumpFileName(FileFormatEnum.DMP.name().equals(fileFormat) ? fileName : null);
|
||||
file.setLogFileName(logFileName);
|
||||
file.setFileName(fileName);
|
||||
file.setFilePath(filePath.toString());
|
||||
file.setLogFilePath(logFilePath == null ? null : logFilePath.toString());
|
||||
file.setMetadataFilePath(metadataFilePath == null ? null : metadataFilePath.toString());
|
||||
file.setFileSize(readFileSize(filePath));
|
||||
file.setChecksum(DatabaseChecksumUtil.sha256(filePath));
|
||||
file.setState(DatabaseOpsConst.STATE_ENABLED);
|
||||
file.setCreateTime(LocalDateTime.now());
|
||||
file.setUpdateTime(LocalDateTime.now());
|
||||
return file;
|
||||
}
|
||||
|
||||
private Long readFileSize(Path filePath) {
|
||||
try {
|
||||
if (filePath != null && Files.exists(filePath) && !Files.isDirectory(filePath)) {
|
||||
return Files.size(filePath);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Path buildManagedPath(String rootPath, String fileName) {
|
||||
Path root = DatabasePathUtil.normalize(rootPath);
|
||||
if (root == null) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "备份目录未配置");
|
||||
}
|
||||
return root.resolve(fileName).normalize();
|
||||
}
|
||||
|
||||
private String buildBaseFileName(DatabaseConnection connection, DatabaseOperationTask task) {
|
||||
return connection.getSchemaName() + "_" + task.getBackupStrategy().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
private DatabaseOperationTask buildBackupTask(DatabaseBackupParam.CreateParam param, DatabaseConnection connection) {
|
||||
DatabaseOperationTask task = new DatabaseOperationTask();
|
||||
task.setId(DatabaseOpsIdUtil.uuid());
|
||||
task.setTaskNo(DatabaseOpsIdUtil.taskNo("DBMSB"));
|
||||
task.setConnectionId(connection.getId());
|
||||
task.setDbType(connection.getDbType());
|
||||
task.setOperationType(OperationTypeEnum.BACKUP.name());
|
||||
task.setBackupStrategy(resolveBackupStrategy(param.getBackupStrategy()));
|
||||
task.setTaskStatus(TaskStatusEnum.WAITING.name());
|
||||
task.setSchemaName(StrUtil.blankToDefault(param.getSchemaName(), connection.getSchemaName()));
|
||||
task.setTargetNamesJson(writeJson(param.getTargetNames()));
|
||||
task.setRequestParamJson(writeJsonWithoutPassword(param));
|
||||
task.setProgressPercent(BigDecimal.ZERO);
|
||||
task.setState(DatabaseOpsConst.STATE_ENABLED);
|
||||
task.setCreateTime(LocalDateTime.now());
|
||||
task.setUpdateTime(LocalDateTime.now());
|
||||
return task;
|
||||
}
|
||||
|
||||
private void validateBackupParam(DatabaseBackupParam.CreateParam param, DatabaseConnection connection) {
|
||||
if (!DatabaseOpsConst.DB_TYPE_ORACLE.equals(connection.getDbType())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "一期仅支持 ORACLE");
|
||||
}
|
||||
if (param.getTargetNames() == null || param.getTargetNames().isEmpty()) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "备份表不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(StrUtil.blankToDefault(param.getSchemaName(), connection.getSchemaName()))) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "备份 Schema 不能为空");
|
||||
}
|
||||
String backupMode = StrUtil.blankToDefault(param.getBackupMode(), BackupModeEnum.FULL_TABLE.name()).toUpperCase(Locale.ROOT);
|
||||
if (BackupModeEnum.TIME_RANGE.name().equals(backupMode)
|
||||
&& (param.getStartTime() == null || param.getEndTime() == null)) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "按时间备份必须传入开始时间和结束时间");
|
||||
}
|
||||
if (BackupModeEnum.TIME_RANGE.name().equals(backupMode)
|
||||
&& param.getStartTime() != null && param.getEndTime() != null
|
||||
&& param.getStartTime().isAfter(param.getEndTime())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "开始时间不能晚于结束时间");
|
||||
}
|
||||
if (BackupModeEnum.SIZE_SPLIT.name().equals(backupMode)
|
||||
&& (param.getMaxFileSizeMb() == null || param.getMaxFileSizeMb() <= 0)) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "按大小分片必须传入大于 0 的文件大小");
|
||||
}
|
||||
if (BackupStrategyEnum.JDBC_EXPORT.name().equals(resolveBackupStrategy(param.getBackupStrategy()))
|
||||
&& BackupModeEnum.TIME_RANGE.name().equals(backupMode)
|
||||
&& StrUtil.isBlank(param.getTimeColumn())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "JDBC 按时间备份必须传入时间字段");
|
||||
}
|
||||
}
|
||||
|
||||
private String resolveBackupStrategy(String backupStrategy) {
|
||||
String value = StrUtil.blankToDefault(backupStrategy, BackupStrategyEnum.DATA_PUMP.name()).trim().toUpperCase(Locale.ROOT);
|
||||
try {
|
||||
return BackupStrategyEnum.valueOf(value).name();
|
||||
} catch (Exception exception) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "不支持的备份策略:" + backupStrategy);
|
||||
}
|
||||
}
|
||||
|
||||
private void markRunning(DatabaseOperationTask task) {
|
||||
task.setTaskStatus(TaskStatusEnum.RUNNING.name());
|
||||
task.setStartedAt(LocalDateTime.now());
|
||||
task.setUpdateTime(LocalDateTime.now());
|
||||
this.updateById(task);
|
||||
}
|
||||
|
||||
private void markSuccess(DatabaseOperationTask task, String message) {
|
||||
task.setTaskStatus(TaskStatusEnum.SUCCESS.name());
|
||||
task.setResultMessage(message);
|
||||
task.setProgressPercent(new BigDecimal("100.00"));
|
||||
task.setFinishedAt(LocalDateTime.now());
|
||||
task.setUpdateTime(LocalDateTime.now());
|
||||
this.updateById(task);
|
||||
}
|
||||
|
||||
private void markFail(DatabaseOperationTask task, String message) {
|
||||
task.setTaskStatus(TaskStatusEnum.FAIL.name());
|
||||
task.setResultMessage(message);
|
||||
task.setFinishedAt(LocalDateTime.now());
|
||||
task.setUpdateTime(LocalDateTime.now());
|
||||
this.updateById(task);
|
||||
}
|
||||
|
||||
private String writeJson(Object value) {
|
||||
try {
|
||||
return objectMapper.writeValueAsString(value);
|
||||
} catch (Exception exception) {
|
||||
throw new BusinessException(CommonResponseEnum.JSON_CONVERT_EXCEPTION, exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private String writeJsonWithoutPassword(DatabaseBackupParam.CreateParam param) {
|
||||
DatabaseBackupParam.CreateParam copy = new DatabaseBackupParam.CreateParam();
|
||||
BeanUtil.copyProperties(param, copy);
|
||||
copy.setTemporaryPassword(null);
|
||||
return writeJson(copy);
|
||||
}
|
||||
|
||||
private DatabaseTaskCreateVO toCreateVO(DatabaseOperationTask task) {
|
||||
DatabaseTaskCreateVO vo = new DatabaseTaskCreateVO();
|
||||
vo.setTaskId(task.getId());
|
||||
vo.setTaskNo(task.getTaskNo());
|
||||
vo.setTaskStatus(task.getTaskStatus());
|
||||
return vo;
|
||||
}
|
||||
|
||||
private DatabaseTaskVO toVO(DatabaseOperationTask task) {
|
||||
DatabaseTaskVO vo = new DatabaseTaskVO();
|
||||
BeanUtil.copyProperties(task, vo);
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
package com.njcn.gather.systemops.database.service.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||
import com.njcn.common.pojo.exception.BusinessException;
|
||||
import com.njcn.gather.systemops.database.component.DataPumpCommandExecutor;
|
||||
import com.njcn.gather.systemops.database.component.JdbcExportComponent;
|
||||
import com.njcn.gather.systemops.database.component.OracleJdbcComponent;
|
||||
import com.njcn.gather.systemops.database.constant.DatabaseOpsConst;
|
||||
import com.njcn.gather.systemops.database.mapper.DatabaseRestoreRecordMapper;
|
||||
import com.njcn.gather.systemops.database.pojo.enums.BackupStrategyEnum;
|
||||
import com.njcn.gather.systemops.database.pojo.enums.FileFormatEnum;
|
||||
import com.njcn.gather.systemops.database.pojo.enums.OperationTypeEnum;
|
||||
import com.njcn.gather.systemops.database.pojo.enums.RestoreModeEnum;
|
||||
import com.njcn.gather.systemops.database.pojo.enums.TaskStatusEnum;
|
||||
import com.njcn.gather.systemops.database.pojo.param.DatabaseRestoreParam;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseBackupFile;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseConnection;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseOperationTask;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseRestoreRecord;
|
||||
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTaskCreateVO;
|
||||
import com.njcn.gather.systemops.database.service.DatabaseBackupFileService;
|
||||
import com.njcn.gather.systemops.database.service.DatabaseConnectionService;
|
||||
import com.njcn.gather.systemops.database.service.DatabaseOperationTaskService;
|
||||
import com.njcn.gather.systemops.database.service.DatabaseRestoreService;
|
||||
import com.njcn.gather.systemops.database.util.DatabaseFileNameUtil;
|
||||
import com.njcn.gather.systemops.database.util.DatabaseOpsIdUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.file.Path;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
/**
|
||||
* 数据库恢复服务实现。
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DatabaseRestoreServiceImpl extends ServiceImpl<DatabaseRestoreRecordMapper, DatabaseRestoreRecord> implements DatabaseRestoreService {
|
||||
|
||||
private final DatabaseConnectionService databaseConnectionService;
|
||||
private final DatabaseOperationTaskService databaseOperationTaskService;
|
||||
private final DatabaseBackupFileService databaseBackupFileService;
|
||||
private final DataPumpCommandExecutor dataPumpCommandExecutor;
|
||||
private final JdbcExportComponent jdbcExportComponent;
|
||||
private final OracleJdbcComponent oracleJdbcComponent;
|
||||
private final ObjectMapper objectMapper;
|
||||
@Resource(name = "dbmsTaskExecutorService")
|
||||
private ExecutorService dbmsTaskExecutorService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public DatabaseTaskCreateVO createRestoreTask(DatabaseRestoreParam.CreateParam param) {
|
||||
DatabaseConnection connection = databaseConnectionService.requireEnabled(param.getConnectionId());
|
||||
DatabaseBackupFile backupFile = requireBackupFile(param.getBackupFileId());
|
||||
validateRestoreParam(param, connection, backupFile);
|
||||
if (databaseOperationTaskService.existsRunningTask(connection.getId())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "当前连接存在运行中的任务");
|
||||
}
|
||||
DatabaseOperationTask task = buildRestoreTask(param, connection, backupFile);
|
||||
databaseOperationTaskService.save(task);
|
||||
DatabaseRestoreRecord record = buildRestoreRecord(param, connection, backupFile, task);
|
||||
this.save(record);
|
||||
dbmsTaskExecutorService.submit(() -> executeRestoreTask(task.getId(), record.getId(), param));
|
||||
DatabaseTaskCreateVO vo = new DatabaseTaskCreateVO();
|
||||
vo.setTaskId(task.getId());
|
||||
vo.setTaskNo(task.getTaskNo());
|
||||
vo.setTaskStatus(task.getTaskStatus());
|
||||
return vo;
|
||||
}
|
||||
|
||||
private void executeRestoreTask(String taskId, String recordId, DatabaseRestoreParam.CreateParam param) {
|
||||
DatabaseOperationTask task = databaseOperationTaskService.getById(taskId);
|
||||
DatabaseRestoreRecord record = this.getById(recordId);
|
||||
try {
|
||||
markRunning(task);
|
||||
DatabaseConnection connection = databaseConnectionService.requireEnabled(task.getConnectionId());
|
||||
DatabaseBackupFile backupFile = requireBackupFile(record.getBackupFileId());
|
||||
databaseBackupFileService.validateBackupFileReadable(backupFile);
|
||||
String password = databaseConnectionService.resolvePassword(connection, param.getTemporaryPassword());
|
||||
if (BackupStrategyEnum.DATA_PUMP.name().equals(backupFile.getBackupStrategy())) {
|
||||
DataPumpCommandExecutor.CommandResult result = dataPumpCommandExecutor.impdp(connection, password,
|
||||
backupFile.getDirectoryName(), backupFile.getDumpFileName(), buildRestoreLogName(task),
|
||||
record.getTableExistsAction());
|
||||
if (!Boolean.TRUE.equals(result.getSuccess())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "Data Pump 恢复失败:" + result.getOutput());
|
||||
}
|
||||
} else if (FileFormatEnum.CSV.name().equalsIgnoreCase(backupFile.getFileFormat())) {
|
||||
Path dataFilePath = databaseBackupFileService.resolveManagedPath(backupFile, backupFile.getFilePath());
|
||||
Path metadataFilePath = databaseBackupFileService.resolveManagedPath(backupFile, backupFile.getMetadataFilePath());
|
||||
jdbcExportComponent.importCsv(connection, password, dataFilePath, metadataFilePath, record.getRestoreMode(), record.getTargetSchemaName());
|
||||
} else {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "暂不支持的恢复文件格式:" + backupFile.getFileFormat());
|
||||
}
|
||||
record.setResultMessage("恢复任务执行成功");
|
||||
record.setUpdateTime(LocalDateTime.now());
|
||||
this.updateById(record);
|
||||
markSuccess(task, "恢复任务执行成功");
|
||||
} catch (Exception exception) {
|
||||
log.error("数据库恢复任务失败,taskId={}", taskId, exception);
|
||||
record.setResultMessage(exception.getMessage());
|
||||
record.setUpdateTime(LocalDateTime.now());
|
||||
this.updateById(record);
|
||||
markFail(task, exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void validateRestoreParam(DatabaseRestoreParam.CreateParam param, DatabaseConnection connection, DatabaseBackupFile backupFile) {
|
||||
if (!connection.getDbType().equals(backupFile.getDbType())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "备份文件数据库类型和目标连接数据库类型不一致");
|
||||
}
|
||||
String restoreMode = resolveRestoreMode(param.getRestoreMode());
|
||||
if ((RestoreModeEnum.TRUNCATE.name().equals(restoreMode) || RestoreModeEnum.REPLACE.name().equals(restoreMode))
|
||||
&& !DatabaseOpsConst.CONFIRM_OVERWRITE.equals(param.getOverwriteConfirmText())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "覆盖类恢复必须输入确认覆盖");
|
||||
}
|
||||
databaseBackupFileService.validateBackupFileReadable(backupFile);
|
||||
String password = databaseConnectionService.resolvePassword(connection, param.getTemporaryPassword());
|
||||
if (!Boolean.TRUE.equals(oracleJdbcComponent.test(connection, password).getSuccess())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "目标连接测试失败,不能创建恢复任务");
|
||||
}
|
||||
if (BackupStrategyEnum.DATA_PUMP.name().equals(backupFile.getBackupStrategy())) {
|
||||
if (StrUtil.isBlank(backupFile.getDirectoryName()) || StrUtil.isBlank(backupFile.getDumpFileName())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "Data Pump 备份记录缺少目录或文件名");
|
||||
}
|
||||
}
|
||||
if (BackupStrategyEnum.JDBC_EXPORT.name().equals(backupFile.getBackupStrategy())
|
||||
&& StrUtil.isBlank(backupFile.getMetadataFilePath())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "JDBC_EXPORT 备份缺少元数据文件,不能恢复");
|
||||
}
|
||||
}
|
||||
|
||||
private DatabaseOperationTask buildRestoreTask(DatabaseRestoreParam.CreateParam param, DatabaseConnection connection, DatabaseBackupFile backupFile) {
|
||||
DatabaseOperationTask task = new DatabaseOperationTask();
|
||||
task.setId(DatabaseOpsIdUtil.uuid());
|
||||
task.setTaskNo(DatabaseOpsIdUtil.taskNo("DBMSR"));
|
||||
task.setConnectionId(connection.getId());
|
||||
task.setDbType(connection.getDbType());
|
||||
task.setOperationType(OperationTypeEnum.RESTORE.name());
|
||||
task.setBackupStrategy(backupFile.getBackupStrategy());
|
||||
task.setTaskStatus(TaskStatusEnum.WAITING.name());
|
||||
task.setSchemaName(StrUtil.blankToDefault(param.getTargetSchemaName(), connection.getSchemaName()));
|
||||
task.setTargetNamesJson(backupFile.getTargetNamesJson());
|
||||
task.setRequestParamJson(writeJsonWithoutPassword(param));
|
||||
task.setProgressPercent(BigDecimal.ZERO);
|
||||
task.setState(DatabaseOpsConst.STATE_ENABLED);
|
||||
task.setCreateTime(LocalDateTime.now());
|
||||
task.setUpdateTime(LocalDateTime.now());
|
||||
return task;
|
||||
}
|
||||
|
||||
private DatabaseRestoreRecord buildRestoreRecord(DatabaseRestoreParam.CreateParam param, DatabaseConnection connection,
|
||||
DatabaseBackupFile backupFile, DatabaseOperationTask task) {
|
||||
String restoreMode = resolveRestoreMode(param.getRestoreMode());
|
||||
DatabaseRestoreRecord record = new DatabaseRestoreRecord();
|
||||
record.setId(DatabaseOpsIdUtil.uuid());
|
||||
record.setTaskId(task.getId());
|
||||
record.setBackupFileId(backupFile.getId());
|
||||
record.setConnectionId(connection.getId());
|
||||
record.setDbType(connection.getDbType());
|
||||
record.setRestoreMode(restoreMode);
|
||||
record.setTargetSchemaName(StrUtil.blankToDefault(param.getTargetSchemaName(), connection.getSchemaName()));
|
||||
record.setTargetNamesJson(backupFile.getTargetNamesJson());
|
||||
record.setTableExistsAction(restoreMode);
|
||||
record.setOverwriteConfirmed(DatabaseOpsConst.CONFIRM_OVERWRITE.equals(param.getOverwriteConfirmText()) ? 1 : 0);
|
||||
record.setState(DatabaseOpsConst.STATE_ENABLED);
|
||||
record.setCreateTime(LocalDateTime.now());
|
||||
record.setUpdateTime(LocalDateTime.now());
|
||||
return record;
|
||||
}
|
||||
|
||||
private DatabaseBackupFile requireBackupFile(String backupFileId) {
|
||||
DatabaseBackupFile backupFile = databaseBackupFileService.getById(backupFileId);
|
||||
if (backupFile == null || !Integer.valueOf(DatabaseOpsConst.STATE_ENABLED).equals(backupFile.getState())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "备份文件不存在或已删除");
|
||||
}
|
||||
return backupFile;
|
||||
}
|
||||
|
||||
private String resolveRestoreMode(String restoreMode) {
|
||||
String value = StrUtil.blankToDefault(restoreMode, RestoreModeEnum.SKIP.name()).trim().toUpperCase(Locale.ROOT);
|
||||
try {
|
||||
return RestoreModeEnum.valueOf(value).name();
|
||||
} catch (Exception exception) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "不支持的恢复模式:" + restoreMode);
|
||||
}
|
||||
}
|
||||
|
||||
private String buildRestoreLogName(DatabaseOperationTask task) {
|
||||
return DatabaseFileNameUtil.appendTodayWithTask(task.getTaskNo() + "_restore.log", task.getTaskNo());
|
||||
}
|
||||
|
||||
private void markRunning(DatabaseOperationTask task) {
|
||||
task.setTaskStatus(TaskStatusEnum.RUNNING.name());
|
||||
task.setStartedAt(LocalDateTime.now());
|
||||
task.setUpdateTime(LocalDateTime.now());
|
||||
databaseOperationTaskService.updateById(task);
|
||||
}
|
||||
|
||||
private void markSuccess(DatabaseOperationTask task, String message) {
|
||||
task.setTaskStatus(TaskStatusEnum.SUCCESS.name());
|
||||
task.setResultMessage(message);
|
||||
task.setProgressPercent(new BigDecimal("100.00"));
|
||||
task.setFinishedAt(LocalDateTime.now());
|
||||
task.setUpdateTime(LocalDateTime.now());
|
||||
databaseOperationTaskService.updateById(task);
|
||||
}
|
||||
|
||||
private void markFail(DatabaseOperationTask task, String message) {
|
||||
task.setTaskStatus(TaskStatusEnum.FAIL.name());
|
||||
task.setResultMessage(message);
|
||||
task.setFinishedAt(LocalDateTime.now());
|
||||
task.setUpdateTime(LocalDateTime.now());
|
||||
databaseOperationTaskService.updateById(task);
|
||||
}
|
||||
|
||||
private String writeJsonWithoutPassword(DatabaseRestoreParam.CreateParam param) {
|
||||
try {
|
||||
DatabaseRestoreParam.CreateParam copy = new DatabaseRestoreParam.CreateParam();
|
||||
copy.setConnectionId(param.getConnectionId());
|
||||
copy.setBackupFileId(param.getBackupFileId());
|
||||
copy.setRestoreMode(param.getRestoreMode());
|
||||
copy.setTargetSchemaName(param.getTargetSchemaName());
|
||||
copy.setOverwriteConfirmText(param.getOverwriteConfirmText());
|
||||
return objectMapper.writeValueAsString(copy);
|
||||
} catch (Exception exception) {
|
||||
throw new BusinessException(CommonResponseEnum.JSON_CONVERT_EXCEPTION, exception.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.njcn.gather.systemops.database.util;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.MessageDigest;
|
||||
|
||||
/**
|
||||
* 文件校验工具。
|
||||
*/
|
||||
public final class DatabaseChecksumUtil {
|
||||
|
||||
private DatabaseChecksumUtil() {
|
||||
}
|
||||
|
||||
public static String sha256(Path path) {
|
||||
if (path == null || !Files.exists(path) || Files.isDirectory(path)) {
|
||||
return null;
|
||||
}
|
||||
try (InputStream inputStream = Files.newInputStream(path)) {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
byte[] buffer = new byte[8192];
|
||||
int length;
|
||||
while ((length = inputStream.read(buffer)) != -1) {
|
||||
digest.update(buffer, 0, length);
|
||||
}
|
||||
byte[] bytes = digest.digest();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (byte item : bytes) {
|
||||
builder.append(String.format("%02x", item));
|
||||
}
|
||||
return builder.toString();
|
||||
} catch (Exception exception) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.njcn.gather.systemops.database.util;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* 数据库运维文件名工具。
|
||||
*/
|
||||
public final class DatabaseFileNameUtil {
|
||||
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
|
||||
|
||||
private DatabaseFileNameUtil() {
|
||||
}
|
||||
|
||||
public static String appendTodayWithTask(String fileName, String taskNo) {
|
||||
String datedName = appendDate(fileName, LocalDate.now());
|
||||
int dotIndex = datedName.lastIndexOf('.');
|
||||
if (dotIndex > 0) {
|
||||
return datedName.substring(0, dotIndex) + "_" + taskNo + datedName.substring(dotIndex);
|
||||
}
|
||||
return datedName + "_" + taskNo;
|
||||
}
|
||||
|
||||
private static String appendDate(String fileName, LocalDate date) {
|
||||
if (fileName == null || date == null) {
|
||||
return fileName;
|
||||
}
|
||||
String dateText = DATE_FORMATTER.format(date);
|
||||
int separatorIndex = Math.max(fileName.lastIndexOf('/'), fileName.lastIndexOf('\\'));
|
||||
int dotIndex = fileName.lastIndexOf('.');
|
||||
if (dotIndex > separatorIndex) {
|
||||
return fileName.substring(0, dotIndex) + "_" + dateText + fileName.substring(dotIndex);
|
||||
}
|
||||
return fileName + "_" + dateText;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.njcn.gather.systemops.database.util;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 数据库运维编号工具。
|
||||
*/
|
||||
public final class DatabaseOpsIdUtil {
|
||||
|
||||
private static final DateTimeFormatter TASK_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
|
||||
|
||||
private DatabaseOpsIdUtil() {
|
||||
}
|
||||
|
||||
public static String uuid() {
|
||||
return UUID.randomUUID().toString().replace("-", "");
|
||||
}
|
||||
|
||||
public static String taskNo(String prefix) {
|
||||
return prefix + LocalDateTime.now().format(TASK_FORMATTER);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.njcn.gather.systemops.database.util;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* 数据库运维文件路径工具。
|
||||
*/
|
||||
public final class DatabasePathUtil {
|
||||
|
||||
private DatabasePathUtil() {
|
||||
}
|
||||
|
||||
public static Path normalize(String filePath) {
|
||||
if (StrUtil.isBlank(filePath)) {
|
||||
return null;
|
||||
}
|
||||
return Paths.get(filePath).toAbsolutePath().normalize();
|
||||
}
|
||||
|
||||
public static boolean isUnder(Path path, Path root) {
|
||||
if (path == null || root == null) {
|
||||
return false;
|
||||
}
|
||||
Path normalizedPath = path.toAbsolutePath().normalize();
|
||||
Path normalizedRoot = root.toAbsolutePath().normalize();
|
||||
return normalizedPath.startsWith(normalizedRoot);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user