diff --git a/system-ops/dbms/README.md b/system-ops/dbms/README.md index 6f63251..7d41369 100644 --- a/system-ops/dbms/README.md +++ b/system-ops/dbms/README.md @@ -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_.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` 编译、测试或真实库联调。 diff --git a/system-ops/dbms/pom.xml b/system-ops/dbms/pom.xml index 79df890..46f43a7 100644 --- a/system-ops/dbms/pom.xml +++ b/system-ops/dbms/pom.xml @@ -26,5 +26,14 @@ spingboot2.3.12 2.3.12 + + com.njcn + mybatis-plus + 0.0.1 + + + org.springframework + spring-tx + diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/component/DataPumpCommandExecutor.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/component/DataPumpCommandExecutor.java new file mode 100644 index 0000000..37e27c7 --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/component/DataPumpCommandExecutor.java @@ -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 tableNames) { + List 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 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 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 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 command) { + if (command.size() < 2) { + return String.join(" ", command); + } + List 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; + } +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/component/DatabasePasswordComponent.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/component/DatabasePasswordComponent.java new file mode 100644 index 0000000..079abbd --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/component/DatabasePasswordComponent.java @@ -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("当前环境未确认密码解密方法,请传入临时密码执行本次操作"); + } +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/config/DbmsExecutorConfig.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/config/DbmsExecutorConfig.java new file mode 100644 index 0000000..5fc486a --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/config/DbmsExecutorConfig.java @@ -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(8), + runnable -> { + Thread thread = new Thread(runnable); + thread.setName("dbms-task-" + threadIndex.getAndIncrement()); + return thread; + }, + (runnable, executor) -> log.warn("数据库运维任务线程池已满,拒绝新的任务") + ); + } +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/config/DbmsProperties.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/config/DbmsProperties.java new file mode 100644 index 0000000..a442266 --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/config/DbmsProperties.java @@ -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; + } +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/constant/DatabaseOpsConst.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/constant/DatabaseOpsConst.java new file mode 100644 index 0000000..6a4fbb1 --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/constant/DatabaseOpsConst.java @@ -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() { + } +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/controller/DatabaseBackupController.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/controller/DatabaseBackupController.java new file mode 100644 index 0000000..47f5104 --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/controller/DatabaseBackupController.java @@ -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 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> 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 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> listFiles(@RequestBody @Validated DatabaseBackupParam.FileQueryParam param) { + String methodDescribe = getMethodDescribe("listFiles"); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, databaseBackupFileService.listFiles(param), methodDescribe); + } +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/controller/DatabaseConnectionController.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/controller/DatabaseConnectionController.java new file mode 100644 index 0000000..6330671 --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/controller/DatabaseConnectionController.java @@ -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> 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 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 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 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 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> tables(@RequestBody @Validated DatabaseConnectionParam.TablesParam param) { + String methodDescribe = getMethodDescribe("tables"); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, databaseConnectionService.listTables(param), methodDescribe); + } +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/controller/DatabaseDeleteController.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/controller/DatabaseDeleteController.java new file mode 100644 index 0000000..78bee9e --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/controller/DatabaseDeleteController.java @@ -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 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 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); + } +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/controller/DatabaseRestoreController.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/controller/DatabaseRestoreController.java new file mode 100644 index 0000000..7add150 --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/controller/DatabaseRestoreController.java @@ -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 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 status(@RequestParam("taskId") String taskId) { + String methodDescribe = getMethodDescribe("status"); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, databaseOperationTaskService.getStatus(taskId), methodDescribe); + } +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/mapper/DatabaseBackupFileMapper.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/mapper/DatabaseBackupFileMapper.java new file mode 100644 index 0000000..b7539ea --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/mapper/DatabaseBackupFileMapper.java @@ -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 { +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/mapper/DatabaseConnectionMapper.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/mapper/DatabaseConnectionMapper.java new file mode 100644 index 0000000..48addbc --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/mapper/DatabaseConnectionMapper.java @@ -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 { +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/mapper/DatabaseOperationTaskMapper.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/mapper/DatabaseOperationTaskMapper.java new file mode 100644 index 0000000..83fa09b --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/mapper/DatabaseOperationTaskMapper.java @@ -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 { +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/mapper/DatabaseRestoreRecordMapper.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/mapper/DatabaseRestoreRecordMapper.java new file mode 100644 index 0000000..99b2e33 --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/mapper/DatabaseRestoreRecordMapper.java @@ -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 { +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/enums/BackupModeEnum.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/enums/BackupModeEnum.java new file mode 100644 index 0000000..9671cc6 --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/enums/BackupModeEnum.java @@ -0,0 +1,10 @@ +package com.njcn.gather.systemops.database.pojo.enums; + +/** + * 备份模式。 + */ +public enum BackupModeEnum { + FULL_TABLE, + TIME_RANGE, + SIZE_SPLIT +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/enums/BackupStrategyEnum.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/enums/BackupStrategyEnum.java new file mode 100644 index 0000000..6d51df7 --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/enums/BackupStrategyEnum.java @@ -0,0 +1,9 @@ +package com.njcn.gather.systemops.database.pojo.enums; + +/** + * 备份策略。 + */ +public enum BackupStrategyEnum { + DATA_PUMP, + JDBC_EXPORT +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/enums/FileFormatEnum.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/enums/FileFormatEnum.java new file mode 100644 index 0000000..62ac2b2 --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/enums/FileFormatEnum.java @@ -0,0 +1,10 @@ +package com.njcn.gather.systemops.database.pojo.enums; + +/** + * 备份文件格式。 + */ +public enum FileFormatEnum { + DMP, + SQL, + CSV +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/enums/TaskStatusEnum.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/enums/TaskStatusEnum.java new file mode 100644 index 0000000..f132981 --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/enums/TaskStatusEnum.java @@ -0,0 +1,12 @@ +package com.njcn.gather.systemops.database.pojo.enums; + +/** + * 运维任务状态。 + */ +public enum TaskStatusEnum { + WAITING, + RUNNING, + SUCCESS, + FAIL, + CANCELLED +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/param/DatabaseBackupParam.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/param/DatabaseBackupParam.java new file mode 100644 index 0000000..1e110bf --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/param/DatabaseBackupParam.java @@ -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 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; + } +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/param/DatabaseConnectionParam.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/param/DatabaseConnectionParam.java new file mode 100644 index 0000000..409d4ae --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/param/DatabaseConnectionParam.java @@ -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; + } +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/param/DatabaseDeleteParam.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/param/DatabaseDeleteParam.java new file mode 100644 index 0000000..e094c9b --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/param/DatabaseDeleteParam.java @@ -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; + } +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/param/DatabaseRestoreParam.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/param/DatabaseRestoreParam.java new file mode 100644 index 0000000..bbc93a3 --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/param/DatabaseRestoreParam.java @@ -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; + } +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/po/DatabaseBackupFile.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/po/DatabaseBackupFile.java new file mode 100644 index 0000000..e1d3ec9 --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/po/DatabaseBackupFile.java @@ -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; +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/po/DatabaseConnection.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/po/DatabaseConnection.java new file mode 100644 index 0000000..5da7c57 --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/po/DatabaseConnection.java @@ -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; +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/po/DatabaseOperationTask.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/po/DatabaseOperationTask.java new file mode 100644 index 0000000..6c4cdfd --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/po/DatabaseOperationTask.java @@ -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; +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/po/DatabaseRestoreRecord.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/po/DatabaseRestoreRecord.java new file mode 100644 index 0000000..c247e76 --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/po/DatabaseRestoreRecord.java @@ -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; +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/vo/DatabaseBackupFileVO.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/vo/DatabaseBackupFileVO.java new file mode 100644 index 0000000..00b8aaa --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/vo/DatabaseBackupFileVO.java @@ -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; +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/vo/DatabaseConnectionVO.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/vo/DatabaseConnectionVO.java new file mode 100644 index 0000000..537e0b5 --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/vo/DatabaseConnectionVO.java @@ -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; +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/vo/DatabaseTableVO.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/vo/DatabaseTableVO.java new file mode 100644 index 0000000..996eeef --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/vo/DatabaseTableVO.java @@ -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; +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/vo/DatabaseTaskCreateVO.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/vo/DatabaseTaskCreateVO.java new file mode 100644 index 0000000..97ac094 --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/vo/DatabaseTaskCreateVO.java @@ -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; +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/vo/DatabaseTaskVO.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/vo/DatabaseTaskVO.java new file mode 100644 index 0000000..2745759 --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/vo/DatabaseTaskVO.java @@ -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; +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/vo/DatabaseTestResultVO.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/vo/DatabaseTestResultVO.java new file mode 100644 index 0000000..60aa171 --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/pojo/vo/DatabaseTestResultVO.java @@ -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; +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/service/DatabaseBackupFileService.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/service/DatabaseBackupFileService.java new file mode 100644 index 0000000..1a502c6 --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/service/DatabaseBackupFileService.java @@ -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 { + + Page listFiles(DatabaseBackupParam.FileQueryParam param); + + boolean deleteBackupFile(String backupFileId, String confirmText); + + void validateBackupFileReadable(DatabaseBackupFile backupFile); + + Path resolveManagedPath(DatabaseBackupFile backupFile, String filePath); +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/service/DatabaseConnectionService.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/service/DatabaseConnectionService.java new file mode 100644 index 0000000..84fd418 --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/service/DatabaseConnectionService.java @@ -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 { + + Page listConnections(DatabaseConnectionParam.QueryParam queryParam); + + boolean addConnection(DatabaseConnectionParam param); + + boolean updateConnection(DatabaseConnectionParam.UpdateParam param); + + boolean deleteConnection(DatabaseConnectionParam.DeleteParam param); + + DatabaseTestResultVO testConnection(DatabaseConnectionParam.TestParam param); + + List listTables(DatabaseConnectionParam.TablesParam param); + + DatabaseConnection requireEnabled(String connectionId); + + String resolvePassword(DatabaseConnection connection, String temporaryPassword); +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/service/DatabaseOperationTaskService.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/service/DatabaseOperationTaskService.java new file mode 100644 index 0000000..64f623b --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/service/DatabaseOperationTaskService.java @@ -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 { + + DatabaseTaskCreateVO createBackupTask(DatabaseBackupParam.CreateParam param); + + Page listBackupTasks(DatabaseBackupParam.TaskQueryParam param); + + DatabaseTaskVO getStatus(String taskId); + + boolean deleteTask(String taskId, String confirmText); + + boolean existsRunningTask(String connectionId); +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/service/DatabaseRestoreService.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/service/DatabaseRestoreService.java new file mode 100644 index 0000000..457e32a --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/service/DatabaseRestoreService.java @@ -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 { + + DatabaseTaskCreateVO createRestoreTask(DatabaseRestoreParam.CreateParam param); +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/service/impl/DatabaseBackupFileServiceImpl.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/service/impl/DatabaseBackupFileServiceImpl.java new file mode 100644 index 0000000..4f32ed8 --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/service/impl/DatabaseBackupFileServiceImpl.java @@ -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 implements DatabaseBackupFileService { + + private final DbmsProperties dbmsProperties; + + @Override + public Page listFiles(DatabaseBackupParam.FileQueryParam param) { + DatabaseBackupParam.FileQueryParam query = param == null ? new DatabaseBackupParam.FileQueryParam() : param; + LambdaQueryWrapper 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 page = this.page(new Page<>(PageFactory.getPageNum(query), PageFactory.getPageSize(query)), wrapper); + Page 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; + } +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/service/impl/DatabaseConnectionServiceImpl.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/service/impl/DatabaseConnectionServiceImpl.java new file mode 100644 index 0000000..3faf739 --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/service/impl/DatabaseConnectionServiceImpl.java @@ -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 implements DatabaseConnectionService { + + private final DatabasePasswordComponent databasePasswordComponent; + private final OracleJdbcComponent oracleJdbcComponent; + private final ObjectProvider databaseOperationTaskServiceProvider; + + @Override + public Page listConnections(DatabaseConnectionParam.QueryParam queryParam) { + DatabaseConnectionParam.QueryParam query = queryParam == null ? new DatabaseConnectionParam.QueryParam() : queryParam; + LambdaQueryWrapper 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 page = this.page(new Page<>(PageFactory.getPageNum(query), PageFactory.getPageSize(query)), wrapper); + Page 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 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; + } +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/service/impl/DatabaseOperationTaskServiceImpl.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/service/impl/DatabaseOperationTaskServiceImpl.java new file mode 100644 index 0000000..ade1d6d --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/service/impl/DatabaseOperationTaskServiceImpl.java @@ -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 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 listBackupTasks(DatabaseBackupParam.TaskQueryParam param) { + DatabaseBackupParam.TaskQueryParam query = param == null ? new DatabaseBackupParam.TaskQueryParam() : param; + LambdaQueryWrapper 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 page = this.page(new Page<>(PageFactory.getPageNum(query), PageFactory.getPageSize(query)), wrapper); + Page 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; + } +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/service/impl/DatabaseRestoreServiceImpl.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/service/impl/DatabaseRestoreServiceImpl.java new file mode 100644 index 0000000..1861618 --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/service/impl/DatabaseRestoreServiceImpl.java @@ -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 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()); + } + } +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/util/DatabaseChecksumUtil.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/util/DatabaseChecksumUtil.java new file mode 100644 index 0000000..26a2298 --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/util/DatabaseChecksumUtil.java @@ -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; + } + } +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/util/DatabaseFileNameUtil.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/util/DatabaseFileNameUtil.java new file mode 100644 index 0000000..7b7de9e --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/util/DatabaseFileNameUtil.java @@ -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; + } +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/util/DatabaseOpsIdUtil.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/util/DatabaseOpsIdUtil.java new file mode 100644 index 0000000..f479a58 --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/util/DatabaseOpsIdUtil.java @@ -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); + } +} diff --git a/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/util/DatabasePathUtil.java b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/util/DatabasePathUtil.java new file mode 100644 index 0000000..39fd5f7 --- /dev/null +++ b/system-ops/dbms/src/main/java/com/njcn/gather/systemops/database/util/DatabasePathUtil.java @@ -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); + } +} diff --git a/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/controller/AddLedgerController.java b/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/controller/AddLedgerController.java index 1ec5e3d..510d5c5 100644 --- a/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/controller/AddLedgerController.java +++ b/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/controller/AddLedgerController.java @@ -6,10 +6,13 @@ 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.common.utils.LogUtil; +import com.njcn.gather.tool.addledger.pojo.param.AddDeviceUnitSaveParam; import com.njcn.gather.tool.addledger.pojo.param.AddLedgerEngineeringSaveParam; import com.njcn.gather.tool.addledger.pojo.param.AddLedgerEquipmentSaveParam; import com.njcn.gather.tool.addledger.pojo.param.AddLedgerLineSaveParam; import com.njcn.gather.tool.addledger.pojo.param.AddLedgerProjectSaveParam; +import com.njcn.gather.tool.addledger.pojo.po.AddOverlimitPO; +import com.njcn.gather.tool.addledger.pojo.vo.AddDeviceUnitVO; import com.njcn.gather.tool.addledger.pojo.vo.AddLedgerDetailVO; import com.njcn.gather.tool.addledger.pojo.vo.AddLedgerTreeNodeVO; import com.njcn.gather.tool.addledger.service.AddLedgerService; @@ -93,6 +96,26 @@ public class AddLedgerController extends BaseController { return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); } + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("查询设备单位") + @GetMapping("/equipment/unit") + public HttpResult getDeviceUnit(@RequestParam("devId") String devId) { + String methodDescribe = getMethodDescribe("getDeviceUnit"); + LogUtil.njcnDebug(log, "{},开始查询设备单位,devId={}", methodDescribe, devId); + AddDeviceUnitVO result = addLedgerService.getDeviceUnit(devId); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.UPDATE) + @ApiOperation("保存设备单位") + @PostMapping("/equipment/unit/save") + public HttpResult saveDeviceUnit(@RequestBody @Validated AddDeviceUnitSaveParam param) { + String methodDescribe = getMethodDescribe("saveDeviceUnit"); + LogUtil.njcnDebug(log, "{},开始保存设备单位,devId={}", methodDescribe, param.getDevId()); + AddDeviceUnitVO result = addLedgerService.saveDeviceUnit(param); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + @OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.ADD) @ApiOperation("新增或保存测点") @PostMapping("/line/save") @@ -103,6 +126,16 @@ public class AddLedgerController extends BaseController { return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); } + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("查询监测点限值") + @GetMapping("/line/overlimit") + public HttpResult getLineOverlimit(@RequestParam("lineId") String lineId) { + String methodDescribe = getMethodDescribe("getLineOverlimit"); + LogUtil.njcnDebug(log, "{},开始查询监测点限值,lineId={}", methodDescribe, lineId); + AddOverlimitPO result = addLedgerService.getLineOverlimit(lineId); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + @OperateInfo(info = LogEnum.BUSINESS_COMMON) @ApiOperation("查询设备可用线路号") @GetMapping("/line/availableLineNos") diff --git a/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/mapper/AddDeviceUnitMapper.java b/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/mapper/AddDeviceUnitMapper.java new file mode 100644 index 0000000..bdc3f92 --- /dev/null +++ b/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/mapper/AddDeviceUnitMapper.java @@ -0,0 +1,10 @@ +package com.njcn.gather.tool.addledger.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.gather.tool.addledger.pojo.po.AddDeviceUnit; + +/** + * 设备单位 Mapper。 + */ +public interface AddDeviceUnitMapper extends BaseMapper { +} diff --git a/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/mapper/AddOverlimitMapper.java b/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/mapper/AddOverlimitMapper.java new file mode 100644 index 0000000..5f5a379 --- /dev/null +++ b/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/mapper/AddOverlimitMapper.java @@ -0,0 +1,10 @@ +package com.njcn.gather.tool.addledger.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.gather.tool.addledger.pojo.po.AddOverlimitPO; + +/** + * 监测点限值 Mapper。 + */ +public interface AddOverlimitMapper extends BaseMapper { +} diff --git a/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/pojo/constant/AddLedgerConst.java b/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/pojo/constant/AddLedgerConst.java index 6d3b017..92f1487 100644 --- a/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/pojo/constant/AddLedgerConst.java +++ b/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/pojo/constant/AddLedgerConst.java @@ -33,12 +33,19 @@ public final class AddLedgerConst { public static final int LINE_RUN_STATUS_RUNNING = 0; public static final int LINE_INTERVAL_DEFAULT = 1; public static final String LOG_LEVEL_WARN = "WARN"; + public static final int LINE_TYPE_MAIN = 0; + public static final int LINE_TYPE_DISTRIBUTION = 1; public static final int MIN_LINE_NO = 1; public static final int MAX_LINE_NO = 20; public static final Set CON_TYPES = new LinkedHashSet(Arrays.asList(0, 1, 2)); + public static final Set LINE_TYPES = new LinkedHashSet(Arrays.asList( + LINE_TYPE_MAIN, + LINE_TYPE_DISTRIBUTION + )); + public static final Set VOL_GRADES = new LinkedHashSet(Arrays.asList( new BigDecimal("0.38"), new BigDecimal("10"), diff --git a/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/pojo/param/AddDeviceUnitSaveParam.java b/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/pojo/param/AddDeviceUnitSaveParam.java new file mode 100644 index 0000000..8ff1782 --- /dev/null +++ b/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/pojo/param/AddDeviceUnitSaveParam.java @@ -0,0 +1,91 @@ +package com.njcn.gather.tool.addledger.pojo.param; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * 设备单位保存参数。 + */ +@Data +@ApiModel("设备单位保存参数") +public class AddDeviceUnitSaveParam { + + @ApiModelProperty(value = "设备ID", required = true) + @NotBlank(message = "设备 ID 不能为空") + private String devId; + + @ApiModelProperty("频率") + private String unitFrequency; + + @ApiModelProperty("频率偏差") + private String unitFrequencyDev; + + @ApiModelProperty("相电压有效值") + private String phaseVoltage; + + @ApiModelProperty("线电压有效值") + private String lineVoltage; + + @ApiModelProperty("电压上偏差") + private String voltageDev; + + @ApiModelProperty("电压下偏差") + private String uvoltageDev; + + @ApiModelProperty("电流有效值") + private String ieffective; + + @ApiModelProperty("单相有功功率") + private String singleP; + + @ApiModelProperty("单相视在功率") + private String singleViewP; + + @ApiModelProperty("单相无功功率") + private String singleNoP; + + @ApiModelProperty("总有功功率") + private String totalActiveP; + + @ApiModelProperty("总视在功率") + private String totalViewP; + + @ApiModelProperty("总无功功率") + private String totalNoP; + + @ApiModelProperty("相线电压基波有效值") + private String vfundEffective; + + @ApiModelProperty("基波电流") + private String ifund; + + @ApiModelProperty("基波有功功率") + private String fundActiveP; + + @ApiModelProperty("基波无功功率") + private String fundNoP; + + @ApiModelProperty("电压总谐波畸变率") + private String vdistortion; + + @ApiModelProperty("2-50次谐波电压含有率") + private String vharmonicRate; + + @ApiModelProperty("2-50次谐波电流有效值") + private String iharmonic; + + @ApiModelProperty("2-50次谐波有功功率") + private String pharmonic; + + @ApiModelProperty("0.5-49.5次间谐波电流有效值") + private String iiharmonic; + + @ApiModelProperty("正序电压") + private String positiveV; + + @ApiModelProperty("零序负序电压") + private String noPositiveV; +} diff --git a/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/pojo/param/AddLedgerLineSaveParam.java b/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/pojo/param/AddLedgerLineSaveParam.java index 2617baa..1a22732 100644 --- a/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/pojo/param/AddLedgerLineSaveParam.java +++ b/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/pojo/param/AddLedgerLineSaveParam.java @@ -81,6 +81,9 @@ public class AddLedgerLineSaveParam { @DecimalMin(value = "0", inclusive = true, message = "protocol_capacity 不能为负数") private BigDecimal protocolCapacity; + @ApiModelProperty("线路类型:0 主网,1 配网") + private Integer lineType; + @ApiModelProperty("监测对象类型") private String monitorObj; diff --git a/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/pojo/po/AddDeviceUnit.java b/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/pojo/po/AddDeviceUnit.java new file mode 100644 index 0000000..ff97dec --- /dev/null +++ b/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/pojo/po/AddDeviceUnit.java @@ -0,0 +1,118 @@ +package com.njcn.gather.tool.addledger.pojo.po; + + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 数据单位管理表。 + */ +@Data +@TableName("cs_device_unit") +public class AddDeviceUnit { + + private static final long serialVersionUID = 1L; + + @TableId(value = "dev_id") + @ApiModelProperty(value = "终端id") + private String devId; + + @TableField("unit_frequency") + @ApiModelProperty(value = "频率") + private String unitFrequency = "Hz"; + + @TableField("unit_frequency_dev") + @ApiModelProperty(value = "频率偏差") + private String unitFrequencyDev = "Hz"; + + @TableField("phase_voltage") + @ApiModelProperty(value = "相电压有效值") + private String phaseVoltage = "kV"; + + @TableField("line_voltage") + @ApiModelProperty(value = "线电压有效值") + private String lineVoltage = "kV"; + + @TableField("voltage_dev") + @ApiModelProperty(value = "电压上偏差") + private String voltageDev = "%"; + + @TableField("uvoltage_dev") + @ApiModelProperty(value = "电压下偏差") + private String uvoltageDev = "%"; + + @TableField("i_effective") + @ApiModelProperty(value = "电流有效值") + private String ieffective = "A"; + + @TableField("single_p") + @ApiModelProperty(value = "单相有功功率") + private String singleP = "kW"; + + @TableField("single_view_p") + @ApiModelProperty(value = "单相视在功率") + private String singleViewP = "kVA"; + + @TableField("single_no_p") + @ApiModelProperty(value = "单相无功功率") + private String singleNoP = "kVar"; + + @TableField("total_active_p") + @ApiModelProperty(value = "总有功功率") + private String totalActiveP = "kW"; + + @TableField("total_view_p") + @ApiModelProperty(value = "总视在功率") + private String totalViewP = "kVA"; + + @TableField("total_no_p") + @ApiModelProperty(value = "总无功功率") + private String totalNoP = "kVar"; + + @TableField("v_fund_effective") + @ApiModelProperty(value = "相(线)电压基波有效值") + private String vfundEffective = "kV"; + + @TableField("i_fund") + @ApiModelProperty(value = "基波电流") + private String ifund = "A"; + + @TableField("fund_active_p") + @ApiModelProperty(value = "基波有功功率") + private String fundActiveP = "kW"; + + @TableField("fund_no_p") + @ApiModelProperty(value = "基波无功功率") + private String fundNoP = "kVar"; + + @TableField("v_distortion") + @ApiModelProperty(value = "电压总谐波畸变率") + private String vdistortion = "%"; + + @TableField("v_harmonic_rate") + @ApiModelProperty(value = "2~50次谐波电压含有率") + private String vharmonicRate = "%"; + + @TableField("i_harmonic") + @ApiModelProperty(value = "2~50次谐波电流有效值") + private String iharmonic = "A"; + + @TableField("p_harmonic") + @ApiModelProperty(value = "2~50次谐波有功功率") + private String pharmonic = "kW"; + + @TableField("i_iharmonic") + @ApiModelProperty(value = "0.5~49.5次间谐波电流有效值") + private String iiharmonic = "A"; + + @TableField("positive_v") + @ApiModelProperty(value = "正序电压") + private String positiveV = "kV"; + + @TableField("no_positive_v") + @ApiModelProperty(value = "零序负序电压") + private String noPositiveV = "V"; +} diff --git a/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/pojo/po/AddLedgerLinePO.java b/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/pojo/po/AddLedgerLinePO.java index b3e9a78..3454af6 100644 --- a/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/pojo/po/AddLedgerLinePO.java +++ b/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/pojo/po/AddLedgerLinePO.java @@ -64,6 +64,12 @@ public class AddLedgerLinePO extends BaseEntity { private BigDecimal protocolCapacity; + /** + * 线路类型:0 主网,1 配网。 + */ + @TableField(exist = false) + private Integer lineType; + private String monitorObj; private Integer isGovern; diff --git a/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/pojo/po/AddOverlimitPO.java b/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/pojo/po/AddOverlimitPO.java new file mode 100644 index 0000000..49a3a63 --- /dev/null +++ b/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/pojo/po/AddOverlimitPO.java @@ -0,0 +1,869 @@ +package com.njcn.gather.tool.addledger.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * 越限阈值配置表。 + */ +@Data +@TableName("cs_overlimit") +public class AddOverlimitPO { + + private static final long serialVersionUID = 1L; + + /** + * 监测点序号 + */ + @TableId("line_id") + private String id; + + /** + * 频率限值 + */ + private Float freqDev; + + /** + * 电压波动 + */ + private Float voltageFluctuation; + + /** + * 电压上偏差限值 + */ + private Float voltageDev; + + /** + * 电压下偏差限值 + */ + private Float uvoltageDev; + + /** + * 三相电压不平衡度限值 + */ + private Float ubalance; + + /** + * 短时电压不平衡度限值 + */ + private Float shortUbalance; + + /** + * 闪变限值 + */ + private Float flicker; + + /** + * 电压总谐波畸变率限值 + */ + private Float uaberrance; + + /** + * 负序电流限值 + */ + private Float iNeg; + + /** + * 2次谐波电压限值 + */ + @TableField("uharm_2") + private Float uharm2; + + /** + * 3次谐波电压限值 + */ + @TableField("uharm_3") + private Float uharm3; + + /** + * 4次谐波电压限值 + */ + @TableField("uharm_4") + private Float uharm4; + + /** + * 5次谐波电压限值 + */ + @TableField("uharm_5") + private Float uharm5; + + /** + * 6次谐波电压限值 + */ + @TableField("uharm_6") + private Float uharm6; + + /** + * 7次谐波电压限值 + */ + @TableField("uharm_7") + private Float uharm7; + + /** + * 8次谐波电压限值 + */ + @TableField("uharm_8") + private Float uharm8; + + /** + * 9次谐波电压限值 + */ + @TableField("uharm_9") + private Float uharm9; + + /** + * 10次谐波电压限值 + */ + @TableField("uharm_10") + private Float uharm10; + + /** + * 11次谐波电压限值 + */ + @TableField("uharm_11") + private Float uharm11; + + /** + * 12次谐波电压限值 + */ + @TableField("uharm_12") + private Float uharm12; + + /** + * 13次谐波电压限值 + */ + @TableField("uharm_13") + private Float uharm13; + + /** + * 14次谐波电压限值 + */ + @TableField("uharm_14") + private Float uharm14; + + /** + * 15次谐波电压限值 + */ + @TableField("uharm_15") + private Float uharm15; + + /** + * 16次谐波电压限值 + */ + @TableField("uharm_16") + private Float uharm16; + + /** + * 17次谐波电压限值 + */ + @TableField("uharm_17") + private Float uharm17; + + /** + * 18次谐波电压限值 + */ + @TableField("uharm_18") + private Float uharm18; + + /** + * 19次谐波电压限值 + */ + @TableField("uharm_19") + private Float uharm19; + + /** + * 20次谐波电压限值 + */ + @TableField("uharm_20") + private Float uharm20; + + /** + * 21次谐波电压限值 + */ + @TableField("uharm_21") + private Float uharm21; + + /** + * 22次谐波电压限值 + */ + @TableField("uharm_22") + private Float uharm22; + + /** + * 23次谐波电压限值 + */ + @TableField("uharm_23") + private Float uharm23; + + /** + * 24次谐波电压限值 + */ + @TableField("uharm_24") + private Float uharm24; + + /** + * 25次谐波电压限值 + */ + @TableField("uharm_25") + private Float uharm25; + + /** + * 2次谐波电压限值 + */ + @TableField("uharm_26") + private Float uharm26; + + /** + * 3次谐波电压限值 + */ + @TableField("uharm_27") + private Float uharm27; + + /** + * 4次谐波电压限值 + */ + @TableField("uharm_28") + private Float uharm28; + + /** + * 5次谐波电压限值 + */ + @TableField("uharm_29") + private Float uharm29; + + /** + * 6次谐波电压限值 + */ + @TableField("uharm_30") + private Float uharm30; + + /** + * 7次谐波电压限值 + */ + @TableField("uharm_31") + private Float uharm31; + + /** + * 8次谐波电压限值 + */ + @TableField("uharm_32") + private Float uharm32; + + /** + * 9次谐波电压限值 + */ + @TableField("uharm_33") + private Float uharm33; + + /** + * 10次谐波电压限值 + */ + @TableField("uharm_34") + private Float uharm34; + + /** + * 11次谐波电压限值 + */ + @TableField("uharm_35") + private Float uharm35; + + /** + * 12次谐波电压限值 + */ + @TableField("uharm_36") + private Float uharm36; + + /** + * 13次谐波电压限值 + */ + @TableField("uharm_37") + private Float uharm37; + + /** + * 14次谐波电压限值 + */ + @TableField("uharm_38") + private Float uharm38; + + /** + * 15次谐波电压限值 + */ + @TableField("uharm_39") + private Float uharm39; + + /** + * 16次谐波电压限值 + */ + @TableField("uharm_40") + private Float uharm40; + + /** + * 17次谐波电压限值 + */ + @TableField("uharm_41") + private Float uharm41; + + /** + * 18次谐波电压限值 + */ + @TableField("uharm_42") + private Float uharm42; + + /** + * 19次谐波电压限值 + */ + @TableField("uharm_43") + private Float uharm43; + + /** + * 20次谐波电压限值 + */ + @TableField("uharm_44") + private Float uharm44; + + /** + * 21次谐波电压限值 + */ + @TableField("uharm_45") + private Float uharm45; + + /** + * 22次谐波电压限值 + */ + @TableField("uharm_46") + private Float uharm46; + + /** + * 23次谐波电压限值 + */ + @TableField("uharm_47") + private Float uharm47; + + /** + * 24次谐波电压限值 + */ + @TableField("uharm_48") + private Float uharm48; + + /** + * 25次谐波电压限值 + */ + @TableField("uharm_49") + private Float uharm49; + + /** + * 50次谐波电压限值 + */ + @TableField("uharm_50") + private Float uharm50; + + + + /** + * 2次谐波电流限值 + */ + @TableField("iharm_2") + private Float iharm2; + + /** + * 3次谐波电流限值 + */ + @TableField("iharm_3") + private Float iharm3; + + /** + * 4次谐波电流限值 + */ + @TableField("iharm_4") + private Float iharm4; + + /** + * 5次谐波电流限值 + */ + @TableField("iharm_5") + private Float iharm5; + + /** + * 6次谐波电流限值 + */ + @TableField("iharm_6") + private Float iharm6; + + /** + * 7次谐波电流限值 + */ + @TableField("iharm_7") + private Float iharm7; + + /** + * 8次谐波电流限值 + */ + @TableField("iharm_8") + private Float iharm8; + + /** + * 9次谐波电流限值 + */ + @TableField("iharm_9") + private Float iharm9; + + /** + * 10次谐波电流限值 + */ + @TableField("iharm_10") + private Float iharm10; + + /** + * 11次谐波电流限值 + */ + @TableField("iharm_11") + private Float iharm11; + + /** + * 12次谐波电流限值 + */ + @TableField("iharm_12") + private Float iharm12; + + /** + * 13次谐波电流限值 + */ + @TableField("iharm_13") + private Float iharm13; + + /** + * 14次谐波电流限值 + */ + @TableField("iharm_14") + private Float iharm14; + + /** + * 15次谐波电流限值 + */ + @TableField("iharm_15") + private Float iharm15; + + /** + * 16次谐波电流限值 + */ + @TableField("iharm_16") + private Float iharm16; + + /** + * 17次谐波电流限值 + */ + @TableField("iharm_17") + private Float iharm17; + + /** + * 18次谐波电流限值 + */ + @TableField("iharm_18") + private Float iharm18; + + /** + * 19次谐波电流限值 + */ + @TableField("iharm_19") + private Float iharm19; + + /** + * 20次谐波电流限值 + */ + @TableField("iharm_20") + private Float iharm20; + + /** + * 21次谐波电流限值 + */ + @TableField("iharm_21") + private Float iharm21; + + /** + * 22次谐波电流限值 + */ + @TableField("iharm_22") + private Float iharm22; + + /** + * 23次谐波电流限值 + */ + @TableField("iharm_23") + private Float iharm23; + + /** + * 24次谐波电流限值 + */ + @TableField("iharm_24") + private Float iharm24; + + /** + * 25次谐波电流限值 + */ + @TableField("iharm_25") + private Float iharm25; + + /** + * 2次谐波电压限值 + */ + @TableField("iharm_26") + private Float iharm26; + + /** + * 3次谐波电压限值 + */ + @TableField("iharm_27") + private Float iharm27; + + /** + * 4次谐波电压限值 + */ + @TableField("iharm_28") + private Float iharm28; + + /** + * 5次谐波电压限值 + */ + @TableField("iharm_29") + private Float iharm29; + + /** + * 6次谐波电压限值 + */ + @TableField("iharm_30") + private Float iharm30; + + /** + * 7次谐波电压限值 + */ + @TableField("iharm_31") + private Float iharm31; + + /** + * 8次谐波电压限值 + */ + @TableField("iharm_32") + private Float iharm32; + + /** + * 9次谐波电压限值 + */ + @TableField("iharm_33") + private Float iharm33; + + /** + * 10次谐波电压限值 + */ + @TableField("iharm_34") + private Float iharm34; + + /** + * 11次谐波电压限值 + */ + @TableField("iharm_35") + private Float iharm35; + + /** + * 12次谐波电压限值 + */ + @TableField("iharm_36") + private Float iharm36; + + /** + * 13次谐波电压限值 + */ + @TableField("iharm_37") + private Float iharm37; + + /** + * 14次谐波电压限值 + */ + @TableField("iharm_38") + private Float iharm38; + + /** + * 15次谐波电压限值 + */ + @TableField("iharm_39") + private Float iharm39; + + /** + * 16次谐波电压限值 + */ + @TableField("iharm_40") + private Float iharm40; + + /** + * 17次谐波电压限值 + */ + @TableField("iharm_41") + private Float iharm41; + + /** + * 18次谐波电压限值 + */ + @TableField("iharm_42") + private Float iharm42; + + /** + * 19次谐波电压限值 + */ + @TableField("iharm_43") + private Float iharm43; + + /** + * 20次谐波电压限值 + */ + @TableField("iharm_44") + private Float iharm44; + + /** + * 21次谐波电压限值 + */ + @TableField("iharm_45") + private Float iharm45; + + /** + * 22次谐波电压限值 + */ + @TableField("iharm_46") + private Float iharm46; + + /** + * 23次谐波电压限值 + */ + @TableField("iharm_47") + private Float iharm47; + + /** + * 24次谐波电压限值 + */ + @TableField("iharm_48") + private Float iharm48; + + /** + * 25次谐波电压限值 + */ + @TableField("iharm_49") + private Float iharm49; + + /** + * 50次谐波电压限值 + */ + @TableField("iharm_50") + private Float iharm50; + + + + /** + * 0.5次间谐波电压限值 + */ + @TableField("inuharm_1") + private Float inuharm1; + + /** + * 1.5次间谐波电压限值 + */ + @TableField("inuharm_2") + private Float inuharm2; + + /** + * 2.5次间谐波电压限值 + */ + @TableField("inuharm_3") + private Float inuharm3; + + /** + * 3.5次间谐波电压限值 + */ + @TableField("inuharm_4") + private Float inuharm4; + + /** + * 4.5次间谐波电压限值 + */ + @TableField("inuharm_5") + private Float inuharm5; + + /** + * 5.5次间谐波电压限值 + */ + @TableField("inuharm_6") + private Float inuharm6; + + /** + * 6.5次间谐波电压限值 + */ + @TableField("inuharm_7") + private Float inuharm7; + + /** + * 7.5次间谐波电压限值 + */ + @TableField("inuharm_8") + private Float inuharm8; + + /** + * 8.5次间谐波电压限值 + */ + @TableField("inuharm_9") + private Float inuharm9; + + /** + * 9.5次间谐波电压限值 + */ + @TableField("inuharm_10") + private Float inuharm10; + + /** + * 10.5次间谐波电压限值 + */ + @TableField("inuharm_11") + private Float inuharm11; + + /** + * 11.5次间谐波电压限值 + */ + @TableField("inuharm_12") + private Float inuharm12; + + /** + * 12.5次间谐波电压限值 + */ + @TableField("inuharm_13") + private Float inuharm13; + + /** + * 13.5次间谐波电压限值 + */ + @TableField("inuharm_14") + private Float inuharm14; + + /** + * 14.5次间谐波电压限值 + */ + @TableField("inuharm_15") + private Float inuharm15; + + /** + * 15.5次间谐波电压限值 + */ + @TableField("inuharm_16") + private Float inuharm16; + + public AddOverlimitPO(){} + + + + public void buildIHarm(Float[] iHarmTem){ + this.iharm2= iHarmTem[0]; + this.iharm4= iHarmTem[2]; + this.iharm6= iHarmTem[4]; + this.iharm8= iHarmTem[6]; + this.iharm10= iHarmTem[8]; + this.iharm12= iHarmTem[10]; + this.iharm14= iHarmTem[12]; + this.iharm16= iHarmTem[14]; + this.iharm18= iHarmTem[16]; + this.iharm20= iHarmTem[18]; + this.iharm22= iHarmTem[20]; + this.iharm24= iHarmTem[22]; + this.iharm26= iHarmTem[24]; + this.iharm28= iHarmTem[26]; + this.iharm30= iHarmTem[28]; + this.iharm32= iHarmTem[30]; + this.iharm34= iHarmTem[32]; + this.iharm36= iHarmTem[34]; + this.iharm38= iHarmTem[36]; + this.iharm40= iHarmTem[38]; + this.iharm42= iHarmTem[40]; + this.iharm44= iHarmTem[42]; + this.iharm46= iHarmTem[44]; + this.iharm48= iHarmTem[46]; + this.iharm50= iHarmTem[48]; + + + + this.iharm3= iHarmTem[1]; + this.iharm5= iHarmTem[3]; + this.iharm7= iHarmTem[5]; + this.iharm9= iHarmTem[7]; + this.iharm11= iHarmTem[9]; + this.iharm13= iHarmTem[11]; + this.iharm15= iHarmTem[13]; + this.iharm17= iHarmTem[15]; + this.iharm19= iHarmTem[17]; + this.iharm21= iHarmTem[19]; + this.iharm23= iHarmTem[21]; + this.iharm25= iHarmTem[23]; + this.iharm27= iHarmTem[25]; + this.iharm29= iHarmTem[27]; + this.iharm31= iHarmTem[29]; + this.iharm33= iHarmTem[31]; + this.iharm35= iHarmTem[33]; + this.iharm37= iHarmTem[35]; + this.iharm39= iHarmTem[37]; + this.iharm41= iHarmTem[39]; + this.iharm43= iHarmTem[41]; + this.iharm45= iHarmTem[43]; + this.iharm47= iHarmTem[45]; + this.iharm49= iHarmTem[47]; + } + + public void buildUharm(Float resultEven,Float resultOdd){ + this.uharm2=resultEven; + this.uharm4=resultEven; + this.uharm6=resultEven; + this.uharm8=resultEven; + this.uharm10=resultEven; + this.uharm12=resultEven; + this.uharm14=resultEven; + this.uharm16=resultEven; + this.uharm18=resultEven; + this.uharm20=resultEven; + this.uharm22=resultEven; + this.uharm24=resultEven; + this.uharm26=resultEven; + this.uharm28=resultEven; + this.uharm30=resultEven; + this.uharm32=resultEven; + this.uharm34=resultEven; + this.uharm36=resultEven; + this.uharm38=resultEven; + this.uharm40=resultEven; + this.uharm42=resultEven; + this.uharm44=resultEven; + this.uharm46=resultEven; + this.uharm48=resultEven; + this.uharm50=resultEven; + + + this.uharm3=resultOdd; + this.uharm5=resultOdd; + this.uharm7=resultOdd; + this.uharm9=resultOdd; + this.uharm11=resultOdd; + this.uharm13=resultOdd; + this.uharm15=resultOdd; + this.uharm17=resultOdd; + this.uharm19=resultOdd; + this.uharm21=resultOdd; + this.uharm23=resultOdd; + this.uharm25=resultOdd; + this.uharm27=resultOdd; + this.uharm29=resultOdd; + this.uharm31=resultOdd; + this.uharm33=resultOdd; + this.uharm35=resultOdd; + this.uharm37=resultOdd; + this.uharm39=resultOdd; + this.uharm41=resultOdd; + this.uharm43=resultOdd; + this.uharm45=resultOdd; + this.uharm47=resultOdd; + this.uharm49=resultOdd; + } + +} diff --git a/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/pojo/vo/AddDeviceUnitVO.java b/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/pojo/vo/AddDeviceUnitVO.java new file mode 100644 index 0000000..a4ba429 --- /dev/null +++ b/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/pojo/vo/AddDeviceUnitVO.java @@ -0,0 +1,60 @@ +package com.njcn.gather.tool.addledger.pojo.vo; + +import lombok.Data; + +/** + * 设备单位配置。 + */ +@Data +public class AddDeviceUnitVO { + + private String devId; + + private String unitFrequency; + + private String unitFrequencyDev; + + private String phaseVoltage; + + private String lineVoltage; + + private String voltageDev; + + private String uvoltageDev; + + private String ieffective; + + private String singleP; + + private String singleViewP; + + private String singleNoP; + + private String totalActiveP; + + private String totalViewP; + + private String totalNoP; + + private String vfundEffective; + + private String ifund; + + private String fundActiveP; + + private String fundNoP; + + private String vdistortion; + + private String vharmonicRate; + + private String iharmonic; + + private String pharmonic; + + private String iiharmonic; + + private String positiveV; + + private String noPositiveV; +} diff --git a/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/pojo/vo/AddLedgerDetailVO.java b/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/pojo/vo/AddLedgerDetailVO.java index b1611f2..f6cf799 100644 --- a/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/pojo/vo/AddLedgerDetailVO.java +++ b/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/pojo/vo/AddLedgerDetailVO.java @@ -1,5 +1,6 @@ package com.njcn.gather.tool.addledger.pojo.vo; +import com.njcn.gather.tool.addledger.pojo.po.AddOverlimitPO; import lombok.Data; import java.math.BigDecimal; @@ -70,6 +71,10 @@ public class AddLedgerDetailVO { private BigDecimal protocolCapacity; + private Integer lineType; + + private AddOverlimitPO overlimit; + private String monitorObj; private Integer isGovern; diff --git a/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/service/AddLedgerService.java b/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/service/AddLedgerService.java index e3ceb3c..71437cb 100644 --- a/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/service/AddLedgerService.java +++ b/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/service/AddLedgerService.java @@ -1,10 +1,13 @@ package com.njcn.gather.tool.addledger.service; +import com.njcn.gather.tool.addledger.pojo.param.AddDeviceUnitSaveParam; import com.njcn.gather.tool.addledger.pojo.param.AddLedgerEngineeringSaveParam; import com.njcn.gather.tool.addledger.pojo.param.AddLedgerEquipmentSaveParam; import com.njcn.gather.tool.addledger.pojo.param.AddLedgerLinePathQueryParam; import com.njcn.gather.tool.addledger.pojo.param.AddLedgerLineSaveParam; import com.njcn.gather.tool.addledger.pojo.param.AddLedgerProjectSaveParam; +import com.njcn.gather.tool.addledger.pojo.po.AddOverlimitPO; +import com.njcn.gather.tool.addledger.pojo.vo.AddDeviceUnitVO; import com.njcn.gather.tool.addledger.pojo.vo.AddLedgerDetailVO; import com.njcn.gather.tool.addledger.pojo.vo.AddLedgerLinePathVO; import com.njcn.gather.tool.addledger.pojo.vo.AddLedgerTreeNodeVO; @@ -27,8 +30,14 @@ public interface AddLedgerService { AddLedgerDetailVO saveEquipment(AddLedgerEquipmentSaveParam param); + AddDeviceUnitVO getDeviceUnit(String devId); + + AddDeviceUnitVO saveDeviceUnit(AddDeviceUnitSaveParam param); + AddLedgerDetailVO saveLine(AddLedgerLineSaveParam param); + AddOverlimitPO getLineOverlimit(String lineId); + List availableLineNos(String deviceId, String lineId); Map listLinePathByLineIds(List lineIds); diff --git a/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/service/impl/AddLedgerServiceImpl.java b/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/service/impl/AddLedgerServiceImpl.java index 3ef72b2..cb3cd65 100644 --- a/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/service/impl/AddLedgerServiceImpl.java +++ b/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/service/impl/AddLedgerServiceImpl.java @@ -7,23 +7,30 @@ import com.njcn.gather.tool.addledger.mapper.AddLedgerEquipmentMapper; import com.njcn.gather.tool.addledger.mapper.AddLedgerLedgerMapper; import com.njcn.gather.tool.addledger.mapper.AddLedgerLineMapper; import com.njcn.gather.tool.addledger.mapper.AddLedgerProjectMapper; +import com.njcn.gather.tool.addledger.mapper.AddDeviceUnitMapper; +import com.njcn.gather.tool.addledger.mapper.AddOverlimitMapper; import com.njcn.gather.tool.addledger.pojo.constant.AddLedgerConst; +import com.njcn.gather.tool.addledger.pojo.param.AddDeviceUnitSaveParam; import com.njcn.gather.tool.addledger.pojo.param.AddLedgerEngineeringSaveParam; import com.njcn.gather.tool.addledger.pojo.param.AddLedgerEquipmentSaveParam; import com.njcn.gather.tool.addledger.pojo.param.AddLedgerLinePathQueryParam; import com.njcn.gather.tool.addledger.pojo.param.AddLedgerLineSaveParam; import com.njcn.gather.tool.addledger.pojo.param.AddLedgerProjectSaveParam; +import com.njcn.gather.tool.addledger.pojo.po.AddDeviceUnit; import com.njcn.gather.tool.addledger.pojo.po.AddLedgerEngineeringPO; import com.njcn.gather.tool.addledger.pojo.po.AddLedgerEquipmentPO; import com.njcn.gather.tool.addledger.pojo.po.AddLedgerLedgerPO; import com.njcn.gather.tool.addledger.pojo.po.AddLedgerLinePO; import com.njcn.gather.tool.addledger.pojo.po.AddLedgerProjectPO; +import com.njcn.gather.tool.addledger.pojo.po.AddOverlimitPO; +import com.njcn.gather.tool.addledger.pojo.vo.AddDeviceUnitVO; import com.njcn.gather.tool.addledger.pojo.vo.AddLedgerDetailVO; import com.njcn.gather.tool.addledger.pojo.vo.AddLedgerLinePathVO; import com.njcn.gather.tool.addledger.pojo.vo.AddLedgerTreeNodeVO; import com.njcn.gather.tool.addledger.service.AddLedgerService; import com.njcn.gather.tool.addledger.util.AddLedgerIdUtil; import com.njcn.gather.tool.addledger.util.AddLedgerLineNoUtil; +import com.njcn.gather.tool.addledger.util.COverlimitUtil; import com.njcn.web.utils.RequestUtil; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -51,6 +58,8 @@ public class AddLedgerServiceImpl implements AddLedgerService { private final AddLedgerEquipmentMapper equipmentMapper; private final AddLedgerLineMapper lineMapper; private final AddLedgerLedgerMapper ledgerMapper; + private final AddDeviceUnitMapper deviceUnitMapper; + private final AddOverlimitMapper overlimitMapper; private final AddLedgerTreeBuilder treeBuilder; @Override @@ -167,6 +176,7 @@ public class AddLedgerServiceImpl implements AddLedgerService { equipment.setUsageStatus(AddLedgerConst.ENABLE); equipment.setSort(0); equipmentMapper.insert(equipment); + saveDefaultDeviceUnit(id); saveLedger(id, projectLedger.getId(), buildChildPids(projectLedger), equipment.getName(), AddLedgerConst.LEVEL_EQUIPMENT); } else { equipmentMapper.updateById(equipment); @@ -175,6 +185,39 @@ public class AddLedgerServiceImpl implements AddLedgerService { return detail(id, AddLedgerConst.LEVEL_EQUIPMENT); } + @Override + public AddDeviceUnitVO getDeviceUnit(String devId) { + String deviceId = requireText(devId, "设备 ID 不能为空"); + requireEquipment(deviceId); + AddDeviceUnit unit = deviceUnitMapper.selectById(deviceId); + if (unit == null) { + unit = buildDefaultDeviceUnit(deviceId); + } + return buildDeviceUnitVO(unit); + } + + @Override + @Transactional + public AddDeviceUnitVO saveDeviceUnit(AddDeviceUnitSaveParam param) { + if (param == null) { + throw new IllegalArgumentException("设备单位参数不能为空"); + } + String deviceId = requireText(param.getDevId(), "设备 ID 不能为空"); + requireEquipment(deviceId); + AddDeviceUnit unit = deviceUnitMapper.selectById(deviceId); + boolean create = unit == null; + if (create) { + unit = buildDefaultDeviceUnit(deviceId); + } + applyDeviceUnitParam(unit, param); + if (create) { + deviceUnitMapper.insert(unit); + } else { + deviceUnitMapper.updateById(unit); + } + return buildDeviceUnitVO(unit); + } + @Override @Transactional public AddLedgerDetailVO saveLine(AddLedgerLineSaveParam param) { @@ -206,6 +249,7 @@ public class AddLedgerServiceImpl implements AddLedgerService { line.setDevCapacity(param.getDevCapacity()); line.setBasicCapacity(param.getBasicCapacity()); line.setProtocolCapacity(param.getProtocolCapacity()); + line.setLineType(param.getLineType() == null ? AddLedgerConst.LINE_TYPE_MAIN : param.getLineType()); line.setMonitorObj(trimToNull(param.getMonitorObj())); line.setIsGovern(param.getIsGovern() == null ? AddLedgerConst.DISABLE : param.getIsGovern()); line.setMonitorUser(trimToNull(param.getMonitorUser())); @@ -221,6 +265,7 @@ public class AddLedgerServiceImpl implements AddLedgerService { lineMapper.updateById(line); updateLedgerName(id, AddLedgerConst.LEVEL_LINE, line.getName()); } + saveOrUpdateOverlimit(line); return detail(id, AddLedgerConst.LEVEL_LINE); } @@ -231,6 +276,12 @@ public class AddLedgerServiceImpl implements AddLedgerService { return AddLedgerLineNoUtil.resolveAvailableLineNos(usedLineNos, null); } + @Override + public AddOverlimitPO getLineOverlimit(String lineId) { + AddLedgerLinePO line = requireLine(lineId); + return overlimitMapper.selectById(line.getLineId()); + } + @Override public Map listLinePathByLineIds(List lineIds) { List normalizedLineIds = normalizeIds(lineIds); @@ -296,6 +347,7 @@ public class AddLedgerServiceImpl implements AddLedgerService { String updateBy = currentUserId(); if (!lineIds.isEmpty()) { lineMapper.softDeleteByIds(lineIds, updateBy); + overlimitMapper.deleteBatchIds(lineIds); } if (!equipmentIds.isEmpty()) { equipmentMapper.softDeleteByIds(equipmentIds, updateBy); @@ -344,6 +396,9 @@ public class AddLedgerServiceImpl implements AddLedgerService { requireNonNegativeIfPresent(param.getDevCapacity(), "dev_capacity 不能为负数"); requireNonNegativeIfPresent(param.getBasicCapacity(), "basic_capacity 不能为负数"); requireNonNegativeIfPresent(param.getProtocolCapacity(), "protocol_capacity 不能为负数"); + if (param.getLineType() != null && !AddLedgerConst.LINE_TYPES.contains(param.getLineType())) { + throw new IllegalArgumentException("lineType 只能是 0 或 1"); + } } private void assertLineNoUnique(String deviceId, Integer lineNo, String lineId) { @@ -437,6 +492,16 @@ public class AddLedgerServiceImpl implements AddLedgerService { ledgerMapper.insert(ledger); } + private void saveDefaultDeviceUnit(String devId) { + deviceUnitMapper.insert(buildDefaultDeviceUnit(devId)); + } + + private AddDeviceUnit buildDefaultDeviceUnit(String devId) { + AddDeviceUnit unit = new AddDeviceUnit(); + unit.setDevId(devId); + return unit; + } + private void updateLedgerName(String id, Integer level, String name) { AddLedgerLedgerPO ledger = requireLedger(id, level, levelName(level) + "节点"); ledger.setName(name); @@ -493,6 +558,8 @@ public class AddLedgerServiceImpl implements AddLedgerService { detail.setDevCapacity(line.getDevCapacity()); detail.setBasicCapacity(line.getBasicCapacity()); detail.setProtocolCapacity(line.getProtocolCapacity()); + detail.setLineType(AddLedgerConst.LINE_TYPE_MAIN); + detail.setOverlimit(overlimitMapper.selectById(line.getLineId())); detail.setMonitorObj(line.getMonitorObj()); detail.setIsGovern(line.getIsGovern()); detail.setMonitorUser(line.getMonitorUser()); @@ -500,6 +567,83 @@ public class AddLedgerServiceImpl implements AddLedgerService { return detail; } + private void saveOrUpdateOverlimit(AddLedgerLinePO line) { + AddOverlimitPO overlimit = COverlimitUtil.globalAssemble( + toFloat(line.getVolGrade()), + toFloat(line.getProtocolCapacity()), + toFloat(line.getDevCapacity()), + toFloat(line.getShortCircuitCapacity()), + null, + line.getLineType() == null ? AddLedgerConst.LINE_TYPE_MAIN : line.getLineType()); + overlimit.setId(line.getLineId()); + if (overlimitMapper.selectById(line.getLineId()) == null) { + overlimitMapper.insert(overlimit); + } else { + overlimitMapper.updateById(overlimit); + } + } + + private Float toFloat(BigDecimal value) { + return value == null ? null : value.floatValue(); + } + + private void applyDeviceUnitParam(AddDeviceUnit unit, AddDeviceUnitSaveParam param) { + unit.setUnitFrequency(defaultIfBlank(param.getUnitFrequency(), unit.getUnitFrequency())); + unit.setUnitFrequencyDev(defaultIfBlank(param.getUnitFrequencyDev(), unit.getUnitFrequencyDev())); + unit.setPhaseVoltage(defaultIfBlank(param.getPhaseVoltage(), unit.getPhaseVoltage())); + unit.setLineVoltage(defaultIfBlank(param.getLineVoltage(), unit.getLineVoltage())); + unit.setVoltageDev(defaultIfBlank(param.getVoltageDev(), unit.getVoltageDev())); + unit.setUvoltageDev(defaultIfBlank(param.getUvoltageDev(), unit.getUvoltageDev())); + unit.setIeffective(defaultIfBlank(param.getIeffective(), unit.getIeffective())); + unit.setSingleP(defaultIfBlank(param.getSingleP(), unit.getSingleP())); + unit.setSingleViewP(defaultIfBlank(param.getSingleViewP(), unit.getSingleViewP())); + unit.setSingleNoP(defaultIfBlank(param.getSingleNoP(), unit.getSingleNoP())); + unit.setTotalActiveP(defaultIfBlank(param.getTotalActiveP(), unit.getTotalActiveP())); + unit.setTotalViewP(defaultIfBlank(param.getTotalViewP(), unit.getTotalViewP())); + unit.setTotalNoP(defaultIfBlank(param.getTotalNoP(), unit.getTotalNoP())); + unit.setVfundEffective(defaultIfBlank(param.getVfundEffective(), unit.getVfundEffective())); + unit.setIfund(defaultIfBlank(param.getIfund(), unit.getIfund())); + unit.setFundActiveP(defaultIfBlank(param.getFundActiveP(), unit.getFundActiveP())); + unit.setFundNoP(defaultIfBlank(param.getFundNoP(), unit.getFundNoP())); + unit.setVdistortion(defaultIfBlank(param.getVdistortion(), unit.getVdistortion())); + unit.setVharmonicRate(defaultIfBlank(param.getVharmonicRate(), unit.getVharmonicRate())); + unit.setIharmonic(defaultIfBlank(param.getIharmonic(), unit.getIharmonic())); + unit.setPharmonic(defaultIfBlank(param.getPharmonic(), unit.getPharmonic())); + unit.setIiharmonic(defaultIfBlank(param.getIiharmonic(), unit.getIiharmonic())); + unit.setPositiveV(defaultIfBlank(param.getPositiveV(), unit.getPositiveV())); + unit.setNoPositiveV(defaultIfBlank(param.getNoPositiveV(), unit.getNoPositiveV())); + } + + private AddDeviceUnitVO buildDeviceUnitVO(AddDeviceUnit unit) { + AddDeviceUnitVO vo = new AddDeviceUnitVO(); + vo.setDevId(unit.getDevId()); + vo.setUnitFrequency(unit.getUnitFrequency()); + vo.setUnitFrequencyDev(unit.getUnitFrequencyDev()); + vo.setPhaseVoltage(unit.getPhaseVoltage()); + vo.setLineVoltage(unit.getLineVoltage()); + vo.setVoltageDev(unit.getVoltageDev()); + vo.setUvoltageDev(unit.getUvoltageDev()); + vo.setIeffective(unit.getIeffective()); + vo.setSingleP(unit.getSingleP()); + vo.setSingleViewP(unit.getSingleViewP()); + vo.setSingleNoP(unit.getSingleNoP()); + vo.setTotalActiveP(unit.getTotalActiveP()); + vo.setTotalViewP(unit.getTotalViewP()); + vo.setTotalNoP(unit.getTotalNoP()); + vo.setVfundEffective(unit.getVfundEffective()); + vo.setIfund(unit.getIfund()); + vo.setFundActiveP(unit.getFundActiveP()); + vo.setFundNoP(unit.getFundNoP()); + vo.setVdistortion(unit.getVdistortion()); + vo.setVharmonicRate(unit.getVharmonicRate()); + vo.setIharmonic(unit.getIharmonic()); + vo.setPharmonic(unit.getPharmonic()); + vo.setIiharmonic(unit.getIiharmonic()); + vo.setPositiveV(unit.getPositiveV()); + vo.setNoPositiveV(unit.getNoPositiveV()); + return vo; + } + private AddLedgerDetailVO buildBaseDetail(AddLedgerLedgerPO ledger) { AddLedgerDetailVO detail = new AddLedgerDetailVO(); detail.setId(ledger.getId()); @@ -551,6 +695,11 @@ public class AddLedgerServiceImpl implements AddLedgerService { return trimmed.isEmpty() ? null : trimmed; } + private String defaultIfBlank(String value, String defaultValue) { + String text = trimToNull(value); + return text == null ? defaultValue : text; + } + private boolean isBlank(String value) { return trimToNull(value) == null; } diff --git a/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/util/COverlimitUtil.java b/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/util/COverlimitUtil.java new file mode 100644 index 0000000..b680793 --- /dev/null +++ b/tools/add-ledger/src/main/java/com/njcn/gather/tool/addledger/util/COverlimitUtil.java @@ -0,0 +1,414 @@ +package com.njcn.gather.tool.addledger.util; + +import com.njcn.gather.tool.addledger.pojo.po.AddOverlimitPO; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.Arrays; + + +/** + * pqs + * 限值计算工具类 + * + * @author cdf + * @date 2023/5/15 + */ +public class COverlimitUtil { + + private static final float DEFAULT_LIMIT = 3.14159f; + private static final float DEFAULT_CURRENT_LIMIT = -3.14159f; + private static final int LINE_TYPE_DISTRIBUTION = 1; + + private static final float KV_0_22 = 0.22f; + private static final float KV_0_6 = 0.6f; + private static final float KV_1 = 1.0f; + private static final float KV_6 = 6.0f; + private static final float KV_10 = 10.0f; + private static final float KV_20 = 20.0f; + private static final float KV_35 = 35.0f; + private static final float KV_66 = 66.0f; + private static final float KV_110 = 110.0f; + private static final float KV_220 = 220.0f; + private static final float KV_330 = 330.0f; + private static final float KV_500 = 500.0f; + private static final float KV_750 = 750.0f; + private static final float KV_1000 = 1000.0f; + + + /** + * 谐波电流系数 + */ + private static final double[][] ARR = { + {78, 62, 39, 62, 26, 44, 19, 21, 16, 28, 13, 24, 11, 12, 9.7, 18, 8.6, 16, 7.8, 8.9, 7.1, 14, 6.5, 12, 6.0, 6.9, 5.6, 11, 5.2, 10, 4.9, 5.6, 4.6, 8.9, 4.3, 8.4, 4.1, 4.8, 3.9, 7.6, 3.7, 7.2, 3.5, 4.1, 3.4, 6.6, 3.3, 6.3, 3.1}, + {43, 34, 21, 34, 14, 24, 11, 11, 8.5, 16, 7.1, 13, 6.1, 6.8, 5.3, 10, 4.7, 9, 4.3, 4.9, 3.9, 7.4, 3.6, 6.8, 3.3, 3.8, 3.1, 5.9, 2.9, 5.5, 2.7, 3.1, 2.5, 4.9, 2.4, 4.6, 2.3, 2.6, 2.2, 4.1, 2.0, 4.0, 2.0, 2.3, 1.9, 3.6, 1.8, 3.5, 1.7}, + {26, 20, 13, 20, 8.5, 15, 6.4, 6.8, 5.1, 9.3, 4.3, 7.9, 3.7, 4.1, 3.2, 6, 2.8, 5.4, 2.6, 2.9, 2.3, 4.5, 2.1, 4.1, 2.0, 2.2, 1.9, 3.4, 1.7, 3.2, 1.6, 1.8, 1.5, 2.9, 1.4, 2.7, 1.4, 1.5, 1.3, 2.4, 1.2, 2.3, 1.2, 1.3, 1.1, 2.1, 1.1, 2.0, 1.0}, + {15, 12, 7.7, 12, 5.1, 8.8, 3.8, 4.1, 3.1, 5.6, 2.6, 4.7, 2.2, 2.5, 1.9, 3.6, 1.7, 3.2, 1.5, 1.8, 1.4, 2.7, 1.3, 2.5, 1.2, 1.3, 1.1, 2.1, 1.0, 1.9, 0.9, 1.1, 0.9, 1.7, 0.8, 1.6, 0.8, 0.9, 0.8, 1.5, 0.7, 1.4, 0.7, 0.8, 0.7, 1.3, 0.6, 1.2, 0.6}, + {16, 13, 8.1, 13, 5.4, 9.3, 4.1, 4.3, 3.3, 5.9, 2.7, 5, 2.3, 2.6, 2, 3.8, 1.8, 3.4, 1.6, 1.9, 1.5, 2.8, 1.4, 2.6, 1.2, 1.4, 1.1, 2.2, 1.1, 2.1, 1.0, 1.2, 0.9, 1.9, 0.9, 1.8, 0.8, 1.0, 0.8, 1.6, 0.8, 1.5, 0.7, 0.9, 0.7, 1.4, 0.7, 1.3, 0.6}, + {12, 9.6, 6, 9.6, 4, 6.8, 3, 3.2, 2.4, 4.3, 2, 3.7, 1.7, 1.9, 1.5, 2.8, 1.3, 2.5, 1.2, 1.4, 1.1, 2.1, 1, 1.9, 0.9, 1.1, 0.9, 1.7, 0.8, 1.5, 0.8, 0.9, 0.7, 1.4, 0.7, 1.3, 0.6, 0.7, 0.6, 1.2, 0.6, 1.1, 0.5, 0.6, 0.5, 1.0, 0.5, 1.0, 0.5} + }; + + + /** + * 计算监测点限值 + * @param voltageLevel 电压等级(10kV = 10 220kV = 220 ) + * @param protocolCapacity 协议容量 + * @param devCapacity 设备容量 + * @param shortCapacity 短路容量 + * @param powerFlag 0.用户侧 1.电网侧 + * @param lineType 0.主网 1.配网 需要注意配网目前没有四种容量,谐波电流幅值限值,负序电流限值无法计算默认-3.14159 + */ + public static AddOverlimitPO globalAssemble(Float voltageLevel, Float protocolCapacity, Float devCapacity, + Float shortCapacity, Integer powerFlag, Integer lineType) { + if (voltageLevel == null) { + throw new IllegalArgumentException("电压等级不能为空"); + } + AddOverlimitPO overlimit = new AddOverlimitPO(); + voltageDeviation(overlimit,voltageLevel); + frequency(overlimit); + voltageFluctuation(overlimit,voltageLevel); + voltageFlicker(overlimit,voltageLevel); + totalHarmonicDistortion(overlimit,voltageLevel); + uHarm(overlimit,voltageLevel); + threeVoltageUnbalance(overlimit); + interharmonicCurrent(overlimit,voltageLevel); + + if(isDistributionLine(lineType)) { + //配网 + Float[] iHarmTem = new Float[49]; + for (int i = 0; i <= 48; i++) { + //目前只处理了配网II类测点,III类测点暂未处理,III类测点参考主网 + iHarmTem[i] = getHarmTag(i+2,voltageLevel).floatValue(); + } + overlimit.buildIHarm(iHarmTem); + overlimit.setINeg(DEFAULT_CURRENT_LIMIT); + }else if (hasMainNetworkCapacity(protocolCapacity, devCapacity, shortCapacity)) { + //主网 + iHarm(overlimit, voltageLevel, protocolCapacity, devCapacity, shortCapacity); + negativeSequenceCurrent(overlimit, voltageLevel, shortCapacity); + } else { + setDefaultCurrentLimit(overlimit); + } + return overlimit; + } + + private static boolean isDistributionLine(Integer lineType) { + return lineType != null && lineType == LINE_TYPE_DISTRIBUTION; + } + + private static boolean hasMainNetworkCapacity(Float protocolCapacity, Float devCapacity, Float shortCapacity) { + return protocolCapacity != null && devCapacity != null && shortCapacity != null + && devCapacity > 0 && shortCapacity > 0; + } + + private static void setDefaultCurrentLimit(AddOverlimitPO overlimit) { + Float[] iHarmTem = new Float[49]; + Arrays.fill(iHarmTem, DEFAULT_CURRENT_LIMIT); + overlimit.buildIHarm(iHarmTem); + overlimit.setINeg(DEFAULT_CURRENT_LIMIT); + } + + + /** + * 电压偏差限值 + * + */ + public static void voltageDeviation(AddOverlimitPO overlimit,Float voltageLevel) { + float voltageDev = DEFAULT_LIMIT,uvoltageDev = DEFAULT_LIMIT; + if(voltageLevel <= KV_0_22){ + voltageDev = 7.0f; + uvoltageDev=-10.0f; + }else if(voltageLevel>KV_0_22&&voltageLevel=KV_20&&voltageLevel=KV_35&&voltageLevel=KV_66&&voltageLevel<=KV_110){ + voltageDev = 7.0f; + uvoltageDev=-3.0f; + }else if(voltageLevel>KV_110){ + voltageDev = 10.0f; + uvoltageDev=-10.0f; + } + overlimit.setVoltageDev(voltageDev); + overlimit.setUvoltageDev(uvoltageDev); + } + + + /** + * 频率偏差 + * 默认限值:±0.2Hz(即:-0.2 Hz≤限值≤0.2 Hz) + */ + public static void frequency(AddOverlimitPO overlimit) { + overlimit.setFreqDev(0.2f); + } + + + /** + * 电压波动 + * 对LV、MV:0≤限值≤3%;对HV:0≤限值≤2.5%。 + * LV、MV、HV的定义: + * 低压(LV) UN≤1kV + * 中压(MV) 1kV<UN≤35kV + * 高压(HV) 35kV<UN≤220kV + * 超高压(EHV),220kV<UN,参照HV执行 + */ + public static void voltageFluctuation(AddOverlimitPO overlimit, Float voltageLevel) { + if (voltageLevel < KV_35) { + overlimit.setVoltageFluctuation(3.0f); + } else { + overlimit.setVoltageFluctuation(2.5f); + } + } + + + + /** + * 电压闪变 + * ≤110kV 1 + * >110kV 0.8 + */ + public static void voltageFlicker(AddOverlimitPO overlimit, Float voltageLevel) { + if (voltageLevel <= KV_110) { + overlimit.setFlicker(1.0f); + } else { + overlimit.setFlicker(0.8f); + } + } + + + /** + * 总谐波电压畸变率 + * + * + */ + public static void totalHarmonicDistortion(AddOverlimitPO overlimit, Float voltageLevel) { + float result = DEFAULT_LIMIT; + if (voltageLevel < KV_6) { + result = 5.0f; + } else if(voltageLevel >= KV_6 && voltageLevel <= KV_20){ + result = 4.0f; + } else if(voltageLevel >= KV_35 && voltageLevel <= KV_66){ + result = 3.0f; + } else if(voltageLevel >= KV_110 && voltageLevel <= KV_1000){ + result = 2.0f; + } + overlimit.setUaberrance(result); + } + + + + /** + * 谐波电压含有率 + */ + public static void uHarm(AddOverlimitPO overlimit, Float voltageLevel) { + float resultOdd = DEFAULT_LIMIT,resultEven = DEFAULT_LIMIT; + if (voltageLevel < KV_6) { + resultOdd = 4.0f; + resultEven = 2.0f; + } else if(voltageLevel >= KV_6 && voltageLevel <= KV_20){ + resultOdd = 3.2f; + resultEven = 1.6f; + } else if(voltageLevel >= KV_35 && voltageLevel <= KV_66){ + resultOdd = 2.4f; + resultEven = 1.2f; + } else if(voltageLevel >= KV_110 && voltageLevel <= KV_1000){ + resultOdd = 1.6f; + resultEven = 0.8f; + } + overlimit.buildUharm(resultEven,resultOdd); + } + + + /** + * 负序电压不平衡(三相电压不平衡度) + * + */ + public static void threeVoltageUnbalance(AddOverlimitPO overlimit) { + overlimit.setUbalance(2.0f); + overlimit.setShortUbalance(4.0f); + } + + + /*---------------------------------谐波电流限值start-----------------------------------*/ + + /** + * 谐波电流限值 + */ + public static void iHarm(AddOverlimitPO overlimit, Float voltageLevel,Float protocolCapacity,Float devCapacity,Float shortCapacity) { + float calCap = shortCapacity/getDlCapByVoltageLevel(voltageLevel); + //24谐波电流幅值 + Float[] iHarmTem = new Float[49]; + for (int i = 0; i <= 48; i++) { + float inHarm = iHarmCalculate(i+2,voltageLevel,protocolCapacity,devCapacity,calCap); + iHarmTem[i] = inHarm; + } + overlimit.buildIHarm(iHarmTem); + } + /** + * @Description: iHarmCalculate + * @Param: protocolCapacity 协议容量 devCapacity设备容量 calCap 短路容量 + * @return: float + * @Author: clam + * @Date: 2024/2/4 + */ + private static float iHarmCalculate(int nHarm, Float voltageLevel,float protocolCapacity, float devCapacity,float calCap) { + double tag = calCap*getHarmTag(nHarm,voltageLevel); + Double limit = getHarmonicLimit(nHarm,tag,new BigDecimal(String.valueOf(devCapacity)).doubleValue(),new BigDecimal(String.valueOf(protocolCapacity)).doubleValue()); + BigDecimal bigDecimal = BigDecimal.valueOf(limit).setScale(4,RoundingMode.HALF_UP); + return bigDecimal.floatValue(); + } + + + /** + * 电流谐波限值 + */ + private static Double getHarmTag(Integer iCount, Float voltageLevel) { + int x, y; + if (voltageLevel < KV_6) { + x = 0; + } else if (voltageLevel buildLedger(invocation.getArgument(0), "project-001", + AddLedgerConst.ROOT_PARENT_ID + ",engineering-001,project-001", AddLedgerConst.LEVEL_EQUIPMENT)); + when(equipmentMapper.selectOne(any())).thenReturn(buildActiveEquipment("device-001")); + + service.saveEquipment(param); + + ArgumentCaptor captor = ArgumentCaptor.forClass(AddDeviceUnit.class); + verify(deviceUnitMapper).insert(captor.capture()); + Assertions.assertNotNull(captor.getValue().getDevId()); + Assertions.assertEquals("Hz", captor.getValue().getUnitFrequency()); + Assertions.assertEquals("kV", captor.getValue().getPhaseVoltage()); + } + + @Test + void saveDeviceUnitShouldUpdateExistingDeviceUnit() { + AddDeviceUnit existing = new AddDeviceUnit(); + existing.setDevId("device-001"); + when(deviceUnitMapper.selectById(eq("device-001"))).thenReturn(existing); + when(equipmentMapper.selectOne(any())).thenReturn(buildActiveEquipment("device-001")); + + AddDeviceUnitSaveParam param = new AddDeviceUnitSaveParam(); + param.setDevId("device-001"); + param.setUnitFrequency("MHz"); + param.setPhaseVoltage("V"); + + AddDeviceUnitVO result = service.saveDeviceUnit(param); + + verify(deviceUnitMapper).updateById(any(AddDeviceUnit.class)); + Assertions.assertEquals("device-001", result.getDevId()); + Assertions.assertEquals("MHz", result.getUnitFrequency()); + Assertions.assertEquals("V", result.getPhaseVoltage()); + } + + @Test + void getLineOverlimitShouldQueryExistingLineOverlimit() { + AddLedgerLinePO line = new AddLedgerLinePO(); + line.setLineId("line-001"); + AddOverlimitPO overlimit = new AddOverlimitPO(); + overlimit.setId("line-001"); + + when(lineMapper.selectOne(any())).thenReturn(line); + when(overlimitMapper.selectById(eq("line-001"))).thenReturn(overlimit); + + AddOverlimitPO result = service.getLineOverlimit("line-001"); + + Assertions.assertSame(overlimit, result); + } + private AddLedgerLineSaveParam buildValidLineParam() { AddLedgerLineSaveParam param = new AddLedgerLineSaveParam(); param.setDeviceId("device-001"); @@ -63,4 +139,33 @@ class AddLedgerServiceImplTest { param.setPt2Ratio(BigDecimal.ONE); return param; } + + private AddLedgerEquipmentSaveParam buildValidEquipmentParam() { + AddLedgerEquipmentSaveParam param = new AddLedgerEquipmentSaveParam(); + param.setProjectId("project-001"); + param.setName("设备A"); + param.setNdid("ndid-001"); + param.setMac("00:11:22:33:44:55"); + param.setDevModel("model-001"); + return param; + } + + private AddLedgerLedgerPO buildLedger(String id, String pid, String pids, Integer level) { + AddLedgerLedgerPO ledger = new AddLedgerLedgerPO(); + ledger.setId(id); + ledger.setPid(pid); + ledger.setPids(pids); + ledger.setLevel(level); + ledger.setState(AddLedgerConst.STATE_NORMAL); + ledger.setName(id); + return ledger; + } + + private AddLedgerEquipmentPO buildActiveEquipment(String id) { + AddLedgerEquipmentPO equipment = new AddLedgerEquipmentPO(); + equipment.setId(id); + equipment.setName("设备A"); + equipment.setRunStatus(AddLedgerConst.EQUIPMENT_RUN_STATUS_OFFLINE); + return equipment; + } }