Compare commits
4 Commits
9a9614a9e5
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 5f6c10b9cb | |||
| 66d351afe4 | |||
| e5369fef5a | |||
| ba8bc43377 |
1922
docs/superpowers/plans/2026-05-21-deploy-linux-ssh-sftp.md
Normal file
1922
docs/superpowers/plans/2026-05-21-deploy-linux-ssh-sftp.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,514 @@
|
|||||||
|
# Linux 服务器部署运维设计
|
||||||
|
|
||||||
|
## 1. 背景
|
||||||
|
|
||||||
|
`system-ops/deploy` 当前只提供系统部署菜单的基础入口:
|
||||||
|
|
||||||
|
- `GET /deploy/overview`
|
||||||
|
- `DeployController`
|
||||||
|
- `DeployService`
|
||||||
|
- `DeployOverviewVO`
|
||||||
|
|
||||||
|
本次需求是在 `deploy` 模块中补充 Linux 服务器远程运维能力。用户可以维护 Linux 服务器连接配置,基于 SSH/SFTP 连接服务器,完成远程文件上传、下载和基础命令终端操作。命令终端目标体验接近 Xshell 的基础能力。
|
||||||
|
|
||||||
|
当前仓库没有前端代码,本设计只定义页面布局、接口契约、后端模块拆分、数据存储和验证方式,不实现真实前端页面。
|
||||||
|
|
||||||
|
## 2. 范围确认
|
||||||
|
|
||||||
|
本期只支持 Linux 服务器。
|
||||||
|
|
||||||
|
本期包含:
|
||||||
|
|
||||||
|
- Linux SSH 连接配置的新增、编辑、删除、查询。
|
||||||
|
- SSH 连接测试。
|
||||||
|
- SFTP 文件列表、上传、下载、删除、新建目录。
|
||||||
|
- SSH Shell 基础命令交互。
|
||||||
|
- 前端单页运维工作台布局设计。
|
||||||
|
- 连接配置使用文件方式存储,不新建数据库表。
|
||||||
|
|
||||||
|
本期不包含:
|
||||||
|
|
||||||
|
- Windows 服务器。
|
||||||
|
- FTP 协议。
|
||||||
|
- 数据库存储连接配置。
|
||||||
|
- 部署任务编排。
|
||||||
|
- 命令审批、命令黑名单、命令历史。
|
||||||
|
- 批量文件压缩下载。
|
||||||
|
- 数据库专用客户端封装。
|
||||||
|
- Maven 编译、打包、测试。
|
||||||
|
|
||||||
|
说明:需求中提到的 “FPT” 本期按 Linux 服务器常用能力理解为 SFTP。SFTP 复用 SSH 账号、密码和端口,比单独 FTP 更适合本期场景。
|
||||||
|
|
||||||
|
## 3. 总体方案
|
||||||
|
|
||||||
|
推荐采用 “SSH/SFTP + WebSocket 终端” 方案:
|
||||||
|
|
||||||
|
- 服务器连接配置保存到本地 JSON 文件。
|
||||||
|
- 后端通过 SSH 建立 Linux 连接。
|
||||||
|
- 文件操作通过 SFTP 通道完成。
|
||||||
|
- 终端操作通过 SSH Shell 通道完成。
|
||||||
|
- 前端通过 WebSocket 与后端交换终端输入输出。
|
||||||
|
|
||||||
|
该方案可以复用同一份服务器连接配置,不需要引入 Windows 远程协议,也能满足类 Xshell 的基础交互需求。
|
||||||
|
|
||||||
|
## 4. 前端页面布局
|
||||||
|
|
||||||
|
页面路径建议沿用当前菜单路径:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/systemOps/deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
页面采用三块工作区:
|
||||||
|
|
||||||
|
- 左侧:服务器列表。
|
||||||
|
- 中间:远程文件管理。
|
||||||
|
- 右侧:连接详情和快捷操作。
|
||||||
|
- 底部:SSH 终端区。
|
||||||
|
|
||||||
|
推荐布局:
|
||||||
|
|
||||||
|
```text
|
||||||
|
┌──────────────────────────────────────────────────────────────┐
|
||||||
|
│ 顶部工具栏:新增连接 测试连接 刷新 当前连接状态 │
|
||||||
|
├──────────────┬──────────────────────────────┬────────────────┤
|
||||||
|
│ 服务器列表 │ 远程文件管理 │ 连接详情/操作 │
|
||||||
|
│ │ │ │
|
||||||
|
│ Linux-测试 │ 路径栏:/opt/app │ 主机/IP │
|
||||||
|
│ Linux-生产 │ 上传 下载 新建目录 删除 刷新 │ 用户名 │
|
||||||
|
│ │ │ 端口 │
|
||||||
|
│ │ 文件表格 │ 测试连接 │
|
||||||
|
│ │ │ 打开终端 │
|
||||||
|
├──────────────┴──────────────────────────────┴────────────────┤
|
||||||
|
│ 终端 Tabs:Linux-测试 │
|
||||||
|
│ $ pwd │
|
||||||
|
│ /opt/app │
|
||||||
|
└──────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.1 服务器列表
|
||||||
|
|
||||||
|
左侧服务器列表用于选择当前操作目标。
|
||||||
|
|
||||||
|
展示字段:
|
||||||
|
|
||||||
|
| 字段 | 说明 |
|
||||||
|
|---|---|
|
||||||
|
| 名称 | 服务器显示名称 |
|
||||||
|
| 主机地址 | IP 或域名 |
|
||||||
|
| SSH 端口 | 默认 22 |
|
||||||
|
| 连接状态 | 未测试、连接成功、连接失败 |
|
||||||
|
|
||||||
|
交互:
|
||||||
|
|
||||||
|
- 支持按名称、主机地址搜索。
|
||||||
|
- 点击服务器后加载连接详情,并将文件管理区切换到该服务器。
|
||||||
|
- 列表项提供编辑、删除、测试连接入口。
|
||||||
|
- 删除连接前必须二次确认。
|
||||||
|
|
||||||
|
### 4.2 连接配置弹窗
|
||||||
|
|
||||||
|
新增和编辑使用同一个弹窗。
|
||||||
|
|
||||||
|
字段:
|
||||||
|
|
||||||
|
| 字段 | 是否必填 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| 名称 | 是 | 页面展示名称 |
|
||||||
|
| 主机地址 | 是 | Linux 服务器 IP 或域名 |
|
||||||
|
| SSH 端口 | 是 | 默认 22,范围 1-65535 |
|
||||||
|
| 用户名 | 是 | SSH 登录用户 |
|
||||||
|
| 密码 | 新增必填 | 编辑时留空表示不修改 |
|
||||||
|
| 备注 | 否 | 环境说明 |
|
||||||
|
|
||||||
|
按钮:
|
||||||
|
|
||||||
|
- 测试连接。
|
||||||
|
- 保存。
|
||||||
|
- 取消。
|
||||||
|
|
||||||
|
密码规则:
|
||||||
|
|
||||||
|
- 新增连接时密码必填。
|
||||||
|
- 编辑连接时密码不回显。
|
||||||
|
- 编辑时密码为空表示沿用原密码。
|
||||||
|
- 查询列表和详情接口均不返回密码。
|
||||||
|
|
||||||
|
### 4.3 远程文件管理
|
||||||
|
|
||||||
|
中间文件管理区基于当前选中的服务器工作。
|
||||||
|
|
||||||
|
顶部路径栏:
|
||||||
|
|
||||||
|
- 展示当前远程目录,例如 `/opt/app`。
|
||||||
|
- 支持返回上级目录。
|
||||||
|
- 支持点击面包屑跳转到上级路径。
|
||||||
|
|
||||||
|
工具栏:
|
||||||
|
|
||||||
|
- 上传。
|
||||||
|
- 下载。
|
||||||
|
- 新建目录。
|
||||||
|
- 删除。
|
||||||
|
- 刷新。
|
||||||
|
|
||||||
|
文件表格字段:
|
||||||
|
|
||||||
|
| 字段 | 说明 |
|
||||||
|
|---|---|
|
||||||
|
| 名称 | 文件或目录名称 |
|
||||||
|
| 类型 | 文件、目录、软链接 |
|
||||||
|
| 大小 | 文件大小,目录可为空 |
|
||||||
|
| 权限 | Linux 权限字符串 |
|
||||||
|
| 修改时间 | 远程文件修改时间 |
|
||||||
|
|
||||||
|
交互规则:
|
||||||
|
|
||||||
|
- 双击目录进入下级目录。
|
||||||
|
- 下载只支持普通文件。
|
||||||
|
- 删除文件或目录前必须二次确认。
|
||||||
|
- 本期支持单文件上传和单文件下载。
|
||||||
|
- 上传目标目录为当前路径。
|
||||||
|
- 下载目录、批量压缩下载不在本期范围。
|
||||||
|
|
||||||
|
### 4.4 SSH 终端区
|
||||||
|
|
||||||
|
底部终端区用于执行 Linux 命令。
|
||||||
|
|
||||||
|
交互规则:
|
||||||
|
|
||||||
|
- 点击“打开终端”后创建 SSH Shell 会话。
|
||||||
|
- 前端输入通过 WebSocket 实时发送给后端。
|
||||||
|
- 后端将 Shell 输出通过 WebSocket 实时推送给前端。
|
||||||
|
- 本期建议限制为每台服务器最多一个终端会话。
|
||||||
|
- 关闭终端 Tab 时通知后端释放 SSH 会话。
|
||||||
|
- 终端断开后显示状态,不自动重连。
|
||||||
|
|
||||||
|
用户可以在终端中自行执行数据库命令,例如:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mysql -uroot -p
|
||||||
|
psql -h 127.0.0.1 -U postgres
|
||||||
|
redis-cli
|
||||||
|
```
|
||||||
|
|
||||||
|
后端不解析数据库命令,也不保存命令历史。
|
||||||
|
|
||||||
|
## 5. 后端结构设计
|
||||||
|
|
||||||
|
在 `system-ops/deploy` 模块内按职责新增类,保留现有 `DeployController` 的 `/deploy/overview`。
|
||||||
|
|
||||||
|
建议结构:
|
||||||
|
|
||||||
|
```text
|
||||||
|
system-ops/deploy/src/main/java/com/njcn/gather/systemops/deploy/
|
||||||
|
├── config/
|
||||||
|
├── controller/
|
||||||
|
├── pojo/param/
|
||||||
|
├── pojo/vo/
|
||||||
|
├── pojo/dto/
|
||||||
|
├── repository/
|
||||||
|
├── service/
|
||||||
|
├── service/impl/
|
||||||
|
└── websocket/
|
||||||
|
```
|
||||||
|
|
||||||
|
职责拆分:
|
||||||
|
|
||||||
|
| 类 | 职责 |
|
||||||
|
|---|---|
|
||||||
|
| `DeployServerController` | 连接配置查询、新增、编辑、删除、测试连接 |
|
||||||
|
| `DeployFileController` | SFTP 文件列表、上传、下载、删除、新建目录 |
|
||||||
|
| `DeployTerminalWebSocketHandler` | SSH 终端 WebSocket 输入输出转发 |
|
||||||
|
| `DeployServerConfigService` | 连接配置业务校验和编排 |
|
||||||
|
| `DeployServerConfigRepository` | JSON 文件读写 |
|
||||||
|
| `DeploySftpService` | SFTP 文件操作 |
|
||||||
|
| `DeploySshTerminalService` | SSH Shell 会话创建、输入、输出、关闭 |
|
||||||
|
| `DeployCryptoService` | 密码加密和解密 |
|
||||||
|
| `DeployProperties` | deploy 配置项绑定 |
|
||||||
|
|
||||||
|
## 6. 连接配置存储
|
||||||
|
|
||||||
|
连接配置不入库,使用 JSON 文件落盘。存储目录通过配置指定。
|
||||||
|
|
||||||
|
建议配置:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
deploy:
|
||||||
|
storage-dir: ${log.homeDir}/deploy
|
||||||
|
terminal-idle-timeout-minutes: 30
|
||||||
|
```
|
||||||
|
|
||||||
|
`deploy.crypto-key` 不建议在默认 `application.yml` 中配置明文值。后续实现时可通过环境覆盖或外部配置提供,业务代码只读取配置,不写死密钥。
|
||||||
|
|
||||||
|
落盘文件:
|
||||||
|
|
||||||
|
```text
|
||||||
|
D:\logs\deploy\deploy-server-connections.json
|
||||||
|
```
|
||||||
|
|
||||||
|
JSON 结构:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"id": "uuid",
|
||||||
|
"name": "测试服务器",
|
||||||
|
"host": "192.168.1.10",
|
||||||
|
"sshPort": 22,
|
||||||
|
"username": "root",
|
||||||
|
"password": "加密密文",
|
||||||
|
"description": "测试环境",
|
||||||
|
"createdTime": "2026-05-21 14:00:00",
|
||||||
|
"updatedTime": "2026-05-21 14:00:00"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
写文件规则:
|
||||||
|
|
||||||
|
- 启动时如果文件不存在,自动创建空配置文件。
|
||||||
|
- 读写方法集中在 `DeployServerConfigRepository`。
|
||||||
|
- 写入时先写临时文件,再替换正式文件,避免进程中断导致 JSON 损坏。
|
||||||
|
- 保存和删除操作需要加进程内锁,避免并发写入互相覆盖。
|
||||||
|
|
||||||
|
密码规则:
|
||||||
|
|
||||||
|
- 密码必须加密后落盘。
|
||||||
|
- 接口返回不包含密码。
|
||||||
|
- 日志不打印密码。
|
||||||
|
- 优先复用项目已有加密能力;如没有合适工具,则在 `deploy` 内封装 AES 加解密组件。
|
||||||
|
- 加密密钥通过配置提供,不在业务代码中硬编码。
|
||||||
|
|
||||||
|
## 7. 接口设计
|
||||||
|
|
||||||
|
接口风格沿用当前仓库常见写法:查询和变更优先使用 `POST`,返回 `HttpResult<T>`。
|
||||||
|
|
||||||
|
### 7.1 连接配置接口
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `POST` | `/deploy/server/list` | 查询服务器连接配置列表 |
|
||||||
|
| `POST` | `/deploy/server/add` | 新增服务器连接配置 |
|
||||||
|
| `POST` | `/deploy/server/update` | 修改服务器连接配置 |
|
||||||
|
| `POST` | `/deploy/server/delete` | 删除服务器连接配置 |
|
||||||
|
| `POST` | `/deploy/server/test` | 测试 SSH 连接 |
|
||||||
|
|
||||||
|
列表返回字段:
|
||||||
|
|
||||||
|
| 字段 | 说明 |
|
||||||
|
|---|---|
|
||||||
|
| `id` | 连接 ID |
|
||||||
|
| `name` | 服务器名称 |
|
||||||
|
| `host` | 主机地址 |
|
||||||
|
| `sshPort` | SSH 端口 |
|
||||||
|
| `username` | 用户名 |
|
||||||
|
| `description` | 备注 |
|
||||||
|
| `createdTime` | 创建时间 |
|
||||||
|
| `updatedTime` | 更新时间 |
|
||||||
|
|
||||||
|
新增参数:
|
||||||
|
|
||||||
|
| 字段 | 是否必填 |
|
||||||
|
|---|---|
|
||||||
|
| `name` | 是 |
|
||||||
|
| `host` | 是 |
|
||||||
|
| `sshPort` | 是 |
|
||||||
|
| `username` | 是 |
|
||||||
|
| `password` | 是 |
|
||||||
|
| `description` | 否 |
|
||||||
|
|
||||||
|
编辑参数:
|
||||||
|
|
||||||
|
| 字段 | 是否必填 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `id` | 是 | 连接 ID |
|
||||||
|
| `name` | 是 | 服务器名称 |
|
||||||
|
| `host` | 是 | 主机地址 |
|
||||||
|
| `sshPort` | 是 | SSH 端口 |
|
||||||
|
| `username` | 是 | 用户名 |
|
||||||
|
| `password` | 否 | 为空表示不修改 |
|
||||||
|
| `description` | 否 | 备注 |
|
||||||
|
|
||||||
|
### 7.2 文件接口
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `POST` | `/deploy/file/list` | 查询远程目录文件列表 |
|
||||||
|
| `POST` | `/deploy/file/mkdir` | 新建远程目录 |
|
||||||
|
| `POST` | `/deploy/file/delete` | 删除远程文件或目录 |
|
||||||
|
| `POST` | `/deploy/file/upload` | 上传本地文件到远程目录 |
|
||||||
|
| `POST` | `/deploy/file/download` | 下载远程普通文件 |
|
||||||
|
|
||||||
|
文件列表参数:
|
||||||
|
|
||||||
|
| 字段 | 是否必填 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `serverId` | 是 | 服务器连接 ID |
|
||||||
|
| `path` | 是 | 远程目录路径 |
|
||||||
|
|
||||||
|
文件列表返回字段:
|
||||||
|
|
||||||
|
| 字段 | 说明 |
|
||||||
|
|---|---|
|
||||||
|
| `name` | 文件名 |
|
||||||
|
| `path` | 完整路径 |
|
||||||
|
| `type` | `FILE`、`DIRECTORY`、`LINK` |
|
||||||
|
| `size` | 文件大小 |
|
||||||
|
| `permissions` | 权限字符串 |
|
||||||
|
| `modifiedTime` | 修改时间 |
|
||||||
|
|
||||||
|
下载接口直接写入 `HttpServletResponse`。下载文件名沿用远程文件名,不追加日期;仓库“导出或生成文件追加日期”的规则适用于后端生成或导出文件,本功能是下载远程已有文件,不改变原文件名。
|
||||||
|
|
||||||
|
### 7.3 终端 WebSocket
|
||||||
|
|
||||||
|
终端连接:
|
||||||
|
|
||||||
|
```text
|
||||||
|
WebSocket /deploy/terminal?serverId={serverId}
|
||||||
|
```
|
||||||
|
|
||||||
|
前端发送输入:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "input",
|
||||||
|
"data": "ls -la\n"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
前端发送窗口大小:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "resize",
|
||||||
|
"cols": 120,
|
||||||
|
"rows": 30
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
后端输出:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "output",
|
||||||
|
"data": "total 20\r\n..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
后端状态:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "status",
|
||||||
|
"status": "CONNECTED"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
异常消息:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "error",
|
||||||
|
"message": "SSH连接失败"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. 参数校验
|
||||||
|
|
||||||
|
后端至少补充以下校验:
|
||||||
|
|
||||||
|
- 服务器名称不能为空。
|
||||||
|
- 主机地址不能为空。
|
||||||
|
- SSH 端口范围为 `1-65535`。
|
||||||
|
- 用户名不能为空。
|
||||||
|
- 新增连接时密码不能为空。
|
||||||
|
- 编辑连接时 `id` 必须存在。
|
||||||
|
- 删除连接时 `id` 必须存在。
|
||||||
|
- 同一主机、端口、用户名组合不建议重复保存。
|
||||||
|
- 文件路径不能为空。
|
||||||
|
- 文件上传目标必须是远程目录。
|
||||||
|
- 下载目标必须是远程普通文件。
|
||||||
|
- 删除路径不能为空,不能删除空路径或根目录 `/`。
|
||||||
|
- 新建目录名称不能为空,不能包含路径分隔符。
|
||||||
|
|
||||||
|
## 9. 安全与资源控制
|
||||||
|
|
||||||
|
安全规则:
|
||||||
|
|
||||||
|
- 密码不明文落盘。
|
||||||
|
- 接口返回不包含密码。
|
||||||
|
- 日志不打印密码、终端输入内容、文件内容。
|
||||||
|
- 终端不保存命令历史。
|
||||||
|
- 文件路径需要做基础规范化,避免空路径、非法路径和目录穿越。
|
||||||
|
- 下载只允许下载普通文件。
|
||||||
|
|
||||||
|
资源规则:
|
||||||
|
|
||||||
|
- SSH 连接测试设置连接超时,例如 5 秒。
|
||||||
|
- SFTP 操作每次请求创建短连接,操作完成后释放。
|
||||||
|
- 终端会话保持长连接,关闭 WebSocket 后释放 SSH Session 和 Channel。
|
||||||
|
- 终端会话设置空闲超时,默认 30 分钟。
|
||||||
|
- 本期每台服务器最多保留一个终端会话。
|
||||||
|
|
||||||
|
## 10. 依赖建议
|
||||||
|
|
||||||
|
后续实现 SSH/SFTP 时建议优先选择 Java 8 可用、项目易接入的 SSH 客户端库,例如 JSch 或 sshj。
|
||||||
|
|
||||||
|
选择标准:
|
||||||
|
|
||||||
|
- 支持 SSH 密码登录。
|
||||||
|
- 支持 SFTP 文件操作。
|
||||||
|
- 支持 Shell Channel。
|
||||||
|
- 能在 Spring Boot 2.3 和 Java 8 下稳定使用。
|
||||||
|
|
||||||
|
最终依赖需要写入 `system-ops/deploy/pom.xml`,不影响其他模块。
|
||||||
|
|
||||||
|
## 11. 错误处理
|
||||||
|
|
||||||
|
连接测试需要区分常见错误:
|
||||||
|
|
||||||
|
| 场景 | 返回说明 |
|
||||||
|
|---|---|
|
||||||
|
| 主机不可达 | 连接服务器失败 |
|
||||||
|
| 端口不通 | SSH端口连接失败 |
|
||||||
|
| 账号或密码错误 | SSH认证失败 |
|
||||||
|
| SFTP 打开失败 | 文件通道打开失败 |
|
||||||
|
| 终端打开失败 | Shell通道打开失败 |
|
||||||
|
|
||||||
|
接口层仍使用项目现有 `HttpResult` 和 `CommonResponseEnum` 风格。具体错误文案由 Service 返回给 Controller,不新增全局异常体系。
|
||||||
|
|
||||||
|
## 12. 验证方式
|
||||||
|
|
||||||
|
默认不执行 Maven 编译、打包、测试命令。后续实现完成后按以下方式验证:
|
||||||
|
|
||||||
|
- 检查 `deploy` 新增代码只位于 `system-ops/deploy`。
|
||||||
|
- 新增连接后,接口返回和 JSON 文件内容一致。
|
||||||
|
- 编辑连接时密码留空不会覆盖原密码。
|
||||||
|
- 删除连接后,JSON 文件同步移除对应记录。
|
||||||
|
- 查询接口不返回密码。
|
||||||
|
- JSON 文件中密码不是明文。
|
||||||
|
- 测试连接能识别成功、主机不可达、端口不通、账号密码错误。
|
||||||
|
- 文件列表能展示远程目录内容。
|
||||||
|
- 上传文件后远程目录可见。
|
||||||
|
- 下载普通文件内容与远程文件一致。
|
||||||
|
- 删除文件或目录后远程路径不存在。
|
||||||
|
- 新建目录后远程路径存在。
|
||||||
|
- 终端能打开 Linux Shell,执行 `pwd`、`ls -la`、`mysql --version` 等基础命令。
|
||||||
|
- 关闭终端后,后端 SSH 会话被释放。
|
||||||
|
|
||||||
|
## 13. 后续扩展
|
||||||
|
|
||||||
|
后续如需求增加,可以在当前方案基础上扩展:
|
||||||
|
|
||||||
|
- SSH 私钥登录。
|
||||||
|
- 多终端 Tab。
|
||||||
|
- 命令审计和历史记录。
|
||||||
|
- 命令黑名单或审批。
|
||||||
|
- 部署脚本编排。
|
||||||
|
- 文件批量上传和批量下载。
|
||||||
|
- Windows WinRM 或 PowerShell Remoting。
|
||||||
|
|
||||||
|
这些能力不进入本期实现,避免当前 `deploy` 模块从基础入口一次扩张为完整运维平台。
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package com.njcn.gather.steady.checksquare.component;
|
||||||
|
|
||||||
|
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareSegmentVO;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据校验连续性计算组件。
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class SteadyChecksquareCalculator {
|
||||||
|
|
||||||
|
public static final String STATUS_NORMAL = "NORMAL";
|
||||||
|
public static final String STATUS_MISSING = "MISSING";
|
||||||
|
private static final DateTimeFormatter OUTPUT_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||||
|
|
||||||
|
public List<SteadyChecksquareSegmentVO> buildSegments(List<LocalDateTime> slots, Set<LocalDateTime> actualSlots,
|
||||||
|
int intervalMinutes) {
|
||||||
|
List<SteadyChecksquareSegmentVO> result = new ArrayList<SteadyChecksquareSegmentVO>();
|
||||||
|
if (slots == null || slots.isEmpty()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
String currentStatus = resolveStatus(slots.get(0), actualSlots);
|
||||||
|
LocalDateTime segmentStart = slots.get(0);
|
||||||
|
LocalDateTime previousSlot = slots.get(0);
|
||||||
|
int pointCount = 1;
|
||||||
|
for (int i = 1; i < slots.size(); i++) {
|
||||||
|
LocalDateTime slot = slots.get(i);
|
||||||
|
String status = resolveStatus(slot, actualSlots);
|
||||||
|
if (!currentStatus.equals(status)) {
|
||||||
|
result.add(buildSegment(segmentStart, previousSlot, currentStatus, pointCount, intervalMinutes));
|
||||||
|
segmentStart = slot;
|
||||||
|
pointCount = 0;
|
||||||
|
currentStatus = status;
|
||||||
|
}
|
||||||
|
previousSlot = slot;
|
||||||
|
pointCount++;
|
||||||
|
}
|
||||||
|
result.add(buildSegment(segmentStart, previousSlot, currentStatus, pointCount, intervalMinutes));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int maxContinuousMissingMinutes(List<SteadyChecksquareSegmentVO> segments) {
|
||||||
|
int result = 0;
|
||||||
|
if (segments == null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
for (SteadyChecksquareSegmentVO segment : segments) {
|
||||||
|
if (segment != null && STATUS_MISSING.equals(segment.getStatus()) && segment.getDurationMinutes() != null) {
|
||||||
|
result = Math.max(result, segment.getDurationMinutes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SteadyChecksquareSegmentVO buildSegment(LocalDateTime startTime, LocalDateTime endTime, String status,
|
||||||
|
int pointCount, int intervalMinutes) {
|
||||||
|
SteadyChecksquareSegmentVO segment = new SteadyChecksquareSegmentVO();
|
||||||
|
segment.setStartTime(OUTPUT_TIME_FORMATTER.format(startTime));
|
||||||
|
segment.setEndTime(OUTPUT_TIME_FORMATTER.format(endTime));
|
||||||
|
segment.setStatus(status);
|
||||||
|
segment.setMissingPointCount(STATUS_MISSING.equals(status) ? pointCount : 0);
|
||||||
|
segment.setDurationMinutes(pointCount * intervalMinutes);
|
||||||
|
return segment;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveStatus(LocalDateTime slot, Set<LocalDateTime> actualSlots) {
|
||||||
|
return actualSlots != null && actualSlots.contains(slot) ? STATUS_NORMAL : STATUS_MISSING;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
package com.njcn.gather.steady.checksquare.component;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
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.steady.datavie.config.SteadyInfluxDbProperties;
|
||||||
|
import com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendResolvedFieldBO;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据校验 InfluxDB 查询组件。
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SteadyChecksquareInfluxQueryComponent {
|
||||||
|
|
||||||
|
private static final DateTimeFormatter INFLUX_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'");
|
||||||
|
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||||
|
|
||||||
|
private final SteadyInfluxDbProperties properties;
|
||||||
|
|
||||||
|
public Set<LocalDateTime> queryExistingSlots(SteadyTrendResolvedFieldBO field, LocalDateTime startTime,
|
||||||
|
LocalDateTime endTime, int intervalMinutes) {
|
||||||
|
validateConfig();
|
||||||
|
String query = buildChecksquareQuery(field, startTime, endTime);
|
||||||
|
long startMillis = System.currentTimeMillis();
|
||||||
|
log.info("数据校验 InfluxDB 查询开始,measurement={},field={},lineId={},phase={},statType={},query={}",
|
||||||
|
field.getMeasurement(), field.getField(), field.getLineId(), field.getPhase(), field.getStatType(), query);
|
||||||
|
try {
|
||||||
|
String body = executeQuery(query);
|
||||||
|
Set<LocalDateTime> slots = parseExistingSlots(body, intervalMinutes);
|
||||||
|
log.info("数据校验 InfluxDB 查询结束,slotCount={},costMs={}", slots.size(), System.currentTimeMillis() - startMillis);
|
||||||
|
return slots;
|
||||||
|
} catch (RuntimeException ex) {
|
||||||
|
log.warn("数据校验 InfluxDB 查询异常,costMs={},error={}", System.currentTimeMillis() - startMillis, ex.getMessage());
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String buildChecksquareQuery(SteadyTrendResolvedFieldBO field, LocalDateTime startTime, LocalDateTime endTime) {
|
||||||
|
StringBuilder sql = new StringBuilder();
|
||||||
|
sql.append("SELECT \"").append(field.getField()).append("\" AS \"value\"");
|
||||||
|
sql.append(" FROM \"").append(field.getMeasurement()).append("\"");
|
||||||
|
sql.append(" WHERE time >= '").append(INFLUX_TIME_FORMATTER.format(startTime)).append("'");
|
||||||
|
sql.append(" AND time <= '").append(INFLUX_TIME_FORMATTER.format(endTime)).append("'");
|
||||||
|
sql.append(" AND \"line_id\" = '").append(escapeTagValue(field.getLineId())).append("'");
|
||||||
|
sql.append(" AND \"phasic_type\" = '").append(escapeTagValue(field.getPhase())).append("'");
|
||||||
|
if (hasValueTypeTag(field.getMeasurement())) {
|
||||||
|
sql.append(" AND \"value_type\" = '").append(resolveValueType(field.getStatType())).append("'");
|
||||||
|
}
|
||||||
|
sql.append(" ORDER BY time ASC");
|
||||||
|
return sql.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<LocalDateTime> parseExistingSlots(String body, int intervalMinutes) {
|
||||||
|
try {
|
||||||
|
JsonNode root = OBJECT_MAPPER.readTree(body);
|
||||||
|
JsonNode values = root.path("results").path(0).path("series").path(0).path("values");
|
||||||
|
Set<LocalDateTime> result = new HashSet<LocalDateTime>();
|
||||||
|
if (!values.isArray()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
for (JsonNode value : values) {
|
||||||
|
if (value.size() < 2 || value.get(1).isNull()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
LocalDateTime time = parseInfluxTime(value.get(0).asText());
|
||||||
|
if (time != null) {
|
||||||
|
result.add(alignToPreviousSlot(time, intervalMinutes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw fail("InfluxDB 返回结果解析失败:" + ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private LocalDateTime alignToPreviousSlot(LocalDateTime time, int intervalMinutes) {
|
||||||
|
LocalDateTime minuteFloor = time.withSecond(0).withNano(0);
|
||||||
|
int minuteOfDay = minuteFloor.getHour() * 60 + minuteFloor.getMinute();
|
||||||
|
int remainder = minuteOfDay % intervalMinutes;
|
||||||
|
return minuteFloor.minusMinutes(remainder);
|
||||||
|
}
|
||||||
|
|
||||||
|
private LocalDateTime parseInfluxTime(String value) {
|
||||||
|
try {
|
||||||
|
return OffsetDateTime.parse(value).withOffsetSameInstant(ZoneOffset.UTC).toLocalDateTime();
|
||||||
|
} catch (RuntimeException ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String executeQuery(String query) {
|
||||||
|
HttpURLConnection connection = null;
|
||||||
|
try {
|
||||||
|
URL url = new URL(buildQueryUrl(query));
|
||||||
|
connection = (HttpURLConnection) url.openConnection();
|
||||||
|
connection.setRequestMethod("GET");
|
||||||
|
connection.setConnectTimeout(properties.getConnectTimeoutMs());
|
||||||
|
connection.setReadTimeout(properties.getReadTimeoutMs());
|
||||||
|
int status = connection.getResponseCode();
|
||||||
|
InputStream stream = status >= 200 && status < 300 ? connection.getInputStream() : connection.getErrorStream();
|
||||||
|
String body = readBody(stream);
|
||||||
|
if (status < 200 || status >= 300) {
|
||||||
|
throw fail("InfluxDB 查询失败:" + body);
|
||||||
|
}
|
||||||
|
return body;
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw fail("InfluxDB 查询异常:" + ex.getMessage());
|
||||||
|
} finally {
|
||||||
|
if (connection != null) {
|
||||||
|
connection.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildQueryUrl(String query) throws IOException {
|
||||||
|
StringBuilder url = new StringBuilder(trimRightSlash(properties.getUrl())).append("/query?");
|
||||||
|
url.append("db=").append(encode(properties.getDatabase()));
|
||||||
|
if (properties.getUsername() != null && !properties.getUsername().trim().isEmpty()) {
|
||||||
|
url.append("&u=").append(encode(properties.getUsername().trim()));
|
||||||
|
}
|
||||||
|
if (properties.getPassword() != null && !properties.getPassword().trim().isEmpty()) {
|
||||||
|
url.append("&p=").append(encode(properties.getPassword()));
|
||||||
|
}
|
||||||
|
url.append("&q=").append(encode(query));
|
||||||
|
return url.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateConfig() {
|
||||||
|
if (properties.getUrl() == null || properties.getUrl().trim().isEmpty()) {
|
||||||
|
throw fail("InfluxDB 地址未配置");
|
||||||
|
}
|
||||||
|
if (properties.getDatabase() == null || properties.getDatabase().trim().isEmpty()) {
|
||||||
|
throw fail("InfluxDB database 未配置");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String readBody(InputStream stream) throws IOException {
|
||||||
|
if (stream == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8));
|
||||||
|
StringBuilder body = new StringBuilder();
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
body.append(line);
|
||||||
|
}
|
||||||
|
return body.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String escapeTagValue(String value) {
|
||||||
|
return value == null ? "" : value.replace("\\", "\\\\").replace("'", "\\'");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveValueType(String statType) {
|
||||||
|
if (statType == null || statType.trim().isEmpty()) {
|
||||||
|
return "AVG";
|
||||||
|
}
|
||||||
|
return statType.trim().toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasValueTypeTag(String measurement) {
|
||||||
|
return !"data_flicker".equals(measurement) && !"data_fluc".equals(measurement) && !"data_plt".equals(measurement);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String trimRightSlash(String value) {
|
||||||
|
String text = value.trim();
|
||||||
|
while (text.endsWith("/")) {
|
||||||
|
text = text.substring(0, text.length() - 1);
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String encode(String value) throws IOException {
|
||||||
|
return URLEncoder.encode(value, StandardCharsets.UTF_8.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
private BusinessException fail(String message) {
|
||||||
|
return new BusinessException(CommonResponseEnum.FAIL, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.njcn.gather.steady.checksquare.controller;
|
||||||
|
|
||||||
|
import com.njcn.common.pojo.annotation.OperateInfo;
|
||||||
|
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.steady.checksquare.pojo.param.SteadyChecksquareQueryParam;
|
||||||
|
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareQueryVO;
|
||||||
|
import com.njcn.gather.steady.checksquare.service.SteadyChecksquareService;
|
||||||
|
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 lombok.extern.slf4j.Slf4j;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据校验接口。
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Api(tags = "数据校验")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/steady/data-view/checksquare")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SteadyChecksquareController extends BaseController {
|
||||||
|
|
||||||
|
private final SteadyChecksquareService checksquareService;
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||||
|
@ApiOperation("查询数据校验结果")
|
||||||
|
@PostMapping("/query")
|
||||||
|
public HttpResult<SteadyChecksquareQueryVO> query(@RequestBody SteadyChecksquareQueryParam param) {
|
||||||
|
String methodDescribe = getMethodDescribe("query");
|
||||||
|
LogUtil.njcnDebug(log, "{},开始查询数据校验结果,param={}", methodDescribe, param);
|
||||||
|
SteadyChecksquareQueryVO result = checksquareService.query(param);
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.njcn.gather.steady.checksquare.pojo.param;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据校验查询参数。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ApiModel("数据校验查询参数")
|
||||||
|
public class SteadyChecksquareQueryParam implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@ApiModelProperty("监测点 ID")
|
||||||
|
private String lineId;
|
||||||
|
|
||||||
|
@ApiModelProperty("指标编码")
|
||||||
|
private List<String> indicatorCodes;
|
||||||
|
|
||||||
|
@ApiModelProperty("开始时间,格式 yyyy-MM-dd HH:mm:ss")
|
||||||
|
private String timeStart;
|
||||||
|
|
||||||
|
@ApiModelProperty("结束时间,格式 yyyy-MM-dd HH:mm:ss")
|
||||||
|
private String timeEnd;
|
||||||
|
|
||||||
|
@ApiModelProperty("谐波次数,谐波指标按请求次数查询")
|
||||||
|
private List<Integer> harmonicOrders;
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package com.njcn.gather.steady.checksquare.pojo.vo;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据校验总览项。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ApiModel("数据校验总览项")
|
||||||
|
public class SteadyChecksquareItemVO implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@ApiModelProperty("校验项唯一键")
|
||||||
|
private String itemKey;
|
||||||
|
|
||||||
|
@ApiModelProperty("指标编码")
|
||||||
|
private String indicatorCode;
|
||||||
|
|
||||||
|
@ApiModelProperty("指标名称")
|
||||||
|
private String indicatorName;
|
||||||
|
|
||||||
|
@ApiModelProperty("谐波次数")
|
||||||
|
private Integer harmonicOrder;
|
||||||
|
|
||||||
|
@ApiModelProperty("当前校验项统计间隔,单位分钟")
|
||||||
|
private Integer intervalMinutes;
|
||||||
|
|
||||||
|
@ApiModelProperty("时间范围内是否存在任意数据")
|
||||||
|
private Boolean hasData;
|
||||||
|
|
||||||
|
@ApiModelProperty("期望点数")
|
||||||
|
private Integer expectedPointCount;
|
||||||
|
|
||||||
|
@ApiModelProperty("实际点数")
|
||||||
|
private Integer actualPointCount;
|
||||||
|
|
||||||
|
@ApiModelProperty("缺失点数")
|
||||||
|
private Integer missingPointCount;
|
||||||
|
|
||||||
|
@ApiModelProperty("缺失率")
|
||||||
|
private BigDecimal missingRate;
|
||||||
|
|
||||||
|
@ApiModelProperty("缺失率文本")
|
||||||
|
private String missingRateText;
|
||||||
|
|
||||||
|
@ApiModelProperty("最大连续缺失时长,单位分钟")
|
||||||
|
private Integer maxContinuousMissingMinutes;
|
||||||
|
|
||||||
|
@ApiModelProperty("统计类型摘要")
|
||||||
|
private List<SteadyChecksquareStatSummaryVO> statSummaries = new ArrayList<SteadyChecksquareStatSummaryVO>();
|
||||||
|
|
||||||
|
@ApiModelProperty("统计类型明细")
|
||||||
|
private List<SteadyChecksquareStatDetailVO> statDetails = new ArrayList<SteadyChecksquareStatDetailVO>();
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.njcn.gather.steady.checksquare.pojo.vo;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据校验查询结果。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ApiModel("数据校验查询结果")
|
||||||
|
public class SteadyChecksquareQueryVO implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@ApiModelProperty("监测点 ID")
|
||||||
|
private String lineId;
|
||||||
|
|
||||||
|
@ApiModelProperty("监测点名称")
|
||||||
|
private String lineName;
|
||||||
|
|
||||||
|
@ApiModelProperty("开始时间")
|
||||||
|
private String timeStart;
|
||||||
|
|
||||||
|
@ApiModelProperty("结束时间")
|
||||||
|
private String timeEnd;
|
||||||
|
|
||||||
|
@ApiModelProperty("统计间隔,单位分钟")
|
||||||
|
private Integer intervalMinutes;
|
||||||
|
|
||||||
|
@ApiModelProperty("校验项")
|
||||||
|
private List<SteadyChecksquareItemVO> items = new ArrayList<SteadyChecksquareItemVO>();
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.njcn.gather.steady.checksquare.pojo.vo;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据校验连续性区间。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ApiModel("数据校验连续性区间")
|
||||||
|
public class SteadyChecksquareSegmentVO implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@ApiModelProperty("开始时间")
|
||||||
|
private String startTime;
|
||||||
|
|
||||||
|
@ApiModelProperty("结束时间")
|
||||||
|
private String endTime;
|
||||||
|
|
||||||
|
@ApiModelProperty("状态,NORMAL/MISSING")
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@ApiModelProperty("缺失点数")
|
||||||
|
private Integer missingPointCount;
|
||||||
|
|
||||||
|
@ApiModelProperty("持续时长,单位分钟")
|
||||||
|
private Integer durationMinutes;
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.njcn.gather.steady.checksquare.pojo.vo;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据校验统计类型明细。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ApiModel("数据校验统计类型明细")
|
||||||
|
public class SteadyChecksquareStatDetailVO implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@ApiModelProperty("统计类型")
|
||||||
|
private String statType;
|
||||||
|
|
||||||
|
@ApiModelProperty("是否支持")
|
||||||
|
private Boolean supported;
|
||||||
|
|
||||||
|
@ApiModelProperty("连续性区间")
|
||||||
|
private List<SteadyChecksquareSegmentVO> segments = new ArrayList<SteadyChecksquareSegmentVO>();
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.njcn.gather.steady.checksquare.pojo.vo;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据校验统计类型摘要。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ApiModel("数据校验统计类型摘要")
|
||||||
|
public class SteadyChecksquareStatSummaryVO implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@ApiModelProperty("统计类型")
|
||||||
|
private String statType;
|
||||||
|
|
||||||
|
@ApiModelProperty("是否支持")
|
||||||
|
private Boolean supported;
|
||||||
|
|
||||||
|
@ApiModelProperty("是否存在数据")
|
||||||
|
private Boolean hasData;
|
||||||
|
|
||||||
|
@ApiModelProperty("期望点数")
|
||||||
|
private Integer expectedPointCount;
|
||||||
|
|
||||||
|
@ApiModelProperty("实际点数")
|
||||||
|
private Integer actualPointCount;
|
||||||
|
|
||||||
|
@ApiModelProperty("缺失点数")
|
||||||
|
private Integer missingPointCount;
|
||||||
|
|
||||||
|
@ApiModelProperty("缺失率")
|
||||||
|
private BigDecimal missingRate;
|
||||||
|
|
||||||
|
@ApiModelProperty("缺失率文本")
|
||||||
|
private String missingRateText;
|
||||||
|
|
||||||
|
@ApiModelProperty("最大连续缺失时长,单位分钟")
|
||||||
|
private Integer maxContinuousMissingMinutes;
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.njcn.gather.steady.checksquare.service;
|
||||||
|
|
||||||
|
import com.njcn.gather.steady.checksquare.pojo.param.SteadyChecksquareQueryParam;
|
||||||
|
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareQueryVO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据校验服务。
|
||||||
|
*/
|
||||||
|
public interface SteadyChecksquareService {
|
||||||
|
|
||||||
|
SteadyChecksquareQueryVO query(SteadyChecksquareQueryParam param);
|
||||||
|
}
|
||||||
@@ -0,0 +1,349 @@
|
|||||||
|
package com.njcn.gather.steady.checksquare.service.impl;
|
||||||
|
|
||||||
|
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||||
|
import com.njcn.common.pojo.exception.BusinessException;
|
||||||
|
import com.njcn.gather.steady.checksquare.component.SteadyChecksquareCalculator;
|
||||||
|
import com.njcn.gather.steady.checksquare.component.SteadyChecksquareInfluxQueryComponent;
|
||||||
|
import com.njcn.gather.steady.checksquare.pojo.param.SteadyChecksquareQueryParam;
|
||||||
|
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareItemVO;
|
||||||
|
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareQueryVO;
|
||||||
|
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareSegmentVO;
|
||||||
|
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareStatDetailVO;
|
||||||
|
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareStatSummaryVO;
|
||||||
|
import com.njcn.gather.steady.checksquare.service.SteadyChecksquareService;
|
||||||
|
import com.njcn.gather.steady.datavie.component.SteadyTrendIndicatorCatalog;
|
||||||
|
import com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendIndicatorDefinitionBO;
|
||||||
|
import com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendResolvedFieldBO;
|
||||||
|
import com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendSeriesFieldBO;
|
||||||
|
import com.njcn.gather.tool.adddata.component.AddDataTimeSlotCalculator;
|
||||||
|
import com.njcn.gather.tool.addledger.pojo.constant.AddLedgerConst;
|
||||||
|
import com.njcn.gather.tool.addledger.pojo.vo.AddLedgerLinePathVO;
|
||||||
|
import com.njcn.gather.tool.addledger.service.AddLedgerService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.format.DateTimeParseException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据校验服务实现。
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
|
||||||
|
|
||||||
|
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||||
|
private static final String EMPTY_TEXT = "-";
|
||||||
|
private static final int FLICKER_SHORT_INTERVAL_MINUTES = 10;
|
||||||
|
private static final int FLICKER_LONG_INTERVAL_MINUTES = 120;
|
||||||
|
|
||||||
|
private final SteadyTrendIndicatorCatalog indicatorCatalog;
|
||||||
|
private final SteadyChecksquareInfluxQueryComponent influxQueryComponent;
|
||||||
|
private final SteadyChecksquareCalculator calculator;
|
||||||
|
private final AddDataTimeSlotCalculator timeSlotCalculator;
|
||||||
|
private final AddLedgerService addLedgerService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SteadyChecksquareQueryVO query(SteadyChecksquareQueryParam param) {
|
||||||
|
validateParam(param);
|
||||||
|
String lineId = trimToNull(param.getLineId());
|
||||||
|
LocalDateTime startTime = parseRequiredTime(param.getTimeStart(), "开始时间不能为空");
|
||||||
|
LocalDateTime endTime = parseRequiredTime(param.getTimeEnd(), "结束时间不能为空");
|
||||||
|
if (startTime.isAfter(endTime)) {
|
||||||
|
throw fail("开始时间不能大于结束时间");
|
||||||
|
}
|
||||||
|
AddLedgerLinePathVO linePath = requireLinePath(lineId);
|
||||||
|
int intervalMinutes = resolveIntervalMinutes(linePath);
|
||||||
|
SteadyChecksquareQueryVO result = new SteadyChecksquareQueryVO();
|
||||||
|
result.setLineId(lineId);
|
||||||
|
result.setLineName(trimToNull(linePath.getLineName()) == null ? EMPTY_TEXT : linePath.getLineName());
|
||||||
|
result.setTimeStart(param.getTimeStart());
|
||||||
|
result.setTimeEnd(param.getTimeEnd());
|
||||||
|
result.setIntervalMinutes(intervalMinutes);
|
||||||
|
|
||||||
|
long startMillis = System.currentTimeMillis();
|
||||||
|
List<String> indicatorCodes = normalizeTextList(param.getIndicatorCodes());
|
||||||
|
List<Integer> harmonicOrders = normalizeHarmonicOrders(param.getHarmonicOrders());
|
||||||
|
log.info("数据校验查询开始,lineId={},indicatorCount={},timeStart={},timeEnd={},intervalMinutes={}",
|
||||||
|
lineId, indicatorCodes.size(), startTime, endTime, intervalMinutes);
|
||||||
|
for (String indicatorCode : indicatorCodes) {
|
||||||
|
SteadyTrendIndicatorDefinitionBO indicator = requireIndicator(indicatorCode);
|
||||||
|
int itemIntervalMinutes = resolveIndicatorIntervalMinutes(indicator, intervalMinutes);
|
||||||
|
List<LocalDateTime> itemSlots = timeSlotCalculator.buildTimeSlots(startTime, endTime, itemIntervalMinutes);
|
||||||
|
result.getItems().addAll(buildIndicatorItems(lineId, indicator, harmonicOrders, startTime, endTime, itemSlots, itemIntervalMinutes));
|
||||||
|
}
|
||||||
|
log.info("数据校验查询结束,lineId={},itemCount={},costMs={}", lineId, result.getItems().size(), System.currentTimeMillis() - startMillis);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SteadyChecksquareItemVO> buildIndicatorItems(String lineId, SteadyTrendIndicatorDefinitionBO indicator,
|
||||||
|
List<Integer> harmonicOrders,
|
||||||
|
LocalDateTime startTime, LocalDateTime endTime,
|
||||||
|
List<LocalDateTime> slots, int intervalMinutes) {
|
||||||
|
List<SteadyChecksquareItemVO> result = new ArrayList<SteadyChecksquareItemVO>();
|
||||||
|
if (Boolean.TRUE.equals(indicator.getHarmonic())) {
|
||||||
|
for (Integer order : requireValidHarmonicOrders(indicator, harmonicOrders)) {
|
||||||
|
result.add(buildItem(lineId, indicator, order, startTime, endTime, slots, intervalMinutes));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result.add(buildItem(lineId, indicator, null, startTime, endTime, slots, intervalMinutes));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SteadyChecksquareItemVO buildItem(String lineId, SteadyTrendIndicatorDefinitionBO indicator, Integer harmonicOrder,
|
||||||
|
LocalDateTime startTime, LocalDateTime endTime,
|
||||||
|
List<LocalDateTime> slots, int intervalMinutes) {
|
||||||
|
SteadyChecksquareItemVO item = new SteadyChecksquareItemVO();
|
||||||
|
item.setItemKey(buildItemKey(lineId, indicator, harmonicOrder));
|
||||||
|
item.setIndicatorCode(indicator.getIndicatorCode());
|
||||||
|
item.setIndicatorName(indicator.getName());
|
||||||
|
item.setHarmonicOrder(harmonicOrder);
|
||||||
|
item.setIntervalMinutes(intervalMinutes);
|
||||||
|
|
||||||
|
int totalExpected = 0;
|
||||||
|
int totalActual = 0;
|
||||||
|
int maxContinuousMissingMinutes = 0;
|
||||||
|
boolean hasData = false;
|
||||||
|
for (String statType : indicator.getSupportStats()) {
|
||||||
|
Set<LocalDateTime> actualSlots = queryMergedActualSlots(lineId, indicator, harmonicOrder, statType, startTime, endTime, intervalMinutes);
|
||||||
|
Set<LocalDateTime> effectiveActualSlots = retainExpectedSlots(slots, actualSlots);
|
||||||
|
List<SteadyChecksquareSegmentVO> segments = calculator.buildSegments(slots, effectiveActualSlots, intervalMinutes);
|
||||||
|
SteadyChecksquareStatSummaryVO summary = buildSummary(statType, slots.size(), effectiveActualSlots.size(), segments);
|
||||||
|
SteadyChecksquareStatDetailVO detail = buildDetail(statType, segments);
|
||||||
|
item.getStatSummaries().add(summary);
|
||||||
|
item.getStatDetails().add(detail);
|
||||||
|
totalExpected += summary.getExpectedPointCount();
|
||||||
|
totalActual += summary.getActualPointCount();
|
||||||
|
maxContinuousMissingMinutes = Math.max(maxContinuousMissingMinutes, summary.getMaxContinuousMissingMinutes());
|
||||||
|
hasData = hasData || Boolean.TRUE.equals(summary.getHasData());
|
||||||
|
}
|
||||||
|
|
||||||
|
item.setHasData(hasData);
|
||||||
|
item.setExpectedPointCount(totalExpected);
|
||||||
|
item.setActualPointCount(totalActual);
|
||||||
|
item.setMissingPointCount(Math.max(0, totalExpected - totalActual));
|
||||||
|
item.setMissingRate(calculateRate(item.getMissingPointCount(), totalExpected));
|
||||||
|
item.setMissingRateText(formatRateText(item.getMissingRate()));
|
||||||
|
item.setMaxContinuousMissingMinutes(maxContinuousMissingMinutes);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<LocalDateTime> queryMergedActualSlots(String lineId, SteadyTrendIndicatorDefinitionBO indicator, Integer harmonicOrder,
|
||||||
|
String statType, LocalDateTime startTime, LocalDateTime endTime,
|
||||||
|
int intervalMinutes) {
|
||||||
|
Set<LocalDateTime> result = new HashSet<LocalDateTime>();
|
||||||
|
for (String phase : indicator.getPhaseCodes()) {
|
||||||
|
SteadyTrendResolvedFieldBO field = buildResolvedField(lineId, indicator, harmonicOrder, phase, statType);
|
||||||
|
result.addAll(influxQueryComponent.queryExistingSlots(field, startTime, endTime, intervalMinutes));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<LocalDateTime> retainExpectedSlots(List<LocalDateTime> slots, Set<LocalDateTime> actualSlots) {
|
||||||
|
Set<LocalDateTime> result = new HashSet<LocalDateTime>();
|
||||||
|
if (slots == null || actualSlots == null || actualSlots.isEmpty()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
for (LocalDateTime slot : slots) {
|
||||||
|
if (actualSlots.contains(slot)) {
|
||||||
|
result.add(slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SteadyTrendResolvedFieldBO buildResolvedField(String lineId, SteadyTrendIndicatorDefinitionBO indicator,
|
||||||
|
Integer harmonicOrder, String phase, String statType) {
|
||||||
|
SteadyTrendResolvedFieldBO field = new SteadyTrendResolvedFieldBO();
|
||||||
|
field.setMeasurement(indicator.getTableName());
|
||||||
|
field.setField(resolveField(indicator, harmonicOrder));
|
||||||
|
field.setLineId(lineId);
|
||||||
|
field.setIndicatorCode(indicator.getIndicatorCode());
|
||||||
|
field.setIndicatorName(indicator.getName());
|
||||||
|
field.setPhase(phase);
|
||||||
|
field.setStatType(statType);
|
||||||
|
field.setUnit(indicator.getUnit());
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveField(SteadyTrendIndicatorDefinitionBO indicator, Integer harmonicOrder) {
|
||||||
|
if (Boolean.TRUE.equals(indicator.getHarmonic())) {
|
||||||
|
return indicator.getHarmonicFieldPrefix() + "_" + harmonicOrder;
|
||||||
|
}
|
||||||
|
List<SteadyTrendSeriesFieldBO> fields = indicator.getSeriesFields();
|
||||||
|
if (fields == null || fields.isEmpty()) {
|
||||||
|
throw fail("稳态指标不支持:" + indicator.getIndicatorCode());
|
||||||
|
}
|
||||||
|
return fields.get(0).getField();
|
||||||
|
}
|
||||||
|
|
||||||
|
private SteadyChecksquareStatSummaryVO buildSummary(String statType, int expectedCount, int actualCount,
|
||||||
|
List<SteadyChecksquareSegmentVO> segments) {
|
||||||
|
SteadyChecksquareStatSummaryVO summary = new SteadyChecksquareStatSummaryVO();
|
||||||
|
summary.setStatType(statType);
|
||||||
|
summary.setSupported(true);
|
||||||
|
summary.setHasData(actualCount > 0);
|
||||||
|
summary.setExpectedPointCount(expectedCount);
|
||||||
|
summary.setActualPointCount(actualCount);
|
||||||
|
summary.setMissingPointCount(Math.max(0, expectedCount - actualCount));
|
||||||
|
summary.setMissingRate(calculateRate(summary.getMissingPointCount(), expectedCount));
|
||||||
|
summary.setMissingRateText(formatRateText(summary.getMissingRate()));
|
||||||
|
summary.setMaxContinuousMissingMinutes(calculator.maxContinuousMissingMinutes(segments));
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SteadyChecksquareStatDetailVO buildDetail(String statType, List<SteadyChecksquareSegmentVO> segments) {
|
||||||
|
SteadyChecksquareStatDetailVO detail = new SteadyChecksquareStatDetailVO();
|
||||||
|
detail.setStatType(statType);
|
||||||
|
detail.setSupported(true);
|
||||||
|
detail.setSegments(segments);
|
||||||
|
return detail;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildItemKey(String lineId, SteadyTrendIndicatorDefinitionBO indicator, Integer harmonicOrder) {
|
||||||
|
if (harmonicOrder == null) {
|
||||||
|
return lineId + "|" + indicator.getIndicatorCode();
|
||||||
|
}
|
||||||
|
return lineId + "|" + indicator.getIndicatorCode() + "|" + harmonicOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateParam(SteadyChecksquareQueryParam param) {
|
||||||
|
if (param == null) {
|
||||||
|
throw fail("数据校验参数不能为空");
|
||||||
|
}
|
||||||
|
if (trimToNull(param.getLineId()) == null) {
|
||||||
|
throw fail("监测点 ID 不能为空");
|
||||||
|
}
|
||||||
|
if (normalizeTextList(param.getIndicatorCodes()).isEmpty()) {
|
||||||
|
throw fail("指标不能为空");
|
||||||
|
}
|
||||||
|
parseRequiredTime(param.getTimeStart(), "开始时间不能为空");
|
||||||
|
parseRequiredTime(param.getTimeEnd(), "结束时间不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
private LocalDateTime parseRequiredTime(String time, String emptyMessage) {
|
||||||
|
String text = trimToNull(time);
|
||||||
|
if (text == null) {
|
||||||
|
throw fail(emptyMessage);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return LocalDateTime.parse(text, TIME_FORMATTER);
|
||||||
|
} catch (DateTimeParseException ex) {
|
||||||
|
throw fail("时间格式不正确,仅支持 yyyy-MM-dd HH:mm:ss");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private AddLedgerLinePathVO requireLinePath(String lineId) {
|
||||||
|
Map<String, AddLedgerLinePathVO> linePathMap = addLedgerService.listLinePathByLineIds(Collections.singletonList(lineId));
|
||||||
|
AddLedgerLinePathVO linePath = linePathMap.get(lineId);
|
||||||
|
if (linePath == null) {
|
||||||
|
throw fail("监测点不存在或不可用");
|
||||||
|
}
|
||||||
|
return linePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int resolveIntervalMinutes(AddLedgerLinePathVO linePath) {
|
||||||
|
Integer interval = linePath.getLineInterval();
|
||||||
|
if (interval == null || interval <= 0) {
|
||||||
|
return AddLedgerConst.LINE_INTERVAL_DEFAULT;
|
||||||
|
}
|
||||||
|
return interval;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int resolveIndicatorIntervalMinutes(SteadyTrendIndicatorDefinitionBO indicator, int lineIntervalMinutes) {
|
||||||
|
String indicatorCode = indicator == null ? null : indicator.getIndicatorCode();
|
||||||
|
if ("FLUC".equals(indicatorCode) || "PST".equals(indicatorCode)) {
|
||||||
|
return FLICKER_SHORT_INTERVAL_MINUTES;
|
||||||
|
}
|
||||||
|
if ("PLT".equals(indicatorCode)) {
|
||||||
|
return FLICKER_LONG_INTERVAL_MINUTES;
|
||||||
|
}
|
||||||
|
return lineIntervalMinutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SteadyTrendIndicatorDefinitionBO requireIndicator(String indicatorCode) {
|
||||||
|
SteadyTrendIndicatorDefinitionBO indicator = indicatorCatalog.getIndicator(indicatorCode);
|
||||||
|
if (indicator == null) {
|
||||||
|
throw fail("稳态指标不支持:" + indicatorCode);
|
||||||
|
}
|
||||||
|
return indicator;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigDecimal calculateRate(int missingCount, int expectedCount) {
|
||||||
|
if (expectedCount <= 0) {
|
||||||
|
return BigDecimal.ZERO.setScale(6, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
return new BigDecimal(missingCount).divide(new BigDecimal(expectedCount), 6, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatRateText(BigDecimal rate) {
|
||||||
|
if (rate == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return rate.multiply(new BigDecimal("100")).setScale(2, RoundingMode.HALF_UP).toPlainString() + "%";
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> normalizeTextList(List<String> values) {
|
||||||
|
if (values == null || values.isEmpty()) {
|
||||||
|
return new ArrayList<String>();
|
||||||
|
}
|
||||||
|
Set<String> result = new LinkedHashSet<String>();
|
||||||
|
for (String value : values) {
|
||||||
|
String text = trimToNull(value);
|
||||||
|
if (text != null) {
|
||||||
|
result.add(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ArrayList<String>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Integer> normalizeHarmonicOrders(List<Integer> values) {
|
||||||
|
if (values == null || values.isEmpty()) {
|
||||||
|
return new ArrayList<Integer>();
|
||||||
|
}
|
||||||
|
List<Integer> result = new ArrayList<Integer>();
|
||||||
|
for (Integer value : values) {
|
||||||
|
if (value != null && !result.contains(value)) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Integer> requireValidHarmonicOrders(SteadyTrendIndicatorDefinitionBO indicator, List<Integer> harmonicOrders) {
|
||||||
|
if (harmonicOrders == null || harmonicOrders.isEmpty()) {
|
||||||
|
throw fail("谐波次数不能为空");
|
||||||
|
}
|
||||||
|
for (Integer order : harmonicOrders) {
|
||||||
|
if (order < indicator.getHarmonicOrderStart() || order > indicator.getHarmonicOrderEnd()) {
|
||||||
|
throw fail("谐波次数只能在 " + indicator.getHarmonicOrderStart() + " 到 " + indicator.getHarmonicOrderEnd() + " 之间");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return harmonicOrders;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String trimToNull(String value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String trimmed = value.trim();
|
||||||
|
return trimmed.isEmpty() ? null : trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BusinessException fail(String message) {
|
||||||
|
return new BusinessException(CommonResponseEnum.FAIL, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import com.njcn.gather.steady.datavie.config.SteadyInfluxDbProperties;
|
|||||||
import com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendResolvedFieldBO;
|
import com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendResolvedFieldBO;
|
||||||
import com.njcn.gather.steady.datavie.pojo.vo.SteadyTrendPointVO;
|
import com.njcn.gather.steady.datavie.pojo.vo.SteadyTrendPointVO;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
@@ -29,6 +30,7 @@ import java.util.List;
|
|||||||
/**
|
/**
|
||||||
* 稳态趋势 InfluxDB 查询组件。
|
* 稳态趋势 InfluxDB 查询组件。
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class SteadyInfluxQueryComponent {
|
public class SteadyInfluxQueryComponent {
|
||||||
@@ -43,8 +45,32 @@ public class SteadyInfluxQueryComponent {
|
|||||||
LocalDateTime endTime, Integer qualityFlag) {
|
LocalDateTime endTime, Integer qualityFlag) {
|
||||||
validateConfig();
|
validateConfig();
|
||||||
String query = buildTrendQuery(field, startTime, endTime, qualityFlag);
|
String query = buildTrendQuery(field, startTime, endTime, qualityFlag);
|
||||||
String body = executeQuery(query);
|
String diagnostic = buildTrendQueryDiagnostic(field, startTime, endTime, qualityFlag);
|
||||||
return parseTrendPoints(body);
|
long startMillis = System.currentTimeMillis();
|
||||||
|
log.info("稳态趋势 InfluxDB 查询开始,{},query={}", diagnostic, query);
|
||||||
|
try {
|
||||||
|
String body = executeQuery(query);
|
||||||
|
List<SteadyTrendPointVO> points = parseTrendPoints(body);
|
||||||
|
log.info("稳态趋势 InfluxDB 查询结束,{},pointCount={},costMs={}", diagnostic, points.size(), System.currentTimeMillis() - startMillis);
|
||||||
|
return points;
|
||||||
|
} catch (RuntimeException ex) {
|
||||||
|
log.warn("稳态趋势 InfluxDB 查询异常,{},costMs={},error={}", diagnostic, System.currentTimeMillis() - startMillis, ex.getMessage());
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String buildTrendQueryDiagnostic(SteadyTrendResolvedFieldBO field, LocalDateTime startTime, LocalDateTime endTime,
|
||||||
|
Integer qualityFlag) {
|
||||||
|
StringBuilder diagnostic = new StringBuilder();
|
||||||
|
diagnostic.append("measurement=").append(field.getMeasurement());
|
||||||
|
diagnostic.append(", field=").append(field.getField());
|
||||||
|
diagnostic.append(", lineId=").append(field.getLineId());
|
||||||
|
diagnostic.append(", phase=").append(field.getPhase());
|
||||||
|
diagnostic.append(", statType=").append(resolveValueType(field.getStatType()));
|
||||||
|
diagnostic.append(", qualityFlag=").append(qualityFlag);
|
||||||
|
diagnostic.append(", timeStart=").append(startTime);
|
||||||
|
diagnostic.append(", timeEnd=").append(endTime);
|
||||||
|
return diagnostic.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String buildTrendQuery(SteadyTrendResolvedFieldBO field, LocalDateTime startTime, LocalDateTime endTime,
|
public String buildTrendQuery(SteadyTrendResolvedFieldBO field, LocalDateTime startTime, LocalDateTime endTime,
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public class SteadyTrendFieldResolver {
|
|||||||
private static final int MAX_LINE_COUNT = 8;
|
private static final int MAX_LINE_COUNT = 8;
|
||||||
private static final int MAX_INDICATOR_COUNT = 8;
|
private static final int MAX_INDICATOR_COUNT = 8;
|
||||||
private static final int MAX_SERIES_COUNT = 24;
|
private static final int MAX_SERIES_COUNT = 24;
|
||||||
private static final int MAX_HARMONIC_ORDER_COUNT = 6;
|
private static final int MAX_HARMONIC_ORDER_COUNT = 3;
|
||||||
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||||
|
|
||||||
private final SteadyTrendIndicatorCatalog indicatorCatalog;
|
private final SteadyTrendIndicatorCatalog indicatorCatalog;
|
||||||
@@ -89,7 +89,7 @@ public class SteadyTrendFieldResolver {
|
|||||||
throw fail("谐波次数不能为空");
|
throw fail("谐波次数不能为空");
|
||||||
}
|
}
|
||||||
if (orders.size() > MAX_HARMONIC_ORDER_COUNT) {
|
if (orders.size() > MAX_HARMONIC_ORDER_COUNT) {
|
||||||
throw fail("谐波次数最多选择 6 个");
|
throw fail("谐波次数不允许一次展示超过3个");
|
||||||
}
|
}
|
||||||
List<SteadyTrendResolvedFieldBO> result = new ArrayList<SteadyTrendResolvedFieldBO>();
|
List<SteadyTrendResolvedFieldBO> result = new ArrayList<SteadyTrendResolvedFieldBO>();
|
||||||
for (Integer order : orders) {
|
for (Integer order : orders) {
|
||||||
|
|||||||
@@ -32,6 +32,6 @@ public class SteadyTrendQueryParam {
|
|||||||
@ApiModelProperty("质量标识")
|
@ApiModelProperty("质量标识")
|
||||||
private Integer qualityFlag;
|
private Integer qualityFlag;
|
||||||
|
|
||||||
@ApiModelProperty("谐波次数,谐波指标必填,最多 6 个")
|
@ApiModelProperty("谐波次数,谐波指标必填,默认最多展示 3 个")
|
||||||
private List<Integer> harmonicOrders = new ArrayList<Integer>();
|
private List<Integer> harmonicOrders = new ArrayList<Integer>();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import com.njcn.gather.steady.datavie.service.SteadyDataViewTrendService;
|
|||||||
import com.njcn.gather.tool.addledger.pojo.vo.AddLedgerLinePathVO;
|
import com.njcn.gather.tool.addledger.pojo.vo.AddLedgerLinePathVO;
|
||||||
import com.njcn.gather.tool.addledger.service.AddLedgerService;
|
import com.njcn.gather.tool.addledger.service.AddLedgerService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
@@ -28,6 +29,7 @@ import java.util.Map;
|
|||||||
/**
|
/**
|
||||||
* 稳态趋势查询服务实现。
|
* 稳态趋势查询服务实现。
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class SteadyDataViewTrendServiceImpl implements SteadyDataViewTrendService {
|
public class SteadyDataViewTrendServiceImpl implements SteadyDataViewTrendService {
|
||||||
@@ -70,11 +72,14 @@ public class SteadyDataViewTrendServiceImpl implements SteadyDataViewTrendServic
|
|||||||
result.setSampled(false);
|
result.setSampled(false);
|
||||||
result.setLoadableDays(resolveLoadableDays(startTime, endTime));
|
result.setLoadableDays(resolveLoadableDays(startTime, endTime));
|
||||||
int displayPointCount = 0;
|
int displayPointCount = 0;
|
||||||
|
long startMillis = System.currentTimeMillis();
|
||||||
|
log.info("稳态趋势查询开始,seriesCount={},timeStart={},timeEnd={},qualityFlag={}", fields.size(), startTime, endTime, param.getQualityFlag());
|
||||||
for (SteadyTrendResolvedFieldBO field : fields) {
|
for (SteadyTrendResolvedFieldBO field : fields) {
|
||||||
List<SteadyTrendPointVO> points = influxQueryComponent.queryTrendPoints(field, startTime, endTime, param.getQualityFlag());
|
List<SteadyTrendPointVO> points = influxQueryComponent.queryTrendPoints(field, startTime, endTime, param.getQualityFlag());
|
||||||
displayPointCount += points.size();
|
displayPointCount += points.size();
|
||||||
result.getSeries().add(buildSeries(field, points));
|
result.getSeries().add(buildSeries(field, points));
|
||||||
}
|
}
|
||||||
|
log.info("稳态趋势查询结束,seriesCount={},displayPointCount={},costMs={}", fields.size(), displayPointCount, System.currentTimeMillis() - startMillis);
|
||||||
/*
|
/*
|
||||||
* 当前 Influx 查询按曲线独立执行,未额外发 count 查询;sourcePointCount 保持与实际返回点数一致。
|
* 当前 Influx 查询按曲线独立执行,未额外发 count 查询;sourcePointCount 保持与实际返回点数一致。
|
||||||
* 后续如需要精确原始点数,可单独增加 count(field) 查询。
|
* 后续如需要精确原始点数,可单独增加 count(field) 查询。
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package com.njcn.gather.steady.checksquare.component;
|
||||||
|
|
||||||
|
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareSegmentVO;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据校验缺失区间计算测试。
|
||||||
|
*/
|
||||||
|
class SteadyChecksquareCalculatorTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldMergeContinuousMissingSlots() {
|
||||||
|
SteadyChecksquareCalculator calculator = new SteadyChecksquareCalculator();
|
||||||
|
List<LocalDateTime> slots = Arrays.asList(
|
||||||
|
LocalDateTime.of(2026, 5, 1, 0, 0),
|
||||||
|
LocalDateTime.of(2026, 5, 1, 0, 1),
|
||||||
|
LocalDateTime.of(2026, 5, 1, 0, 2),
|
||||||
|
LocalDateTime.of(2026, 5, 1, 0, 3),
|
||||||
|
LocalDateTime.of(2026, 5, 1, 0, 4)
|
||||||
|
);
|
||||||
|
|
||||||
|
List<SteadyChecksquareSegmentVO> segments = calculator.buildSegments(slots,
|
||||||
|
new HashSet<LocalDateTime>(Arrays.asList(slots.get(0), slots.get(3))), 1);
|
||||||
|
|
||||||
|
Assertions.assertEquals(4, segments.size());
|
||||||
|
Assertions.assertEquals("MISSING", segments.get(1).getStatus());
|
||||||
|
Assertions.assertEquals("2026-05-01 00:01:00", segments.get(1).getStartTime());
|
||||||
|
Assertions.assertEquals("2026-05-01 00:02:00", segments.get(1).getEndTime());
|
||||||
|
Assertions.assertEquals(Integer.valueOf(2), segments.get(1).getMissingPointCount());
|
||||||
|
Assertions.assertEquals(Integer.valueOf(2), segments.get(1).getDurationMinutes());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.njcn.gather.steady.checksquare.component;
|
||||||
|
|
||||||
|
import com.njcn.gather.steady.datavie.config.SteadyInfluxDbProperties;
|
||||||
|
import com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendResolvedFieldBO;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据校验 InfluxQL 构造契约测试。
|
||||||
|
*/
|
||||||
|
class SteadyChecksquareInfluxQueryComponentTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldBuildChecksquareQueryWithoutQualityFlag() {
|
||||||
|
SteadyChecksquareInfluxQueryComponent component = new SteadyChecksquareInfluxQueryComponent(new SteadyInfluxDbProperties());
|
||||||
|
SteadyTrendResolvedFieldBO field = new SteadyTrendResolvedFieldBO();
|
||||||
|
field.setMeasurement("data_v");
|
||||||
|
field.setField("rms");
|
||||||
|
field.setLineId("line-001");
|
||||||
|
field.setPhase("A");
|
||||||
|
field.setStatType("AVG");
|
||||||
|
|
||||||
|
String query = component.buildChecksquareQuery(field,
|
||||||
|
LocalDateTime.of(2026, 5, 1, 0, 0, 0),
|
||||||
|
LocalDateTime.of(2026, 5, 1, 23, 59, 59));
|
||||||
|
|
||||||
|
Assertions.assertTrue(query.contains("SELECT \"rms\" AS \"value\""));
|
||||||
|
Assertions.assertTrue(query.contains("\"line_id\" = 'line-001'"));
|
||||||
|
Assertions.assertTrue(query.contains("\"phasic_type\" = 'A'"));
|
||||||
|
Assertions.assertTrue(query.contains("\"value_type\" = 'AVG'"));
|
||||||
|
Assertions.assertFalse(query.contains("quality_flag"));
|
||||||
|
Assertions.assertFalse(query.contains("GROUP BY time"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.njcn.gather.steady.checksquare.controller;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据校验接口契约测试。
|
||||||
|
*/
|
||||||
|
class SteadyChecksquareControllerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExposeChecksquareQueryEndpointInSeparateController() throws Exception {
|
||||||
|
RequestMapping requestMapping = SteadyChecksquareController.class.getAnnotation(RequestMapping.class);
|
||||||
|
Assertions.assertArrayEquals(new String[]{"/steady/data-view/checksquare"}, requestMapping.value());
|
||||||
|
|
||||||
|
Method method = SteadyChecksquareController.class.getDeclaredMethod("query", com.njcn.gather.steady.checksquare.pojo.param.SteadyChecksquareQueryParam.class);
|
||||||
|
PostMapping postMapping = method.getAnnotation(PostMapping.class);
|
||||||
|
Assertions.assertArrayEquals(new String[]{"/query"}, postMapping.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.njcn.gather.steady.checksquare.pojo.param;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据校验查询参数契约测试。
|
||||||
|
*/
|
||||||
|
class SteadyChecksquareQueryParamTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldOnlyExposeChecksquareQueryFields() {
|
||||||
|
Assertions.assertNotNull(field("lineId"));
|
||||||
|
Assertions.assertNotNull(field("indicatorCodes"));
|
||||||
|
Assertions.assertNotNull(field("timeStart"));
|
||||||
|
Assertions.assertNotNull(field("timeEnd"));
|
||||||
|
Assertions.assertNull(field("qualityFlag"));
|
||||||
|
Assertions.assertNull(field("statTypes"));
|
||||||
|
Assertions.assertNull(field("phases"));
|
||||||
|
Assertions.assertNotNull(field("harmonicOrders"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Field field(String name) {
|
||||||
|
try {
|
||||||
|
return SteadyChecksquareQueryParam.class.getDeclaredField(name);
|
||||||
|
} catch (NoSuchFieldException ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package com.njcn.gather.steady.checksquare.service.impl;
|
||||||
|
|
||||||
|
import com.njcn.gather.steady.checksquare.component.SteadyChecksquareCalculator;
|
||||||
|
import com.njcn.gather.steady.checksquare.component.SteadyChecksquareInfluxQueryComponent;
|
||||||
|
import com.njcn.gather.steady.checksquare.pojo.param.SteadyChecksquareQueryParam;
|
||||||
|
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareItemVO;
|
||||||
|
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareQueryVO;
|
||||||
|
import com.njcn.gather.steady.datavie.component.SteadyTrendIndicatorCatalog;
|
||||||
|
import com.njcn.gather.tool.adddata.component.AddDataTimeSlotCalculator;
|
||||||
|
import com.njcn.gather.tool.addledger.pojo.vo.AddLedgerLinePathVO;
|
||||||
|
import com.njcn.gather.tool.addledger.service.AddLedgerService;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据校验服务测试。
|
||||||
|
*/
|
||||||
|
class SteadyChecksquareServiceImplTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldUseFixedFlickerIntervalsPerIndicator() {
|
||||||
|
SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class);
|
||||||
|
AddLedgerService addLedgerService = mock(AddLedgerService.class);
|
||||||
|
SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(),
|
||||||
|
influxQueryComponent, new SteadyChecksquareCalculator(), new AddDataTimeSlotCalculator(), addLedgerService);
|
||||||
|
AddLedgerLinePathVO linePath = new AddLedgerLinePathVO();
|
||||||
|
linePath.setLineId("line-001");
|
||||||
|
linePath.setLineName("进线一");
|
||||||
|
linePath.setLineInterval(1);
|
||||||
|
when(addLedgerService.listLinePathByLineIds(eq(Collections.singletonList("line-001"))))
|
||||||
|
.thenReturn(Collections.singletonMap("line-001", linePath));
|
||||||
|
when(influxQueryComponent.queryExistingSlots(any(), any(LocalDateTime.class), any(LocalDateTime.class), eq(10)))
|
||||||
|
.thenReturn(new HashSet<LocalDateTime>(Arrays.asList(
|
||||||
|
LocalDateTime.of(2026, 5, 1, 0, 0),
|
||||||
|
LocalDateTime.of(2026, 5, 1, 0, 10))));
|
||||||
|
when(influxQueryComponent.queryExistingSlots(any(), any(LocalDateTime.class), any(LocalDateTime.class), eq(120)))
|
||||||
|
.thenReturn(new HashSet<LocalDateTime>(Collections.singletonList(
|
||||||
|
LocalDateTime.of(2026, 5, 1, 0, 0))));
|
||||||
|
|
||||||
|
SteadyChecksquareQueryParam param = new SteadyChecksquareQueryParam();
|
||||||
|
param.setLineId("line-001");
|
||||||
|
param.setIndicatorCodes(Arrays.asList("FLUC", "PST", "PLT"));
|
||||||
|
param.setTimeStart("2026-05-01 00:00:00");
|
||||||
|
param.setTimeEnd("2026-05-01 02:00:00");
|
||||||
|
|
||||||
|
SteadyChecksquareQueryVO result = service.query(param);
|
||||||
|
|
||||||
|
Assertions.assertEquals(Integer.valueOf(1), result.getIntervalMinutes());
|
||||||
|
Assertions.assertEquals(3, result.getItems().size());
|
||||||
|
assertItemInterval(result.getItems().get(0), "FLUC", 10, 13);
|
||||||
|
assertItemInterval(result.getItems().get(1), "PST", 10, 13);
|
||||||
|
assertItemInterval(result.getItems().get(2), "PLT", 120, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldOnlyQueryRequestedHarmonicOrders() {
|
||||||
|
SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class);
|
||||||
|
AddLedgerService addLedgerService = mock(AddLedgerService.class);
|
||||||
|
SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(),
|
||||||
|
influxQueryComponent, new SteadyChecksquareCalculator(), new AddDataTimeSlotCalculator(), addLedgerService);
|
||||||
|
AddLedgerLinePathVO linePath = new AddLedgerLinePathVO();
|
||||||
|
linePath.setLineId("line-001");
|
||||||
|
linePath.setLineName("进线一");
|
||||||
|
linePath.setLineInterval(1);
|
||||||
|
when(addLedgerService.listLinePathByLineIds(eq(Collections.singletonList("line-001"))))
|
||||||
|
.thenReturn(Collections.singletonMap("line-001", linePath));
|
||||||
|
when(influxQueryComponent.queryExistingSlots(any(), any(LocalDateTime.class), any(LocalDateTime.class), eq(1)))
|
||||||
|
.thenReturn(new HashSet<LocalDateTime>(Collections.singletonList(
|
||||||
|
LocalDateTime.of(2026, 5, 1, 0, 0))));
|
||||||
|
|
||||||
|
SteadyChecksquareQueryParam param = new SteadyChecksquareQueryParam();
|
||||||
|
param.setLineId("line-001");
|
||||||
|
param.setIndicatorCodes(Collections.singletonList("V_HARMONIC"));
|
||||||
|
param.setHarmonicOrders(Collections.singletonList(5));
|
||||||
|
param.setTimeStart("2026-05-01 00:00:00");
|
||||||
|
param.setTimeEnd("2026-05-01 00:01:00");
|
||||||
|
|
||||||
|
SteadyChecksquareQueryVO result = service.query(param);
|
||||||
|
|
||||||
|
Assertions.assertEquals(1, result.getItems().size());
|
||||||
|
Assertions.assertEquals(Integer.valueOf(5), result.getItems().get(0).getHarmonicOrder());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldKeepRequestedHarmonicOrdersDistinctAndOrdered() {
|
||||||
|
SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class);
|
||||||
|
AddLedgerService addLedgerService = mock(AddLedgerService.class);
|
||||||
|
SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(),
|
||||||
|
influxQueryComponent, new SteadyChecksquareCalculator(), new AddDataTimeSlotCalculator(), addLedgerService);
|
||||||
|
AddLedgerLinePathVO linePath = new AddLedgerLinePathVO();
|
||||||
|
linePath.setLineId("line-001");
|
||||||
|
linePath.setLineName("进线一");
|
||||||
|
linePath.setLineInterval(1);
|
||||||
|
when(addLedgerService.listLinePathByLineIds(eq(Collections.singletonList("line-001"))))
|
||||||
|
.thenReturn(Collections.singletonMap("line-001", linePath));
|
||||||
|
when(influxQueryComponent.queryExistingSlots(any(), any(LocalDateTime.class), any(LocalDateTime.class), eq(1)))
|
||||||
|
.thenReturn(new HashSet<LocalDateTime>());
|
||||||
|
|
||||||
|
SteadyChecksquareQueryParam param = new SteadyChecksquareQueryParam();
|
||||||
|
param.setLineId("line-001");
|
||||||
|
param.setIndicatorCodes(Collections.singletonList("V_HARMONIC"));
|
||||||
|
param.setHarmonicOrders(Arrays.asList(7, 5, 7));
|
||||||
|
param.setTimeStart("2026-05-01 00:00:00");
|
||||||
|
param.setTimeEnd("2026-05-01 00:01:00");
|
||||||
|
|
||||||
|
SteadyChecksquareQueryVO result = service.query(param);
|
||||||
|
|
||||||
|
List<SteadyChecksquareItemVO> items = result.getItems();
|
||||||
|
Assertions.assertEquals(2, items.size());
|
||||||
|
Assertions.assertEquals(Integer.valueOf(7), items.get(0).getHarmonicOrder());
|
||||||
|
Assertions.assertEquals(Integer.valueOf(5), items.get(1).getHarmonicOrder());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertItemInterval(SteadyChecksquareItemVO item, String indicatorCode, int intervalMinutes, int expectedPointCount) {
|
||||||
|
Assertions.assertEquals(indicatorCode, item.getIndicatorCode());
|
||||||
|
Assertions.assertEquals(Integer.valueOf(intervalMinutes), item.getIntervalMinutes());
|
||||||
|
Assertions.assertEquals(Integer.valueOf(expectedPointCount), item.getExpectedPointCount());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -73,6 +73,31 @@ class SteadyInfluxQueryComponentTest {
|
|||||||
Assertions.assertTrue(query.contains("\"value_type\" = 'AVG'"));
|
Assertions.assertTrue(query.contains("\"value_type\" = 'AVG'"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldBuildDiagnosticTextForTrendQuery() {
|
||||||
|
SteadyInfluxQueryComponent component = new SteadyInfluxQueryComponent(new SteadyInfluxDbProperties());
|
||||||
|
SteadyTrendResolvedFieldBO field = new SteadyTrendResolvedFieldBO();
|
||||||
|
field.setMeasurement("data_harmpower_p");
|
||||||
|
field.setField("p_3");
|
||||||
|
field.setLineId("f828bc42132841c2aeebc6859f5a9b7c");
|
||||||
|
field.setPhase("A");
|
||||||
|
field.setStatType("AVG");
|
||||||
|
|
||||||
|
String diagnostic = component.buildTrendQueryDiagnostic(field,
|
||||||
|
LocalDateTime.of(2026, 5, 1, 0, 0, 0),
|
||||||
|
LocalDateTime.of(2026, 5, 31, 23, 59, 59),
|
||||||
|
0);
|
||||||
|
|
||||||
|
Assertions.assertTrue(diagnostic.contains("measurement=data_harmpower_p"));
|
||||||
|
Assertions.assertTrue(diagnostic.contains("field=p_3"));
|
||||||
|
Assertions.assertTrue(diagnostic.contains("lineId=f828bc42132841c2aeebc6859f5a9b7c"));
|
||||||
|
Assertions.assertTrue(diagnostic.contains("phase=A"));
|
||||||
|
Assertions.assertTrue(diagnostic.contains("statType=AVG"));
|
||||||
|
Assertions.assertTrue(diagnostic.contains("qualityFlag=0"));
|
||||||
|
Assertions.assertTrue(diagnostic.contains("timeStart=2026-05-01T00:00"));
|
||||||
|
Assertions.assertTrue(diagnostic.contains("timeEnd=2026-05-31T23:59:59"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldSkipValueTypeWhenMeasurementHasNoValueTypeTag() {
|
void shouldSkipValueTypeWhenMeasurementHasNoValueTypeTag() {
|
||||||
SteadyInfluxQueryComponent component = new SteadyInfluxQueryComponent(new SteadyInfluxDbProperties());
|
SteadyInfluxQueryComponent component = new SteadyInfluxQueryComponent(new SteadyInfluxDbProperties());
|
||||||
|
|||||||
@@ -109,6 +109,21 @@ class SteadyTrendFieldResolverTest {
|
|||||||
Assertions.assertTrue(exception.getMessage().contains("谐波次数不能为空"));
|
Assertions.assertTrue(exception.getMessage().contains("谐波次数不能为空"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRejectHarmonicTrendWithMoreThanThreeOrders() {
|
||||||
|
SteadyTrendQueryParam param = new SteadyTrendQueryParam();
|
||||||
|
param.setLineIds(Arrays.asList("line-001"));
|
||||||
|
param.setIndicatorCodes(Arrays.asList("V_HARMONIC"));
|
||||||
|
param.setStatTypes(Arrays.asList("AVG"));
|
||||||
|
param.setHarmonicOrders(Arrays.asList(3, 5, 7, 11));
|
||||||
|
param.setTimeStart("2026-05-01 00:00:00");
|
||||||
|
param.setTimeEnd("2026-05-01 01:00:00");
|
||||||
|
|
||||||
|
BusinessException exception = Assertions.assertThrows(BusinessException.class, () -> resolver.resolveFields(param));
|
||||||
|
|
||||||
|
Assertions.assertTrue(exception.getMessage().contains("谐波次数不允许一次展示超过3个"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldResolveSelectedHarmonicOrdersForAllCatalogPhases() {
|
void shouldResolveSelectedHarmonicOrdersForAllCatalogPhases() {
|
||||||
SteadyTrendQueryParam param = new SteadyTrendQueryParam();
|
SteadyTrendQueryParam param = new SteadyTrendQueryParam();
|
||||||
|
|||||||
@@ -2,13 +2,88 @@
|
|||||||
|
|
||||||
## 模块定位
|
## 模块定位
|
||||||
|
|
||||||
`dbms` 是 `system-ops` 下的数据库监控模块,当前先提供数据库监控菜单对应的后端基础入口。
|
`dbms` 是 `system-ops` 下的数据库运维模块,当前面向 Oracle 数据库提供连接配置、连接测试、表列表查询、备份、恢复、任务状态查询和删除接口。
|
||||||
|
|
||||||
## 当前接口
|
## 当前接口
|
||||||
|
|
||||||
- `GET /database/overview`
|
- `GET /database/overview`
|
||||||
- 查询数据库监控基础信息。
|
- 查询数据库运维概览信息。
|
||||||
|
- `POST /database/connections/list`
|
||||||
|
- 查询数据库连接配置。
|
||||||
|
- `POST /database/connections/add`
|
||||||
|
- 新增 Oracle 数据库连接配置。
|
||||||
|
- `POST /database/connections/update`
|
||||||
|
- 修改 Oracle 数据库连接配置。
|
||||||
|
- `POST /database/connections/delete`
|
||||||
|
- 删除数据库连接配置。
|
||||||
|
- `POST /database/connections/test`
|
||||||
|
- 测试 Oracle 数据库连接。
|
||||||
|
- `POST /database/connections/tables`
|
||||||
|
- 查询 Oracle 表列表。
|
||||||
|
- `POST /database/backups/create`
|
||||||
|
- 创建备份任务,默认使用 `DATA_PUMP`,可选 `JDBC_EXPORT`。
|
||||||
|
- `POST /database/backups/tasks/list`
|
||||||
|
- 查询备份任务列表。
|
||||||
|
- `GET /database/backups/tasks/status`
|
||||||
|
- 查询任务状态。
|
||||||
|
- `POST /database/backups/files/list`
|
||||||
|
- 查询备份文件记录。
|
||||||
|
- `POST /database/restores/create`
|
||||||
|
- 创建恢复任务。
|
||||||
|
- `GET /database/restores/tasks/status`
|
||||||
|
- 查询恢复任务状态。
|
||||||
|
- `POST /database/delete/backup-file`
|
||||||
|
- 删除备份文件,要求 `confirmText=确认删除`。
|
||||||
|
- `POST /database/delete/task`
|
||||||
|
- 删除任务记录,要求 `confirmText=确认删除`。
|
||||||
|
|
||||||
|
## 数据脚本
|
||||||
|
|
||||||
|
- `src/main/resources/sql/system-ops/system-ops-init.sql`
|
||||||
|
- 系统运维菜单初始化脚本。
|
||||||
|
- `src/main/resources/sql/system-ops/dbms-database-ops-init.sql`
|
||||||
|
- 数据库运维连接、任务、备份文件和恢复记录表结构。
|
||||||
|
|
||||||
|
## 配置项
|
||||||
|
|
||||||
|
建议通过环境配置覆盖:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
dbms:
|
||||||
|
backup:
|
||||||
|
storage-path: D:/dbms-backup
|
||||||
|
default-max-file-size-mb: 512
|
||||||
|
tools:
|
||||||
|
expdp-path:
|
||||||
|
impdp-path:
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- `backup.storage-path`
|
||||||
|
- `JDBC_EXPORT` 生成的 CSV 和元数据 JSON 的受管根目录。
|
||||||
|
- `tools.expdp-path`、`tools.impdp-path`
|
||||||
|
- Oracle Data Pump 工具路径;为空时尝试走系统 `PATH`。
|
||||||
|
|
||||||
|
## 当前行为
|
||||||
|
|
||||||
|
- 一期仅支持 `ORACLE`。
|
||||||
|
- 连接密码支持两种运行方式:
|
||||||
|
- 前端每次传 `temporaryPassword`。
|
||||||
|
- 连接已保存密文,且公共 `Sm4Utils` 提供 `decryptData_ECB` 时由后端自动解密复用。
|
||||||
|
- 新增连接前的测试接口允许只传 `temporaryPassword`,不强制把密码写进 `connection.password`。
|
||||||
|
- 备份任务异步执行,只有实际文件生成成功后才会写入 `dbms_backup_file` 记录。
|
||||||
|
- `JDBC_EXPORT` 当前会生成两类文件:
|
||||||
|
- 主数据文件:`*.csv`
|
||||||
|
- 元数据文件:`*_metadata_yyyyMMdd_<taskNo>.json`
|
||||||
|
- `JDBC_EXPORT` 恢复依赖元数据文件,不再允许缺少元数据直接发起恢复。
|
||||||
|
- 删除备份文件时,会校验目标路径必须位于受管备份目录下,避免误删非备份文件。
|
||||||
|
|
||||||
## 当前限制
|
## 当前限制
|
||||||
|
|
||||||
- 当前只完成后端基础入口,不包含真实数据库连接状态、容量或性能指标探测逻辑。
|
- `DATA_PUMP` 仍依赖部署机器可执行 `expdp`、`impdp`,并且 Oracle 侧已准备好 `directory` 对象和权限。
|
||||||
|
- 当前代码要求 `DATA_PUMP` 连接配置里补齐可管理的 `directoryPath`,否则虽然 Oracle 端可能已导出成功,后端无法安全管理文件记录与删除。
|
||||||
|
- `JDBC_EXPORT` 恢复当前仅覆盖表数据,不承诺恢复索引、约束、触发器、序列、存储过程、权限等 Oracle 对象。
|
||||||
|
- `TIME_RANGE` 模式当前只在 `JDBC_EXPORT` 场景真正参与查询过滤;`DATA_PUMP` 尚未接入 Oracle `QUERY` 参数。
|
||||||
|
- `SIZE_SPLIT` 参数目前已做入参校验,但尚未实现真正的导出分片。
|
||||||
|
- 本轮仅完成代码路径和文档收口,未执行 `mvn` 编译、测试或真实库联调。
|
||||||
|
|||||||
@@ -26,5 +26,14 @@
|
|||||||
<artifactId>spingboot2.3.12</artifactId>
|
<artifactId>spingboot2.3.12</artifactId>
|
||||||
<version>2.3.12</version>
|
<version>2.3.12</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.njcn</groupId>
|
||||||
|
<artifactId>mybatis-plus</artifactId>
|
||||||
|
<version>0.0.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-tx</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
package com.njcn.gather.systemops.database.component;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.njcn.gather.systemops.database.config.DbmsProperties;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.po.DatabaseConnection;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Oracle Data Pump 命令执行组件。
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DataPumpCommandExecutor {
|
||||||
|
|
||||||
|
private final DbmsProperties dbmsProperties;
|
||||||
|
|
||||||
|
public CommandResult expdp(DatabaseConnection connection, String password, String directoryName,
|
||||||
|
String dumpFileName, String logFileName, List<String> tableNames) {
|
||||||
|
List<String> command = new ArrayList<>();
|
||||||
|
command.add(resolveTool(dbmsProperties.getTools().getExpdpPath(), "expdp"));
|
||||||
|
fillCommonArgs(command, connection, password, directoryName, dumpFileName, logFileName);
|
||||||
|
if (tableNames != null && !tableNames.isEmpty()) {
|
||||||
|
command.add("tables=" + connection.getSchemaName() + "." + String.join("," + connection.getSchemaName() + ".", tableNames));
|
||||||
|
}
|
||||||
|
return execute(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandResult impdp(DatabaseConnection connection, String password, String directoryName,
|
||||||
|
String dumpFileName, String logFileName, String tableExistsAction) {
|
||||||
|
List<String> command = new ArrayList<>();
|
||||||
|
command.add(resolveTool(dbmsProperties.getTools().getImpdpPath(), "impdp"));
|
||||||
|
fillCommonArgs(command, connection, password, directoryName, dumpFileName, logFileName);
|
||||||
|
if (StrUtil.isNotBlank(tableExistsAction)) {
|
||||||
|
command.add("table_exists_action=" + tableExistsAction);
|
||||||
|
}
|
||||||
|
return execute(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fillCommonArgs(List<String> command, DatabaseConnection connection, String password,
|
||||||
|
String directoryName, String dumpFileName, String logFileName) {
|
||||||
|
command.add(connection.getUsername() + "/" + password + "@" + buildConnectIdentifier(connection));
|
||||||
|
command.add("directory=" + directoryName);
|
||||||
|
command.add("dumpfile=" + dumpFileName);
|
||||||
|
command.add("logfile=" + logFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildConnectIdentifier(DatabaseConnection connection) {
|
||||||
|
if (StrUtil.isNotBlank(connection.getServiceName())) {
|
||||||
|
return "//" + connection.getHost() + ":" + connection.getPort() + "/" + connection.getServiceName();
|
||||||
|
}
|
||||||
|
return connection.getHost() + ":" + connection.getPort() + ":" + connection.getSid();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveTool(String configuredPath, String defaultName) {
|
||||||
|
return StrUtil.blankToDefault(configuredPath, defaultName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CommandResult execute(List<String> command) {
|
||||||
|
CommandResult result = new CommandResult();
|
||||||
|
result.setCommand(maskPassword(command));
|
||||||
|
try {
|
||||||
|
Process process = new ProcessBuilder(command).redirectErrorStream(true).start();
|
||||||
|
StringBuilder output = new StringBuilder();
|
||||||
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), Charset.defaultCharset()))) {
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
output.append(line).append(System.lineSeparator());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int exitCode = process.waitFor();
|
||||||
|
result.setExitCode(exitCode);
|
||||||
|
result.setOutput(output.toString());
|
||||||
|
result.setSuccess(exitCode == 0);
|
||||||
|
} catch (Exception exception) {
|
||||||
|
result.setExitCode(-1);
|
||||||
|
result.setOutput(exception.getMessage());
|
||||||
|
result.setSuccess(false);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String maskPassword(List<String> command) {
|
||||||
|
if (command.size() < 2) {
|
||||||
|
return String.join(" ", command);
|
||||||
|
}
|
||||||
|
List<String> masked = new ArrayList<>(command);
|
||||||
|
String credential = masked.get(1);
|
||||||
|
int slashIndex = credential.indexOf('/');
|
||||||
|
int atIndex = credential.indexOf('@');
|
||||||
|
if (slashIndex > 0 && atIndex > slashIndex) {
|
||||||
|
masked.set(1, credential.substring(0, slashIndex + 1) + "******" + credential.substring(atIndex));
|
||||||
|
}
|
||||||
|
return String.join(" ", masked);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class CommandResult {
|
||||||
|
private Boolean success;
|
||||||
|
private Integer exitCode;
|
||||||
|
private String command;
|
||||||
|
private String output;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.njcn.gather.systemops.database.component;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.njcn.common.utils.sm.Sm4Utils;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库连接密码处理组件。
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class DatabasePasswordComponent {
|
||||||
|
|
||||||
|
public String encrypt(String plainText) {
|
||||||
|
if (StrUtil.isBlank(plainText)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new Sm4Utils(Sm4Utils.globalSecretKey).encryptData_ECB(plainText);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优先使用本次请求传入的临时密码;如果公共 SM4 工具存在解密能力,则复用已保存密文。
|
||||||
|
*/
|
||||||
|
public String resolveRuntimePassword(String passwordCipher, String temporaryPassword) {
|
||||||
|
if (StrUtil.isNotBlank(temporaryPassword)) {
|
||||||
|
return temporaryPassword;
|
||||||
|
}
|
||||||
|
if (StrUtil.isBlank(passwordCipher)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Sm4Utils sm4Utils = new Sm4Utils(Sm4Utils.globalSecretKey);
|
||||||
|
Method decryptMethod = Sm4Utils.class.getMethod("decryptData_ECB", String.class);
|
||||||
|
Object plainText = decryptMethod.invoke(sm4Utils, passwordCipher);
|
||||||
|
if (plainText instanceof String && StrUtil.isNotBlank((String) plainText)) {
|
||||||
|
return (String) plainText;
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
// 兼容公共工具不同版本,未找到解密方法时继续走统一失败提示。
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("当前环境未确认密码解密方法,请传入临时密码执行本次操作");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.njcn.gather.systemops.database.config;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库运维后台任务线程池。
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Configuration
|
||||||
|
public class DbmsExecutorConfig {
|
||||||
|
|
||||||
|
@Bean(name = "dbmsTaskExecutorService", destroyMethod = "shutdown")
|
||||||
|
public ExecutorService dbmsTaskExecutorService() {
|
||||||
|
AtomicInteger threadIndex = new AtomicInteger(1);
|
||||||
|
return new ThreadPoolExecutor(
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
30,
|
||||||
|
TimeUnit.SECONDS,
|
||||||
|
new LinkedBlockingQueue<Runnable>(8),
|
||||||
|
runnable -> {
|
||||||
|
Thread thread = new Thread(runnable);
|
||||||
|
thread.setName("dbms-task-" + threadIndex.getAndIncrement());
|
||||||
|
return thread;
|
||||||
|
},
|
||||||
|
(runnable, executor) -> log.warn("数据库运维任务线程池已满,拒绝新的任务")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.njcn.gather.systemops.database.config;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库运维配置。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Component
|
||||||
|
@ConfigurationProperties(prefix = "dbms")
|
||||||
|
public class DbmsProperties {
|
||||||
|
|
||||||
|
private Backup backup = new Backup();
|
||||||
|
private Tools tools = new Tools();
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class Backup {
|
||||||
|
private String storagePath = "D:/dbms-backup";
|
||||||
|
private Integer defaultMaxFileSizeMb = 512;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class Tools {
|
||||||
|
private String expdpPath;
|
||||||
|
private String impdpPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.njcn.gather.systemops.database.constant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库运维常量。
|
||||||
|
*/
|
||||||
|
public final class DatabaseOpsConst {
|
||||||
|
|
||||||
|
public static final String DB_TYPE_ORACLE = "ORACLE";
|
||||||
|
public static final String CONNECT_TYPE_SERVICE_NAME = "SERVICE_NAME";
|
||||||
|
public static final String CONNECT_TYPE_SID = "SID";
|
||||||
|
public static final String CONFIRM_DELETE = "确认删除";
|
||||||
|
public static final String CONFIRM_OVERWRITE = "确认覆盖";
|
||||||
|
public static final int STATE_DELETED = 0;
|
||||||
|
public static final int STATE_ENABLED = 1;
|
||||||
|
public static final int SAVE_PASSWORD_YES = 1;
|
||||||
|
public static final int SAVE_PASSWORD_NO = 0;
|
||||||
|
|
||||||
|
private DatabaseOpsConst() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package com.njcn.gather.systemops.database.controller;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.njcn.common.pojo.annotation.OperateInfo;
|
||||||
|
import com.njcn.common.pojo.constant.OperateType;
|
||||||
|
import com.njcn.common.pojo.enums.common.LogEnum;
|
||||||
|
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||||
|
import com.njcn.common.pojo.response.HttpResult;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.param.DatabaseBackupParam;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.vo.DatabaseBackupFileVO;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTaskCreateVO;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTaskVO;
|
||||||
|
import com.njcn.gather.systemops.database.service.DatabaseBackupFileService;
|
||||||
|
import com.njcn.gather.systemops.database.service.DatabaseOperationTaskService;
|
||||||
|
import com.njcn.web.controller.BaseController;
|
||||||
|
import com.njcn.web.utils.HttpResultUtil;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库备份接口。
|
||||||
|
*/
|
||||||
|
@Api(tags = "数据库备份")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/database/backups")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DatabaseBackupController extends BaseController {
|
||||||
|
|
||||||
|
private final DatabaseOperationTaskService databaseOperationTaskService;
|
||||||
|
private final DatabaseBackupFileService databaseBackupFileService;
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.ADD)
|
||||||
|
@ApiOperation("创建备份任务")
|
||||||
|
@PostMapping("/create")
|
||||||
|
public HttpResult<DatabaseTaskCreateVO> create(@RequestBody @Validated DatabaseBackupParam.CreateParam param) {
|
||||||
|
String methodDescribe = getMethodDescribe("create");
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, databaseOperationTaskService.createBackupTask(param), methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||||
|
@ApiOperation("查询备份任务")
|
||||||
|
@PostMapping("/tasks/list")
|
||||||
|
public HttpResult<Page<DatabaseTaskVO>> listTasks(@RequestBody @Validated DatabaseBackupParam.TaskQueryParam param) {
|
||||||
|
String methodDescribe = getMethodDescribe("listTasks");
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, databaseOperationTaskService.listBackupTasks(param), methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||||
|
@ApiOperation("查询任务状态")
|
||||||
|
@GetMapping("/tasks/status")
|
||||||
|
public HttpResult<DatabaseTaskVO> status(@RequestParam("taskId") String taskId) {
|
||||||
|
String methodDescribe = getMethodDescribe("status");
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, databaseOperationTaskService.getStatus(taskId), methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||||
|
@ApiOperation("查询备份文件")
|
||||||
|
@PostMapping("/files/list")
|
||||||
|
public HttpResult<Page<DatabaseBackupFileVO>> listFiles(@RequestBody @Validated DatabaseBackupParam.FileQueryParam param) {
|
||||||
|
String methodDescribe = getMethodDescribe("listFiles");
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, databaseBackupFileService.listFiles(param), methodDescribe);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package com.njcn.gather.systemops.database.controller;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.njcn.common.pojo.annotation.OperateInfo;
|
||||||
|
import com.njcn.common.pojo.constant.OperateType;
|
||||||
|
import com.njcn.common.pojo.enums.common.LogEnum;
|
||||||
|
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||||
|
import com.njcn.common.pojo.response.HttpResult;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.param.DatabaseConnectionParam;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.vo.DatabaseConnectionVO;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTableVO;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTestResultVO;
|
||||||
|
import com.njcn.gather.systemops.database.service.DatabaseConnectionService;
|
||||||
|
import com.njcn.web.controller.BaseController;
|
||||||
|
import com.njcn.web.utils.HttpResultUtil;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库连接配置接口。
|
||||||
|
*/
|
||||||
|
@Api(tags = "数据库连接配置")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/database/connections")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DatabaseConnectionController extends BaseController {
|
||||||
|
|
||||||
|
private final DatabaseConnectionService databaseConnectionService;
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||||
|
@ApiOperation("查询数据库连接配置")
|
||||||
|
@PostMapping("/list")
|
||||||
|
public HttpResult<Page<DatabaseConnectionVO>> list(@RequestBody @Validated DatabaseConnectionParam.QueryParam param) {
|
||||||
|
String methodDescribe = getMethodDescribe("list");
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, databaseConnectionService.listConnections(param), methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.ADD)
|
||||||
|
@ApiOperation("新增数据库连接配置")
|
||||||
|
@PostMapping("/add")
|
||||||
|
public HttpResult<Boolean> add(@RequestBody @Validated DatabaseConnectionParam param) {
|
||||||
|
String methodDescribe = getMethodDescribe("add");
|
||||||
|
boolean result = databaseConnectionService.addConnection(param);
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(result ? CommonResponseEnum.SUCCESS : CommonResponseEnum.FAIL, result, methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.UPDATE)
|
||||||
|
@ApiOperation("修改数据库连接配置")
|
||||||
|
@PostMapping("/update")
|
||||||
|
public HttpResult<Boolean> update(@RequestBody @Validated DatabaseConnectionParam.UpdateParam param) {
|
||||||
|
String methodDescribe = getMethodDescribe("update");
|
||||||
|
boolean result = databaseConnectionService.updateConnection(param);
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(result ? CommonResponseEnum.SUCCESS : CommonResponseEnum.FAIL, result, methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.DELETE)
|
||||||
|
@ApiOperation("删除数据库连接配置")
|
||||||
|
@PostMapping("/delete")
|
||||||
|
public HttpResult<Boolean> delete(@RequestBody @Validated DatabaseConnectionParam.DeleteParam param) {
|
||||||
|
String methodDescribe = getMethodDescribe("delete");
|
||||||
|
boolean result = databaseConnectionService.deleteConnection(param);
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(result ? CommonResponseEnum.SUCCESS : CommonResponseEnum.FAIL, result, methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||||
|
@ApiOperation("测试数据库连接")
|
||||||
|
@PostMapping("/test")
|
||||||
|
public HttpResult<DatabaseTestResultVO> test(@RequestBody @Validated DatabaseConnectionParam.TestParam param) {
|
||||||
|
String methodDescribe = getMethodDescribe("test");
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, databaseConnectionService.testConnection(param), methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||||
|
@ApiOperation("查询 Oracle 表列表")
|
||||||
|
@PostMapping("/tables")
|
||||||
|
public HttpResult<List<DatabaseTableVO>> tables(@RequestBody @Validated DatabaseConnectionParam.TablesParam param) {
|
||||||
|
String methodDescribe = getMethodDescribe("tables");
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, databaseConnectionService.listTables(param), methodDescribe);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package com.njcn.gather.systemops.database.controller;
|
||||||
|
|
||||||
|
import com.njcn.common.pojo.annotation.OperateInfo;
|
||||||
|
import com.njcn.common.pojo.constant.OperateType;
|
||||||
|
import com.njcn.common.pojo.enums.common.LogEnum;
|
||||||
|
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||||
|
import com.njcn.common.pojo.response.HttpResult;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.param.DatabaseDeleteParam;
|
||||||
|
import com.njcn.gather.systemops.database.service.DatabaseBackupFileService;
|
||||||
|
import com.njcn.gather.systemops.database.service.DatabaseOperationTaskService;
|
||||||
|
import com.njcn.web.controller.BaseController;
|
||||||
|
import com.njcn.web.utils.HttpResultUtil;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库运维删除接口。
|
||||||
|
*/
|
||||||
|
@Api(tags = "数据库运维删除")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/database/delete")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DatabaseDeleteController extends BaseController {
|
||||||
|
|
||||||
|
private final DatabaseBackupFileService databaseBackupFileService;
|
||||||
|
private final DatabaseOperationTaskService databaseOperationTaskService;
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.DELETE)
|
||||||
|
@ApiOperation("删除备份文件")
|
||||||
|
@PostMapping("/backup-file")
|
||||||
|
public HttpResult<Boolean> deleteBackupFile(@RequestBody @Validated DatabaseDeleteParam.BackupFileParam param) {
|
||||||
|
String methodDescribe = getMethodDescribe("deleteBackupFile");
|
||||||
|
boolean result = databaseBackupFileService.deleteBackupFile(param.getBackupFileId(), param.getConfirmText());
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(result ? CommonResponseEnum.SUCCESS : CommonResponseEnum.FAIL, result, methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.DELETE)
|
||||||
|
@ApiOperation("删除任务记录")
|
||||||
|
@PostMapping("/task")
|
||||||
|
public HttpResult<Boolean> deleteTask(@RequestBody @Validated DatabaseDeleteParam.TaskParam param) {
|
||||||
|
String methodDescribe = getMethodDescribe("deleteTask");
|
||||||
|
boolean result = databaseOperationTaskService.deleteTask(param.getTaskId(), param.getConfirmText());
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(result ? CommonResponseEnum.SUCCESS : CommonResponseEnum.FAIL, result, methodDescribe);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package com.njcn.gather.systemops.database.controller;
|
||||||
|
|
||||||
|
import com.njcn.common.pojo.annotation.OperateInfo;
|
||||||
|
import com.njcn.common.pojo.constant.OperateType;
|
||||||
|
import com.njcn.common.pojo.enums.common.LogEnum;
|
||||||
|
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||||
|
import com.njcn.common.pojo.response.HttpResult;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.param.DatabaseRestoreParam;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTaskCreateVO;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTaskVO;
|
||||||
|
import com.njcn.gather.systemops.database.service.DatabaseOperationTaskService;
|
||||||
|
import com.njcn.gather.systemops.database.service.DatabaseRestoreService;
|
||||||
|
import com.njcn.web.controller.BaseController;
|
||||||
|
import com.njcn.web.utils.HttpResultUtil;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库恢复接口。
|
||||||
|
*/
|
||||||
|
@Api(tags = "数据库恢复")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/database/restores")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DatabaseRestoreController extends BaseController {
|
||||||
|
|
||||||
|
private final DatabaseRestoreService databaseRestoreService;
|
||||||
|
private final DatabaseOperationTaskService databaseOperationTaskService;
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.ADD)
|
||||||
|
@ApiOperation("创建恢复任务")
|
||||||
|
@PostMapping("/create")
|
||||||
|
public HttpResult<DatabaseTaskCreateVO> create(@RequestBody @Validated DatabaseRestoreParam.CreateParam param) {
|
||||||
|
String methodDescribe = getMethodDescribe("create");
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, databaseRestoreService.createRestoreTask(param), methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||||
|
@ApiOperation("查询恢复任务状态")
|
||||||
|
@GetMapping("/tasks/status")
|
||||||
|
public HttpResult<DatabaseTaskVO> status(@RequestParam("taskId") String taskId) {
|
||||||
|
String methodDescribe = getMethodDescribe("status");
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, databaseOperationTaskService.getStatus(taskId), methodDescribe);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.njcn.gather.systemops.database.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.po.DatabaseBackupFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库备份文件 Mapper。
|
||||||
|
*/
|
||||||
|
public interface DatabaseBackupFileMapper extends BaseMapper<DatabaseBackupFile> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.njcn.gather.systemops.database.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.po.DatabaseConnection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库连接配置 Mapper。
|
||||||
|
*/
|
||||||
|
public interface DatabaseConnectionMapper extends BaseMapper<DatabaseConnection> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.njcn.gather.systemops.database.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.po.DatabaseOperationTask;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库运维任务 Mapper。
|
||||||
|
*/
|
||||||
|
public interface DatabaseOperationTaskMapper extends BaseMapper<DatabaseOperationTask> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.njcn.gather.systemops.database.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.po.DatabaseRestoreRecord;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库恢复记录 Mapper。
|
||||||
|
*/
|
||||||
|
public interface DatabaseRestoreRecordMapper extends BaseMapper<DatabaseRestoreRecord> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.njcn.gather.systemops.database.pojo.enums;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备份模式。
|
||||||
|
*/
|
||||||
|
public enum BackupModeEnum {
|
||||||
|
FULL_TABLE,
|
||||||
|
TIME_RANGE,
|
||||||
|
SIZE_SPLIT
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.njcn.gather.systemops.database.pojo.enums;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备份策略。
|
||||||
|
*/
|
||||||
|
public enum BackupStrategyEnum {
|
||||||
|
DATA_PUMP,
|
||||||
|
JDBC_EXPORT
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.njcn.gather.systemops.database.pojo.enums;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备份文件格式。
|
||||||
|
*/
|
||||||
|
public enum FileFormatEnum {
|
||||||
|
DMP,
|
||||||
|
SQL,
|
||||||
|
CSV
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.njcn.gather.systemops.database.pojo.enums;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 运维任务状态。
|
||||||
|
*/
|
||||||
|
public enum TaskStatusEnum {
|
||||||
|
WAITING,
|
||||||
|
RUNNING,
|
||||||
|
SUCCESS,
|
||||||
|
FAIL,
|
||||||
|
CANCELLED
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package com.njcn.gather.systemops.database.pojo.param;
|
||||||
|
|
||||||
|
import com.njcn.web.pojo.param.BaseParam;
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库备份参数。
|
||||||
|
*/
|
||||||
|
public class DatabaseBackupParam {
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ApiModel("创建备份任务参数")
|
||||||
|
public static class CreateParam {
|
||||||
|
@ApiModelProperty("连接 ID")
|
||||||
|
@NotBlank(message = "连接 ID 不能为空")
|
||||||
|
private String connectionId;
|
||||||
|
@ApiModelProperty("备份策略:DATA_PUMP、JDBC_EXPORT,默认 DATA_PUMP")
|
||||||
|
private String backupStrategy;
|
||||||
|
@ApiModelProperty("Schema")
|
||||||
|
private String schemaName;
|
||||||
|
@ApiModelProperty("表名列表")
|
||||||
|
private List<String> targetNames;
|
||||||
|
@ApiModelProperty("备份模式:FULL_TABLE、TIME_RANGE、SIZE_SPLIT")
|
||||||
|
private String backupMode;
|
||||||
|
@ApiModelProperty("时间字段")
|
||||||
|
private String timeColumn;
|
||||||
|
@ApiModelProperty("开始时间")
|
||||||
|
private LocalDateTime startTime;
|
||||||
|
@ApiModelProperty("结束时间")
|
||||||
|
private LocalDateTime endTime;
|
||||||
|
@ApiModelProperty("最大文件大小 MB")
|
||||||
|
private Integer maxFileSizeMb;
|
||||||
|
@ApiModelProperty("Oracle Directory 名称")
|
||||||
|
private String directoryName;
|
||||||
|
@ApiModelProperty("临时密码,不保存密码时传入")
|
||||||
|
private String temporaryPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@ApiModel("备份任务查询参数")
|
||||||
|
public static class TaskQueryParam extends BaseParam {
|
||||||
|
@ApiModelProperty("连接 ID")
|
||||||
|
private String connectionId;
|
||||||
|
@ApiModelProperty("任务状态")
|
||||||
|
private String taskStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@ApiModel("备份文件查询参数")
|
||||||
|
public static class FileQueryParam extends BaseParam {
|
||||||
|
@ApiModelProperty("连接 ID")
|
||||||
|
private String connectionId;
|
||||||
|
@ApiModelProperty("任务 ID")
|
||||||
|
private String taskId;
|
||||||
|
@ApiModelProperty("备份策略")
|
||||||
|
private String backupStrategy;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
package com.njcn.gather.systemops.database.pojo.param;
|
||||||
|
|
||||||
|
import com.njcn.web.pojo.param.BaseParam;
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库连接配置参数。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ApiModel("数据库连接配置参数")
|
||||||
|
public class DatabaseConnectionParam {
|
||||||
|
|
||||||
|
@ApiModelProperty("连接名称")
|
||||||
|
@NotBlank(message = "连接名称不能为空")
|
||||||
|
private String connectionName;
|
||||||
|
|
||||||
|
@ApiModelProperty("数据库类型,一期固定 ORACLE")
|
||||||
|
private String dbType;
|
||||||
|
|
||||||
|
@ApiModelProperty("数据库主机地址")
|
||||||
|
@NotBlank(message = "数据库主机地址不能为空")
|
||||||
|
private String host;
|
||||||
|
|
||||||
|
@ApiModelProperty("数据库端口")
|
||||||
|
@NotNull(message = "数据库端口不能为空")
|
||||||
|
private Integer port;
|
||||||
|
|
||||||
|
@ApiModelProperty("连接类型:SERVICE_NAME、SID")
|
||||||
|
private String connectType;
|
||||||
|
|
||||||
|
@ApiModelProperty("服务名")
|
||||||
|
private String serviceName;
|
||||||
|
|
||||||
|
@ApiModelProperty("SID")
|
||||||
|
private String sid;
|
||||||
|
|
||||||
|
@ApiModelProperty("Schema")
|
||||||
|
private String schemaName;
|
||||||
|
|
||||||
|
@ApiModelProperty("用户名")
|
||||||
|
@NotBlank(message = "用户名不能为空")
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@ApiModelProperty("密码")
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
@ApiModelProperty("是否保存密码:0-否,1-是")
|
||||||
|
private Integer savePassword;
|
||||||
|
|
||||||
|
@ApiModelProperty("Oracle Directory 名称")
|
||||||
|
private String directoryName;
|
||||||
|
|
||||||
|
@ApiModelProperty("Oracle Directory 物理路径")
|
||||||
|
private String directoryPath;
|
||||||
|
|
||||||
|
@ApiModelProperty("扩展配置 JSON")
|
||||||
|
private String extraConfigJson;
|
||||||
|
|
||||||
|
@ApiModelProperty("备注")
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@ApiModel("数据库连接更新参数")
|
||||||
|
public static class UpdateParam extends DatabaseConnectionParam {
|
||||||
|
@ApiModelProperty("连接 ID")
|
||||||
|
@NotBlank(message = "连接 ID 不能为空")
|
||||||
|
private String id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@ApiModel("数据库连接查询参数")
|
||||||
|
public static class QueryParam extends BaseParam {
|
||||||
|
@ApiModelProperty("连接名称")
|
||||||
|
private String connectionName;
|
||||||
|
@ApiModelProperty("数据库类型")
|
||||||
|
private String dbType;
|
||||||
|
@ApiModelProperty("Schema")
|
||||||
|
private String schemaName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ApiModel("数据库连接删除参数")
|
||||||
|
public static class DeleteParam {
|
||||||
|
@ApiModelProperty("连接 ID")
|
||||||
|
@NotBlank(message = "连接 ID 不能为空")
|
||||||
|
private String id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ApiModel("数据库连接测试参数")
|
||||||
|
public static class TestParam {
|
||||||
|
@ApiModelProperty("连接 ID,已有连接测试时传入")
|
||||||
|
private String connectionId;
|
||||||
|
@ApiModelProperty("临时连接参数,新增前测试时传入")
|
||||||
|
private DatabaseConnectionParam connection;
|
||||||
|
@ApiModelProperty("临时密码,测试时允许只传该字段而不写入 connection.password")
|
||||||
|
private String temporaryPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ApiModel("数据库表查询参数")
|
||||||
|
public static class TablesParam {
|
||||||
|
@ApiModelProperty("连接 ID")
|
||||||
|
@NotBlank(message = "连接 ID 不能为空")
|
||||||
|
private String connectionId;
|
||||||
|
@ApiModelProperty("临时密码,不保存密码时传入")
|
||||||
|
private String temporaryPassword;
|
||||||
|
@ApiModelProperty("Schema,不传则使用连接默认 Schema")
|
||||||
|
private String schemaName;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.njcn.gather.systemops.database.pojo.param;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库运维删除参数。
|
||||||
|
*/
|
||||||
|
public class DatabaseDeleteParam {
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ApiModel("删除备份文件参数")
|
||||||
|
public static class BackupFileParam {
|
||||||
|
@ApiModelProperty("备份文件 ID")
|
||||||
|
@NotBlank(message = "备份文件 ID 不能为空")
|
||||||
|
private String backupFileId;
|
||||||
|
@ApiModelProperty("确认文案")
|
||||||
|
private String confirmText;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ApiModel("删除任务参数")
|
||||||
|
public static class TaskParam {
|
||||||
|
@ApiModelProperty("任务 ID")
|
||||||
|
@NotBlank(message = "任务 ID 不能为空")
|
||||||
|
private String taskId;
|
||||||
|
@ApiModelProperty("确认文案")
|
||||||
|
private String confirmText;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.njcn.gather.systemops.database.pojo.param;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库恢复参数。
|
||||||
|
*/
|
||||||
|
public class DatabaseRestoreParam {
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ApiModel("创建恢复任务参数")
|
||||||
|
public static class CreateParam {
|
||||||
|
@ApiModelProperty("目标连接 ID")
|
||||||
|
@NotBlank(message = "连接 ID 不能为空")
|
||||||
|
private String connectionId;
|
||||||
|
@ApiModelProperty("备份文件 ID")
|
||||||
|
@NotBlank(message = "备份文件 ID 不能为空")
|
||||||
|
private String backupFileId;
|
||||||
|
@ApiModelProperty("恢复模式:SKIP、APPEND、TRUNCATE、REPLACE")
|
||||||
|
private String restoreMode;
|
||||||
|
@ApiModelProperty("目标 Schema")
|
||||||
|
private String targetSchemaName;
|
||||||
|
@ApiModelProperty("临时密码,不保存密码时传入")
|
||||||
|
private String temporaryPassword;
|
||||||
|
@ApiModelProperty("覆盖确认文案")
|
||||||
|
private String overwriteConfirmText;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package com.njcn.gather.systemops.database.pojo.po;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库备份文件记录。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("dbms_backup_file")
|
||||||
|
public class DatabaseBackupFile implements Serializable {
|
||||||
|
private static final long serialVersionUID = 3119981982091873277L;
|
||||||
|
|
||||||
|
@TableId("id")
|
||||||
|
private String id;
|
||||||
|
@TableField("task_id")
|
||||||
|
private String taskId;
|
||||||
|
@TableField("connection_id")
|
||||||
|
private String connectionId;
|
||||||
|
@TableField("db_type")
|
||||||
|
private String dbType;
|
||||||
|
@TableField("backup_strategy")
|
||||||
|
private String backupStrategy;
|
||||||
|
@TableField("file_format")
|
||||||
|
private String fileFormat;
|
||||||
|
@TableField("schema_name")
|
||||||
|
private String schemaName;
|
||||||
|
@TableField("target_names_json")
|
||||||
|
private String targetNamesJson;
|
||||||
|
@TableField("backup_mode")
|
||||||
|
private String backupMode;
|
||||||
|
@TableField("backup_start_time")
|
||||||
|
private LocalDateTime backupStartTime;
|
||||||
|
@TableField("backup_end_time")
|
||||||
|
private LocalDateTime backupEndTime;
|
||||||
|
@TableField("time_column")
|
||||||
|
private String timeColumn;
|
||||||
|
@TableField("directory_name")
|
||||||
|
private String directoryName;
|
||||||
|
@TableField("dump_file_name")
|
||||||
|
private String dumpFileName;
|
||||||
|
@TableField("log_file_name")
|
||||||
|
private String logFileName;
|
||||||
|
@TableField("file_name")
|
||||||
|
private String fileName;
|
||||||
|
@TableField("file_path")
|
||||||
|
private String filePath;
|
||||||
|
@TableField("log_file_path")
|
||||||
|
private String logFilePath;
|
||||||
|
@TableField("metadata_file_path")
|
||||||
|
private String metadataFilePath;
|
||||||
|
@TableField("file_size")
|
||||||
|
private Long fileSize;
|
||||||
|
@TableField("checksum")
|
||||||
|
private String checksum;
|
||||||
|
@TableField("state")
|
||||||
|
private Integer state;
|
||||||
|
@TableField("create_by")
|
||||||
|
private String createBy;
|
||||||
|
@TableField("create_time")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
@TableField("update_by")
|
||||||
|
private String updateBy;
|
||||||
|
@TableField("update_time")
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package com.njcn.gather.systemops.database.pojo.po;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库连接配置。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("dbms_connection")
|
||||||
|
public class DatabaseConnection implements Serializable {
|
||||||
|
private static final long serialVersionUID = -5821519248914313778L;
|
||||||
|
|
||||||
|
@TableId("id")
|
||||||
|
private String id;
|
||||||
|
@TableField("connection_name")
|
||||||
|
private String connectionName;
|
||||||
|
@TableField("db_type")
|
||||||
|
private String dbType;
|
||||||
|
@TableField("host")
|
||||||
|
private String host;
|
||||||
|
@TableField("port")
|
||||||
|
private Integer port;
|
||||||
|
@TableField("connect_type")
|
||||||
|
private String connectType;
|
||||||
|
@TableField("service_name")
|
||||||
|
private String serviceName;
|
||||||
|
@TableField("sid")
|
||||||
|
private String sid;
|
||||||
|
@TableField("database_name")
|
||||||
|
private String databaseName;
|
||||||
|
@TableField("schema_name")
|
||||||
|
private String schemaName;
|
||||||
|
@TableField("username")
|
||||||
|
private String username;
|
||||||
|
@TableField("password_cipher")
|
||||||
|
private String passwordCipher;
|
||||||
|
@TableField("save_password")
|
||||||
|
private Integer savePassword;
|
||||||
|
@TableField("directory_name")
|
||||||
|
private String directoryName;
|
||||||
|
@TableField("directory_path")
|
||||||
|
private String directoryPath;
|
||||||
|
@TableField("extra_config_json")
|
||||||
|
private String extraConfigJson;
|
||||||
|
@TableField("remark")
|
||||||
|
private String remark;
|
||||||
|
@TableField("last_test_status")
|
||||||
|
private String lastTestStatus;
|
||||||
|
@TableField("last_test_message")
|
||||||
|
private String lastTestMessage;
|
||||||
|
@TableField("last_test_time")
|
||||||
|
private LocalDateTime lastTestTime;
|
||||||
|
@TableField("state")
|
||||||
|
private Integer state;
|
||||||
|
@TableField("create_by")
|
||||||
|
private String createBy;
|
||||||
|
@TableField("create_time")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
@TableField("update_by")
|
||||||
|
private String updateBy;
|
||||||
|
@TableField("update_time")
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package com.njcn.gather.systemops.database.pojo.po;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库运维任务。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("dbms_operation_task")
|
||||||
|
public class DatabaseOperationTask implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1831235987236858769L;
|
||||||
|
|
||||||
|
@TableId("id")
|
||||||
|
private String id;
|
||||||
|
@TableField("task_no")
|
||||||
|
private String taskNo;
|
||||||
|
@TableField("connection_id")
|
||||||
|
private String connectionId;
|
||||||
|
@TableField("db_type")
|
||||||
|
private String dbType;
|
||||||
|
@TableField("operation_type")
|
||||||
|
private String operationType;
|
||||||
|
@TableField("backup_strategy")
|
||||||
|
private String backupStrategy;
|
||||||
|
@TableField("task_status")
|
||||||
|
private String taskStatus;
|
||||||
|
@TableField("schema_name")
|
||||||
|
private String schemaName;
|
||||||
|
@TableField("target_names_json")
|
||||||
|
private String targetNamesJson;
|
||||||
|
@TableField("request_param_json")
|
||||||
|
private String requestParamJson;
|
||||||
|
@TableField("result_message")
|
||||||
|
private String resultMessage;
|
||||||
|
@TableField("progress_percent")
|
||||||
|
private BigDecimal progressPercent;
|
||||||
|
@TableField("started_at")
|
||||||
|
private LocalDateTime startedAt;
|
||||||
|
@TableField("finished_at")
|
||||||
|
private LocalDateTime finishedAt;
|
||||||
|
@TableField("state")
|
||||||
|
private Integer state;
|
||||||
|
@TableField("create_by")
|
||||||
|
private String createBy;
|
||||||
|
@TableField("create_time")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
@TableField("update_by")
|
||||||
|
private String updateBy;
|
||||||
|
@TableField("update_time")
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package com.njcn.gather.systemops.database.pojo.po;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库恢复记录。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("dbms_restore_record")
|
||||||
|
public class DatabaseRestoreRecord implements Serializable {
|
||||||
|
private static final long serialVersionUID = -5638979151924581277L;
|
||||||
|
|
||||||
|
@TableId("id")
|
||||||
|
private String id;
|
||||||
|
@TableField("task_id")
|
||||||
|
private String taskId;
|
||||||
|
@TableField("backup_file_id")
|
||||||
|
private String backupFileId;
|
||||||
|
@TableField("connection_id")
|
||||||
|
private String connectionId;
|
||||||
|
@TableField("db_type")
|
||||||
|
private String dbType;
|
||||||
|
@TableField("restore_mode")
|
||||||
|
private String restoreMode;
|
||||||
|
@TableField("target_schema_name")
|
||||||
|
private String targetSchemaName;
|
||||||
|
@TableField("target_names_json")
|
||||||
|
private String targetNamesJson;
|
||||||
|
@TableField("table_exists_action")
|
||||||
|
private String tableExistsAction;
|
||||||
|
@TableField("overwrite_confirmed")
|
||||||
|
private Integer overwriteConfirmed;
|
||||||
|
@TableField("result_message")
|
||||||
|
private String resultMessage;
|
||||||
|
@TableField("state")
|
||||||
|
private Integer state;
|
||||||
|
@TableField("create_by")
|
||||||
|
private String createBy;
|
||||||
|
@TableField("create_time")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
@TableField("update_by")
|
||||||
|
private String updateBy;
|
||||||
|
@TableField("update_time")
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.njcn.gather.systemops.database.pojo.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库备份文件响应。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DatabaseBackupFileVO {
|
||||||
|
private String id;
|
||||||
|
private String taskId;
|
||||||
|
private String connectionId;
|
||||||
|
private String dbType;
|
||||||
|
private String backupStrategy;
|
||||||
|
private String fileFormat;
|
||||||
|
private String schemaName;
|
||||||
|
private String targetNamesJson;
|
||||||
|
private String backupMode;
|
||||||
|
private String fileName;
|
||||||
|
private String filePath;
|
||||||
|
private String logFileName;
|
||||||
|
private String logFilePath;
|
||||||
|
private Long fileSize;
|
||||||
|
private String checksum;
|
||||||
|
private Integer state;
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.njcn.gather.systemops.database.pojo.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库连接配置响应。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DatabaseConnectionVO {
|
||||||
|
private String id;
|
||||||
|
private String connectionName;
|
||||||
|
private String dbType;
|
||||||
|
private String host;
|
||||||
|
private Integer port;
|
||||||
|
private String connectType;
|
||||||
|
private String serviceName;
|
||||||
|
private String sid;
|
||||||
|
private String schemaName;
|
||||||
|
private String username;
|
||||||
|
private Integer savePassword;
|
||||||
|
private String directoryName;
|
||||||
|
private String directoryPath;
|
||||||
|
private String extraConfigJson;
|
||||||
|
private String remark;
|
||||||
|
private String lastTestStatus;
|
||||||
|
private String lastTestMessage;
|
||||||
|
private LocalDateTime lastTestTime;
|
||||||
|
private Integer state;
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.njcn.gather.systemops.database.pojo.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库表信息。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DatabaseTableVO {
|
||||||
|
private String owner;
|
||||||
|
private String tableName;
|
||||||
|
private String comments;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.njcn.gather.systemops.database.pojo.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 运维任务创建结果。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DatabaseTaskCreateVO {
|
||||||
|
private String taskId;
|
||||||
|
private String taskNo;
|
||||||
|
private String taskStatus;
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.njcn.gather.systemops.database.pojo.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库运维任务响应。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DatabaseTaskVO {
|
||||||
|
private String id;
|
||||||
|
private String taskNo;
|
||||||
|
private String connectionId;
|
||||||
|
private String dbType;
|
||||||
|
private String operationType;
|
||||||
|
private String backupStrategy;
|
||||||
|
private String taskStatus;
|
||||||
|
private String schemaName;
|
||||||
|
private String targetNamesJson;
|
||||||
|
private String resultMessage;
|
||||||
|
private BigDecimal progressPercent;
|
||||||
|
private LocalDateTime startedAt;
|
||||||
|
private LocalDateTime finishedAt;
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.njcn.gather.systemops.database.pojo.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库连接测试结果。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DatabaseTestResultVO {
|
||||||
|
private Boolean success;
|
||||||
|
private String message;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.njcn.gather.systemops.database.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.param.DatabaseBackupParam;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.po.DatabaseBackupFile;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.vo.DatabaseBackupFileVO;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库备份文件服务。
|
||||||
|
*/
|
||||||
|
public interface DatabaseBackupFileService extends IService<DatabaseBackupFile> {
|
||||||
|
|
||||||
|
Page<DatabaseBackupFileVO> listFiles(DatabaseBackupParam.FileQueryParam param);
|
||||||
|
|
||||||
|
boolean deleteBackupFile(String backupFileId, String confirmText);
|
||||||
|
|
||||||
|
void validateBackupFileReadable(DatabaseBackupFile backupFile);
|
||||||
|
|
||||||
|
Path resolveManagedPath(DatabaseBackupFile backupFile, String filePath);
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.njcn.gather.systemops.database.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.param.DatabaseConnectionParam;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.po.DatabaseConnection;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.vo.DatabaseConnectionVO;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTableVO;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTestResultVO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库连接配置服务。
|
||||||
|
*/
|
||||||
|
public interface DatabaseConnectionService extends IService<DatabaseConnection> {
|
||||||
|
|
||||||
|
Page<DatabaseConnectionVO> listConnections(DatabaseConnectionParam.QueryParam queryParam);
|
||||||
|
|
||||||
|
boolean addConnection(DatabaseConnectionParam param);
|
||||||
|
|
||||||
|
boolean updateConnection(DatabaseConnectionParam.UpdateParam param);
|
||||||
|
|
||||||
|
boolean deleteConnection(DatabaseConnectionParam.DeleteParam param);
|
||||||
|
|
||||||
|
DatabaseTestResultVO testConnection(DatabaseConnectionParam.TestParam param);
|
||||||
|
|
||||||
|
List<DatabaseTableVO> listTables(DatabaseConnectionParam.TablesParam param);
|
||||||
|
|
||||||
|
DatabaseConnection requireEnabled(String connectionId);
|
||||||
|
|
||||||
|
String resolvePassword(DatabaseConnection connection, String temporaryPassword);
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.njcn.gather.systemops.database.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.param.DatabaseBackupParam;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.po.DatabaseOperationTask;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTaskCreateVO;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTaskVO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库运维任务服务。
|
||||||
|
*/
|
||||||
|
public interface DatabaseOperationTaskService extends IService<DatabaseOperationTask> {
|
||||||
|
|
||||||
|
DatabaseTaskCreateVO createBackupTask(DatabaseBackupParam.CreateParam param);
|
||||||
|
|
||||||
|
Page<DatabaseTaskVO> listBackupTasks(DatabaseBackupParam.TaskQueryParam param);
|
||||||
|
|
||||||
|
DatabaseTaskVO getStatus(String taskId);
|
||||||
|
|
||||||
|
boolean deleteTask(String taskId, String confirmText);
|
||||||
|
|
||||||
|
boolean existsRunningTask(String connectionId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.njcn.gather.systemops.database.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.param.DatabaseRestoreParam;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.po.DatabaseRestoreRecord;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTaskCreateVO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库恢复服务。
|
||||||
|
*/
|
||||||
|
public interface DatabaseRestoreService extends IService<DatabaseRestoreRecord> {
|
||||||
|
|
||||||
|
DatabaseTaskCreateVO createRestoreTask(DatabaseRestoreParam.CreateParam param);
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
package com.njcn.gather.systemops.database.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||||
|
import com.njcn.common.pojo.exception.BusinessException;
|
||||||
|
import com.njcn.gather.systemops.database.config.DbmsProperties;
|
||||||
|
import com.njcn.gather.systemops.database.constant.DatabaseOpsConst;
|
||||||
|
import com.njcn.gather.systemops.database.mapper.DatabaseBackupFileMapper;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.param.DatabaseBackupParam;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.po.DatabaseBackupFile;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.vo.DatabaseBackupFileVO;
|
||||||
|
import com.njcn.gather.systemops.database.service.DatabaseBackupFileService;
|
||||||
|
import com.njcn.gather.systemops.database.util.DatabaseChecksumUtil;
|
||||||
|
import com.njcn.gather.systemops.database.util.DatabasePathUtil;
|
||||||
|
import com.njcn.web.factory.PageFactory;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库备份文件服务实现。
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DatabaseBackupFileServiceImpl extends ServiceImpl<DatabaseBackupFileMapper, DatabaseBackupFile> implements DatabaseBackupFileService {
|
||||||
|
|
||||||
|
private final DbmsProperties dbmsProperties;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Page<DatabaseBackupFileVO> listFiles(DatabaseBackupParam.FileQueryParam param) {
|
||||||
|
DatabaseBackupParam.FileQueryParam query = param == null ? new DatabaseBackupParam.FileQueryParam() : param;
|
||||||
|
LambdaQueryWrapper<DatabaseBackupFile> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(DatabaseBackupFile::getState, DatabaseOpsConst.STATE_ENABLED)
|
||||||
|
.eq(StrUtil.isNotBlank(query.getConnectionId()), DatabaseBackupFile::getConnectionId, query.getConnectionId())
|
||||||
|
.eq(StrUtil.isNotBlank(query.getTaskId()), DatabaseBackupFile::getTaskId, query.getTaskId())
|
||||||
|
.eq(StrUtil.isNotBlank(query.getBackupStrategy()), DatabaseBackupFile::getBackupStrategy, query.getBackupStrategy())
|
||||||
|
.orderByDesc(DatabaseBackupFile::getCreateTime);
|
||||||
|
Page<DatabaseBackupFile> page = this.page(new Page<>(PageFactory.getPageNum(query), PageFactory.getPageSize(query)), wrapper);
|
||||||
|
Page<DatabaseBackupFileVO> result = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
|
||||||
|
result.setRecords(page.getRecords().stream().map(this::toVO).collect(Collectors.toList()));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public boolean deleteBackupFile(String backupFileId, String confirmText) {
|
||||||
|
if (!DatabaseOpsConst.CONFIRM_DELETE.equals(confirmText)) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "确认文案不正确");
|
||||||
|
}
|
||||||
|
DatabaseBackupFile file = this.lambdaQuery()
|
||||||
|
.eq(DatabaseBackupFile::getId, backupFileId)
|
||||||
|
.eq(DatabaseBackupFile::getState, DatabaseOpsConst.STATE_ENABLED)
|
||||||
|
.one();
|
||||||
|
if (file == null) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "备份文件不存在或已删除");
|
||||||
|
}
|
||||||
|
deletePhysicalFile(file, file.getFilePath());
|
||||||
|
deletePhysicalFile(file, file.getLogFilePath());
|
||||||
|
deletePhysicalFile(file, file.getMetadataFilePath());
|
||||||
|
file.setState(DatabaseOpsConst.STATE_DELETED);
|
||||||
|
file.setUpdateTime(LocalDateTime.now());
|
||||||
|
return this.updateById(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateBackupFileReadable(DatabaseBackupFile backupFile) {
|
||||||
|
validateReadablePath(backupFile, backupFile.getFilePath(), "备份文件", false);
|
||||||
|
validateReadablePath(backupFile, backupFile.getMetadataFilePath(), "备份元数据文件",
|
||||||
|
StrUtil.isBlank(backupFile.getMetadataFilePath()));
|
||||||
|
if (StrUtil.isBlank(backupFile.getChecksum())) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "备份文件缺少校验值");
|
||||||
|
}
|
||||||
|
Path filePath = resolveManagedPath(backupFile, backupFile.getFilePath());
|
||||||
|
String actualChecksum = DatabaseChecksumUtil.sha256(filePath);
|
||||||
|
if (!backupFile.getChecksum().equalsIgnoreCase(actualChecksum)) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "备份文件校验失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Path resolveManagedPath(DatabaseBackupFile backupFile, String filePath) {
|
||||||
|
if (StrUtil.isBlank(filePath)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Path path = DatabasePathUtil.normalize(filePath);
|
||||||
|
if (path == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Path storageRoot = DatabasePathUtil.normalize(dbmsProperties.getBackup().getStoragePath());
|
||||||
|
if (DatabasePathUtil.isUnder(path, storageRoot)) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
Path primaryFilePath = DatabasePathUtil.normalize(backupFile.getFilePath());
|
||||||
|
if (primaryFilePath != null && primaryFilePath.getParent() != null
|
||||||
|
&& DatabasePathUtil.isUnder(path, primaryFilePath.getParent())) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "文件路径不在允许的备份目录内");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deletePhysicalFile(DatabaseBackupFile backupFile, String filePath) {
|
||||||
|
if (StrUtil.isBlank(filePath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Path path = resolveManagedPath(backupFile, filePath);
|
||||||
|
if (path != null && Files.exists(path) && !Files.isDirectory(path)) {
|
||||||
|
Files.delete(path);
|
||||||
|
}
|
||||||
|
} catch (BusinessException exception) {
|
||||||
|
throw exception;
|
||||||
|
} catch (Exception exception) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "删除物理文件失败:" + exception.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateReadablePath(DatabaseBackupFile backupFile, String filePath, String fileType, boolean allowBlank) {
|
||||||
|
if (StrUtil.isBlank(filePath)) {
|
||||||
|
if (allowBlank) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, fileType + "路径不能为空");
|
||||||
|
}
|
||||||
|
Path path = resolveManagedPath(backupFile, filePath);
|
||||||
|
if (path == null || !Files.exists(path) || Files.isDirectory(path)) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, fileType + "不存在");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private DatabaseBackupFileVO toVO(DatabaseBackupFile file) {
|
||||||
|
DatabaseBackupFileVO vo = new DatabaseBackupFileVO();
|
||||||
|
BeanUtil.copyProperties(file, vo);
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
package com.njcn.gather.systemops.database.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||||
|
import com.njcn.common.pojo.exception.BusinessException;
|
||||||
|
import com.njcn.gather.systemops.database.component.DatabasePasswordComponent;
|
||||||
|
import com.njcn.gather.systemops.database.component.OracleJdbcComponent;
|
||||||
|
import com.njcn.gather.systemops.database.constant.DatabaseOpsConst;
|
||||||
|
import com.njcn.gather.systemops.database.mapper.DatabaseConnectionMapper;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.param.DatabaseConnectionParam;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.po.DatabaseConnection;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.vo.DatabaseConnectionVO;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTableVO;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTestResultVO;
|
||||||
|
import com.njcn.gather.systemops.database.service.DatabaseConnectionService;
|
||||||
|
import com.njcn.gather.systemops.database.service.DatabaseOperationTaskService;
|
||||||
|
import com.njcn.gather.systemops.database.util.DatabaseOpsIdUtil;
|
||||||
|
import com.njcn.web.factory.PageFactory;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库连接配置服务实现。
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DatabaseConnectionServiceImpl extends ServiceImpl<DatabaseConnectionMapper, DatabaseConnection> implements DatabaseConnectionService {
|
||||||
|
|
||||||
|
private final DatabasePasswordComponent databasePasswordComponent;
|
||||||
|
private final OracleJdbcComponent oracleJdbcComponent;
|
||||||
|
private final ObjectProvider<DatabaseOperationTaskService> databaseOperationTaskServiceProvider;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Page<DatabaseConnectionVO> listConnections(DatabaseConnectionParam.QueryParam queryParam) {
|
||||||
|
DatabaseConnectionParam.QueryParam query = queryParam == null ? new DatabaseConnectionParam.QueryParam() : queryParam;
|
||||||
|
LambdaQueryWrapper<DatabaseConnection> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(DatabaseConnection::getState, DatabaseOpsConst.STATE_ENABLED)
|
||||||
|
.like(StrUtil.isNotBlank(query.getConnectionName()), DatabaseConnection::getConnectionName, query.getConnectionName())
|
||||||
|
.eq(StrUtil.isNotBlank(query.getDbType()), DatabaseConnection::getDbType, query.getDbType())
|
||||||
|
.like(StrUtil.isNotBlank(query.getSchemaName()), DatabaseConnection::getSchemaName, query.getSchemaName())
|
||||||
|
.orderByDesc(DatabaseConnection::getUpdateTime);
|
||||||
|
Page<DatabaseConnection> page = this.page(new Page<>(PageFactory.getPageNum(query), PageFactory.getPageSize(query)), wrapper);
|
||||||
|
Page<DatabaseConnectionVO> result = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
|
||||||
|
result.setRecords(page.getRecords().stream().map(this::toVO).collect(Collectors.toList()));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public boolean addConnection(DatabaseConnectionParam param) {
|
||||||
|
DatabaseConnection connection = new DatabaseConnection();
|
||||||
|
fillConnection(connection, param, true);
|
||||||
|
connection.setId(DatabaseOpsIdUtil.uuid());
|
||||||
|
connection.setState(DatabaseOpsConst.STATE_ENABLED);
|
||||||
|
connection.setCreateTime(LocalDateTime.now());
|
||||||
|
connection.setUpdateTime(LocalDateTime.now());
|
||||||
|
return this.save(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public boolean updateConnection(DatabaseConnectionParam.UpdateParam param) {
|
||||||
|
DatabaseConnection connection = requireEnabled(param.getId());
|
||||||
|
fillConnection(connection, param, false);
|
||||||
|
connection.setUpdateTime(LocalDateTime.now());
|
||||||
|
return this.updateById(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public boolean deleteConnection(DatabaseConnectionParam.DeleteParam param) {
|
||||||
|
requireEnabled(param.getId());
|
||||||
|
if (databaseOperationTaskServiceProvider.getObject().existsRunningTask(param.getId())) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "存在运行中的任务,不能删除连接");
|
||||||
|
}
|
||||||
|
return this.lambdaUpdate()
|
||||||
|
.set(DatabaseConnection::getState, DatabaseOpsConst.STATE_DELETED)
|
||||||
|
.set(DatabaseConnection::getUpdateTime, LocalDateTime.now())
|
||||||
|
.eq(DatabaseConnection::getId, param.getId())
|
||||||
|
.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public DatabaseTestResultVO testConnection(DatabaseConnectionParam.TestParam param) {
|
||||||
|
DatabaseConnection connection = resolveTestConnection(param);
|
||||||
|
DatabaseTestResultVO result = oracleJdbcComponent.test(connection, resolvePassword(connection, param.getTemporaryPassword()));
|
||||||
|
if (StrUtil.isNotBlank(connection.getId())) {
|
||||||
|
connection.setLastTestStatus(Boolean.TRUE.equals(result.getSuccess()) ? "SUCCESS" : "FAIL");
|
||||||
|
connection.setLastTestMessage(result.getMessage());
|
||||||
|
connection.setLastTestTime(LocalDateTime.now());
|
||||||
|
this.updateById(connection);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<DatabaseTableVO> listTables(DatabaseConnectionParam.TablesParam param) {
|
||||||
|
DatabaseConnection connection = requireEnabled(param.getConnectionId());
|
||||||
|
try {
|
||||||
|
return oracleJdbcComponent.listTables(connection, resolvePassword(connection, param.getTemporaryPassword()), param.getSchemaName());
|
||||||
|
} catch (Exception exception) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, exception.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DatabaseConnection requireEnabled(String connectionId) {
|
||||||
|
if (StrUtil.isBlank(connectionId)) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "连接 ID 不能为空");
|
||||||
|
}
|
||||||
|
DatabaseConnection connection = this.lambdaQuery()
|
||||||
|
.eq(DatabaseConnection::getId, connectionId)
|
||||||
|
.eq(DatabaseConnection::getState, DatabaseOpsConst.STATE_ENABLED)
|
||||||
|
.one();
|
||||||
|
if (connection == null) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "数据库连接不存在或已删除");
|
||||||
|
}
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String resolvePassword(DatabaseConnection connection, String temporaryPassword) {
|
||||||
|
try {
|
||||||
|
return databasePasswordComponent.resolveRuntimePassword(connection.getPasswordCipher(), temporaryPassword);
|
||||||
|
} catch (IllegalArgumentException exception) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, exception.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private DatabaseConnection resolveTestConnection(DatabaseConnectionParam.TestParam param) {
|
||||||
|
if (StrUtil.isNotBlank(param.getConnectionId())) {
|
||||||
|
return requireEnabled(param.getConnectionId());
|
||||||
|
}
|
||||||
|
if (param.getConnection() == null) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "连接测试参数不能为空");
|
||||||
|
}
|
||||||
|
DatabaseConnection connection = new DatabaseConnection();
|
||||||
|
fillConnection(connection, param.getConnection(), true, StrUtil.isNotBlank(param.getTemporaryPassword()));
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fillConnection(DatabaseConnection connection, DatabaseConnectionParam param, boolean create) {
|
||||||
|
fillConnection(connection, param, create, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fillConnection(DatabaseConnection connection, DatabaseConnectionParam param, boolean create,
|
||||||
|
boolean allowTemporaryPasswordOnly) {
|
||||||
|
validateConnectionParam(param);
|
||||||
|
connection.setConnectionName(param.getConnectionName().trim());
|
||||||
|
connection.setDbType(DatabaseOpsConst.DB_TYPE_ORACLE);
|
||||||
|
connection.setHost(param.getHost().trim());
|
||||||
|
connection.setPort(param.getPort());
|
||||||
|
connection.setConnectType(resolveConnectType(param.getConnectType()));
|
||||||
|
connection.setServiceName(trimToNull(param.getServiceName()));
|
||||||
|
connection.setSid(trimToNull(param.getSid()));
|
||||||
|
connection.setSchemaName(trimToNull(param.getSchemaName()));
|
||||||
|
connection.setUsername(param.getUsername().trim());
|
||||||
|
connection.setSavePassword(param.getSavePassword() == null ? DatabaseOpsConst.SAVE_PASSWORD_YES : param.getSavePassword());
|
||||||
|
if (connection.getSavePassword() != DatabaseOpsConst.SAVE_PASSWORD_YES
|
||||||
|
&& connection.getSavePassword() != DatabaseOpsConst.SAVE_PASSWORD_NO) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "savePassword 只能是 0 或 1");
|
||||||
|
}
|
||||||
|
if (DatabaseOpsConst.SAVE_PASSWORD_YES == connection.getSavePassword() && StrUtil.isNotBlank(param.getPassword())) {
|
||||||
|
connection.setPasswordCipher(databasePasswordComponent.encrypt(param.getPassword()));
|
||||||
|
}
|
||||||
|
if (DatabaseOpsConst.SAVE_PASSWORD_NO == connection.getSavePassword()) {
|
||||||
|
connection.setPasswordCipher(null);
|
||||||
|
} else if (create && StrUtil.isBlank(param.getPassword()) && !allowTemporaryPasswordOnly) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "保存密码时密码不能为空");
|
||||||
|
}
|
||||||
|
connection.setDirectoryName(trimToNull(param.getDirectoryName()));
|
||||||
|
connection.setDirectoryPath(trimToNull(param.getDirectoryPath()));
|
||||||
|
connection.setExtraConfigJson(trimToNull(param.getExtraConfigJson()));
|
||||||
|
connection.setRemark(param.getRemark());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateConnectionParam(DatabaseConnectionParam param) {
|
||||||
|
String connectType = resolveConnectType(param.getConnectType());
|
||||||
|
if (DatabaseOpsConst.CONNECT_TYPE_SERVICE_NAME.equals(connectType) && StrUtil.isBlank(param.getServiceName())) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "SERVICE_NAME 连接方式下服务名不能为空");
|
||||||
|
}
|
||||||
|
if (DatabaseOpsConst.CONNECT_TYPE_SID.equals(connectType) && StrUtil.isBlank(param.getSid())) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "SID 连接方式下 SID 不能为空");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveConnectType(String connectType) {
|
||||||
|
return StrUtil.blankToDefault(connectType, DatabaseOpsConst.CONNECT_TYPE_SERVICE_NAME).trim().toUpperCase(Locale.ROOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String trimToNull(String value) {
|
||||||
|
return StrUtil.isBlank(value) ? null : value.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private DatabaseConnectionVO toVO(DatabaseConnection connection) {
|
||||||
|
DatabaseConnectionVO vo = new DatabaseConnectionVO();
|
||||||
|
BeanUtil.copyProperties(connection, vo);
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,353 @@
|
|||||||
|
package com.njcn.gather.systemops.database.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||||
|
import com.njcn.common.pojo.exception.BusinessException;
|
||||||
|
import com.njcn.gather.systemops.database.component.DataPumpCommandExecutor;
|
||||||
|
import com.njcn.gather.systemops.database.component.JdbcExportComponent;
|
||||||
|
import com.njcn.gather.systemops.database.config.DbmsProperties;
|
||||||
|
import com.njcn.gather.systemops.database.constant.DatabaseOpsConst;
|
||||||
|
import com.njcn.gather.systemops.database.mapper.DatabaseOperationTaskMapper;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.enums.BackupModeEnum;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.enums.BackupStrategyEnum;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.enums.FileFormatEnum;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.enums.OperationTypeEnum;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.enums.TaskStatusEnum;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.param.DatabaseBackupParam;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.po.DatabaseBackupFile;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.po.DatabaseConnection;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.po.DatabaseOperationTask;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTaskCreateVO;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTaskVO;
|
||||||
|
import com.njcn.gather.systemops.database.service.DatabaseBackupFileService;
|
||||||
|
import com.njcn.gather.systemops.database.service.DatabaseConnectionService;
|
||||||
|
import com.njcn.gather.systemops.database.service.DatabaseOperationTaskService;
|
||||||
|
import com.njcn.gather.systemops.database.util.DatabaseChecksumUtil;
|
||||||
|
import com.njcn.gather.systemops.database.util.DatabaseFileNameUtil;
|
||||||
|
import com.njcn.gather.systemops.database.util.DatabaseOpsIdUtil;
|
||||||
|
import com.njcn.gather.systemops.database.util.DatabasePathUtil;
|
||||||
|
import com.njcn.web.factory.PageFactory;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库运维任务服务实现。
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DatabaseOperationTaskServiceImpl extends ServiceImpl<DatabaseOperationTaskMapper, DatabaseOperationTask> implements DatabaseOperationTaskService {
|
||||||
|
|
||||||
|
private final DatabaseConnectionService databaseConnectionService;
|
||||||
|
private final DatabaseBackupFileService databaseBackupFileService;
|
||||||
|
private final DataPumpCommandExecutor dataPumpCommandExecutor;
|
||||||
|
private final JdbcExportComponent jdbcExportComponent;
|
||||||
|
private final DbmsProperties dbmsProperties;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
@Resource(name = "dbmsTaskExecutorService")
|
||||||
|
private ExecutorService dbmsTaskExecutorService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public DatabaseTaskCreateVO createBackupTask(DatabaseBackupParam.CreateParam param) {
|
||||||
|
DatabaseConnection connection = databaseConnectionService.requireEnabled(param.getConnectionId());
|
||||||
|
validateBackupParam(param, connection);
|
||||||
|
if (existsRunningTask(connection.getId())) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "当前连接存在运行中的任务");
|
||||||
|
}
|
||||||
|
DatabaseOperationTask task = buildBackupTask(param, connection);
|
||||||
|
this.save(task);
|
||||||
|
dbmsTaskExecutorService.submit(() -> executeBackupTask(task.getId(), param));
|
||||||
|
return toCreateVO(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Page<DatabaseTaskVO> listBackupTasks(DatabaseBackupParam.TaskQueryParam param) {
|
||||||
|
DatabaseBackupParam.TaskQueryParam query = param == null ? new DatabaseBackupParam.TaskQueryParam() : param;
|
||||||
|
LambdaQueryWrapper<DatabaseOperationTask> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(DatabaseOperationTask::getState, DatabaseOpsConst.STATE_ENABLED)
|
||||||
|
.eq(DatabaseOperationTask::getOperationType, OperationTypeEnum.BACKUP.name())
|
||||||
|
.eq(StrUtil.isNotBlank(query.getConnectionId()), DatabaseOperationTask::getConnectionId, query.getConnectionId())
|
||||||
|
.eq(StrUtil.isNotBlank(query.getTaskStatus()), DatabaseOperationTask::getTaskStatus, query.getTaskStatus())
|
||||||
|
.orderByDesc(DatabaseOperationTask::getCreateTime);
|
||||||
|
Page<DatabaseOperationTask> page = this.page(new Page<>(PageFactory.getPageNum(query), PageFactory.getPageSize(query)), wrapper);
|
||||||
|
Page<DatabaseTaskVO> result = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
|
||||||
|
result.setRecords(page.getRecords().stream().map(this::toVO).collect(Collectors.toList()));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DatabaseTaskVO getStatus(String taskId) {
|
||||||
|
DatabaseOperationTask task = this.getById(taskId);
|
||||||
|
if (task == null || !Integer.valueOf(DatabaseOpsConst.STATE_ENABLED).equals(task.getState())) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "任务不存在或已删除");
|
||||||
|
}
|
||||||
|
return toVO(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public boolean deleteTask(String taskId, String confirmText) {
|
||||||
|
if (!DatabaseOpsConst.CONFIRM_DELETE.equals(confirmText)) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "确认文案不正确");
|
||||||
|
}
|
||||||
|
DatabaseOperationTask task = this.getById(taskId);
|
||||||
|
if (task == null || !Integer.valueOf(DatabaseOpsConst.STATE_ENABLED).equals(task.getState())) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "任务不存在或已删除");
|
||||||
|
}
|
||||||
|
if (TaskStatusEnum.RUNNING.name().equals(task.getTaskStatus()) || TaskStatusEnum.WAITING.name().equals(task.getTaskStatus())) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "运行中的任务不能删除");
|
||||||
|
}
|
||||||
|
task.setState(DatabaseOpsConst.STATE_DELETED);
|
||||||
|
task.setUpdateTime(LocalDateTime.now());
|
||||||
|
return this.updateById(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean existsRunningTask(String connectionId) {
|
||||||
|
return this.lambdaQuery()
|
||||||
|
.eq(DatabaseOperationTask::getConnectionId, connectionId)
|
||||||
|
.eq(DatabaseOperationTask::getState, DatabaseOpsConst.STATE_ENABLED)
|
||||||
|
.in(DatabaseOperationTask::getTaskStatus, Arrays.asList(TaskStatusEnum.WAITING.name(), TaskStatusEnum.RUNNING.name()))
|
||||||
|
.count() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeBackupTask(String taskId, DatabaseBackupParam.CreateParam param) {
|
||||||
|
DatabaseOperationTask task = this.getById(taskId);
|
||||||
|
try {
|
||||||
|
markRunning(task);
|
||||||
|
DatabaseConnection connection = databaseConnectionService.requireEnabled(task.getConnectionId());
|
||||||
|
connection.setSchemaName(task.getSchemaName());
|
||||||
|
String password = databaseConnectionService.resolvePassword(connection, param.getTemporaryPassword());
|
||||||
|
DatabaseBackupFile backupFile;
|
||||||
|
if (BackupStrategyEnum.DATA_PUMP.name().equals(task.getBackupStrategy())) {
|
||||||
|
backupFile = executeDataPumpBackup(task, connection, password, param);
|
||||||
|
} else {
|
||||||
|
backupFile = executeJdbcExportBackup(task, connection, password, param);
|
||||||
|
}
|
||||||
|
databaseBackupFileService.save(backupFile);
|
||||||
|
markSuccess(task, "备份任务执行成功");
|
||||||
|
} catch (Exception exception) {
|
||||||
|
log.error("数据库备份任务失败,taskId={}", taskId, exception);
|
||||||
|
markFail(task, exception.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private DatabaseBackupFile executeDataPumpBackup(DatabaseOperationTask task, DatabaseConnection connection, String password,
|
||||||
|
DatabaseBackupParam.CreateParam param) {
|
||||||
|
String directoryName = StrUtil.blankToDefault(param.getDirectoryName(), connection.getDirectoryName());
|
||||||
|
if (StrUtil.isBlank(directoryName)) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "DATA_PUMP 备份需要 Oracle Directory 名称");
|
||||||
|
}
|
||||||
|
String baseName = buildBaseFileName(connection, task);
|
||||||
|
String dumpFileName = DatabaseFileNameUtil.appendTodayWithTask(baseName + ".dmp", task.getTaskNo());
|
||||||
|
String logFileName = DatabaseFileNameUtil.appendTodayWithTask(baseName + ".log", task.getTaskNo());
|
||||||
|
DataPumpCommandExecutor.CommandResult commandResult = dataPumpCommandExecutor.expdp(connection, password, directoryName, dumpFileName, logFileName, param.getTargetNames());
|
||||||
|
if (!Boolean.TRUE.equals(commandResult.getSuccess())) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "Data Pump 执行失败:" + commandResult.getOutput());
|
||||||
|
}
|
||||||
|
if (StrUtil.isBlank(connection.getDirectoryPath())) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "Data Pump 备份需要配置可管理的 directoryPath");
|
||||||
|
}
|
||||||
|
Path dumpPath = buildManagedPath(connection.getDirectoryPath(), dumpFileName);
|
||||||
|
Path logPath = buildManagedPath(connection.getDirectoryPath(), logFileName);
|
||||||
|
return buildBackupFile(task, connection, param, FileFormatEnum.DMP.name(), dumpFileName, dumpPath, logFileName, logPath, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DatabaseBackupFile executeJdbcExportBackup(DatabaseOperationTask task, DatabaseConnection connection, String password,
|
||||||
|
DatabaseBackupParam.CreateParam param) throws Exception {
|
||||||
|
String baseName = buildBaseFileName(connection, task);
|
||||||
|
String fileName = DatabaseFileNameUtil.appendTodayWithTask(baseName + ".csv", task.getTaskNo());
|
||||||
|
String metadataFileName = DatabaseFileNameUtil.appendTodayWithTask(baseName + "_metadata.json", task.getTaskNo());
|
||||||
|
Path dataFilePath = buildManagedPath(dbmsProperties.getBackup().getStoragePath(), fileName);
|
||||||
|
Path metadataFilePath = buildManagedPath(dbmsProperties.getBackup().getStoragePath(), metadataFileName);
|
||||||
|
jdbcExportComponent.exportCsv(connection, password, param, dataFilePath, metadataFilePath);
|
||||||
|
return buildBackupFile(task, connection, param, FileFormatEnum.CSV.name(), fileName, dataFilePath, null, null, metadataFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DatabaseBackupFile buildBackupFile(DatabaseOperationTask task, DatabaseConnection connection, DatabaseBackupParam.CreateParam param,
|
||||||
|
String fileFormat, String fileName, Path filePath, String logFileName, Path logFilePath,
|
||||||
|
Path metadataFilePath) {
|
||||||
|
if (filePath == null || !Files.exists(filePath)) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "备份文件未生成");
|
||||||
|
}
|
||||||
|
DatabaseBackupFile file = new DatabaseBackupFile();
|
||||||
|
file.setId(DatabaseOpsIdUtil.uuid());
|
||||||
|
file.setTaskId(task.getId());
|
||||||
|
file.setConnectionId(connection.getId());
|
||||||
|
file.setDbType(connection.getDbType());
|
||||||
|
file.setBackupStrategy(task.getBackupStrategy());
|
||||||
|
file.setFileFormat(fileFormat);
|
||||||
|
file.setSchemaName(task.getSchemaName());
|
||||||
|
file.setTargetNamesJson(task.getTargetNamesJson());
|
||||||
|
file.setBackupMode(StrUtil.blankToDefault(param.getBackupMode(), BackupModeEnum.FULL_TABLE.name()).toUpperCase(Locale.ROOT));
|
||||||
|
file.setBackupStartTime(param.getStartTime());
|
||||||
|
file.setBackupEndTime(param.getEndTime());
|
||||||
|
file.setTimeColumn(param.getTimeColumn());
|
||||||
|
file.setDirectoryName(StrUtil.blankToDefault(param.getDirectoryName(), connection.getDirectoryName()));
|
||||||
|
file.setDumpFileName(FileFormatEnum.DMP.name().equals(fileFormat) ? fileName : null);
|
||||||
|
file.setLogFileName(logFileName);
|
||||||
|
file.setFileName(fileName);
|
||||||
|
file.setFilePath(filePath.toString());
|
||||||
|
file.setLogFilePath(logFilePath == null ? null : logFilePath.toString());
|
||||||
|
file.setMetadataFilePath(metadataFilePath == null ? null : metadataFilePath.toString());
|
||||||
|
file.setFileSize(readFileSize(filePath));
|
||||||
|
file.setChecksum(DatabaseChecksumUtil.sha256(filePath));
|
||||||
|
file.setState(DatabaseOpsConst.STATE_ENABLED);
|
||||||
|
file.setCreateTime(LocalDateTime.now());
|
||||||
|
file.setUpdateTime(LocalDateTime.now());
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long readFileSize(Path filePath) {
|
||||||
|
try {
|
||||||
|
if (filePath != null && Files.exists(filePath) && !Files.isDirectory(filePath)) {
|
||||||
|
return Files.size(filePath);
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path buildManagedPath(String rootPath, String fileName) {
|
||||||
|
Path root = DatabasePathUtil.normalize(rootPath);
|
||||||
|
if (root == null) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "备份目录未配置");
|
||||||
|
}
|
||||||
|
return root.resolve(fileName).normalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildBaseFileName(DatabaseConnection connection, DatabaseOperationTask task) {
|
||||||
|
return connection.getSchemaName() + "_" + task.getBackupStrategy().toLowerCase(Locale.ROOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DatabaseOperationTask buildBackupTask(DatabaseBackupParam.CreateParam param, DatabaseConnection connection) {
|
||||||
|
DatabaseOperationTask task = new DatabaseOperationTask();
|
||||||
|
task.setId(DatabaseOpsIdUtil.uuid());
|
||||||
|
task.setTaskNo(DatabaseOpsIdUtil.taskNo("DBMSB"));
|
||||||
|
task.setConnectionId(connection.getId());
|
||||||
|
task.setDbType(connection.getDbType());
|
||||||
|
task.setOperationType(OperationTypeEnum.BACKUP.name());
|
||||||
|
task.setBackupStrategy(resolveBackupStrategy(param.getBackupStrategy()));
|
||||||
|
task.setTaskStatus(TaskStatusEnum.WAITING.name());
|
||||||
|
task.setSchemaName(StrUtil.blankToDefault(param.getSchemaName(), connection.getSchemaName()));
|
||||||
|
task.setTargetNamesJson(writeJson(param.getTargetNames()));
|
||||||
|
task.setRequestParamJson(writeJsonWithoutPassword(param));
|
||||||
|
task.setProgressPercent(BigDecimal.ZERO);
|
||||||
|
task.setState(DatabaseOpsConst.STATE_ENABLED);
|
||||||
|
task.setCreateTime(LocalDateTime.now());
|
||||||
|
task.setUpdateTime(LocalDateTime.now());
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateBackupParam(DatabaseBackupParam.CreateParam param, DatabaseConnection connection) {
|
||||||
|
if (!DatabaseOpsConst.DB_TYPE_ORACLE.equals(connection.getDbType())) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "一期仅支持 ORACLE");
|
||||||
|
}
|
||||||
|
if (param.getTargetNames() == null || param.getTargetNames().isEmpty()) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "备份表不能为空");
|
||||||
|
}
|
||||||
|
if (StrUtil.isBlank(StrUtil.blankToDefault(param.getSchemaName(), connection.getSchemaName()))) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "备份 Schema 不能为空");
|
||||||
|
}
|
||||||
|
String backupMode = StrUtil.blankToDefault(param.getBackupMode(), BackupModeEnum.FULL_TABLE.name()).toUpperCase(Locale.ROOT);
|
||||||
|
if (BackupModeEnum.TIME_RANGE.name().equals(backupMode)
|
||||||
|
&& (param.getStartTime() == null || param.getEndTime() == null)) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "按时间备份必须传入开始时间和结束时间");
|
||||||
|
}
|
||||||
|
if (BackupModeEnum.TIME_RANGE.name().equals(backupMode)
|
||||||
|
&& param.getStartTime() != null && param.getEndTime() != null
|
||||||
|
&& param.getStartTime().isAfter(param.getEndTime())) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "开始时间不能晚于结束时间");
|
||||||
|
}
|
||||||
|
if (BackupModeEnum.SIZE_SPLIT.name().equals(backupMode)
|
||||||
|
&& (param.getMaxFileSizeMb() == null || param.getMaxFileSizeMb() <= 0)) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "按大小分片必须传入大于 0 的文件大小");
|
||||||
|
}
|
||||||
|
if (BackupStrategyEnum.JDBC_EXPORT.name().equals(resolveBackupStrategy(param.getBackupStrategy()))
|
||||||
|
&& BackupModeEnum.TIME_RANGE.name().equals(backupMode)
|
||||||
|
&& StrUtil.isBlank(param.getTimeColumn())) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "JDBC 按时间备份必须传入时间字段");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveBackupStrategy(String backupStrategy) {
|
||||||
|
String value = StrUtil.blankToDefault(backupStrategy, BackupStrategyEnum.DATA_PUMP.name()).trim().toUpperCase(Locale.ROOT);
|
||||||
|
try {
|
||||||
|
return BackupStrategyEnum.valueOf(value).name();
|
||||||
|
} catch (Exception exception) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "不支持的备份策略:" + backupStrategy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void markRunning(DatabaseOperationTask task) {
|
||||||
|
task.setTaskStatus(TaskStatusEnum.RUNNING.name());
|
||||||
|
task.setStartedAt(LocalDateTime.now());
|
||||||
|
task.setUpdateTime(LocalDateTime.now());
|
||||||
|
this.updateById(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void markSuccess(DatabaseOperationTask task, String message) {
|
||||||
|
task.setTaskStatus(TaskStatusEnum.SUCCESS.name());
|
||||||
|
task.setResultMessage(message);
|
||||||
|
task.setProgressPercent(new BigDecimal("100.00"));
|
||||||
|
task.setFinishedAt(LocalDateTime.now());
|
||||||
|
task.setUpdateTime(LocalDateTime.now());
|
||||||
|
this.updateById(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void markFail(DatabaseOperationTask task, String message) {
|
||||||
|
task.setTaskStatus(TaskStatusEnum.FAIL.name());
|
||||||
|
task.setResultMessage(message);
|
||||||
|
task.setFinishedAt(LocalDateTime.now());
|
||||||
|
task.setUpdateTime(LocalDateTime.now());
|
||||||
|
this.updateById(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String writeJson(Object value) {
|
||||||
|
try {
|
||||||
|
return objectMapper.writeValueAsString(value);
|
||||||
|
} catch (Exception exception) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.JSON_CONVERT_EXCEPTION, exception.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String writeJsonWithoutPassword(DatabaseBackupParam.CreateParam param) {
|
||||||
|
DatabaseBackupParam.CreateParam copy = new DatabaseBackupParam.CreateParam();
|
||||||
|
BeanUtil.copyProperties(param, copy);
|
||||||
|
copy.setTemporaryPassword(null);
|
||||||
|
return writeJson(copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DatabaseTaskCreateVO toCreateVO(DatabaseOperationTask task) {
|
||||||
|
DatabaseTaskCreateVO vo = new DatabaseTaskCreateVO();
|
||||||
|
vo.setTaskId(task.getId());
|
||||||
|
vo.setTaskNo(task.getTaskNo());
|
||||||
|
vo.setTaskStatus(task.getTaskStatus());
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DatabaseTaskVO toVO(DatabaseOperationTask task) {
|
||||||
|
DatabaseTaskVO vo = new DatabaseTaskVO();
|
||||||
|
BeanUtil.copyProperties(task, vo);
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,239 @@
|
|||||||
|
package com.njcn.gather.systemops.database.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||||
|
import com.njcn.common.pojo.exception.BusinessException;
|
||||||
|
import com.njcn.gather.systemops.database.component.DataPumpCommandExecutor;
|
||||||
|
import com.njcn.gather.systemops.database.component.JdbcExportComponent;
|
||||||
|
import com.njcn.gather.systemops.database.component.OracleJdbcComponent;
|
||||||
|
import com.njcn.gather.systemops.database.constant.DatabaseOpsConst;
|
||||||
|
import com.njcn.gather.systemops.database.mapper.DatabaseRestoreRecordMapper;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.enums.BackupStrategyEnum;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.enums.FileFormatEnum;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.enums.OperationTypeEnum;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.enums.RestoreModeEnum;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.enums.TaskStatusEnum;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.param.DatabaseRestoreParam;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.po.DatabaseBackupFile;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.po.DatabaseConnection;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.po.DatabaseOperationTask;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.po.DatabaseRestoreRecord;
|
||||||
|
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTaskCreateVO;
|
||||||
|
import com.njcn.gather.systemops.database.service.DatabaseBackupFileService;
|
||||||
|
import com.njcn.gather.systemops.database.service.DatabaseConnectionService;
|
||||||
|
import com.njcn.gather.systemops.database.service.DatabaseOperationTaskService;
|
||||||
|
import com.njcn.gather.systemops.database.service.DatabaseRestoreService;
|
||||||
|
import com.njcn.gather.systemops.database.util.DatabaseFileNameUtil;
|
||||||
|
import com.njcn.gather.systemops.database.util.DatabaseOpsIdUtil;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库恢复服务实现。
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DatabaseRestoreServiceImpl extends ServiceImpl<DatabaseRestoreRecordMapper, DatabaseRestoreRecord> implements DatabaseRestoreService {
|
||||||
|
|
||||||
|
private final DatabaseConnectionService databaseConnectionService;
|
||||||
|
private final DatabaseOperationTaskService databaseOperationTaskService;
|
||||||
|
private final DatabaseBackupFileService databaseBackupFileService;
|
||||||
|
private final DataPumpCommandExecutor dataPumpCommandExecutor;
|
||||||
|
private final JdbcExportComponent jdbcExportComponent;
|
||||||
|
private final OracleJdbcComponent oracleJdbcComponent;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
@Resource(name = "dbmsTaskExecutorService")
|
||||||
|
private ExecutorService dbmsTaskExecutorService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public DatabaseTaskCreateVO createRestoreTask(DatabaseRestoreParam.CreateParam param) {
|
||||||
|
DatabaseConnection connection = databaseConnectionService.requireEnabled(param.getConnectionId());
|
||||||
|
DatabaseBackupFile backupFile = requireBackupFile(param.getBackupFileId());
|
||||||
|
validateRestoreParam(param, connection, backupFile);
|
||||||
|
if (databaseOperationTaskService.existsRunningTask(connection.getId())) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "当前连接存在运行中的任务");
|
||||||
|
}
|
||||||
|
DatabaseOperationTask task = buildRestoreTask(param, connection, backupFile);
|
||||||
|
databaseOperationTaskService.save(task);
|
||||||
|
DatabaseRestoreRecord record = buildRestoreRecord(param, connection, backupFile, task);
|
||||||
|
this.save(record);
|
||||||
|
dbmsTaskExecutorService.submit(() -> executeRestoreTask(task.getId(), record.getId(), param));
|
||||||
|
DatabaseTaskCreateVO vo = new DatabaseTaskCreateVO();
|
||||||
|
vo.setTaskId(task.getId());
|
||||||
|
vo.setTaskNo(task.getTaskNo());
|
||||||
|
vo.setTaskStatus(task.getTaskStatus());
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeRestoreTask(String taskId, String recordId, DatabaseRestoreParam.CreateParam param) {
|
||||||
|
DatabaseOperationTask task = databaseOperationTaskService.getById(taskId);
|
||||||
|
DatabaseRestoreRecord record = this.getById(recordId);
|
||||||
|
try {
|
||||||
|
markRunning(task);
|
||||||
|
DatabaseConnection connection = databaseConnectionService.requireEnabled(task.getConnectionId());
|
||||||
|
DatabaseBackupFile backupFile = requireBackupFile(record.getBackupFileId());
|
||||||
|
databaseBackupFileService.validateBackupFileReadable(backupFile);
|
||||||
|
String password = databaseConnectionService.resolvePassword(connection, param.getTemporaryPassword());
|
||||||
|
if (BackupStrategyEnum.DATA_PUMP.name().equals(backupFile.getBackupStrategy())) {
|
||||||
|
DataPumpCommandExecutor.CommandResult result = dataPumpCommandExecutor.impdp(connection, password,
|
||||||
|
backupFile.getDirectoryName(), backupFile.getDumpFileName(), buildRestoreLogName(task),
|
||||||
|
record.getTableExistsAction());
|
||||||
|
if (!Boolean.TRUE.equals(result.getSuccess())) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "Data Pump 恢复失败:" + result.getOutput());
|
||||||
|
}
|
||||||
|
} else if (FileFormatEnum.CSV.name().equalsIgnoreCase(backupFile.getFileFormat())) {
|
||||||
|
Path dataFilePath = databaseBackupFileService.resolveManagedPath(backupFile, backupFile.getFilePath());
|
||||||
|
Path metadataFilePath = databaseBackupFileService.resolveManagedPath(backupFile, backupFile.getMetadataFilePath());
|
||||||
|
jdbcExportComponent.importCsv(connection, password, dataFilePath, metadataFilePath, record.getRestoreMode(), record.getTargetSchemaName());
|
||||||
|
} else {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "暂不支持的恢复文件格式:" + backupFile.getFileFormat());
|
||||||
|
}
|
||||||
|
record.setResultMessage("恢复任务执行成功");
|
||||||
|
record.setUpdateTime(LocalDateTime.now());
|
||||||
|
this.updateById(record);
|
||||||
|
markSuccess(task, "恢复任务执行成功");
|
||||||
|
} catch (Exception exception) {
|
||||||
|
log.error("数据库恢复任务失败,taskId={}", taskId, exception);
|
||||||
|
record.setResultMessage(exception.getMessage());
|
||||||
|
record.setUpdateTime(LocalDateTime.now());
|
||||||
|
this.updateById(record);
|
||||||
|
markFail(task, exception.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateRestoreParam(DatabaseRestoreParam.CreateParam param, DatabaseConnection connection, DatabaseBackupFile backupFile) {
|
||||||
|
if (!connection.getDbType().equals(backupFile.getDbType())) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "备份文件数据库类型和目标连接数据库类型不一致");
|
||||||
|
}
|
||||||
|
String restoreMode = resolveRestoreMode(param.getRestoreMode());
|
||||||
|
if ((RestoreModeEnum.TRUNCATE.name().equals(restoreMode) || RestoreModeEnum.REPLACE.name().equals(restoreMode))
|
||||||
|
&& !DatabaseOpsConst.CONFIRM_OVERWRITE.equals(param.getOverwriteConfirmText())) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "覆盖类恢复必须输入确认覆盖");
|
||||||
|
}
|
||||||
|
databaseBackupFileService.validateBackupFileReadable(backupFile);
|
||||||
|
String password = databaseConnectionService.resolvePassword(connection, param.getTemporaryPassword());
|
||||||
|
if (!Boolean.TRUE.equals(oracleJdbcComponent.test(connection, password).getSuccess())) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "目标连接测试失败,不能创建恢复任务");
|
||||||
|
}
|
||||||
|
if (BackupStrategyEnum.DATA_PUMP.name().equals(backupFile.getBackupStrategy())) {
|
||||||
|
if (StrUtil.isBlank(backupFile.getDirectoryName()) || StrUtil.isBlank(backupFile.getDumpFileName())) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "Data Pump 备份记录缺少目录或文件名");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (BackupStrategyEnum.JDBC_EXPORT.name().equals(backupFile.getBackupStrategy())
|
||||||
|
&& StrUtil.isBlank(backupFile.getMetadataFilePath())) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "JDBC_EXPORT 备份缺少元数据文件,不能恢复");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private DatabaseOperationTask buildRestoreTask(DatabaseRestoreParam.CreateParam param, DatabaseConnection connection, DatabaseBackupFile backupFile) {
|
||||||
|
DatabaseOperationTask task = new DatabaseOperationTask();
|
||||||
|
task.setId(DatabaseOpsIdUtil.uuid());
|
||||||
|
task.setTaskNo(DatabaseOpsIdUtil.taskNo("DBMSR"));
|
||||||
|
task.setConnectionId(connection.getId());
|
||||||
|
task.setDbType(connection.getDbType());
|
||||||
|
task.setOperationType(OperationTypeEnum.RESTORE.name());
|
||||||
|
task.setBackupStrategy(backupFile.getBackupStrategy());
|
||||||
|
task.setTaskStatus(TaskStatusEnum.WAITING.name());
|
||||||
|
task.setSchemaName(StrUtil.blankToDefault(param.getTargetSchemaName(), connection.getSchemaName()));
|
||||||
|
task.setTargetNamesJson(backupFile.getTargetNamesJson());
|
||||||
|
task.setRequestParamJson(writeJsonWithoutPassword(param));
|
||||||
|
task.setProgressPercent(BigDecimal.ZERO);
|
||||||
|
task.setState(DatabaseOpsConst.STATE_ENABLED);
|
||||||
|
task.setCreateTime(LocalDateTime.now());
|
||||||
|
task.setUpdateTime(LocalDateTime.now());
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DatabaseRestoreRecord buildRestoreRecord(DatabaseRestoreParam.CreateParam param, DatabaseConnection connection,
|
||||||
|
DatabaseBackupFile backupFile, DatabaseOperationTask task) {
|
||||||
|
String restoreMode = resolveRestoreMode(param.getRestoreMode());
|
||||||
|
DatabaseRestoreRecord record = new DatabaseRestoreRecord();
|
||||||
|
record.setId(DatabaseOpsIdUtil.uuid());
|
||||||
|
record.setTaskId(task.getId());
|
||||||
|
record.setBackupFileId(backupFile.getId());
|
||||||
|
record.setConnectionId(connection.getId());
|
||||||
|
record.setDbType(connection.getDbType());
|
||||||
|
record.setRestoreMode(restoreMode);
|
||||||
|
record.setTargetSchemaName(StrUtil.blankToDefault(param.getTargetSchemaName(), connection.getSchemaName()));
|
||||||
|
record.setTargetNamesJson(backupFile.getTargetNamesJson());
|
||||||
|
record.setTableExistsAction(restoreMode);
|
||||||
|
record.setOverwriteConfirmed(DatabaseOpsConst.CONFIRM_OVERWRITE.equals(param.getOverwriteConfirmText()) ? 1 : 0);
|
||||||
|
record.setState(DatabaseOpsConst.STATE_ENABLED);
|
||||||
|
record.setCreateTime(LocalDateTime.now());
|
||||||
|
record.setUpdateTime(LocalDateTime.now());
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DatabaseBackupFile requireBackupFile(String backupFileId) {
|
||||||
|
DatabaseBackupFile backupFile = databaseBackupFileService.getById(backupFileId);
|
||||||
|
if (backupFile == null || !Integer.valueOf(DatabaseOpsConst.STATE_ENABLED).equals(backupFile.getState())) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "备份文件不存在或已删除");
|
||||||
|
}
|
||||||
|
return backupFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveRestoreMode(String restoreMode) {
|
||||||
|
String value = StrUtil.blankToDefault(restoreMode, RestoreModeEnum.SKIP.name()).trim().toUpperCase(Locale.ROOT);
|
||||||
|
try {
|
||||||
|
return RestoreModeEnum.valueOf(value).name();
|
||||||
|
} catch (Exception exception) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "不支持的恢复模式:" + restoreMode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildRestoreLogName(DatabaseOperationTask task) {
|
||||||
|
return DatabaseFileNameUtil.appendTodayWithTask(task.getTaskNo() + "_restore.log", task.getTaskNo());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void markRunning(DatabaseOperationTask task) {
|
||||||
|
task.setTaskStatus(TaskStatusEnum.RUNNING.name());
|
||||||
|
task.setStartedAt(LocalDateTime.now());
|
||||||
|
task.setUpdateTime(LocalDateTime.now());
|
||||||
|
databaseOperationTaskService.updateById(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void markSuccess(DatabaseOperationTask task, String message) {
|
||||||
|
task.setTaskStatus(TaskStatusEnum.SUCCESS.name());
|
||||||
|
task.setResultMessage(message);
|
||||||
|
task.setProgressPercent(new BigDecimal("100.00"));
|
||||||
|
task.setFinishedAt(LocalDateTime.now());
|
||||||
|
task.setUpdateTime(LocalDateTime.now());
|
||||||
|
databaseOperationTaskService.updateById(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void markFail(DatabaseOperationTask task, String message) {
|
||||||
|
task.setTaskStatus(TaskStatusEnum.FAIL.name());
|
||||||
|
task.setResultMessage(message);
|
||||||
|
task.setFinishedAt(LocalDateTime.now());
|
||||||
|
task.setUpdateTime(LocalDateTime.now());
|
||||||
|
databaseOperationTaskService.updateById(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String writeJsonWithoutPassword(DatabaseRestoreParam.CreateParam param) {
|
||||||
|
try {
|
||||||
|
DatabaseRestoreParam.CreateParam copy = new DatabaseRestoreParam.CreateParam();
|
||||||
|
copy.setConnectionId(param.getConnectionId());
|
||||||
|
copy.setBackupFileId(param.getBackupFileId());
|
||||||
|
copy.setRestoreMode(param.getRestoreMode());
|
||||||
|
copy.setTargetSchemaName(param.getTargetSchemaName());
|
||||||
|
copy.setOverwriteConfirmText(param.getOverwriteConfirmText());
|
||||||
|
return objectMapper.writeValueAsString(copy);
|
||||||
|
} catch (Exception exception) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.JSON_CONVERT_EXCEPTION, exception.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.njcn.gather.systemops.database.util;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件校验工具。
|
||||||
|
*/
|
||||||
|
public final class DatabaseChecksumUtil {
|
||||||
|
|
||||||
|
private DatabaseChecksumUtil() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String sha256(Path path) {
|
||||||
|
if (path == null || !Files.exists(path) || Files.isDirectory(path)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try (InputStream inputStream = Files.newInputStream(path)) {
|
||||||
|
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||||
|
byte[] buffer = new byte[8192];
|
||||||
|
int length;
|
||||||
|
while ((length = inputStream.read(buffer)) != -1) {
|
||||||
|
digest.update(buffer, 0, length);
|
||||||
|
}
|
||||||
|
byte[] bytes = digest.digest();
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
for (byte item : bytes) {
|
||||||
|
builder.append(String.format("%02x", item));
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
} catch (Exception exception) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.njcn.gather.systemops.database.util;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库运维文件名工具。
|
||||||
|
*/
|
||||||
|
public final class DatabaseFileNameUtil {
|
||||||
|
|
||||||
|
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
|
||||||
|
|
||||||
|
private DatabaseFileNameUtil() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String appendTodayWithTask(String fileName, String taskNo) {
|
||||||
|
String datedName = appendDate(fileName, LocalDate.now());
|
||||||
|
int dotIndex = datedName.lastIndexOf('.');
|
||||||
|
if (dotIndex > 0) {
|
||||||
|
return datedName.substring(0, dotIndex) + "_" + taskNo + datedName.substring(dotIndex);
|
||||||
|
}
|
||||||
|
return datedName + "_" + taskNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String appendDate(String fileName, LocalDate date) {
|
||||||
|
if (fileName == null || date == null) {
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
String dateText = DATE_FORMATTER.format(date);
|
||||||
|
int separatorIndex = Math.max(fileName.lastIndexOf('/'), fileName.lastIndexOf('\\'));
|
||||||
|
int dotIndex = fileName.lastIndexOf('.');
|
||||||
|
if (dotIndex > separatorIndex) {
|
||||||
|
return fileName.substring(0, dotIndex) + "_" + dateText + fileName.substring(dotIndex);
|
||||||
|
}
|
||||||
|
return fileName + "_" + dateText;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.njcn.gather.systemops.database.util;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库运维编号工具。
|
||||||
|
*/
|
||||||
|
public final class DatabaseOpsIdUtil {
|
||||||
|
|
||||||
|
private static final DateTimeFormatter TASK_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
|
||||||
|
|
||||||
|
private DatabaseOpsIdUtil() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String uuid() {
|
||||||
|
return UUID.randomUUID().toString().replace("-", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String taskNo(String prefix) {
|
||||||
|
return prefix + LocalDateTime.now().format(TASK_FORMATTER);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.njcn.gather.systemops.database.util;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库运维文件路径工具。
|
||||||
|
*/
|
||||||
|
public final class DatabasePathUtil {
|
||||||
|
|
||||||
|
private DatabasePathUtil() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Path normalize(String filePath) {
|
||||||
|
if (StrUtil.isBlank(filePath)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Paths.get(filePath).toAbsolutePath().normalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isUnder(Path path, Path root) {
|
||||||
|
if (path == null || root == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Path normalizedPath = path.toAbsolutePath().normalize();
|
||||||
|
Path normalizedRoot = root.toAbsolutePath().normalize();
|
||||||
|
return normalizedPath.startsWith(normalizedRoot);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.enums.response.CommonResponseEnum;
|
||||||
import com.njcn.common.pojo.response.HttpResult;
|
import com.njcn.common.pojo.response.HttpResult;
|
||||||
import com.njcn.common.utils.LogUtil;
|
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.AddLedgerEngineeringSaveParam;
|
||||||
import com.njcn.gather.tool.addledger.pojo.param.AddLedgerEquipmentSaveParam;
|
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.AddLedgerLineSaveParam;
|
||||||
import com.njcn.gather.tool.addledger.pojo.param.AddLedgerProjectSaveParam;
|
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.AddLedgerDetailVO;
|
||||||
import com.njcn.gather.tool.addledger.pojo.vo.AddLedgerTreeNodeVO;
|
import com.njcn.gather.tool.addledger.pojo.vo.AddLedgerTreeNodeVO;
|
||||||
import com.njcn.gather.tool.addledger.service.AddLedgerService;
|
import com.njcn.gather.tool.addledger.service.AddLedgerService;
|
||||||
@@ -93,6 +96,26 @@ public class AddLedgerController extends BaseController {
|
|||||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||||
|
@ApiOperation("查询设备单位")
|
||||||
|
@GetMapping("/equipment/unit")
|
||||||
|
public HttpResult<AddDeviceUnitVO> 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<AddDeviceUnitVO> 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)
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.ADD)
|
||||||
@ApiOperation("新增或保存测点")
|
@ApiOperation("新增或保存测点")
|
||||||
@PostMapping("/line/save")
|
@PostMapping("/line/save")
|
||||||
@@ -103,6 +126,16 @@ public class AddLedgerController extends BaseController {
|
|||||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||||
|
@ApiOperation("查询监测点限值")
|
||||||
|
@GetMapping("/line/overlimit")
|
||||||
|
public HttpResult<AddOverlimitPO> 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)
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||||
@ApiOperation("查询设备可用线路号")
|
@ApiOperation("查询设备可用线路号")
|
||||||
@GetMapping("/line/availableLineNos")
|
@GetMapping("/line/availableLineNos")
|
||||||
|
|||||||
@@ -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<AddDeviceUnit> {
|
||||||
|
}
|
||||||
@@ -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<AddOverlimitPO> {
|
||||||
|
}
|
||||||
@@ -34,7 +34,8 @@
|
|||||||
equipment.name AS equipmentName,
|
equipment.name AS equipmentName,
|
||||||
equipment.mac AS equipmentMac,
|
equipment.mac AS equipmentMac,
|
||||||
line.line_id AS lineId,
|
line.line_id AS lineId,
|
||||||
line.name AS lineName
|
line.name AS lineName,
|
||||||
|
line.line_interval AS lineInterval
|
||||||
FROM cs_line line
|
FROM cs_line line
|
||||||
INNER JOIN cs_equipment_delivery equipment ON equipment.id = line.device_id
|
INNER JOIN cs_equipment_delivery equipment ON equipment.id = line.device_id
|
||||||
INNER JOIN cs_project project ON project.id = equipment.associated_project
|
INNER JOIN cs_project project ON project.id = equipment.associated_project
|
||||||
@@ -58,7 +59,8 @@
|
|||||||
equipment.name AS equipmentName,
|
equipment.name AS equipmentName,
|
||||||
equipment.mac AS equipmentMac,
|
equipment.mac AS equipmentMac,
|
||||||
line.line_id AS lineId,
|
line.line_id AS lineId,
|
||||||
line.name AS lineName
|
line.name AS lineName,
|
||||||
|
line.line_interval AS lineInterval
|
||||||
FROM cs_line line
|
FROM cs_line line
|
||||||
INNER JOIN cs_equipment_delivery equipment ON equipment.id = line.device_id
|
INNER JOIN cs_equipment_delivery equipment ON equipment.id = line.device_id
|
||||||
INNER JOIN cs_project project ON project.id = equipment.associated_project
|
INNER JOIN cs_project project ON project.id = equipment.associated_project
|
||||||
|
|||||||
@@ -33,12 +33,19 @@ public final class AddLedgerConst {
|
|||||||
public static final int LINE_RUN_STATUS_RUNNING = 0;
|
public static final int LINE_RUN_STATUS_RUNNING = 0;
|
||||||
public static final int LINE_INTERVAL_DEFAULT = 1;
|
public static final int LINE_INTERVAL_DEFAULT = 1;
|
||||||
public static final String LOG_LEVEL_WARN = "WARN";
|
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 MIN_LINE_NO = 1;
|
||||||
public static final int MAX_LINE_NO = 20;
|
public static final int MAX_LINE_NO = 20;
|
||||||
|
|
||||||
public static final Set<Integer> CON_TYPES = new LinkedHashSet<Integer>(Arrays.asList(0, 1, 2));
|
public static final Set<Integer> CON_TYPES = new LinkedHashSet<Integer>(Arrays.asList(0, 1, 2));
|
||||||
|
|
||||||
|
public static final Set<Integer> LINE_TYPES = new LinkedHashSet<Integer>(Arrays.asList(
|
||||||
|
LINE_TYPE_MAIN,
|
||||||
|
LINE_TYPE_DISTRIBUTION
|
||||||
|
));
|
||||||
|
|
||||||
public static final Set<BigDecimal> VOL_GRADES = new LinkedHashSet<BigDecimal>(Arrays.asList(
|
public static final Set<BigDecimal> VOL_GRADES = new LinkedHashSet<BigDecimal>(Arrays.asList(
|
||||||
new BigDecimal("0.38"),
|
new BigDecimal("0.38"),
|
||||||
new BigDecimal("10"),
|
new BigDecimal("10"),
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -81,6 +81,9 @@ public class AddLedgerLineSaveParam {
|
|||||||
@DecimalMin(value = "0", inclusive = true, message = "protocol_capacity 不能为负数")
|
@DecimalMin(value = "0", inclusive = true, message = "protocol_capacity 不能为负数")
|
||||||
private BigDecimal protocolCapacity;
|
private BigDecimal protocolCapacity;
|
||||||
|
|
||||||
|
@ApiModelProperty("线路类型:0 主网,1 配网")
|
||||||
|
private Integer lineType;
|
||||||
|
|
||||||
@ApiModelProperty("监测对象类型")
|
@ApiModelProperty("监测对象类型")
|
||||||
private String monitorObj;
|
private String monitorObj;
|
||||||
|
|
||||||
|
|||||||
@@ -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";
|
||||||
|
}
|
||||||
@@ -64,6 +64,12 @@ public class AddLedgerLinePO extends BaseEntity {
|
|||||||
|
|
||||||
private BigDecimal protocolCapacity;
|
private BigDecimal protocolCapacity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 线路类型:0 主网,1 配网。
|
||||||
|
*/
|
||||||
|
@TableField(exist = false)
|
||||||
|
private Integer lineType;
|
||||||
|
|
||||||
private String monitorObj;
|
private String monitorObj;
|
||||||
|
|
||||||
private Integer isGovern;
|
private Integer isGovern;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.njcn.gather.tool.addledger.pojo.vo;
|
package com.njcn.gather.tool.addledger.pojo.vo;
|
||||||
|
|
||||||
|
import com.njcn.gather.tool.addledger.pojo.po.AddOverlimitPO;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
@@ -70,6 +71,10 @@ public class AddLedgerDetailVO {
|
|||||||
|
|
||||||
private BigDecimal protocolCapacity;
|
private BigDecimal protocolCapacity;
|
||||||
|
|
||||||
|
private Integer lineType;
|
||||||
|
|
||||||
|
private AddOverlimitPO overlimit;
|
||||||
|
|
||||||
private String monitorObj;
|
private String monitorObj;
|
||||||
|
|
||||||
private Integer isGovern;
|
private Integer isGovern;
|
||||||
|
|||||||
@@ -29,4 +29,7 @@ public class AddLedgerLinePathVO implements Serializable {
|
|||||||
private String lineId;
|
private String lineId;
|
||||||
|
|
||||||
private String lineName;
|
private String lineName;
|
||||||
|
|
||||||
|
/** 监测点统计间隔,单位分钟。 */
|
||||||
|
private Integer lineInterval;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
package com.njcn.gather.tool.addledger.service;
|
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.AddLedgerEngineeringSaveParam;
|
||||||
import com.njcn.gather.tool.addledger.pojo.param.AddLedgerEquipmentSaveParam;
|
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.AddLedgerLinePathQueryParam;
|
||||||
import com.njcn.gather.tool.addledger.pojo.param.AddLedgerLineSaveParam;
|
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.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.AddLedgerDetailVO;
|
||||||
import com.njcn.gather.tool.addledger.pojo.vo.AddLedgerLinePathVO;
|
import com.njcn.gather.tool.addledger.pojo.vo.AddLedgerLinePathVO;
|
||||||
import com.njcn.gather.tool.addledger.pojo.vo.AddLedgerTreeNodeVO;
|
import com.njcn.gather.tool.addledger.pojo.vo.AddLedgerTreeNodeVO;
|
||||||
@@ -27,8 +30,14 @@ public interface AddLedgerService {
|
|||||||
|
|
||||||
AddLedgerDetailVO saveEquipment(AddLedgerEquipmentSaveParam param);
|
AddLedgerDetailVO saveEquipment(AddLedgerEquipmentSaveParam param);
|
||||||
|
|
||||||
|
AddDeviceUnitVO getDeviceUnit(String devId);
|
||||||
|
|
||||||
|
AddDeviceUnitVO saveDeviceUnit(AddDeviceUnitSaveParam param);
|
||||||
|
|
||||||
AddLedgerDetailVO saveLine(AddLedgerLineSaveParam param);
|
AddLedgerDetailVO saveLine(AddLedgerLineSaveParam param);
|
||||||
|
|
||||||
|
AddOverlimitPO getLineOverlimit(String lineId);
|
||||||
|
|
||||||
List<Integer> availableLineNos(String deviceId, String lineId);
|
List<Integer> availableLineNos(String deviceId, String lineId);
|
||||||
|
|
||||||
Map<String, AddLedgerLinePathVO> listLinePathByLineIds(List<String> lineIds);
|
Map<String, AddLedgerLinePathVO> listLinePathByLineIds(List<String> lineIds);
|
||||||
|
|||||||
@@ -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.AddLedgerLedgerMapper;
|
||||||
import com.njcn.gather.tool.addledger.mapper.AddLedgerLineMapper;
|
import com.njcn.gather.tool.addledger.mapper.AddLedgerLineMapper;
|
||||||
import com.njcn.gather.tool.addledger.mapper.AddLedgerProjectMapper;
|
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.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.AddLedgerEngineeringSaveParam;
|
||||||
import com.njcn.gather.tool.addledger.pojo.param.AddLedgerEquipmentSaveParam;
|
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.AddLedgerLinePathQueryParam;
|
||||||
import com.njcn.gather.tool.addledger.pojo.param.AddLedgerLineSaveParam;
|
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.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.AddLedgerEngineeringPO;
|
||||||
import com.njcn.gather.tool.addledger.pojo.po.AddLedgerEquipmentPO;
|
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.AddLedgerLedgerPO;
|
||||||
import com.njcn.gather.tool.addledger.pojo.po.AddLedgerLinePO;
|
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.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.AddLedgerDetailVO;
|
||||||
import com.njcn.gather.tool.addledger.pojo.vo.AddLedgerLinePathVO;
|
import com.njcn.gather.tool.addledger.pojo.vo.AddLedgerLinePathVO;
|
||||||
import com.njcn.gather.tool.addledger.pojo.vo.AddLedgerTreeNodeVO;
|
import com.njcn.gather.tool.addledger.pojo.vo.AddLedgerTreeNodeVO;
|
||||||
import com.njcn.gather.tool.addledger.service.AddLedgerService;
|
import com.njcn.gather.tool.addledger.service.AddLedgerService;
|
||||||
import com.njcn.gather.tool.addledger.util.AddLedgerIdUtil;
|
import com.njcn.gather.tool.addledger.util.AddLedgerIdUtil;
|
||||||
import com.njcn.gather.tool.addledger.util.AddLedgerLineNoUtil;
|
import com.njcn.gather.tool.addledger.util.AddLedgerLineNoUtil;
|
||||||
|
import com.njcn.gather.tool.addledger.util.COverlimitUtil;
|
||||||
import com.njcn.web.utils.RequestUtil;
|
import com.njcn.web.utils.RequestUtil;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -51,6 +58,8 @@ public class AddLedgerServiceImpl implements AddLedgerService {
|
|||||||
private final AddLedgerEquipmentMapper equipmentMapper;
|
private final AddLedgerEquipmentMapper equipmentMapper;
|
||||||
private final AddLedgerLineMapper lineMapper;
|
private final AddLedgerLineMapper lineMapper;
|
||||||
private final AddLedgerLedgerMapper ledgerMapper;
|
private final AddLedgerLedgerMapper ledgerMapper;
|
||||||
|
private final AddDeviceUnitMapper deviceUnitMapper;
|
||||||
|
private final AddOverlimitMapper overlimitMapper;
|
||||||
private final AddLedgerTreeBuilder treeBuilder;
|
private final AddLedgerTreeBuilder treeBuilder;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -167,6 +176,7 @@ public class AddLedgerServiceImpl implements AddLedgerService {
|
|||||||
equipment.setUsageStatus(AddLedgerConst.ENABLE);
|
equipment.setUsageStatus(AddLedgerConst.ENABLE);
|
||||||
equipment.setSort(0);
|
equipment.setSort(0);
|
||||||
equipmentMapper.insert(equipment);
|
equipmentMapper.insert(equipment);
|
||||||
|
saveDefaultDeviceUnit(id);
|
||||||
saveLedger(id, projectLedger.getId(), buildChildPids(projectLedger), equipment.getName(), AddLedgerConst.LEVEL_EQUIPMENT);
|
saveLedger(id, projectLedger.getId(), buildChildPids(projectLedger), equipment.getName(), AddLedgerConst.LEVEL_EQUIPMENT);
|
||||||
} else {
|
} else {
|
||||||
equipmentMapper.updateById(equipment);
|
equipmentMapper.updateById(equipment);
|
||||||
@@ -175,6 +185,39 @@ public class AddLedgerServiceImpl implements AddLedgerService {
|
|||||||
return detail(id, AddLedgerConst.LEVEL_EQUIPMENT);
|
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
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public AddLedgerDetailVO saveLine(AddLedgerLineSaveParam param) {
|
public AddLedgerDetailVO saveLine(AddLedgerLineSaveParam param) {
|
||||||
@@ -206,6 +249,7 @@ public class AddLedgerServiceImpl implements AddLedgerService {
|
|||||||
line.setDevCapacity(param.getDevCapacity());
|
line.setDevCapacity(param.getDevCapacity());
|
||||||
line.setBasicCapacity(param.getBasicCapacity());
|
line.setBasicCapacity(param.getBasicCapacity());
|
||||||
line.setProtocolCapacity(param.getProtocolCapacity());
|
line.setProtocolCapacity(param.getProtocolCapacity());
|
||||||
|
line.setLineType(param.getLineType() == null ? AddLedgerConst.LINE_TYPE_MAIN : param.getLineType());
|
||||||
line.setMonitorObj(trimToNull(param.getMonitorObj()));
|
line.setMonitorObj(trimToNull(param.getMonitorObj()));
|
||||||
line.setIsGovern(param.getIsGovern() == null ? AddLedgerConst.DISABLE : param.getIsGovern());
|
line.setIsGovern(param.getIsGovern() == null ? AddLedgerConst.DISABLE : param.getIsGovern());
|
||||||
line.setMonitorUser(trimToNull(param.getMonitorUser()));
|
line.setMonitorUser(trimToNull(param.getMonitorUser()));
|
||||||
@@ -221,6 +265,7 @@ public class AddLedgerServiceImpl implements AddLedgerService {
|
|||||||
lineMapper.updateById(line);
|
lineMapper.updateById(line);
|
||||||
updateLedgerName(id, AddLedgerConst.LEVEL_LINE, line.getName());
|
updateLedgerName(id, AddLedgerConst.LEVEL_LINE, line.getName());
|
||||||
}
|
}
|
||||||
|
saveOrUpdateOverlimit(line);
|
||||||
return detail(id, AddLedgerConst.LEVEL_LINE);
|
return detail(id, AddLedgerConst.LEVEL_LINE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,6 +276,12 @@ public class AddLedgerServiceImpl implements AddLedgerService {
|
|||||||
return AddLedgerLineNoUtil.resolveAvailableLineNos(usedLineNos, null);
|
return AddLedgerLineNoUtil.resolveAvailableLineNos(usedLineNos, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AddOverlimitPO getLineOverlimit(String lineId) {
|
||||||
|
AddLedgerLinePO line = requireLine(lineId);
|
||||||
|
return overlimitMapper.selectById(line.getLineId());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, AddLedgerLinePathVO> listLinePathByLineIds(List<String> lineIds) {
|
public Map<String, AddLedgerLinePathVO> listLinePathByLineIds(List<String> lineIds) {
|
||||||
List<String> normalizedLineIds = normalizeIds(lineIds);
|
List<String> normalizedLineIds = normalizeIds(lineIds);
|
||||||
@@ -296,6 +347,7 @@ public class AddLedgerServiceImpl implements AddLedgerService {
|
|||||||
String updateBy = currentUserId();
|
String updateBy = currentUserId();
|
||||||
if (!lineIds.isEmpty()) {
|
if (!lineIds.isEmpty()) {
|
||||||
lineMapper.softDeleteByIds(lineIds, updateBy);
|
lineMapper.softDeleteByIds(lineIds, updateBy);
|
||||||
|
overlimitMapper.deleteBatchIds(lineIds);
|
||||||
}
|
}
|
||||||
if (!equipmentIds.isEmpty()) {
|
if (!equipmentIds.isEmpty()) {
|
||||||
equipmentMapper.softDeleteByIds(equipmentIds, updateBy);
|
equipmentMapper.softDeleteByIds(equipmentIds, updateBy);
|
||||||
@@ -344,6 +396,9 @@ public class AddLedgerServiceImpl implements AddLedgerService {
|
|||||||
requireNonNegativeIfPresent(param.getDevCapacity(), "dev_capacity 不能为负数");
|
requireNonNegativeIfPresent(param.getDevCapacity(), "dev_capacity 不能为负数");
|
||||||
requireNonNegativeIfPresent(param.getBasicCapacity(), "basic_capacity 不能为负数");
|
requireNonNegativeIfPresent(param.getBasicCapacity(), "basic_capacity 不能为负数");
|
||||||
requireNonNegativeIfPresent(param.getProtocolCapacity(), "protocol_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) {
|
private void assertLineNoUnique(String deviceId, Integer lineNo, String lineId) {
|
||||||
@@ -437,6 +492,16 @@ public class AddLedgerServiceImpl implements AddLedgerService {
|
|||||||
ledgerMapper.insert(ledger);
|
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) {
|
private void updateLedgerName(String id, Integer level, String name) {
|
||||||
AddLedgerLedgerPO ledger = requireLedger(id, level, levelName(level) + "节点");
|
AddLedgerLedgerPO ledger = requireLedger(id, level, levelName(level) + "节点");
|
||||||
ledger.setName(name);
|
ledger.setName(name);
|
||||||
@@ -493,6 +558,8 @@ public class AddLedgerServiceImpl implements AddLedgerService {
|
|||||||
detail.setDevCapacity(line.getDevCapacity());
|
detail.setDevCapacity(line.getDevCapacity());
|
||||||
detail.setBasicCapacity(line.getBasicCapacity());
|
detail.setBasicCapacity(line.getBasicCapacity());
|
||||||
detail.setProtocolCapacity(line.getProtocolCapacity());
|
detail.setProtocolCapacity(line.getProtocolCapacity());
|
||||||
|
detail.setLineType(AddLedgerConst.LINE_TYPE_MAIN);
|
||||||
|
detail.setOverlimit(overlimitMapper.selectById(line.getLineId()));
|
||||||
detail.setMonitorObj(line.getMonitorObj());
|
detail.setMonitorObj(line.getMonitorObj());
|
||||||
detail.setIsGovern(line.getIsGovern());
|
detail.setIsGovern(line.getIsGovern());
|
||||||
detail.setMonitorUser(line.getMonitorUser());
|
detail.setMonitorUser(line.getMonitorUser());
|
||||||
@@ -500,6 +567,83 @@ public class AddLedgerServiceImpl implements AddLedgerService {
|
|||||||
return detail;
|
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) {
|
private AddLedgerDetailVO buildBaseDetail(AddLedgerLedgerPO ledger) {
|
||||||
AddLedgerDetailVO detail = new AddLedgerDetailVO();
|
AddLedgerDetailVO detail = new AddLedgerDetailVO();
|
||||||
detail.setId(ledger.getId());
|
detail.setId(ledger.getId());
|
||||||
@@ -551,6 +695,11 @@ public class AddLedgerServiceImpl implements AddLedgerService {
|
|||||||
return trimmed.isEmpty() ? null : trimmed;
|
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) {
|
private boolean isBlank(String value) {
|
||||||
return trimToNull(value) == null;
|
return trimToNull(value) == null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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){
|
||||||
|
voltageDev = 7.0f;
|
||||||
|
uvoltageDev=-7.0f;
|
||||||
|
}else if(voltageLevel>=KV_20&&voltageLevel<KV_35){
|
||||||
|
voltageDev = 7.0f;
|
||||||
|
uvoltageDev=-7.0f;
|
||||||
|
}else if(voltageLevel>=KV_35&&voltageLevel<KV_66){
|
||||||
|
voltageDev = 10.0f;
|
||||||
|
uvoltageDev=-10.0f;
|
||||||
|
}else if(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<KV_10) {
|
||||||
|
x = 1;
|
||||||
|
} else if (voltageLevel<KV_35) {
|
||||||
|
x = 2;
|
||||||
|
} else if (voltageLevel<KV_66) {
|
||||||
|
x = 3;
|
||||||
|
} else if (voltageLevel<KV_110) {
|
||||||
|
x = 4;
|
||||||
|
} else {
|
||||||
|
x = 5;
|
||||||
|
}
|
||||||
|
y = iCount - 2;
|
||||||
|
return ARR[x][y];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 相位叠加系数的取值
|
||||||
|
*/
|
||||||
|
public static Double getHarmonicLimit(Integer times, double iTag, double supply, double user) {
|
||||||
|
if (supply == 0) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
double coefficient = 2.0;
|
||||||
|
if (times == 3) {
|
||||||
|
coefficient = 1.1;
|
||||||
|
} else if (times == 5) {
|
||||||
|
coefficient = 1.2;
|
||||||
|
} else if (times == 7) {
|
||||||
|
coefficient = 1.4;
|
||||||
|
} else if (times == 11) {
|
||||||
|
coefficient = 1.8;
|
||||||
|
} else if (times == 13) {
|
||||||
|
coefficient = 1.9;
|
||||||
|
}
|
||||||
|
BigDecimal bd = new BigDecimal(iTag * Math.pow((user / supply), (1 / coefficient)));
|
||||||
|
bd = bd.setScale(6, RoundingMode.HALF_UP);
|
||||||
|
return Double.parseDouble(bd.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据电压等级获取基准短路容量
|
||||||
|
*/
|
||||||
|
public static float getDlCapByVoltageLevel(Float voltageLevel){
|
||||||
|
float capValue;
|
||||||
|
if(voltageLevel< KV_0_6){
|
||||||
|
capValue = 10;
|
||||||
|
}else if(voltageLevel<KV_20){
|
||||||
|
capValue = 100;
|
||||||
|
}else if(voltageLevel<KV_35){
|
||||||
|
capValue = 200;
|
||||||
|
}else if(voltageLevel<KV_66){
|
||||||
|
capValue = 250;
|
||||||
|
}else if(voltageLevel<KV_110){
|
||||||
|
capValue = 500;
|
||||||
|
}else if(voltageLevel<KV_220){
|
||||||
|
capValue = 750;
|
||||||
|
}else if(voltageLevel<KV_330){
|
||||||
|
capValue = 2000;
|
||||||
|
}else if(voltageLevel<KV_500){
|
||||||
|
capValue = 3000;
|
||||||
|
}else if(voltageLevel<KV_750){
|
||||||
|
capValue = 4500;
|
||||||
|
}else if(voltageLevel<KV_1000){
|
||||||
|
capValue = 7000;
|
||||||
|
}else {
|
||||||
|
capValue = 9000;
|
||||||
|
}
|
||||||
|
return capValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*---------------------------------谐波电流限值end-----------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 间谐波电压含有率
|
||||||
|
*/
|
||||||
|
|
||||||
|
public static void interharmonicCurrent(AddOverlimitPO overlimit,Float voltageLevel){
|
||||||
|
float aValue,bValue;
|
||||||
|
if(voltageLevel <= KV_1){
|
||||||
|
aValue = 0.2f;bValue = 0.5f;
|
||||||
|
}else {
|
||||||
|
aValue = 0.16f;bValue = 0.4f;
|
||||||
|
}
|
||||||
|
overlimit.setInuharm1(aValue);
|
||||||
|
overlimit.setInuharm2(aValue);
|
||||||
|
|
||||||
|
overlimit.setInuharm3(bValue);
|
||||||
|
overlimit.setInuharm4(bValue);
|
||||||
|
overlimit.setInuharm5(bValue);
|
||||||
|
overlimit.setInuharm6(bValue);
|
||||||
|
overlimit.setInuharm7(bValue);
|
||||||
|
overlimit.setInuharm8(bValue);
|
||||||
|
overlimit.setInuharm9(bValue);
|
||||||
|
overlimit.setInuharm10(bValue);
|
||||||
|
overlimit.setInuharm11(bValue);
|
||||||
|
overlimit.setInuharm12(bValue);
|
||||||
|
overlimit.setInuharm13(bValue);
|
||||||
|
overlimit.setInuharm14(bValue);
|
||||||
|
overlimit.setInuharm15(bValue);
|
||||||
|
overlimit.setInuharm16(bValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 负序电流限值
|
||||||
|
*/
|
||||||
|
public static void negativeSequenceCurrent(AddOverlimitPO overlimit,Float voltageLevel,Float shortCapacity){
|
||||||
|
double v = (0.013*shortCapacity*1000)/(getUl(voltageLevel)*Math.sqrt(3));
|
||||||
|
overlimit.setINeg((float) v);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取额定线电压
|
||||||
|
*/
|
||||||
|
private static float getUl(Float voltageLevel){
|
||||||
|
float value;
|
||||||
|
if(voltageLevel<KV_6){
|
||||||
|
value = 0.4f;
|
||||||
|
}else if(voltageLevel<KV_10){
|
||||||
|
value = 6.3f;
|
||||||
|
}else if(voltageLevel<KV_20){
|
||||||
|
value = 10.5f;
|
||||||
|
}else if(voltageLevel<KV_35){
|
||||||
|
value = 21.0f;
|
||||||
|
}else if(voltageLevel<KV_66){
|
||||||
|
value = 36.5f;
|
||||||
|
}else if(voltageLevel<KV_110){
|
||||||
|
value = 69.0f;
|
||||||
|
}else if(voltageLevel<KV_220){
|
||||||
|
value = 115.5f;
|
||||||
|
}else if(voltageLevel<KV_330){
|
||||||
|
value = 230.0f;
|
||||||
|
}else {
|
||||||
|
value = 345.0f;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -6,15 +6,28 @@ import com.njcn.gather.tool.addledger.mapper.AddLedgerEquipmentMapper;
|
|||||||
import com.njcn.gather.tool.addledger.mapper.AddLedgerLedgerMapper;
|
import com.njcn.gather.tool.addledger.mapper.AddLedgerLedgerMapper;
|
||||||
import com.njcn.gather.tool.addledger.mapper.AddLedgerLineMapper;
|
import com.njcn.gather.tool.addledger.mapper.AddLedgerLineMapper;
|
||||||
import com.njcn.gather.tool.addledger.mapper.AddLedgerProjectMapper;
|
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.constant.AddLedgerConst;
|
||||||
|
import com.njcn.gather.tool.addledger.pojo.param.AddDeviceUnitSaveParam;
|
||||||
|
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.AddLedgerLineSaveParam;
|
||||||
|
import com.njcn.gather.tool.addledger.pojo.po.AddDeviceUnit;
|
||||||
|
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.AddOverlimitPO;
|
||||||
|
import com.njcn.gather.tool.addledger.pojo.vo.AddDeviceUnitVO;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
class AddLedgerServiceImplTest {
|
class AddLedgerServiceImplTest {
|
||||||
@@ -24,6 +37,8 @@ class AddLedgerServiceImplTest {
|
|||||||
private final AddLedgerEquipmentMapper equipmentMapper = mock(AddLedgerEquipmentMapper.class);
|
private final AddLedgerEquipmentMapper equipmentMapper = mock(AddLedgerEquipmentMapper.class);
|
||||||
private final AddLedgerLineMapper lineMapper = mock(AddLedgerLineMapper.class);
|
private final AddLedgerLineMapper lineMapper = mock(AddLedgerLineMapper.class);
|
||||||
private final AddLedgerLedgerMapper ledgerMapper = mock(AddLedgerLedgerMapper.class);
|
private final AddLedgerLedgerMapper ledgerMapper = mock(AddLedgerLedgerMapper.class);
|
||||||
|
private final AddDeviceUnitMapper deviceUnitMapper = mock(AddDeviceUnitMapper.class);
|
||||||
|
private final AddOverlimitMapper overlimitMapper = mock(AddOverlimitMapper.class);
|
||||||
private final AddLedgerTreeBuilder treeBuilder = mock(AddLedgerTreeBuilder.class);
|
private final AddLedgerTreeBuilder treeBuilder = mock(AddLedgerTreeBuilder.class);
|
||||||
|
|
||||||
private final AddLedgerServiceImpl service = new AddLedgerServiceImpl(
|
private final AddLedgerServiceImpl service = new AddLedgerServiceImpl(
|
||||||
@@ -32,6 +47,8 @@ class AddLedgerServiceImplTest {
|
|||||||
equipmentMapper,
|
equipmentMapper,
|
||||||
lineMapper,
|
lineMapper,
|
||||||
ledgerMapper,
|
ledgerMapper,
|
||||||
|
deviceUnitMapper,
|
||||||
|
overlimitMapper,
|
||||||
treeBuilder);
|
treeBuilder);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -50,6 +67,65 @@ class AddLedgerServiceImplTest {
|
|||||||
+ "。如果是新增监测点,请不要传 lineId;如果是编辑监测点,请传已存在的测点 ID", exception.getMessage());
|
+ "。如果是新增监测点,请不要传 lineId;如果是编辑监测点,请传已存在的测点 ID", exception.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void saveEquipmentShouldCreateDefaultDeviceUnitWhenCreatingEquipment() {
|
||||||
|
AddLedgerEquipmentSaveParam param = buildValidEquipmentParam();
|
||||||
|
AddLedgerLedgerPO projectLedger = buildLedger("project-001", "engineering-001",
|
||||||
|
AddLedgerConst.ROOT_PARENT_ID + ",engineering-001", AddLedgerConst.LEVEL_PROJECT);
|
||||||
|
AddLedgerLedgerPO engineeringLedger = buildLedger("engineering-001", AddLedgerConst.ROOT_PARENT_ID,
|
||||||
|
AddLedgerConst.ROOT_PARENT_ID, AddLedgerConst.LEVEL_ENGINEERING);
|
||||||
|
|
||||||
|
when(ledgerMapper.selectActiveNode(eq("project-001"), eq(AddLedgerConst.LEVEL_PROJECT))).thenReturn(projectLedger);
|
||||||
|
when(ledgerMapper.selectActiveNode(eq("engineering-001"), eq(AddLedgerConst.LEVEL_ENGINEERING))).thenReturn(engineeringLedger);
|
||||||
|
when(ledgerMapper.selectActiveNode(any(String.class), eq(AddLedgerConst.LEVEL_EQUIPMENT)))
|
||||||
|
.thenAnswer(invocation -> 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<AddDeviceUnit> 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() {
|
private AddLedgerLineSaveParam buildValidLineParam() {
|
||||||
AddLedgerLineSaveParam param = new AddLedgerLineSaveParam();
|
AddLedgerLineSaveParam param = new AddLedgerLineSaveParam();
|
||||||
param.setDeviceId("device-001");
|
param.setDeviceId("device-001");
|
||||||
@@ -63,4 +139,33 @@ class AddLedgerServiceImplTest {
|
|||||||
param.setPt2Ratio(BigDecimal.ONE);
|
param.setPt2Ratio(BigDecimal.ONE);
|
||||||
return param;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user