Merge pull request #11 from lijiahangmax/dev

Dev
This commit is contained in:
lijiahangmax
2024-05-15 11:31:26 +08:00
committed by GitHub
75 changed files with 2078 additions and 558 deletions

View File

@@ -26,7 +26,7 @@
</a>
</p>
当前版本: **1.0.7**
当前版本: **1.0.8**
**github:** https://github.com/lijiahangmax/orion-ops-pro
**gitee:** https://gitee.com/lijiahangmax/orion-ops-pro

View File

@@ -1,7 +1,7 @@
version: '3.3'
services:
orion-ops-pro:
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-ops-pro:1.0.7
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-ops-pro:1.0.8
ports:
- 1081:80
environment:
@@ -19,7 +19,7 @@ services:
- orion-ops-pro-db
- orion-ops-pro-redis
orion-ops-pro-db:
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-ops-pro-mysql:1.0.7
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-ops-pro-mysql:1.0.8
privileged: true
ports:
- 3307:3306

View File

@@ -1,5 +1,5 @@
#/bin/bash
version=1.0.7
version=1.0.8
cp -r ../../sql ./sql
docker build -t orion-ops-pro-mysql:${version} .
rm -rf ./sql

View File

@@ -1,5 +1,5 @@
#/bin/bash
version=1.0.7
version=1.0.8
mv ../../orion-ops-launch/target/orion-ops-launch.jar ./orion-ops-launch.jar
mv ../../orion-ops-ui/dist ./dist
docker build -t orion-ops-pro:${version} .

View File

@@ -26,7 +26,7 @@
</a>
</p>
当前版本: **1.0.7**
当前版本: **1.0.8**
**github:** https://github.com/lijiahangmax/orion-ops-pro
**gitee:** https://gitee.com/lijiahangmax/orion-ops-pro

View File

@@ -1,4 +1,4 @@
# orion-ops-pro <small>1.0.7</small>
# orion-ops-pro <small>1.0.8</small>
> 一款开箱即用的运维平台。

View File

@@ -7,6 +7,16 @@
* 执行完成菜单 sql 后请刷新缓存 `系统设置` > `系统菜单` > `刷新缓存`
* 执行完成字典 sql 后请刷新缓存 `系统设置` > `数据字典项` > `刷新缓存`
### v1.0.8
`2024-05-15` `release`
* 🌈 新增 站内信模块
* 🔨 优化 执行命令日志跳转逻辑
* 🔨 修改 `exitStatus` 改为 `exitCode`
[如何升级](/update/v1.0.8.md)
### v1.0.7
`2024-05-13` `release`

View File

@@ -1,7 +1,5 @@
## 功能排期
* 批量上传
* 站内消息
* 终端背景图片
* 资产授权 UI 改版
* RDP 远程桌面

View File

@@ -3,9 +3,53 @@
> sql 脚本 - DDL
```sql
-- 修改字段名称
ALTER TABLE `exec_host_log`
CHANGE COLUMN `exit_status` `exit_code` int(0) NULL DEFAULT NULL COMMENT '退出码' AFTER `parameter`;
-- 系统消息
DROP TABLE IF EXISTS `system_message`;
CREATE TABLE `system_message`
(
`id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id',
`classify` char(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '消息分类',
`type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '消息类型',
`status` tinyint(0) NULL DEFAULT NULL COMMENT '消息状态',
`rel_key` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '消息关联',
`title` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标题',
`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容',
`receiver_id` bigint(0) NULL DEFAULT NULL COMMENT '接收人id',
`receiver_username` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '接收人用户名',
`create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '修改时间',
`deleted` tinyint(1) NULL DEFAULT 0 COMMENT '是否删除 0未删除 1已删除',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_receiver_classify` (`receiver_id`, `classify`) USING BTREE
) ENGINE = InnoDB
AUTO_INCREMENT = 1
CHARACTER SET = utf8mb4
COLLATE = utf8mb4_general_ci COMMENT = '系统消息'
ROW_FORMAT = Dynamic;
```
> sql 脚本 - DML
```sql
-- 菜单
DELETE FROM system_menu WHERE id IN (161, 175, 197, 198) OR id > 202;
INSERT INTO `system_menu` VALUES (161, 176, '执行模板', NULL, 2, 50, 1, 1, 1, 0, 'IconBookmark', NULL, 'execTemplate', '2024-03-07 18:32:41', '2024-05-14 15:58:51', '1', '1', 0);
INSERT INTO `system_menu` VALUES (197, 176, '批量上传', NULL, 2, 30, 1, 1, 1, 0, 'IconUpload', NULL, 'batchUpload', '2024-05-08 22:12:23', '2024-05-14 15:58:44', '1', '1', 0);
INSERT INTO `system_menu` VALUES (198, 176, '上传任务', NULL, 2, 40, 1, 1, 1, 0, 'IconCloud', NULL, 'uploadTask', '2024-05-08 22:16:05', '2024-05-14 15:58:46', '1', '1', 0);
-- 字典项
DELETE FROM dict_key WHERE id >= 43;
INSERT INTO `dict_key` VALUES (43, 'messageType', 'STRING', '[{\"name\": \"tagLabel\", \"type\": \"STRING\"}, {\"name\": \"tagVisible\", \"type\": \"STRING\"}, {\"name\": \"tagColor\", \"type\": \"STRING\"}, {\"name\": \"redirectComponent\", \"type\": \"STRING\"}]', '消息类型', '2024-05-13 12:07:56', '2024-05-14 14:48:28', '1', '1', 0);
INSERT INTO `dict_key` VALUES (44, 'messageClassify', 'STRING', '[]', '消息分类', '2024-05-13 15:06:27', '2024-05-13 15:06:27', '1', '1', 0);
-- 字典值
DELETE FROM dict_value WHERE id >= 295;
INSERT INTO `dict_value` VALUES (295, 43, 'messageType', 'EXEC_FAILED', '执行失败', '{\"tagColor\": \"red\", \"tagLabel\": \"部分失败\", \"tagVisible\": \"true\", \"redirectComponent\": \"execCommand\"}', 10, '2024-05-13 12:07:56', '2024-05-14 15:19:19', '1', '1', 0);
INSERT INTO `dict_value` VALUES (296, 43, 'messageType', 'UPLOAD_FAILED', '上传失败', '{\"tagColor\": \"red\", \"tagLabel\": \"部分失败\", \"tagVisible\": \"true\", \"redirectComponent\": \"batchUpload\"}', 20, '2024-05-13 12:07:56', '2024-05-14 15:11:21', '1', '1', 0);
INSERT INTO `dict_value` VALUES (297, 44, 'messageClassify', 'NOTICE', '通知', '{}', 10, '2024-05-13 15:06:27', '2024-05-13 15:06:27', '1', '1', 0);
INSERT INTO `dict_value` VALUES (298, 44, 'messageClassify', 'TODO', '待办', '{}', 20, '2024-05-13 15:06:27', '2024-05-13 15:06:27', '1', '1', 0);
```

11
docs/update/v2.0.0.md Normal file
View File

@@ -0,0 +1,11 @@
## v2.0.0
> sql 脚本 - DDL
```sql
```
> sql 脚本 - DML
```sql
```

View File

@@ -14,7 +14,7 @@
<url>https://github.com/lijiahangmax/orion-ops-pro</url>
<properties>
<revision>1.0.7</revision>
<revision>1.0.8</revision>
<spring.boot.version>2.7.17</spring.boot.version>
<spring.boot.admin.version>2.7.15</spring.boot.admin.version>
<flatten.maven.plugin.version>1.5.0</flatten.maven.plugin.version>

View File

@@ -14,7 +14,7 @@ public interface AppConst extends OrionConst {
/**
* 同 ${orion.version} 迭代时候需要手动更改
*/
String VERSION = "1.0.7";
String VERSION = "1.0.8";
String ORION_OPS_PRO = "orion-ops-pro";

View File

@@ -55,6 +55,10 @@ public interface FieldConst {
String COUNT = "count";
String DATE = "date";
String TIME = "time";
String LOCATION = "location";
String USER_AGENT = "userAgent";

View File

@@ -26,7 +26,7 @@ public class CodeGenerators {
// 作者
String author = Const.ORION_AUTHOR;
// 模块
String module = "asset";
String module = "infra";
// 生成的表
Table[] tables = {
// Template.create("dict_key", "字典配置项", "dict")
@@ -49,24 +49,22 @@ public class CodeGenerators {
// .disableUnitTest()
// .vue("exec", "exec-template-host")
// .build(),
Template.create("upload_task", "上传任务", "upload")
Template.create("system_message", "系统消息", "message")
.disableUnitTest()
.vue("exec", "upload-task")
.enableRowSelection()
.dict("uploadTaskStatus", "status")
.comment("上传任务状态")
.fields("WAITING", "UPLOADING", "FINISHED", "FAILED", "CANCELED")
.labels("等待中", "上传中", "已完成", "已失败", "已取消")
.enableProviderApi()
.vue("system", "message")
.dict("messageClassify", "classify", "messageClassify")
.comment("消息分类")
.fields("NOTICE", "TODO")
.labels("通知", "待办")
.valueUseFields()
.build(),
Template.create("upload_task_file", "上传任务文件", "upload")
.disableUnitTest()
.vue("exec", "upload-task-file")
.enableRowSelection()
.dict("uploadTaskFileStatus", "status")
.comment("上传任务文件状态")
.fields("WAITING", "UPLOADING", "FINISHED", "FAILED", "CANCELED")
.labels("等待中", "上传中", "已完成", "已失败", "已取消")
.dict("messageType", "type", "messageType")
.comment("消息类型")
.fields("EXEC_FAILED", "UPLOAD_FAILED")
.labels("执行失败", "上传失败")
.extra("tagLabel", "执行失败", "上传失败")
.extra("tagVisible", true, true)
.extra("tagColor", "red", "red")
.valueUseFields()
.build(),
};

View File

@@ -14,6 +14,17 @@ import java.util.LinkedHashMap;
*/
public class DictTemplate extends Template {
// // $comment
// export const $field = {
// // labels[0]
// fields[0]: 'values[0]',
// // labels[1]
// fields[0]: 'values[1]',
// };
//
// // $comment 字典项
// export const $keyField = '$keyName';
private final DictMeta dictMeta;
public DictTemplate(Table table, String keyName, String variable) {

View File

@@ -0,0 +1,53 @@
package com.orion.ops.module.asset.define.message;
import com.orion.ops.module.infra.define.SystemMessageDefine;
import com.orion.ops.module.infra.enums.MessageClassifyEnum;
import lombok.Getter;
/**
* 命令执行 系统消息定义
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/5/14 17:23
*/
@Getter
public enum ExecMessageDefine implements SystemMessageDefine {
/**
* 命令执行部分失败
*/
EXEC_FAILED(MessageClassifyEnum.NOTICE,
"命令执行失败",
"您在 <sb>${time}</sb> 执行的命令部分失败, 或者返回了非零的 exitCode。点击查看详情 <sb>#${id}</sb> >>>"),
;
ExecMessageDefine(MessageClassifyEnum classify, String title, String content) {
this.classify = classify;
this.type = this.name();
this.title = title;
this.content = content;
}
/**
* 消息分类
*/
private final MessageClassifyEnum classify;
/**
* 消息类型
*/
private final String type;
/**
* 标题
*/
private final String title;
/**
* 内容
*/
private final String content;
}

View File

@@ -0,0 +1,53 @@
package com.orion.ops.module.asset.define.message;
import com.orion.ops.module.infra.define.SystemMessageDefine;
import com.orion.ops.module.infra.enums.MessageClassifyEnum;
import lombok.Getter;
/**
* 上传任务 系统消息定义
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/5/14 17:23
*/
@Getter
public enum UploadMessageDefine implements SystemMessageDefine {
/**
* 上传任务部分失败
*/
UPLOAD_FAILED(MessageClassifyEnum.NOTICE,
"批量上传失败",
"您在 <sb>${time}</sb> 提交的上传任务中, 有部分主机文件上传失败。点击查看详情 <sb>#${id}</sb> >>>"),
;
UploadMessageDefine(MessageClassifyEnum classify, String title, String content) {
this.classify = classify;
this.type = this.name();
this.title = title;
this.content = content;
}
/**
* 消息分类
*/
private final MessageClassifyEnum classify;
/**
* 消息类型
*/
private final String type;
/**
* 标题
*/
private final String title;
/**
* 内容
*/
private final String content;
}

View File

@@ -61,8 +61,8 @@ public class ExecHostLogDO extends BaseDO {
private String parameter;
@Schema(description = "退出码")
@TableField("exit_status")
private Integer exitStatus;
@TableField("exit_code")
private Integer exitCode;
@Schema(description = "日志路径")
@TableField("log_path")

View File

@@ -50,7 +50,7 @@ public class ExecHostLogVO implements Serializable {
private String parameter;
@Schema(description = "退出码")
private Integer exitStatus;
private Integer exitCode;
@Schema(description = "错误信息")
private String errorMessage;

View File

@@ -22,7 +22,7 @@ import java.util.List;
@Schema(name = "ExecCommandDTO", description = "批量执行启动对象")
public class ExecCommandDTO {
@Schema(description = "hostId")
@Schema(description = "logId")
private Long logId;
@Schema(description = "用户id")

View File

@@ -67,6 +67,9 @@ public abstract class BaseExecCommandHandler implements IExecCommandHandler {
private CommandExecutor executor;
@Getter
private Integer exitCode;
private volatile boolean closed;
private volatile boolean interrupted;
@@ -227,7 +230,8 @@ public abstract class BaseExecCommandHandler implements IExecCommandHandler {
} else if (ExecHostStatusEnum.COMPLETED.equals(status)) {
// 完成
updateRecord.setFinishTime(new Date());
updateRecord.setExitStatus(executor.getExitCode());
updateRecord.setExitCode(executor.getExitCode());
this.exitCode = executor.getExitCode();
} else if (ExecHostStatusEnum.FAILED.equals(status)) {
// 失败
updateRecord.setFinishTime(new Date());

View File

@@ -149,17 +149,17 @@ public class ExecCommandAnsiHandler extends BaseExecCommandHandler {
.append(Dates.current())
.newLine();
} else {
long ms = this.updateRecord.getFinishTime().getTime() - this.updateRecord.getStartTime().getTime();
Integer exitStatus = this.updateRecord.getExitStatus();
long ms = updateRecord.getFinishTime().getTime() - updateRecord.getStartTime().getTime();
Integer exitCode = updateRecord.getExitCode();
// 执行完成
appender.append(AnsiForeground.BRIGHT_GREEN, "< 命令执行完成 ")
.append(Dates.current())
.newLine()
.append(AnsiForeground.BRIGHT_BLUE, "exit: ");
if (ExitCode.isSuccess(exitStatus)) {
appender.append(AnsiForeground.BRIGHT_GREEN, exitStatus);
if (ExitCode.isSuccess(exitCode)) {
appender.append(AnsiForeground.BRIGHT_GREEN, exitCode);
} else {
appender.append(AnsiForeground.BRIGHT_RED, exitStatus);
appender.append(AnsiForeground.BRIGHT_RED, exitCode);
}
appender.newLine()
.append(AnsiForeground.BRIGHT_BLUE, "used: ")

View File

@@ -7,20 +7,29 @@ import com.orion.lang.utils.Booleans;
import com.orion.lang.utils.Threads;
import com.orion.lang.utils.collect.Lists;
import com.orion.lang.utils.io.Streams;
import com.orion.lang.utils.time.Dates;
import com.orion.net.host.ssh.ExitCode;
import com.orion.ops.framework.common.constant.ExtraFieldConst;
import com.orion.ops.module.asset.dao.ExecLogDAO;
import com.orion.ops.module.asset.define.AssetThreadPools;
import com.orion.ops.module.asset.define.config.AppExecLogConfig;
import com.orion.ops.module.asset.define.message.ExecMessageDefine;
import com.orion.ops.module.asset.entity.domain.ExecLogDO;
import com.orion.ops.module.asset.enums.ExecHostStatusEnum;
import com.orion.ops.module.asset.enums.ExecStatusEnum;
import com.orion.ops.module.asset.handler.host.exec.command.dto.ExecCommandDTO;
import com.orion.ops.module.asset.handler.host.exec.command.dto.ExecCommandHostDTO;
import com.orion.ops.module.asset.handler.host.exec.command.manager.ExecTaskManager;
import com.orion.ops.module.infra.api.SystemMessageApi;
import com.orion.ops.module.infra.entity.dto.message.SystemMessageDTO;
import com.orion.spring.SpringHolder;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 命令执行任务
@@ -38,6 +47,8 @@ public class ExecTaskHandler implements IExecTaskHandler {
private static final AppExecLogConfig appExecLogConfig = SpringHolder.getBean(AppExecLogConfig.class);
private static final SystemMessageApi systemMessageApi = SpringHolder.getBean(SystemMessageApi.class);
private final ExecCommandDTO execCommand;
private TimeoutChecker<TimeoutEndpoint> timeoutChecker;
@@ -45,6 +56,8 @@ public class ExecTaskHandler implements IExecTaskHandler {
@Getter
private final List<IExecCommandHandler> handlers;
private Date startTime;
public ExecTaskHandler(ExecCommandDTO execCommand) {
this.execCommand = execCommand;
this.handlers = Lists.newList();
@@ -56,9 +69,9 @@ public class ExecTaskHandler implements IExecTaskHandler {
// 添加任务
execTaskManager.addTask(id, this);
log.info("ExecTaskHandler.run start id: {}", id);
// 更新状态
this.updateStatus(ExecStatusEnum.RUNNING);
try {
// 更新状态
this.updateStatus(ExecStatusEnum.RUNNING);
// 执行命令
this.runHostCommand();
// 更新状态-执行完成
@@ -69,10 +82,12 @@ public class ExecTaskHandler implements IExecTaskHandler {
this.updateStatus(ExecStatusEnum.FAILED);
log.error("ExecTaskHandler.run error id: {}", id, e);
} finally {
// 释放资源
Streams.close(this);
// 检查是否发送消息
this.checkSendMessage();
// 移除任务
execTaskManager.removeTask(id);
// 释放资源
this.close();
}
}
@@ -82,6 +97,13 @@ public class ExecTaskHandler implements IExecTaskHandler {
handlers.forEach(IExecCommandHandler::interrupt);
}
@Override
public void close() {
log.info("ExecTaskHandler-close id: {}", execCommand.getLogId());
Streams.close(timeoutChecker);
this.handlers.forEach(Streams::close);
}
/**
* 执行主机命令
*
@@ -139,6 +161,7 @@ public class ExecTaskHandler implements IExecTaskHandler {
update.setStatus(statusName);
if (ExecStatusEnum.RUNNING.equals(status)) {
// 执行中
this.startTime = new Date();
update.setStartTime(new Date());
} else if (ExecStatusEnum.COMPLETED.equals(status)) {
// 执行完成
@@ -151,11 +174,30 @@ public class ExecTaskHandler implements IExecTaskHandler {
log.info("ExecTaskHandler-updateStatus finish id: {}, effect: {}", id, effect);
}
@Override
public void close() {
log.info("ExecTaskHandler-close id: {}", execCommand.getLogId());
Streams.close(timeoutChecker);
this.handlers.forEach(Streams::close);
/**
* 检查是否发送消息
*/
private void checkSendMessage() {
// 检查是否执行失败/exitCode
boolean hasError = handlers.stream().anyMatch(s ->
ExecHostStatusEnum.FAILED.equals(s.getStatus())
|| ExecHostStatusEnum.TIMEOUT.equals(s.getStatus())
|| !ExitCode.isSuccess(s.getExitCode()));
if (!hasError) {
return;
}
// 参数
Map<String, Object> params = new HashMap<>();
params.put(ExtraFieldConst.ID, execCommand.getLogId());
params.put(ExtraFieldConst.TIME, Dates.format(this.startTime, Dates.MD_HM));
SystemMessageDTO message = SystemMessageDTO.builder()
.receiverId(execCommand.getUserId())
.receiverUsername(execCommand.getUsername())
.relKey(String.valueOf(execCommand.getLogId()))
.params(params)
.build();
// 发送
systemMessageApi.create(ExecMessageDefine.EXEC_FAILED, message);
}
}

View File

@@ -31,6 +31,13 @@ public interface IExecCommandHandler extends Runnable, SafeCloseable {
*/
ExecHostStatusEnum getStatus();
/**
* 获取退出码
*
* @return exit code
*/
Integer getExitCode();
/**
* 获取主机 id
*

View File

@@ -3,10 +3,13 @@ package com.orion.ops.module.asset.handler.host.upload.task;
import com.orion.lang.utils.Threads;
import com.orion.lang.utils.io.Files1;
import com.orion.lang.utils.io.Streams;
import com.orion.lang.utils.time.Dates;
import com.orion.ops.framework.common.constant.Const;
import com.orion.ops.framework.common.constant.ExtraFieldConst;
import com.orion.ops.module.asset.dao.UploadTaskDAO;
import com.orion.ops.module.asset.dao.UploadTaskFileDAO;
import com.orion.ops.module.asset.define.AssetThreadPools;
import com.orion.ops.module.asset.define.message.UploadMessageDefine;
import com.orion.ops.module.asset.entity.domain.UploadTaskDO;
import com.orion.ops.module.asset.entity.domain.UploadTaskFileDO;
import com.orion.ops.module.asset.enums.UploadTaskFileStatusEnum;
@@ -16,14 +19,13 @@ import com.orion.ops.module.asset.handler.host.upload.manager.FileUploadTaskMana
import com.orion.ops.module.asset.handler.host.upload.uploader.FileUploader;
import com.orion.ops.module.asset.handler.host.upload.uploader.IFileUploader;
import com.orion.ops.module.asset.service.UploadTaskService;
import com.orion.ops.module.infra.api.SystemMessageApi;
import com.orion.ops.module.infra.entity.dto.message.SystemMessageDTO;
import com.orion.spring.SpringHolder;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
/**
@@ -42,6 +44,8 @@ public class FileUploadTask implements IFileUploadTask {
private static final UploadTaskService uploadTaskService = SpringHolder.getBean(UploadTaskService.class);
private static final SystemMessageApi systemMessageApi = SpringHolder.getBean(SystemMessageApi.class);
private static final FileUploadTaskManager fileUploadTaskManager = SpringHolder.getBean(FileUploadTaskManager.class);
private final Long id;
@@ -91,6 +95,8 @@ public class FileUploadTask implements IFileUploadTask {
} else {
this.updateStatus(UploadTaskStatusEnum.FINISHED);
}
// 检查是否发送消息
this.checkSendMessage();
// 移除任务
fileUploadTaskManager.removeTask(id);
// 释放资源
@@ -187,4 +193,33 @@ public class FileUploadTask implements IFileUploadTask {
uploadTaskDAO.updateById(update);
}
/**
* 检查是否发送消息
*/
private void checkSendMessage() {
if (canceled) {
return;
}
// 检查是否上传失败
boolean hasError = uploaderList.stream()
.map(IFileUploader::getFiles)
.flatMap(Collection::stream)
.anyMatch(s -> UploadTaskFileStatusEnum.FAILED.name().equals(s.getStatus()));
if (!hasError) {
return;
}
// 参数
Map<String, Object> params = new HashMap<>();
params.put(ExtraFieldConst.ID, record.getId());
params.put(ExtraFieldConst.TIME, Dates.format(record.getCreateTime(), Dates.MD_HM));
SystemMessageDTO message = SystemMessageDTO.builder()
.receiverId(record.getUserId())
.receiverUsername(record.getUsername())
.relKey(String.valueOf(record.getId()))
.params(params)
.build();
// 发送
systemMessageApi.create(UploadMessageDefine.UPLOAD_FAILED, message);
}
}

View File

@@ -156,7 +156,7 @@ public class ExecLogServiceImpl implements ExecLogService {
ExecHostLogDO::getStatus,
ExecHostLogDO::getStartTime,
ExecHostLogDO::getFinishTime,
ExecHostLogDO::getExitStatus,
ExecHostLogDO::getExitCode,
ExecHostLogDO::getErrorMessage)
.in(ExecHostLogDO::getLogId, idList)
.then()

View File

@@ -12,7 +12,7 @@
<result column="status" property="status"/>
<result column="command" property="command"/>
<result column="parameter" property="parameter"/>
<result column="exit_status" property="exitStatus"/>
<result column="exit_code" property="exitCode"/>
<result column="log_path" property="logPath"/>
<result column="script_path" property="scriptPath"/>
<result column="error_message" property="errorMessage"/>
@@ -27,7 +27,7 @@
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, log_id, host_id, host_name, host_address, status, command, parameter, exit_status, log_path, script_path, error_message, start_time, finish_time, create_time, update_time, creator, updater, deleted
id, log_id, host_id, host_name, host_address, status, command, parameter, exit_code, log_path, script_path, error_message, start_time, finish_time, create_time, update_time, creator, updater, deleted
</sql>
</mapper>

View File

@@ -0,0 +1,35 @@
package com.orion.ops.module.infra.api;
import com.orion.ops.module.infra.define.SystemMessageDefine;
import com.orion.ops.module.infra.entity.dto.message.SystemMessageCreateDTO;
import com.orion.ops.module.infra.entity.dto.message.SystemMessageDTO;
import com.orion.ops.module.infra.enums.MessageClassifyEnum;
/**
* 系统消息 对外服务类
*
* @author Jiahang Li
* @version 1.0.8
* @since 2024-5-11 16:29
*/
public interface SystemMessageApi {
/**
* 创建系统消息
*
* @param define define
* @param dto dto
* @return id
*/
Long create(SystemMessageDefine define, SystemMessageDTO dto);
/**
* 创建系统消息
*
* @param classify classify
* @param dto dto
* @return id
*/
Long create(MessageClassifyEnum classify, SystemMessageCreateDTO dto);
}

View File

@@ -0,0 +1,47 @@
package com.orion.ops.module.infra.define;
import com.orion.lang.utils.Strings;
import com.orion.ops.module.infra.enums.MessageClassifyEnum;
import java.util.Map;
/**
* 系统消息定义
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/5/14 17:06
*/
public interface SystemMessageDefine {
/**
* @return 消息分类
*/
MessageClassifyEnum getClassify();
/**
* @return 消息类型
*/
String getType();
/**
* @return 标题
*/
String getTitle();
/**
* @return 内容
*/
String getContent();
/**
* 格式化内容
*
* @param params params
* @return content
*/
default String formatContent(Map<String, Object> params) {
return Strings.format(this.getContent(), params);
}
}

View File

@@ -0,0 +1,61 @@
package com.orion.ops.module.infra.entity.dto.message;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;
/**
* 系统消息 创建请求业务对象
*
* @author Jiahang Li
* @version 1.0.8
* @since 2024-5-11 16:29
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "SystemMessageCreateDTO", description = "系统消息 创建请求业务对象")
public class SystemMessageCreateDTO implements Serializable {
private static final long serialVersionUID = 1L;
@NotBlank
@Size(max = 10)
@Schema(description = "消息分类")
private String classify;
@NotBlank
@Size(max = 32)
@Schema(description = "消息类型")
private String type;
@NotBlank
@Size(max = 64)
@Schema(description = "消息关联")
private String relKey;
@NotBlank
@Size(max = 128)
@Schema(description = "标题")
private String title;
@NotBlank
@Schema(description = "消息内容")
private String content;
@NotNull
@Schema(description = "接收人id")
private Long receiverId;
@Schema(description = "接收人用户名")
private String receiverUsername;
}

View File

@@ -0,0 +1,46 @@
package com.orion.ops.module.infra.entity.dto.message;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;
import java.util.Map;
/**
* 系统消息 请求业务对象
*
* @author Jiahang Li
* @version 1.0.8
* @since 2024-5-11 16:29
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "SystemMessageDTO", description = "系统消息 请求业务对象")
public class SystemMessageDTO implements Serializable {
private static final long serialVersionUID = 1L;
@NotBlank
@Size(max = 64)
@Schema(description = "消息关联")
private String relKey;
@NotNull
@Schema(description = "接收人id")
private Long receiverId;
@Schema(description = "接收人用户名")
private String receiverUsername;
@Schema(description = "参数")
private Map<String, Object> params;
}

View File

@@ -0,0 +1,24 @@
package com.orion.ops.module.infra.enums;
/**
* 消息分类
*
* @author Jiahang Li
* @version 1.0.8
* @since 2024/5/11 16:38
*/
public enum MessageClassifyEnum {
/**
* 通知
*/
NOTICE,
/**
* 待办
*/
TODO,
;
}

View File

@@ -0,0 +1,58 @@
package com.orion.ops.module.infra.api.impl;
import com.orion.ops.framework.common.utils.Valid;
import com.orion.ops.module.infra.api.SystemMessageApi;
import com.orion.ops.module.infra.convert.SystemMessageProviderConvert;
import com.orion.ops.module.infra.define.SystemMessageDefine;
import com.orion.ops.module.infra.entity.dto.message.SystemMessageCreateDTO;
import com.orion.ops.module.infra.entity.dto.message.SystemMessageDTO;
import com.orion.ops.module.infra.entity.request.message.SystemMessageCreateRequest;
import com.orion.ops.module.infra.enums.MessageClassifyEnum;
import com.orion.ops.module.infra.service.SystemMessageService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* 系统消息 对外服务实现类
*
* @author Jiahang Li
* @version 1.0.8
* @since 2024-5-11 16:29
*/
@Slf4j
@Service
public class SystemMessageApiImpl implements SystemMessageApi {
@Resource
private SystemMessageService systemMessageService;
@Override
public Long create(SystemMessageDefine define, SystemMessageDTO dto) {
Valid.valid(dto);
// 转换
SystemMessageCreateRequest request = SystemMessageCreateRequest.builder()
.classify(define.getClassify().name())
.type(define.getType())
.title(define.getTitle())
.content(define.formatContent(dto.getParams()))
.relKey(dto.getRelKey())
.receiverId(dto.getReceiverId())
.receiverUsername(dto.getReceiverUsername())
.build();
// 创建
return systemMessageService.createSystemMessage(request);
}
@Override
public Long create(MessageClassifyEnum classify, SystemMessageCreateDTO dto) {
dto.setClassify(classify.name());
Valid.valid(dto);
// 转换
SystemMessageCreateRequest request = SystemMessageProviderConvert.MAPPER.toRequest(dto);
// 创建
return systemMessageService.createSystemMessage(request);
}
}

View File

@@ -0,0 +1,39 @@
### 查询系统消息列表
POST {{baseUrl}}/infra/system-message/list
Content-Type: application/json
Authorization: {{token}}
{
"limit": 10,
"maxId": null,
"classify": "NOTICE",
"queryCount": true,
"queryUnread": true
}
### 查询是否有未读消息
GET {{baseUrl}}/infra/system-message/has-unread
Authorization: {{token}}
### 查询是否有未读消息
PUT {{baseUrl}}/infra/system-message/read?id=1
Authorization: {{token}}
### 更新全部系统消息为已读
PUT {{baseUrl}}/infra/system-message/read-all?classify=NOTICE
Authorization: {{token}}
### 删除系统消息
DELETE {{baseUrl}}/infra/system-message/delete?id=1
Authorization: {{token}}
### 清理已读的系统消息
DELETE {{baseUrl}}/infra/system-message/clear?classify=NOTICE
Authorization: {{token}}
###

View File

@@ -0,0 +1,90 @@
package com.orion.ops.module.infra.controller;
import com.orion.ops.framework.log.core.annotation.IgnoreLog;
import com.orion.ops.framework.log.core.enums.IgnoreLogMode;
import com.orion.ops.framework.web.core.annotation.RestWrapper;
import com.orion.ops.module.infra.entity.request.message.SystemMessageQueryRequest;
import com.orion.ops.module.infra.entity.vo.SystemMessageVO;
import com.orion.ops.module.infra.service.SystemMessageService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
/**
* 系统消息 api
*
* @author Jiahang Li
* @version 1.0.8
* @since 2024-5-11 16:29
*/
@Tag(name = "infra - 系统消息服务")
@Slf4j
@Validated
@RestWrapper
@RestController
@RequestMapping("/infra/system-message")
@SuppressWarnings({"ELValidationInJSP", "SpringElInspection"})
public class SystemMessageController {
@Resource
private SystemMessageService systemMessageService;
@IgnoreLog(IgnoreLogMode.ALL)
@PostMapping("/list")
@Operation(summary = "查询系统消息列表")
public List<SystemMessageVO> getSystemMessageList(@RequestBody SystemMessageQueryRequest request) {
return systemMessageService.getSystemMessageList(request);
}
@IgnoreLog(IgnoreLogMode.ALL)
@GetMapping("/count")
@Operation(summary = "查询系统消息数量")
@Parameter(name = "queryUnread", description = "queryUnread", required = true)
public Map<String, Integer> getSystemMessageCount(@RequestParam("queryUnread") Boolean queryUnread) {
return systemMessageService.getSystemMessageCount(queryUnread);
}
@IgnoreLog(IgnoreLogMode.ALL)
@GetMapping("/has-unread")
@Operation(summary = "查询是否有未读消息")
public Boolean checkHasUnreadMessage() {
return systemMessageService.checkHasUnreadMessage();
}
@PutMapping("/read")
@Operation(summary = "更新系统消息为已读")
@Parameter(name = "id", description = "id", required = true)
public Integer readSystemMessage(@RequestParam("id") Long id) {
return systemMessageService.readSystemMessage(id);
}
@PutMapping("/read-all")
@Operation(summary = "更新全部系统消息为已读")
@Parameter(name = "classify", description = "classify", required = true)
public Integer readAllSystemMessage(@RequestParam("classify") String classify) {
return systemMessageService.readAllSystemMessage(classify);
}
@DeleteMapping("/delete")
@Operation(summary = "删除系统消息")
@Parameter(name = "id", description = "id", required = true)
public Integer deleteSystemMessage(@RequestParam("id") Long id) {
return systemMessageService.deleteSystemMessageById(id);
}
@DeleteMapping("/clear")
@Operation(summary = "清理已读的系统消息")
@Parameter(name = "classify", description = "classify", required = true)
public Integer clearSystemMessage(@RequestParam("classify") String classify) {
return systemMessageService.clearSystemMessage(classify);
}
}

View File

@@ -0,0 +1,32 @@
package com.orion.ops.module.infra.convert;
import com.orion.ops.module.infra.entity.domain.SystemMessageDO;
import com.orion.ops.module.infra.entity.request.message.SystemMessageCreateRequest;
import com.orion.ops.module.infra.entity.request.message.SystemMessageQueryRequest;
import com.orion.ops.module.infra.entity.vo.SystemMessageVO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
/**
* 系统消息 内部对象转换器
*
* @author Jiahang Li
* @version 1.0.8
* @since 2024-5-11 16:29
*/
@Mapper
public interface SystemMessageConvert {
SystemMessageConvert MAPPER = Mappers.getMapper(SystemMessageConvert.class);
SystemMessageDO to(SystemMessageCreateRequest request);
SystemMessageDO to(SystemMessageQueryRequest request);
SystemMessageVO to(SystemMessageDO domain);
List<SystemMessageVO> to(List<SystemMessageDO> list);
}

View File

@@ -0,0 +1,22 @@
package com.orion.ops.module.infra.convert;
import com.orion.ops.module.infra.entity.dto.message.SystemMessageCreateDTO;
import com.orion.ops.module.infra.entity.request.message.SystemMessageCreateRequest;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* 系统消息 对外服务对象转换器
*
* @author Jiahang Li
* @version 1.0.8
* @since 2024-5-11 16:29
*/
@Mapper
public interface SystemMessageProviderConvert {
SystemMessageProviderConvert MAPPER = Mappers.getMapper(SystemMessageProviderConvert.class);
SystemMessageCreateRequest toRequest(SystemMessageCreateDTO request);
}

View File

@@ -0,0 +1,31 @@
package com.orion.ops.module.infra.dao;
import com.orion.ops.framework.mybatis.core.mapper.IMapper;
import com.orion.ops.module.infra.entity.domain.SystemMessageDO;
import com.orion.ops.module.infra.entity.dto.SystemMessageCountDTO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 系统消息 Mapper 接口
*
* @author Jiahang Li
* @version 1.0.8
* @since 2024-5-11 16:29
*/
@Mapper
public interface SystemMessageDAO extends IMapper<SystemMessageDO> {
/**
* 查询消息数量
*
* @param receiverId receiverId
* @param status status
* @return count
*/
List<SystemMessageCountDTO> selectSystemMessageCount(@Param("receiverId") Long receiverId,
@Param("status") Integer status);
}

View File

@@ -0,0 +1,73 @@
package com.orion.ops.module.infra.entity.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.orion.ops.framework.mybatis.core.domain.BaseDO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
/**
* 系统消息 实体对象
*
* @author Jiahang Li
* @version 1.0.8
* @since 2024-5-11 16:29
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@TableName(value = "system_message", autoResultMap = true)
@Schema(name = "SystemMessageDO", description = "系统消息 实体对象")
public class SystemMessageDO extends BaseDO {
private static final long serialVersionUID = 1L;
@Schema(description = "id")
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@Schema(description = "消息分类")
@TableField("classify")
private String classify;
@Schema(description = "消息类型")
@TableField("type")
private String type;
@Schema(description = "消息状态")
@TableField("status")
private Integer status;
@Schema(description = "消息关联")
@TableField("rel_key")
private String relKey;
@Schema(description = "标题")
@TableField("title")
private String title;
@Schema(description = "消息内容")
@TableField("content")
private String content;
@Schema(description = "接收人id")
@TableField("receiver_id")
private Long receiverId;
@Schema(description = "接收人用户名")
@TableField("receiver_username")
private String receiverUsername;
@Schema(description = "创建人")
@TableField(exist = false)
private String creator;
@Schema(description = "修改人")
@TableField(exist = false)
private String updater;
}

View File

@@ -0,0 +1,33 @@
package com.orion.ops.module.infra.entity.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 系统消息数量对象
*
* @author Jiahang Li
* @version 1.0.8
* @since 2024/5/11 18:15
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "SystemMessageCountDTO", description = "系统消息数量对象")
public class SystemMessageCountDTO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "消息分类")
private String classify;
@Schema(description = "数量")
private Integer count;
}

View File

@@ -0,0 +1,62 @@
package com.orion.ops.module.infra.entity.request.message;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;
/**
* 系统消息 创建请求对象
*
* @author Jiahang Li
* @version 1.0.8
* @since 2024-5-11 16:29
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "SystemMessageCreateRequest", description = "系统消息 创建请求对象")
public class SystemMessageCreateRequest implements Serializable {
private static final long serialVersionUID = 1L;
@NotBlank
@Size(max = 10)
@Schema(description = "消息分类")
private String classify;
@NotBlank
@Size(max = 32)
@Schema(description = "消息类型")
private String type;
@NotBlank
@Size(max = 64)
@Schema(description = "消息关联")
private String relKey;
@NotBlank
@Size(max = 128)
@Schema(description = "标题")
private String title;
@NotBlank
@Schema(description = "消息内容")
private String content;
@NotNull
@Schema(description = "接收人id")
private Long receiverId;
@Size(max = 32)
@Schema(description = "接收人用户名")
private String receiverUsername;
}

View File

@@ -0,0 +1,35 @@
package com.orion.ops.module.infra.entity.request.message;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 系统消息 查询请求对象
*
* @author Jiahang Li
* @version 1.0.8
* @since 2024-5-11 16:29
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "SystemMessageQueryRequest", description = "系统消息 查询请求对象")
public class SystemMessageQueryRequest {
@Schema(description = "大小")
private Integer limit;
@Schema(description = "maxId")
private Long maxId;
@Schema(description = "消息分类")
private String classify;
@Schema(description = "是否查询未读消息")
private Boolean queryUnread;
}

View File

@@ -0,0 +1,52 @@
package com.orion.ops.module.infra.entity.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
/**
* 系统消息 视图响应对象
*
* @author Jiahang Li
* @version 1.0.8
* @since 2024-5-11 16:29
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "SystemMessageVO", description = "系统消息 视图响应对象")
public class SystemMessageVO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "id")
private Long id;
@Schema(description = "消息分类")
private String classify;
@Schema(description = "消息类型")
private String type;
@Schema(description = "消息状态")
private Integer status;
@Schema(description = "消息关联")
private String relKey;
@Schema(description = "标题")
private String title;
@Schema(description = "消息内容")
private String content;
@Schema(description = "创建时间")
private Date createTime;
}

View File

@@ -0,0 +1,31 @@
package com.orion.ops.module.infra.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 消息状态
*
* @author Jiahang Li
* @version 1.0.8
* @since 2024/5/11 16:35
*/
@Getter
@AllArgsConstructor
public enum MessageStatusEnum {
/**
* 未读
*/
UNREAD(0),
/**
* 已读
*/
READ(1),
;
private final Integer status;
}

View File

@@ -0,0 +1,82 @@
package com.orion.ops.module.infra.service;
import com.orion.ops.module.infra.entity.request.message.SystemMessageCreateRequest;
import com.orion.ops.module.infra.entity.request.message.SystemMessageQueryRequest;
import com.orion.ops.module.infra.entity.vo.SystemMessageVO;
import java.util.List;
import java.util.Map;
/**
* 系统消息 服务类
*
* @author Jiahang Li
* @version 1.0.8
* @since 2024-5-11 16:29
*/
public interface SystemMessageService {
/**
* 创建系统消息
*
* @param request request
* @return id
*/
Long createSystemMessage(SystemMessageCreateRequest request);
/**
* 查询系统消息列表
*
* @param request request
* @return rows
*/
List<SystemMessageVO> getSystemMessageList(SystemMessageQueryRequest request);
/**
* 查询系统消息数量
*
* @param queryUnread queryUnread
* @return rows
*/
Map<String, Integer> getSystemMessageCount(Boolean queryUnread);
/**
* 查询是否有未读消息
*
* @return has
*/
Boolean checkHasUnreadMessage();
/**
* 更新系统消息为已读
*
* @param id id
* @return effect
*/
Integer readSystemMessage(Long id);
/**
* 更新全部系统消息为已读
*
* @param classify classify
* @return effect
*/
Integer readAllSystemMessage(String classify);
/**
* 删除系统消息
*
* @param id id
* @return effect
*/
Integer deleteSystemMessageById(Long id);
/**
* 清理已读的系统消息
*
* @param classify classify
* @return effect
*/
Integer clearSystemMessage(String classify);
}

View File

@@ -0,0 +1,167 @@
package com.orion.ops.module.infra.service.impl;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.orion.lang.function.Functions;
import com.orion.lang.utils.Booleans;
import com.orion.ops.framework.common.constant.Const;
import com.orion.ops.framework.security.core.utils.SecurityUtils;
import com.orion.ops.module.infra.convert.SystemMessageConvert;
import com.orion.ops.module.infra.dao.SystemMessageDAO;
import com.orion.ops.module.infra.dao.SystemUserDAO;
import com.orion.ops.module.infra.entity.domain.SystemMessageDO;
import com.orion.ops.module.infra.entity.domain.SystemUserDO;
import com.orion.ops.module.infra.entity.dto.SystemMessageCountDTO;
import com.orion.ops.module.infra.entity.request.message.SystemMessageCreateRequest;
import com.orion.ops.module.infra.entity.request.message.SystemMessageQueryRequest;
import com.orion.ops.module.infra.entity.vo.SystemMessageVO;
import com.orion.ops.module.infra.enums.MessageStatusEnum;
import com.orion.ops.module.infra.service.SystemMessageService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* 系统消息 服务实现类
*
* @author Jiahang Li
* @version 1.0.8
* @since 2024-5-11 16:29
*/
@Slf4j
@Service
public class SystemMessageServiceImpl implements SystemMessageService {
@Resource
private SystemMessageDAO systemMessageDAO;
@Resource
private SystemUserDAO systemUserDAO;
@Override
public Long createSystemMessage(SystemMessageCreateRequest request) {
log.info("SystemMessageService.createSystemMessage request: {}", JSON.toJSONString(request));
// 设置接收人用户名
if (request.getReceiverUsername() == null) {
Optional.ofNullable(request.getReceiverId())
.map(systemUserDAO::selectById)
.map(SystemUserDO::getUsername)
.ifPresent(request::setReceiverUsername);
}
// 转换
SystemMessageDO record = SystemMessageConvert.MAPPER.to(request);
record.setStatus(MessageStatusEnum.UNREAD.getStatus());
// 插入
int effect = systemMessageDAO.insert(record);
Long id = record.getId();
log.info("SystemMessageService-createSystemMessage id: {}, effect: {}", id, effect);
return id;
}
@Override
public List<SystemMessageVO> getSystemMessageList(SystemMessageQueryRequest request) {
Long userId = SecurityUtils.getLoginUserId();
Integer status = Booleans.isTrue(request.getQueryUnread()) ? MessageStatusEnum.UNREAD.getStatus() : null;
// 查询列表
return systemMessageDAO.of()
.createValidateWrapper()
.eq(SystemMessageDO::getReceiverId, userId)
.eq(SystemMessageDO::getClassify, request.getClassify())
.lt(SystemMessageDO::getId, request.getMaxId())
.eq(SystemMessageDO::getStatus, status)
.last(Const.LIMIT + Const.SPACE + request.getLimit())
.orderByDesc(SystemMessageDO::getId)
.then()
.list(SystemMessageConvert.MAPPER::to);
}
@Override
public Map<String, Integer> getSystemMessageCount(Boolean queryUnread) {
Long userId = SecurityUtils.getLoginUserId();
Integer status = queryUnread ? MessageStatusEnum.UNREAD.getStatus() : null;
// 查询数量
List<SystemMessageCountDTO> countList = systemMessageDAO.selectSystemMessageCount(userId, status);
// 返回
return countList.stream()
.collect(Collectors.toMap(SystemMessageCountDTO::getClassify,
SystemMessageCountDTO::getCount,
Functions.right()));
}
@Override
public Boolean checkHasUnreadMessage() {
// 查询
return systemMessageDAO.of()
.createWrapper()
.select(SystemMessageDO::getId)
.eq(SystemMessageDO::getReceiverId, SecurityUtils.getLoginUserId())
.eq(SystemMessageDO::getStatus, MessageStatusEnum.UNREAD.getStatus())
.then()
.only()
.optional()
.isPresent();
}
@Override
public Integer readSystemMessage(Long id) {
Long userId = SecurityUtils.getLoginUserId();
log.info("SystemMessageService-readSystemMessage id: {}, userId: {}", id, userId);
// 修改状态
SystemMessageDO update = new SystemMessageDO();
update.setStatus(MessageStatusEnum.READ.getStatus());
LambdaQueryWrapper<SystemMessageDO> wrapper = systemMessageDAO.wrapper()
.eq(SystemMessageDO::getId, id)
.eq(SystemMessageDO::getReceiverId, userId);
int effect = systemMessageDAO.update(update, wrapper);
log.info("SystemMessageService-readSystemMessage id: {}, effect: {}", id, effect);
return effect;
}
@Override
public Integer readAllSystemMessage(String classify) {
Long userId = SecurityUtils.getLoginUserId();
log.info("SystemMessageService-readAllSystemMessage classify: {}, userId: {}", classify, userId);
// 修改状态
SystemMessageDO update = new SystemMessageDO();
update.setStatus(MessageStatusEnum.READ.getStatus());
LambdaQueryWrapper<SystemMessageDO> wrapper = systemMessageDAO.wrapper()
.eq(SystemMessageDO::getReceiverId, userId)
.eq(SystemMessageDO::getClassify, classify)
.eq(SystemMessageDO::getStatus, MessageStatusEnum.UNREAD.getStatus());
int effect = systemMessageDAO.update(update, wrapper);
log.info("SystemMessageService-readAllSystemMessage classify: {}, effect: {}", classify, effect);
return effect;
}
@Override
public Integer deleteSystemMessageById(Long id) {
log.info("SystemMessageService-deleteSystemMessageById id: {}", id);
// 删除
LambdaQueryWrapper<SystemMessageDO> wrapper = systemMessageDAO.wrapper()
.eq(SystemMessageDO::getId, id)
.eq(SystemMessageDO::getReceiverId, SecurityUtils.getLoginUserId());
int effect = systemMessageDAO.delete(wrapper);
log.info("SystemMessageService-deleteSystemMessageById id: {}, effect: {}", id, effect);
return effect;
}
@Override
public Integer clearSystemMessage(String classify) {
Long userId = SecurityUtils.getLoginUserId();
log.info("SystemMessageService-clearSystemMessage classify: {}, userId: {}", classify, userId);
// 删除
LambdaQueryWrapper<SystemMessageDO> wrapper = systemMessageDAO.wrapper()
.eq(SystemMessageDO::getReceiverId, userId)
.eq(SystemMessageDO::getClassify, classify)
.eq(SystemMessageDO::getStatus, MessageStatusEnum.READ.getStatus());
int effect = systemMessageDAO.delete(wrapper);
log.info("SystemMessageService-clearSystemMessage classify: {}, effect: {}", classify, effect);
return effect;
}
}

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.orion.ops.module.infra.dao.SystemMessageDAO">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.orion.ops.module.infra.entity.domain.SystemMessageDO">
<id column="id" property="id"/>
<result column="classify" property="classify"/>
<result column="type" property="type"/>
<result column="status" property="status"/>
<result column="rel_key" property="relKey"/>
<result column="title" property="title"/>
<result column="content" property="content"/>
<result column="receiver_id" property="receiverId"/>
<result column="receiver_username" property="receiverUsername"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
<result column="deleted" property="deleted"/>
</resultMap>
<resultMap id="CountResultMap" type="com.orion.ops.module.infra.entity.dto.SystemMessageCountDTO">
<result column="classify" property="classify"/>
<result column="count" property="count"/>
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, classify, type, status, rel_key, title, content, receiver_id, receiver_username, create_time, update_time, deleted
</sql>
<!-- 查询消息数量 -->
<select id="selectSystemMessageCount" resultMap="CountResultMap">
SELECT classify, count(1) count
FROM system_message
WHERE deleted = 0
AND receiver_id = #{receiverId}
<if test="status != null">
AND status = #{status}
</if>
GROUP BY classify
</select>
</mapper>

View File

@@ -1,4 +1,4 @@
VITE_API_BASE_URL= 'http://127.0.0.1:9200/orion/api'
VITE_WS_BASE_URL= 'ws://127.0.0.1:9200/orion/keep-alive'
VITE_APP_VERSION= '1.0.7'
VITE_APP_VERSION= '1.0.8'
VITE_SFTP_PREVIEW_MB= 2

View File

@@ -1,4 +1,4 @@
VITE_API_BASE_URL= '/orion/api'
VITE_WS_BASE_URL= '/orion/keep-alive'
VITE_APP_VERSION= '1.0.7'
VITE_APP_VERSION= '1.0.8'
VITE_SFTP_PREVIEW_MB= 2

View File

@@ -1,7 +1,7 @@
{
"name": "orion-ops-pro-ui",
"description": "Orion Ops Pro for Vue",
"version": "1.0.7",
"version": "1.0.8",
"private": true,
"author": "Jiahang Li",
"license": "Apache 2.0",

View File

@@ -54,7 +54,7 @@ export interface ExecHostLogQueryResponse extends TableData {
status: string;
command: string;
parameter: string;
exitStatus: number;
exitCode: number;
errorMessage: string;
startTime: number;
finishTime: number;

View File

@@ -1,38 +1,75 @@
import axios from 'axios';
export interface MessageRecord {
/**
* 系统消息查询请求
*/
export interface MessageQueryRequest {
limit?: number;
maxId?: number;
classify?: string;
queryUnread?: boolean;
}
/**
* 系统消息查询响应
*/
export interface MessageRecordResponse {
id: number;
classify: string;
type: string;
status: number;
relKey: string;
title: string;
subTitle: string;
avatar?: string;
content: string;
time: string;
status: 0 | 1;
messageType?: number;
}
export type MessageListType = MessageRecord[];
export function queryMessageList() {
return axios.post<MessageListType>('/api/message/list');
contentHtml: string;
createTime: number;
}
interface MessageStatus {
ids: number[];
/**
* 查询系统消息列表
*/
export function getSystemMessageList(request: MessageQueryRequest) {
return axios.post<Array<MessageRecordResponse>>('/infra/system-message/list', request);
}
export function setMessageStatus(data: MessageStatus) {
return axios.post<MessageListType>('/api/message/read', data);
/**
* 查询系统消息数量
*/
export function getSystemMessageCount(queryUnread: boolean) {
return axios.get<Record<string, number>>('/infra/system-message/count', { params: { queryUnread } });
}
export interface ChatRecord {
id: number;
username: string;
content: string;
time: string;
isCollect: boolean;
/**
* 查询是否有未读消息
*/
export function checkHasUnreadMessage() {
return axios.get<boolean>('/infra/system-message/has-unread');
}
export function queryChatList() {
return axios.post<ChatRecord[]>('/api/chat/list');
/**
* 更新系统消息为已读
*/
export function updateSystemMessageRead(id: number) {
return axios.put('/infra/system-message/read', undefined, { params: { id } });
}
/**
* 更新全部系统消息为已读
*/
export function updateSystemMessageReadAll(classify: string) {
return axios.put('/infra/system-message/read-all', undefined, { params: { classify } });
}
/**
* 删除系统消息
*/
export function deleteSystemMessage(id: number) {
return axios.delete('/infra/system-message/delete', { params: { id } });
}
/**
* 清理已读的系统消息
*/
export function clearSystemMessage(classify: string) {
return axios.delete('/infra/system-message/clear', { params: { classify } });
}

View File

@@ -80,11 +80,11 @@
</a-button>
</a-tooltip>
</li>
<!-- 消息列表 -->
<li v-if="false">
<a-tooltip content="消息通知">
<!-- 系统消息 -->
<li>
<a-tooltip content="系统消息" :show-arrow="false">
<div class="message-box-trigger">
<a-badge :count="9" dot>
<a-badge :count="messageCount" dot>
<a-button class="nav-btn"
type="outline"
shape="circle"
@@ -95,9 +95,12 @@
</div>
</a-tooltip>
<a-popover trigger="click"
:arrow-style="{ display: 'none' }"
:content-style="{ padding: 0, minWidth: '400px' }"
content-class="message-popover">
content-class="message-popover"
position="br"
:show-arrow="false"
:popup-style="{ marginLeft: '198px' }"
:content-style="{ padding: 0, width: '428px' }"
@hide="pullHasUnreadMessage">
<div ref="messageRef" class="ref-btn" />
<template #content>
<message-box />
@@ -202,7 +205,7 @@
</template>
<script lang="ts" setup>
import { computed, inject, ref } from 'vue';
import { computed, inject, onMounted, onUnmounted, ref } from 'vue';
import useLocale from '@/hooks/locale';
import useUser from '@/hooks/user';
import { useRoute, useRouter } from 'vue-router';
@@ -214,6 +217,7 @@
import { preferenceTipsKey } from './const';
import { REDIRECT_ROUTE_NAME, routerToTag } from '@/router/constants';
import { openNewRoute } from '@/router';
import { checkHasUnreadMessage } from '@/api/system/message';
import SystemMenuTree from '@/components/system/menu/tree/index.vue';
import MessageBox from '@/components/system/message-box/index.vue';
import UpdatePasswordModal from '@/components/user/user/update-password-modal/index.vue';
@@ -258,6 +262,9 @@
const messageRef = ref();
// 语言
const localeRef = ref();
// 消息数量
const messageCount = ref(0);
const messageIntervalId = ref();
// 打开应用设置
const openAppSetting = inject(openAppSettingKey) as () => void;
@@ -302,6 +309,14 @@
await logout();
};
// 获取是否有未读的消息
const pullHasUnreadMessage = () => {
// 查询
checkHasUnreadMessage().then(({ data }) => {
messageCount.value = data ? 1 : 0;
});
};
// 关闭偏好提示
const closePreferenceTip = (ack: boolean) => {
tippedPreference.value = false;
@@ -310,6 +325,18 @@
}
};
onMounted(() => {
// 查询未读消息
pullHasUnreadMessage();
// 注册未读消息轮询
messageIntervalId.value = setInterval(pullHasUnreadMessage, 30000);
});
onUnmounted(() => {
// 清理消息轮询
clearInterval(messageIntervalId.value);
});
</script>
<style lang="less" scoped>

View File

@@ -44,7 +44,7 @@
const logViewRef = ref();
const currentHostExecId = ref();
const statusIntervalId = ref();
const pullIntervalId = ref();
const execLog = ref<ExecLogQueryResponse>();
const appender = ref<ILogAppender>();
@@ -57,9 +57,9 @@
if (record.status === execStatus.WAITING ||
record.status === execStatus.RUNNING) {
// 等待一秒后先查询一下状态
setTimeout(fetchTaskStatus, 1000);
setTimeout(pullExecStatus, 1000);
// 注册状态轮询
statusIntervalId.value = setInterval(fetchTaskStatus, 5000);
pullIntervalId.value = setInterval(pullExecStatus, 5000);
}
// 打开日志
nextTick(() => {
@@ -68,7 +68,7 @@
};
// 加载状态
const fetchTaskStatus = async () => {
const pullExecStatus = async () => {
if (!execLog.value) {
return;
}
@@ -95,7 +95,7 @@
host.startTime = hostStatus.startTime;
// 结束时间绑定了使用时间 如果未完成则使用当前时间
host.finishTime = hostStatus.finishTime || Date.now();
host.exitStatus = hostStatus.exitStatus;
host.exitCode = hostStatus.exitCode;
host.errorMessage = hostStatus.errorMessage;
}
}
@@ -150,7 +150,7 @@
// 清理轮询
const clearAllInterval = () => {
// 关闭状态轮询
clearInterval(statusIntervalId.value);
clearInterval(pullIntervalId.value);
};
// 加载字典值

View File

@@ -12,15 +12,15 @@
<a-tag :color="getDictValue(execHostStatusKey, host.status, 'color')">
{{ getDictValue(execHostStatusKey, host.status) }}
</a-tag>
<!-- exitStatus -->
<a-tag v-if="host.exitStatus || host.exitStatus === 0"
:color="host.exitStatus === 0 ? 'arcoblue' : 'orangered'"
title="exit status">
<!-- exitCode -->
<a-tag v-if="host.exitCode || host.exitCode === 0"
:color="host.exitCode === 0 ? 'arcoblue' : 'orangered'"
title="exit code">
<template #icon>
<icon-check v-if="host.exitStatus === 0" />
<icon-check v-if="host.exitCode === 0" />
<icon-exclamation v-else />
</template>
<span class="tag-value">{{ host.exitStatus }}</span>
<span class="tag-value">{{ host.exitCode }}</span>
</a-tag>
<!-- 持续时间 -->
<a-tag v-if="host.startTime"

View File

@@ -0,0 +1,22 @@
// 消息状态
export const MessageStatus = {
UNREAD: 0,
READ: 1,
};
export const MESSAGE_CONFIG_KEY = 'messageConfig';
// 查询数量
export const messageLimit = 15;
// 默认消息分类 通知
export const defaultClassify = 'NOTICE';
// 消息分类 字典项
export const messageClassifyKey = 'messageClassify';
// 消息类型 字典项
export const messageTypeKey = 'messageType';
// 加载的字典值
export const dictKeys = [messageClassifyKey, messageTypeKey];

View File

@@ -1,108 +1,242 @@
<template>
<a-spin style="display: block" :loading="loading">
<a-tabs v-model:activeKey="messageType" type="rounded" destroy-on-hide>
<a-tab-pane v-for="item in tabList" :key="item.key">
<template #title>
<span> {{ item.title }}{{ formatUnreadLength(item.key) }} </span>
<div class="full">
<!-- 消息分类 -->
<a-spin class="message-classify-container"
:hide-icon="true"
:loading="fetchLoading">
<a-tabs v-model:activeKey="currentClassify"
type="rounded"
:hide-content="true"
@change="loadClassifyMessage">
<!-- 消息列表 -->
<a-tab-pane v-for="item in toOptions(messageClassifyKey)"
:key="item.value as string">
<!-- 标题 -->
<template #title>
<span class="usn">{{ item.label }} ({{ classifyCount[item.value as any] || 0 }})</span>
</template>
<!-- 消息列表 -->
</a-tab-pane>
<!-- 右侧操作 -->
<template #extra>
<a-space>
<!-- 状态 -->
<a-switch v-model="queryUnread"
type="round"
checked-text="未读"
unchecked-text="全部"
@change="reloadAllMessage" />
<!-- 清空 -->
<a-button class="header-button"
type="text"
size="small"
@click="clearAllMessage">
清空
</a-button>
<!-- 全部已读 -->
<a-button class="header-button"
type="text"
size="small"
@click="setAllRead">
全部已读
</a-button>
</a-space>
</template>
<a-result v-if="!renderList.length" status="404">
<template #subtitle>暂无内容</template>
</a-result>
<list :render-list="renderList"
:unread-count="unreadCount"
@item-click="handleItemClick" />
</a-tab-pane>
<template #extra>
<a-button type="text" @click="emptyList">
清空
</a-button>
</template>
</a-tabs>
</a-spin>
</a-tabs>
</a-spin>
<!-- 消息列表 -->
<list :fetch-loading="fetchLoading"
:message-loading="messageLoading"
:has-more="hasMore"
:message-list="messageList"
@load="loadMessage"
@click="clickMessage"
@delete="deleteMessage" />
</div>
</template>
<script lang="ts">
export default {
name: 'messageBox'
};
</script>
<script lang="ts" setup>
import type { MessageRecord, MessageListType } from '@/api/system/message';
import { ref, reactive, toRefs, computed } from 'vue';
import { queryMessageList, setMessageStatus } from '@/api/system/message';
import type { MessageRecordResponse } from '@/api/system/message';
import { ref, onMounted, onUnmounted } from 'vue';
import {
clearSystemMessage,
deleteSystemMessage,
getSystemMessageCount,
getSystemMessageList,
updateSystemMessageRead,
updateSystemMessageReadAll
} from '@/api/system/message';
import useLoading from '@/hooks/loading';
import { useRouter } from 'vue-router';
import { clearHtmlTag, replaceHtmlTag } from '@/utils';
import { useDictStore } from '@/store';
import { dictKeys, messageClassifyKey, messageTypeKey, defaultClassify, MESSAGE_CONFIG_KEY, messageLimit, MessageStatus } from './const';
import List from './list.vue';
interface TabItem {
key: string;
title: string;
avatar?: string;
}
const { loading: fetchLoading, setLoading: setFetchLoading } = useLoading();
const { loading: messageLoading, setLoading: setMessageLoading } = useLoading();
const { loadKeys, toOptions, getDictValue } = useDictStore();
const router = useRouter();
const { loading, setLoading } = useLoading(true);
const currentClassify = ref(defaultClassify);
const queryUnread = ref(false);
const classifyCount = ref<Record<string, number>>({});
const messageList = ref<Array<MessageRecordResponse>>([]);
const hasMore = ref(true);
const messageType = ref('message');
const messageData = reactive<{
renderList: MessageRecord[];
messageList: MessageRecord[];
}>({
renderList: [],
messageList: [],
});
toRefs(messageData);
const tabList: TabItem[] = [
{
key: 'message',
title: '消息',
},
{
key: 'notice',
title: '通知',
},
{
key: 'todo',
title: '待办',
},
];
// 重新加载消息
const reloadAllMessage = async () => {
hasMore.value = true;
messageList.value = [];
// 查询数量
queryMessageCount();
// 加载列表
await loadMessage();
};
async function fetchSourceData() {
setLoading(true);
// 获取数量
const queryMessageCount = async () => {
setFetchLoading(true);
try {
const { data } = await queryMessageList();
messageData.messageList = data;
} catch (err) {
// you can report use errorHandler or other
const { data } = await getSystemMessageCount(queryUnread.value);
classifyCount.value = data;
} catch (ex) {
} finally {
setLoading(false);
setFetchLoading(false);
}
}
};
async function readMessage(data: MessageListType) {
const ids = data.map((item) => item.id);
await setMessageStatus({ ids });
await fetchSourceData();
}
// 查询分类消息
const loadClassifyMessage = async () => {
hasMore.value = true;
messageList.value = [];
await loadMessage();
};
const renderList = computed(() => {
return messageData.messageList.filter(
(item) => messageType.value === item.type
);
// 加载消息
const loadMessage = async () => {
hasMore.value = true;
setFetchLoading(true);
try {
const maxId = messageList.value.length
? messageList.value[messageList.value.length - 1].id
: undefined;
// 查询数据
const { data } = await getSystemMessageList({
limit: messageLimit,
classify: currentClassify.value,
queryUnread: queryUnread.value,
maxId,
});
data.forEach(s => {
messageList.value.push({
...s,
content: clearHtmlTag(s.content),
contentHtml: replaceHtmlTag(s.content),
});
});
hasMore.value = data.length === messageLimit;
} catch (ex) {
} finally {
setFetchLoading(false);
}
};
// 设置全部已读
const setAllRead = async () => {
setMessageLoading(true);
try {
// 设置为已读
await updateSystemMessageReadAll(currentClassify.value);
// 修改状态
messageList.value.forEach(s => s.status = MessageStatus.READ);
} catch (ex) {
} finally {
setMessageLoading(false);
}
};
// 清理已读消息
const clearAllMessage = async () => {
setMessageLoading(true);
try {
// 清理消息
await clearSystemMessage(currentClassify.value);
} catch (ex) {
} finally {
setMessageLoading(false);
}
// 查询消息
await reloadAllMessage();
};
// 点击消息
const clickMessage = (message: MessageRecordResponse) => {
// 设置为已读
if (message.status === MessageStatus.UNREAD) {
updateSystemMessageRead(message.id);
message.status = MessageStatus.READ;
}
const redirectComponent = getDictValue(messageTypeKey, message.type, 'redirectComponent');
if (redirectComponent && redirectComponent !== '0') {
// 跳转组件
router.push({ name: redirectComponent, query: { key: message.relKey } });
}
};
// 删除消息
const deleteMessage = async (message: MessageRecordResponse) => {
setMessageLoading(true);
try {
// 删除消息
await deleteSystemMessage(message.id);
// 减少数量
classifyCount.value[currentClassify.value] -= 1;
// 移除
const index = messageList.value.findIndex(s => s.id === message.id);
messageList.value.splice(index, 1);
} catch (ex) {
} finally {
setMessageLoading(false);
}
};
// 加载字典值
onMounted(() => {
loadKeys(dictKeys);
});
const unreadCount = computed(() => {
return renderList.value.filter((item) => !item.status).length;
// 获取消息
onMounted(() => {
// 获取配置缓存
const item = localStorage.getItem(MESSAGE_CONFIG_KEY);
if (item) {
const config = JSON.parse(item) as Record<string, any>;
if (config?.currentClassify) {
currentClassify.value = config.currentClassify;
}
if (config?.queryUnread) {
queryUnread.value = config.queryUnread;
}
}
// 查询数据
reloadAllMessage();
});
const getUnreadList = (type: string) => {
const list = messageData.messageList.filter(
(item) => item.type === type && !item.status
);
return list;
};
const formatUnreadLength = (type: string) => {
const list = getUnreadList(type);
return list.length ? `(${list.length})` : '';
};
const handleItemClick = (items: MessageListType) => {
if (renderList.value.length) readMessage([...items]);
};
const emptyList = () => {
messageData.messageList = [];
};
fetchSourceData();
// 设置缓存配置
onUnmounted(() => {
localStorage.setItem(MESSAGE_CONFIG_KEY, JSON.stringify({
currentClassify: currentClassify.value,
queryUnread: queryUnread.value,
}));
});
</script>
<style lang="less" scoped>
@@ -110,20 +244,23 @@
padding: 0;
}
:deep(.arco-list-item-meta) {
align-items: flex-start;
}
:deep(.arco-tabs-nav) {
padding: 14px 0 12px 16px;
padding: 10px 12px;
border-bottom: 1px solid var(--color-neutral-3);
}
:deep(.arco-tabs-content) {
padding-top: 0;
}
.arco-result-subtitle {
color: rgb(var(--gray-6));
.message-classify-container {
width: 100%;
height: 100%;
display: block;
.header-button {
padding: 0 6px;
}
}
</style>

View File

@@ -1,149 +1,218 @@
<template>
<a-list :bordered="false">
<a-list-item v-for="item in renderList"
:key="item.id"
action-layout="vertical"
:style="{
opacity: item.status ? 0.5 : 1,
}">
<template #extra>
<a-tag v-if="item.messageType === 0" color="gray">未开始</a-tag>
<a-tag v-else-if="item.messageType === 1" color="green">已开通</a-tag>
<a-tag v-else-if="item.messageType === 2" color="blue">进行中</a-tag>
<a-tag v-else-if="item.messageType === 3" color="red">即将到期</a-tag>
</template>
<div class="item-wrap" @click="onItemClick(item)">
<a-list-item-meta>
<template v-if="item.avatar" #avatar>
<a-avatar shape="circle">
<img v-if="item.avatar" :src="item.avatar" />
<icon-desktop v-else />
</a-avatar>
</template>
<template #title>
<a-space :size="4">
<span>{{ item.title }}</span>
<a-typography-text type="secondary">
{{ item.subTitle }}
</a-typography-text>
</a-space>
</template>
<template #description>
<div>
<a-typography-paragraph :ellipsis="{
rows: 1,
}">
{{ item.content }}
</a-typography-paragraph>
<a-typography-text v-if="item.type === 'message'"
class="time-text">
{{ item.time }}
</a-typography-text>
</div>
</template>
</a-list-item-meta>
</div>
</a-list-item>
<template #footer>
<a-space fill
:size="0"
:class="{ 'add-border-top': renderList.length < showMax }">
<div class="footer-wrap">
<a-link @click="allRead">全部已读</a-link>
</div>
<div class="footer-wrap">
<a-link>查看更多</a-link>
</div>
</a-space>
</template>
<div v-if="renderList.length && renderList.length < 3"
:style="{ height: (showMax - renderList.length) * 86 + 'px' }">
<!-- 消息列表 -->
<a-spin class="message-list-container" :loading="messageLoading">
<!-- 加载中 -->
<div v-if="!messageList.length && fetchLoading">
<!-- 加载中 -->
<a-skeleton class="skeleton-wrapper" :animation="true">
<a-skeleton-line :rows="3"
:line-height="96"
:line-spacing="8" />
</a-skeleton>
</div>
</a-list>
<!-- 无数据 -->
<div v-else-if="!messageList.length && !fetchLoading">
<a-result status="404">
<template #subtitle>暂无内容</template>
</a-result>
</div>
<!-- 消息容器 -->
<div v-else class="message-list-wrapper">
<a-scrollbar style="overflow-y: auto; height: 100%;">
<!-- 消息列表-->
<div v-for="message in messageList"
class="message-item"
:class="[ message.status === MessageStatus.READ ? 'message-item-read' : 'message-item-unread' ]"
@click="emits('click', message)">
<!-- 标题 -->
<div class="message-item-title">
<!-- 标题 -->
<div class="message-item-title-text text-ellipsis" :title="message.title">
{{ message.title }}
</div>
<!-- tag -->
<div class="message-item-title-status">
<template v-if="getDictValue(messageTypeKey, message.type, 'tagVisible', false)">
<a-tag size="small" :color="getDictValue(messageTypeKey, message.type, 'tagColor')">
{{ getDictValue(messageTypeKey, message.type, 'tagLabel') }}
</a-tag>
</template>
</div>
<!-- 操作 -->
<div class="message-item-title-actions">
<!-- 删除 -->
<a-button size="mini"
type="text"
status="danger"
@click.stop="emits('delete', message)">
删除
</a-button>
</div>
</div>
<!-- 内容 -->
<div v-html="message.contentHtml" class="message-item-content" />
<!-- 时间 -->
<div class="message-item-time">
{{ dateFormat(new Date(message.createTime)) }}
</div>
</div>
<!-- 加载中 -->
<a-skeleton v-if="fetchLoading"
class="skeleton-wrapper"
:animation="true">
<a-skeleton-line :rows="3"
:line-height="96"
:line-spacing="8" />
</a-skeleton>
<!-- 加载更多 -->
<div v-if="hasMore" class="load-more-wrapper">
<a-button size="small"
:fetchLoading="fetchLoading"
@click="() => emits('load')">
加载更多
</a-button>
</div>
</a-scrollbar>
</div>
</a-spin>
</template>
<script lang="ts">
export default {
name: 'messageBoxList'
};
</script>
<script lang="ts" setup>
import type { MessageListType, MessageRecord } from '@/api/system/message';
import type { MessageRecordResponse } from '@/api/system/message';
import { MessageStatus, messageTypeKey } from './const';
import { useDictStore } from '@/store';
import { dateFormat } from '@/utils';
const props = withDefaults(defineProps<{
renderList: MessageListType;
unreadCount?: number;
}>(), {
unreadCount: 0,
});
const emits = defineEmits(['load', 'click', 'delete']);
const props = defineProps<{
fetchLoading: boolean;
messageLoading: boolean;
hasMore: boolean;
messageList: Array<MessageRecordResponse>;
}>();
const emit = defineEmits(['itemClick']);
const allRead = () => {
emit('itemClick', [...props.renderList]);
};
const { getDictValue } = useDictStore();
const onItemClick = (item: MessageRecord) => {
if (!item.status) {
emit('itemClick', [item]);
}
};
const showMax = 3;
</script>
<style lang="less" scoped>
:deep(.arco-list) {
.arco-list-item {
min-height: 86px;
border-bottom: 1px solid rgb(var(--gray-3));
@gap: 8px;
@actions-width: 82px;
.skeleton-wrapper {
padding: 8px 12px 0 12px;
}
.message-list-container {
width: 100%;
height: 338px;
display: block;
.message-list-wrapper {
width: 100%;
height: 100%;
position: relative;
}
.arco-list-item-extra {
position: absolute;
right: 20px;
.load-more-wrapper {
display: flex;
justify-content: center;
margin: 12px 0;
}
}
.message-item {
padding: 12px 20px;
border-bottom: 1px solid var(--color-neutral-3);
display: flex;
flex-direction: column;
justify-content: space-between;
font-size: 14px;
cursor: pointer;
transition: all .2s;
&-title {
height: 22px;
display: flex;
justify-content: space-between;
align-items: flex-start;
&-text {
width: calc(100% - @actions-width - @gap);
display: block;
font-size: 14px;
text-overflow: clip;
color: var(--color-text-1);
}
&-status {
width: @actions-width;
display: flex;
align-items: flex-start;
justify-content: flex-end;
}
&-actions {
width: @actions-width;
display: none;
justify-content: flex-end;
align-items: flex-end;
button {
padding: 0 6px !important;
&:hover {
background: var(--color-fill-3) !important;
}
}
}
}
.arco-list-item-meta-content {
flex: 1;
}
.item-wrap {
cursor: pointer;
}
.time-text {
&-content {
display: block;
margin-top: 4px;
font-size: 12px;
color: rgb(var(--gray-6));
color: var(--color-text-2);
}
.arco-empty {
&-time {
height: 18px;
margin-top: 4px;
display: block;
font-size: 12px;
color: var(--color-text-2);
}
}
.message-item:hover {
background: var(--color-fill-1);
.message-item-title-status {
display: none;
}
.arco-list-footer {
padding: 0;
height: 50px;
line-height: 50px;
border-top: none;
.arco-space-item {
width: 100%;
border-right: 1px solid rgb(var(--gray-3));
&:last-child {
border-right: none;
}
}
.add-border-top {
border-top: 1px solid rgb(var(--gray-3));
}
}
.footer-wrap {
text-align: center;
}
.arco-typography {
margin-bottom: 0;
}
.add-border {
border-top: 1px solid rgb(var(--gray-3));
.message-item-title-actions {
display: flex;
opacity: 1;
}
}
.message-item-read {
.message-item-title-text, .message-item-title-status, .message-item-content, .message-item-time {
opacity: .65;
}
}
:deep(.arco-scrollbar) {
position: absolute;
height: 100%;
width: 100%;
}
</style>

View File

@@ -1,51 +1,37 @@
import type { AppRouteRecordRaw } from '../types';
import { DEFAULT_LAYOUT, FULL_LAYOUT } from '../base';
import { DEFAULT_LAYOUT } from '../base';
const EXEC: AppRouteRecordRaw[] = [
{
name: 'execModule',
path: '/exec-module',
component: DEFAULT_LAYOUT,
children: [
{
name: 'execCommand',
path: '/exec-command',
component: () => import('@/views/exec/exec-command/index.vue'),
},
{
name: 'execCommandLog',
path: '/exec-log',
component: () => import('@/views/exec/exec-command-log/index.vue'),
},
{
name: 'batchUpload',
path: '/batch-upload',
component: () => import('@/views/exec/batch-upload/index.vue'),
},
{
name: 'uploadTask',
path: '/upload-task',
component: () => import('@/views/exec/upload-task/index.vue'),
},
{
name: 'execTemplate',
path: '/exec-template',
component: () => import('@/views/exec/exec-template/index.vue'),
},
],
},
{
name: 'execFullModule',
path: '/exec-full-module',
component: FULL_LAYOUT,
children: [
{
name: 'execCommandLogView',
path: '/exec-log-view',
component: () => import('@/views/exec/exec-command-log-view/index.vue'),
},
],
}
];
const EXEC: AppRouteRecordRaw = {
name: 'execModule',
path: '/exec-module',
component: DEFAULT_LAYOUT,
children: [
{
name: 'execCommand',
path: '/exec-command',
component: () => import('@/views/exec/exec-command/index.vue'),
},
{
name: 'execCommandLog',
path: '/exec-log',
component: () => import('@/views/exec/exec-command-log/index.vue'),
},
{
name: 'batchUpload',
path: '/batch-upload',
component: () => import('@/views/exec/batch-upload/index.vue'),
},
{
name: 'uploadTask',
path: '/upload-task',
component: () => import('@/views/exec/upload-task/index.vue'),
},
{
name: 'execTemplate',
path: '/exec-template',
component: () => import('@/views/exec/exec-template/index.vue'),
},
],
};
export default EXEC;

View File

@@ -7,12 +7,12 @@ export default ({ mock, setup }: { mock?: boolean; setup: () => void }) => {
export const successResponseWrap = (data: unknown) => {
return {
data,
msg: '请求成功',
msg: 'success',
code: 200,
};
};
export const failResponseWrap = (data: unknown, msg: string, code = 5000) => {
export const failResponseWrap = (data: unknown, msg: string, code = 500) => {
return {
data,
msg,

View File

@@ -7,99 +7,18 @@ import setupMock, { successResponseWrap } from '@/utils/setup-mock';
const textList = [
{
key: 1,
clickNumber: '346.3w+',
title: '经济日报:财政政策要精准提升…',
clickNumber: '1w+',
title: 'text...',
increases: 35,
},
{
key: 2,
clickNumber: '324.2w+',
title: '双12遇冷消费者厌倦了电商平…',
clickNumber: '2w+',
title: 'text...',
increases: 22,
},
{
key: 3,
clickNumber: '318.9w+',
title: '致敬坚守战“疫”一线的社区工作…',
increases: 9,
},
{
key: 4,
clickNumber: '257.9w+',
title: '普高还是职高?家长们陷入选择…',
increases: 17,
},
{
key: 5,
clickNumber: '124.2w+',
title: '人民快评:没想到“浓眉大眼”的…',
increases: 37,
},
];
const imageList = [
{
key: 1,
clickNumber: '15.3w+',
title: '杨涛接替陆慷出任外交部美大司…',
increases: 15,
},
{
key: 2,
clickNumber: '12.2w+',
title: '图集:龙卷风袭击美国多州房屋…',
increases: 26,
},
{
key: 3,
clickNumber: '18.9w+',
title: '52岁大姐贴钱照顾自闭症儿童八…',
increases: 9,
},
{
key: 4,
clickNumber: '7.9w+',
title: '杭州一家三口公园宿营取暖中毒',
increases: 0,
},
{
key: 5,
clickNumber: '5.2w+',
title: '派出所副所长威胁市民?警方调…',
increases: 4,
},
];
const videoList = [
{
key: 1,
clickNumber: '367.6w+',
title: '这是今日10点的南京',
increases: 5,
},
{
key: 2,
clickNumber: '352.2w+',
title: '立陶宛不断挑衅致经济受损民众…',
increases: 17,
},
{
key: 3,
clickNumber: '348.9w+',
title: '韩国艺人刘在石确诊新冠',
increases: 30,
},
{
key: 4,
clickNumber: '346.3w+',
title: '关于北京冬奥会,文在寅表态',
increases: 12,
},
{
key: 5,
clickNumber: '271.2w+',
title: '95后现役军人荣立一等功',
increases: 2,
},
];
setupMock({
setup() {
Mock.mock(new RegExp('/api/content-data'), () => {
@@ -117,13 +36,11 @@ setupMock({
});
Mock.mock(new RegExp('/api/popular/list'), (params: GetParams) => {
const { type = 'text' } = qs.parseUrl(params.url).query;
if (type === 'image') {
return successResponseWrap([...videoList]);
if (type === 'text') {
return successResponseWrap([...textList]);
} else {
return successResponseWrap([]);
}
if (type === 'video') {
return successResponseWrap([...imageList]);
}
return successResponseWrap([...textList]);
});
},
});

View File

@@ -76,7 +76,7 @@
const { loading, setLoading } = useLoading();
const pullStatusId = ref();
const pullIntervalId = ref();
const taskId = ref();
const task = ref<UploadTaskQueryResponse>({} as UploadTaskQueryResponse);
const selectedHost = ref();
@@ -266,12 +266,12 @@
// 设置轮询状态
onMounted(() => {
pullStatusId.value = setInterval(pullTaskStatus, 5000);
pullIntervalId.value = setInterval(pullTaskStatus, 5000);
});
// 卸载状态查询
onUnmounted(() => {
clearInterval(pullStatusId.value);
clearInterval(pullIntervalId.value);
});
</script>

View File

@@ -28,10 +28,14 @@
await dictStore.loadKeys(dictKeys);
});
// 跳转日志
onMounted(async () => {
const idParam = route.query.id;
const keyParam = route.query.key;
if (idParam) {
await panel.value?.openLog(Number.parseInt(idParam as string));
} else if (keyParam) {
await panel.value?.openLog(Number.parseInt(keyParam as string));
}
});

View File

@@ -1,61 +0,0 @@
<template>
<div class="container">
<div class="wrapper">
<exec-log-panel ref="log"
type="BATCH"
:visible-back="false" />
</div>
</div>
</template>
<script lang="ts">
export default {
name: 'execCommandLogView'
};
</script>
<script lang="ts" setup>
import { onMounted, ref, nextTick } from 'vue';
import { useRoute } from 'vue-router';
import { getExecCommandLog } from '@/api/exec/exec-command-log';
import ExecLogPanel from '@/components/exec/log/panel/index.vue';
const route = useRoute();
const log = ref();
// 初始化
const init = async (id: number) => {
// 获取执行日志
const { data } = await getExecCommandLog(id);
// 打开日志
await nextTick(() => {
setTimeout(() => {
log.value.open(data);
}, 50);
});
};
onMounted(() => {
const idParam = route.query.id;
if (idParam) {
init(Number.parseInt(idParam as string));
}
});
</script>
<style lang="less" scoped>
.container {
width: 100%;
height: 100%;
position: relative;
padding: 16px;
background: var(--color-fill-2);
.wrapper {
width: 100%;
height: 100%;
position: relative;
}
}
</style>

View File

@@ -236,7 +236,7 @@
const { loading, setLoading } = useLoading();
const { toOptions, getDictValue } = useDictStore();
const intervalId = ref();
const pullIntervalId = ref();
const tableRef = ref();
const selectedKeys = ref<number[]>([]);
const tableRenderData = ref<ExecLogQueryResponse[]>([]);
@@ -343,7 +343,7 @@
};
// 加载状态
const fetchTaskStatus = async () => {
const pullExecStatus = async () => {
const unCompleteIdList = tableRenderData.value
.filter(s => s.status === execStatus.WAITING || s.status === execStatus.RUNNING)
.map(s => s.id);
@@ -374,7 +374,7 @@
host.status = s.status;
host.startTime = s.startTime;
host.finishTime = s.finishTime;
host.exitStatus = s.exitStatus;
host.exitCode = s.exitCode;
host.errorMessage = s.errorMessage;
});
};
@@ -409,12 +409,12 @@
// 加载数据
fetchTableData();
// 注册状态轮询
intervalId.value = setInterval(fetchTaskStatus, 10000);
pullIntervalId.value = setInterval(pullExecStatus, 10000);
});
onUnmounted(() => {
// 卸载状态轮询
clearInterval(intervalId.value);
clearInterval(pullIntervalId.value);
});
</script>

View File

@@ -69,7 +69,7 @@
if (newWindow) {
// 跳转新页面
openNewRoute({
name: 'execCommandLogView',
name: 'execCommand',
query: {
id
}

View File

@@ -18,12 +18,12 @@ const columns = [
tooltip: true,
}, {
title: '退出码',
dataIndex: 'exitStatus',
slotName: 'exitStatus',
dataIndex: 'exitCode',
slotName: 'exitCode',
align: 'left',
width: 118,
render: ({ record }) => {
return isNumber(record.exitStatus) ? record.exitStatus : '-';
return isNumber(record.exitCode) ? record.exitCode : '-';
},
}, {
title: '执行状态',

View File

@@ -26,10 +26,13 @@
import useVisible from '@/hooks/visible';
import { useDictStore } from '@/store';
import { dictKeys } from '@/components/exec/log/const';
import { useRoute } from 'vue-router';
import { getExecCommandLog } from '@/api/exec/exec-command-log';
import ExecCommandPanel from './components/exec-command-panel.vue';
import ExecLogPanel from '@/components/exec/log/panel/index.vue';
const { visible: logVisible, setVisible: setLogVisible } = useVisible();
const route = useRoute();
const log = ref();
@@ -41,12 +44,30 @@
});
};
// 打开日志
const openLogWithId = async (id: number) => {
setLogVisible(true);
// 查询日志
const { data } = await getExecCommandLog(id);
openLog(data);
};
// 加载字典值
onMounted(async () => {
const dictStore = useDictStore();
await dictStore.loadKeys(dictKeys);
});
// 跳转日志
onMounted(async () => {
const idParam = route.query.id;
const keyParam = route.query.key;
if (idParam) {
await openLogWithId(Number.parseInt(idParam as string));
} else if (keyParam) {
await openLogWithId(Number.parseInt(keyParam as string));
}
});
</script>
<style lang="less" scoped>

View File

@@ -199,7 +199,7 @@
const { loading, setLoading } = useLoading();
const { toOptions, getDictValue } = useDictStore();
const intervalId = ref();
const pullIntervalId = ref();
const selectedKeys = ref<number[]>([]);
const tableRenderData = ref<UploadTaskQueryResponse[]>([]);
const formModel = reactive<UploadTaskQueryRequest>({
@@ -264,7 +264,7 @@
};
// 加载状态
const fetchTaskStatus = async () => {
const pullTaskStatus = async () => {
const unCompleteIdList = tableRenderData.value
.filter(s => s.status === UploadTaskStatus.WAITING || s.status === UploadTaskStatus.UPLOADING)
.map(s => s.id);
@@ -311,12 +311,12 @@
// 加载数据
fetchTableData();
// 注册状态轮询
intervalId.value = setInterval(fetchTaskStatus, 10000);
pullIntervalId.value = setInterval(pullTaskStatus, 10000);
});
onUnmounted(() => {
// 卸载状态轮询
clearInterval(intervalId.value);
clearInterval(pullIntervalId.value);
});
</script>

View File

@@ -14,7 +14,7 @@ export default class SftpTransferManager implements ISftpTransferManager {
private run: boolean;
private progressId?: number;
private progressIntervalId?: number;
private currentItem?: SftpTransferItem;
@@ -136,7 +136,7 @@ export default class SftpTransferManager implements ISftpTransferManager {
// 处理消息
this.client.onmessage = this.resolveMessage.bind(this);
// 计算传输进度
this.progressId = setInterval(this.calcProgress.bind(this), 500);
this.progressIntervalId = setInterval(this.calcProgress.bind(this), 500);
// 打开后自动传输下一个任务
this.transferNextItem();
}
@@ -275,7 +275,7 @@ export default class SftpTransferManager implements ISftpTransferManager {
// 重置 run
this.run = false;
// 关闭传输进度
clearInterval(this.progressId);
clearInterval(this.progressIntervalId);
}
}

View File

@@ -23,7 +23,7 @@ export default class TerminalSessionManager implements ITerminalSessionManager {
private sessions: Record<string, ITerminalSession>;
private keepAliveTask?: any;
private keepAliveTaskId?: any;
private readonly dispatchResizeFn: () => {};
@@ -136,7 +136,7 @@ export default class TerminalSessionManager implements ITerminalSessionManager {
// 注册 resize 事件
addEventListen(window, 'resize', this.dispatchResizeFn);
// 注册 ping 事件
this.keepAliveTask = setInterval(() => {
this.keepAliveTaskId = setInterval(() => {
this.channel.send(InputProtocol.PING, {});
}, 15000);
}
@@ -158,10 +158,7 @@ export default class TerminalSessionManager implements ITerminalSessionManager {
// 关闭 channel
this.channel.close();
// 清除 ping 事件
if (this.keepAliveTask) {
clearInterval(this.keepAliveTask);
this.keepAliveTask = undefined;
}
clearInterval(this.keepAliveTaskId);
// 移除 resize 事件
removeEventListen(window, 'resize', this.dispatchResizeFn);
} catch (e) {

View File

@@ -217,7 +217,7 @@
const { loading, setLoading } = useLoading();
const { toOptions, getDictValue } = useDictStore();
const intervalId = ref();
const pullIntervalId = ref();
const tableRef = ref();
const selectedKeys = ref<number[]>([]);
const tableRenderData = ref<ExecLogQueryResponse[]>([]);
@@ -308,7 +308,7 @@
};
// 加载状态
const fetchTaskStatus = async () => {
const pullJobStatus = async () => {
const unCompleteIdList = tableRenderData.value
.filter(s => s.status === execStatus.WAITING || s.status === execStatus.RUNNING)
.map(s => s.id);
@@ -339,7 +339,7 @@
host.status = s.status;
host.startTime = s.startTime;
host.finishTime = s.finishTime;
host.exitStatus = s.exitStatus;
host.exitCode = s.exitCode;
host.errorMessage = s.errorMessage;
});
};
@@ -374,12 +374,12 @@
// 加载数据
fetchTableData();
// 注册状态轮询
intervalId.value = setInterval(fetchTaskStatus, 10000);
pullIntervalId.value = setInterval(pullJobStatus, 10000);
});
onUnmounted(() => {
// 卸载状态轮询
clearInterval(intervalId.value);
clearInterval(pullIntervalId.value);
});
</script>

View File

@@ -22,7 +22,7 @@
</modules>
<properties>
<revision>1.0.7</revision>
<revision>1.0.8</revision>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<maven.surefire.plugin.version>3.0.0-M5</maven.surefire.plugin.version>

View File

@@ -211,7 +211,7 @@ CREATE TABLE `exec_host_log`
`status` char(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '执行状态',
`command` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '执行命令',
`parameter` json NULL COMMENT '执行参数',
`exit_status` int(0) NULL DEFAULT NULL COMMENT '退出码',
`exit_code` int(0) NULL DEFAULT NULL COMMENT '退出码',
`log_path` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '日志路径',
`script_path` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '脚本路径',
`error_message` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '错误信息',
@@ -644,6 +644,32 @@ CREATE TABLE `system_menu`
COLLATE = utf8mb4_unicode_ci COMMENT = '菜单表'
ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for system_message
-- ----------------------------
DROP TABLE IF EXISTS `system_message`;
CREATE TABLE `system_message`
(
`id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id',
`classify` char(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '消息分类',
`type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '消息类型',
`status` tinyint(0) NULL DEFAULT NULL COMMENT '消息状态',
`rel_key` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '消息关联',
`title` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标题',
`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容',
`receiver_id` bigint(0) NULL DEFAULT NULL COMMENT '接收人id',
`receiver_username` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '接收人用户名',
`create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '修改时间',
`deleted` tinyint(1) NULL DEFAULT 0 COMMENT '是否删除 0未删除 1已删除',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_receiver_classify` (`receiver_id`, `classify`) USING BTREE
) ENGINE = InnoDB
AUTO_INCREMENT = 1
CHARACTER SET = utf8mb4
COLLATE = utf8mb4_general_ci COMMENT = '系统消息'
ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for system_role
-- ----------------------------

View File

@@ -90,7 +90,7 @@ INSERT INTO `system_menu` VALUES (157, 122, '清空操作日志', 'infra:operato
INSERT INTO `system_menu` VALUES (158, 152, '文件操作日志', NULL, 2, 30, 1, 1, 1, 0, 'IconFile', NULL, 'sftpLog', '2024-03-05 15:30:13', '2024-05-07 11:11:24', '1', '1', 0);
INSERT INTO `system_menu` VALUES (159, 158, '查询文件操作日志', 'asset:host-sftp-log:management:query', 3, 10, 1, 1, 1, 0, NULL, NULL, NULL, '2024-03-05 15:31:02', '2024-04-12 14:49:18', '1', '1', 0);
INSERT INTO `system_menu` VALUES (160, 158, '删除文件操作日志', 'asset:host-sftp-log:management:delete', 3, 20, 1, 1, 1, 0, NULL, NULL, NULL, '2024-03-05 15:31:17', '2024-04-12 14:49:21', '1', '1', 0);
INSERT INTO `system_menu` VALUES (161, 176, '执行模板', NULL, 2, 60, 1, 1, 1, 0, 'IconBookmark', NULL, 'execTemplate', '2024-03-07 18:32:41', '2024-04-28 17:14:30', '1', '1', 0);
INSERT INTO `system_menu` VALUES (161, 176, '执行模板', NULL, 2, 50, 1, 1, 1, 0, 'IconBookmark', NULL, 'execTemplate', '2024-03-07 18:32:41', '2024-05-14 15:58:51', '1', '1', 0);
INSERT INTO `system_menu` VALUES (162, 161, '查询执行模板', 'asset:exec-template:query', 3, 10, 1, 1, 1, 0, NULL, NULL, NULL, '2024-03-07 18:32:41', '2024-03-07 18:32:41', '1', '1', 0);
INSERT INTO `system_menu` VALUES (163, 161, '创建执行模板', 'asset:exec-template:create', 3, 20, 1, 1, 1, 0, NULL, NULL, NULL, '2024-03-07 18:32:41', '2024-03-07 18:32:41', '1', '1', 0);
INSERT INTO `system_menu` VALUES (164, 161, '修改执行模板', 'asset:exec-template:update', 3, 30, 1, 1, 1, 0, NULL, NULL, NULL, '2024-03-07 18:32:41', '2024-03-07 18:32:41', '1', '1', 0);
@@ -102,7 +102,6 @@ INSERT INTO `system_menu` VALUES (171, 167, '清理执行日志', 'asset:exec-co
INSERT INTO `system_menu` VALUES (172, 176, '命令执行', NULL, 2, 10, 1, 1, 1, 0, 'icon-thunderbolt', NULL, 'execCommand', '2024-03-13 15:08:23', '2024-05-08 21:58:24', '1', '1', 0);
INSERT INTO `system_menu` VALUES (173, 172, '执行命令', 'asset:exec-command:exec', 3, 10, 1, 1, 1, 0, NULL, NULL, NULL, '2024-03-13 15:08:23', '2024-04-10 22:04:32', '1', '1', 0);
INSERT INTO `system_menu` VALUES (174, 167, '中断批量执行', 'asset:exec-command-log:interrupt', 3, 40, 1, 1, 1, 0, NULL, NULL, NULL, '2024-03-13 15:25:36', '2024-04-12 12:48:36', '1', '1', 0);
INSERT INTO `system_menu` VALUES (175, 176, '批量执行日志新页面', NULL, 2, 30, 0, 1, 0, 1, '', NULL, 'execCommandLogView', '2024-03-21 14:03:10', '2024-04-11 23:45:08', '1', '1', 0);
INSERT INTO `system_menu` VALUES (176, 0, '批量执行', NULL, 1, 420, 1, 1, 1, 0, 'icon-relation', NULL, 'execModule', '2024-04-10 16:13:27', '2024-04-28 15:30:31', '1', '1', 0);
INSERT INTO `system_menu` VALUES (177, 193, '任务列表', NULL, 2, 10, 1, 1, 1, 0, 'IconUnorderedList', NULL, 'execJob', '2024-04-10 16:13:27', '2024-04-28 15:36:10', '1', '1', 0);
INSERT INTO `system_menu` VALUES (178, 177, '查询计划任务', 'asset:exec-job:query', 3, 10, 1, 1, 1, 0, NULL, NULL, NULL, '2024-04-10 16:13:27', '2024-04-10 16:13:27', '1', '1', 0);
@@ -123,8 +122,8 @@ INSERT INTO `system_menu` VALUES (193, 0, '计划任务', NULL, 1, 430, 1, 1, 1,
INSERT INTO `system_menu` VALUES (194, 152, '在线会话', NULL, 2, 20, 1, 1, 1, 0, 'IconUserGroup', NULL, 'connectSession', '2024-05-07 11:12:17', '2024-05-07 11:12:35', '1', '1', 0);
INSERT INTO `system_menu` VALUES (195, 194, '查询在线会话', 'asset:host-connect-session:management:query', 3, 10, 1, 1, 1, 0, NULL, NULL, NULL, '2024-05-07 11:13:16', '2024-05-07 11:13:16', '1', '1', 0);
INSERT INTO `system_menu` VALUES (196, 194, '强制断开连接', 'asset:host-connect-session:management:force-offline', 3, 20, 1, 1, 1, 0, NULL, NULL, NULL, '2024-05-07 11:13:37', '2024-05-07 11:13:37', '1', '1', 0);
INSERT INTO `system_menu` VALUES (197, 176, '批量上传', NULL, 2, 40, 1, 1, 1, 0, 'IconUpload', NULL, 'batchUpload', '2024-05-08 22:12:23', '2024-05-08 22:12:23', '1', '1', 0);
INSERT INTO `system_menu` VALUES (198, 176, '上传任务', NULL, 2, 50, 1, 1, 1, 0, 'IconCloud', NULL, 'uploadTask', '2024-05-08 22:16:05', '2024-05-10 23:09:58', '1', '1', 0);
INSERT INTO `system_menu` VALUES (197, 176, '批量上传', NULL, 2, 30, 1, 1, 1, 0, 'IconUpload', NULL, 'batchUpload', '2024-05-08 22:12:23', '2024-05-14 15:58:44', '1', '1', 0);
INSERT INTO `system_menu` VALUES (198, 176, '上传任务', NULL, 2, 40, 1, 1, 1, 0, 'IconCloud', NULL, 'uploadTask', '2024-05-08 22:16:05', '2024-05-14 15:58:46', '1', '1', 0);
INSERT INTO `system_menu` VALUES (199, 197, '上传文件', 'asset:upload-task:upload', 3, 10, 1, 1, 1, 0, NULL, NULL, NULL, '2024-05-08 22:19:35', '2024-05-08 22:19:35', '1', '1', 0);
INSERT INTO `system_menu` VALUES (200, 198, '查询上传日志', 'asset:upload-task:query', 3, 10, 1, 1, 1, 0, NULL, NULL, NULL, '2024-05-08 22:20:01', '2024-05-08 22:20:01', '1', '1', 0);
INSERT INTO `system_menu` VALUES (201, 198, '删除上传日志', 'asset:upload-task:delete', 3, 20, 1, 1, 1, 0, NULL, NULL, NULL, '2024-05-08 22:20:26', '2024-05-08 22:20:26', '1', '1', 0);
@@ -165,6 +164,8 @@ INSERT INTO `dict_key` VALUES (39, 'pathBookmarkType', 'STRING', '[]', '路径
INSERT INTO `dict_key` VALUES (40, 'sftpTransferStatus', 'STRING', '[{\"name\": \"status\", \"type\": \"STRING\"}, {\"name\": \"color\", \"type\": \"COLOR\"}, {\"name\": \"icon\", \"type\": \"STRING\"}]', 'SFTP 传输状态', '2024-05-06 11:54:49', '2024-05-06 11:54:49', '1', '1', 0);
INSERT INTO `dict_key` VALUES (41, 'uploadTaskStatus', 'STRING', '[{\"name\": \"color\", \"type\": \"COLOR\"}]', '上传任务状态', '2024-05-07 22:18:48', '2024-05-08 22:06:23', '1', '1', 0);
INSERT INTO `dict_key` VALUES (42, 'uploadTaskFileStatus', 'STRING', '[{\"name\": \"status\", \"type\": \"STRING\"}]', '上传任务文件状态', '2024-05-08 10:30:29', '2024-05-10 17:34:13', '1', '1', 0);
INSERT INTO `dict_key` VALUES (43, 'messageType', 'STRING', '[{\"name\": \"tagLabel\", \"type\": \"STRING\"}, {\"name\": \"tagVisible\", \"type\": \"STRING\"}, {\"name\": \"tagColor\", \"type\": \"STRING\"}, {\"name\": \"redirectComponent\", \"type\": \"STRING\"}]', '消息类型', '2024-05-13 12:07:56', '2024-05-14 14:48:28', '1', '1', 0);
INSERT INTO `dict_key` VALUES (44, 'messageClassify', 'STRING', '[]', '消息分类', '2024-05-13 15:06:27', '2024-05-13 15:06:27', '1', '1', 0);
-- 字典值
INSERT INTO `dict_value` VALUES (3, 4, 'systemMenuType', '1', '父菜单', '{}', 10, '2023-10-26 15:58:59', '2023-10-26 15:58:59', '1', '1', 0);
@@ -405,3 +406,7 @@ INSERT INTO `dict_value` VALUES (291, 2, 'operatorLogType', 'upload-task:cancel'
INSERT INTO `dict_value` VALUES (292, 2, 'operatorLogType', 'upload-task:delete', '删除上传记录', '{}', 30, '2024-05-08 22:23:44', '2024-05-08 22:23:44', '1', '1', 0);
INSERT INTO `dict_value` VALUES (293, 2, 'operatorLogType', 'upload-task:clear', '清理上传记录', '{}', 40, '2024-05-08 22:23:59', '2024-05-08 22:23:59', '1', '1', 0);
INSERT INTO `dict_value` VALUES (294, 41, 'uploadTaskStatus', 'FAILED', '已失败', '{\"color\": \"red\"}', 40, '2024-05-10 11:29:17', '2024-05-10 11:29:17', '1', '1', 0);
INSERT INTO `dict_value` VALUES (295, 43, 'messageType', 'EXEC_FAILED', '执行失败', '{\"tagColor\": \"red\", \"tagLabel\": \"部分失败\", \"tagVisible\": \"true\", \"redirectComponent\": \"execCommand\"}', 10, '2024-05-13 12:07:56', '2024-05-14 15:19:19', '1', '1', 0);
INSERT INTO `dict_value` VALUES (296, 43, 'messageType', 'UPLOAD_FAILED', '上传失败', '{\"tagColor\": \"red\", \"tagLabel\": \"部分失败\", \"tagVisible\": \"true\", \"redirectComponent\": \"batchUpload\"}', 20, '2024-05-13 12:07:56', '2024-05-14 15:11:21', '1', '1', 0);
INSERT INTO `dict_value` VALUES (297, 44, 'messageClassify', 'NOTICE', '通知', '{}', 10, '2024-05-13 15:06:27', '2024-05-13 15:06:27', '1', '1', 0);
INSERT INTO `dict_value` VALUES (298, 44, 'messageClassify', 'TODO', '待办', '{}', 20, '2024-05-13 15:06:27', '2024-05-13 15:06:27', '1', '1', 0);