Compare commits

..

35 Commits

Author SHA1 Message Date
李佳航
fbf4299a61 Merge pull request #150 from dromara/dev
🔨 替换换行符.
2025-11-12 00:35:52 +08:00
lijiahangmax
ad42be8fe8 🔨 替换换行符. 2025-11-12 00:34:48 +08:00
李佳航
d9c8923b6d Merge pull request #149 from dromara/dev
Dev
2025-11-12 00:19:03 +08:00
lijiahangmax
aa913bce8d 🔖 升级版本. 2025-11-12 00:11:39 +08:00
lijiahangmax
3e3af89939 🔖 升级版本. 2025-11-11 11:30:22 +08:00
lijiahangmax
783baaf8c8 🐛 修复 sftp 下载文件失败. 2025-11-11 11:29:51 +08:00
lijiahangmax
a53565e06b 🔨 替换换行符. 2025-11-10 21:46:08 +08:00
lijiahangmax
061495eb13 🔨 修改文案. 2025-11-10 09:57:36 +08:00
lijiahangmax
6514363847 🔨 用户会话列表. 2025-11-08 09:40:44 +08:00
lijiahangmax
c3da882950 🔨 用户锁定列表. 2025-11-07 10:01:16 +08:00
lijiahangmax
6deebedc75 🔨 解锁用户. 2025-11-06 09:50:09 +08:00
lijiahangmax
2012f20a09 Merge remote-tracking branch 'origin/dev' into dev 2025-11-03 14:25:56 +08:00
lijiahangmax
2377c50187 🔨 锁定用户列表. 2025-11-03 14:25:45 +08:00
lijiahangmax
91b22297a2 🔨 修改重复提示. 2025-11-02 01:06:12 +08:00
lijiahangmax
5bbf46d141 🔨 添加规格信息. 2025-11-01 22:52:50 +08:00
lijiahangmax
3c7a0947ee 🔨 优化代码逻辑. 2025-10-31 09:39:03 +08:00
lijiahangmax
83c64dddfb 🔨 修改认证逻辑. 2025-10-30 16:42:43 +08:00
lijiahangmax
5d86c330fe 🔨 tag 管理. 2025-10-29 10:39:19 +08:00
lijiahangmax
6a13d3cb22 🔨 tag 管理. 2025-10-28 13:58:33 +08:00
lijiahangmax
3a8addb4d2 🔨 IP 请求头可配置. 2025-10-27 09:50:17 +08:00
lijiahangmax
90705781f2 🔨 优化操作日志逻辑. 2025-10-24 11:18:54 +08:00
李佳航
d8818c3ec2 Merge pull request #143 from dromara/dev
Dev
2025-10-21 17:04:29 +08:00
lijiahangmax
91fecad956 🔨 升级 sql 脚本. 2025-10-21 16:56:47 +08:00
lijiahangmax
9635aa34a7 🔨 修改标题样式. 2025-10-21 13:52:02 +08:00
lijiahangmax
a2f7ab7f9c 🔨 升级版本. 2025-10-20 00:23:48 +08:00
lijiahangmax
55d0dfd27d 🔨 优化告警引擎. 2025-10-19 15:34:53 +08:00
lijiahangmax
eb18142926 🔨 可调整列宽. 2025-10-18 22:21:30 +08:00
lijiahangmax
0649c4e3de 🔨 优化错误提示. 2025-10-17 14:14:34 +08:00
lijiahangmax
f648e18557 🔨 优化数据分组逻辑. 2025-10-17 14:12:14 +08:00
lijiahangmax
9d3b46e9b3 🔨 策略描述非必填. 2025-10-15 15:07:37 +08:00
lijiahangmax
14dfe457bf 🔨 优化告警引擎. 2025-10-15 01:35:40 +08:00
lijiahangmax
9651354317 🔨 添加策略类型. 2025-10-13 18:23:07 +08:00
lijiahangmax
8929aa2f74 🔨 优化监控逻辑. 2025-10-13 17:39:00 +08:00
lijiahangmax
ea98592012 🔨 监控页面连接终端. 2025-10-10 13:26:01 +08:00
lijiahangmax
b0be444fba 🔨 优化系统配置逻辑. 2025-10-09 14:31:43 +08:00
225 changed files with 3690 additions and 1255 deletions

View File

@@ -6,6 +6,7 @@ SPRING_PROFILES_ACTIVE=prod
DEMO_MODE=false
API_CORS=true
API_IP_HEADERS=X-Forwarded-For,X-Real-IP
API_EXPOSE_TOKEN=pmqeHOyZaumHm0Wt
SECRET_KEY=uQeacXV8b3isvKLK

View File

@@ -1,6 +1,6 @@
version: '3.3'
# latest = 2.5.3
# latest = 2.5.5
# 支持以下源
# lijiahangmax/*
@@ -49,6 +49,7 @@ services:
GUACD_DRIVE_PATH: ${GUACD_DRIVE_PATH:-/drive}
SECRET_KEY: ${SECRET_KEY:-uQeacXV8b3isvKLK}
API_EXPOSE_TOKEN: ${API_EXPOSE_TOKEN:-pmqeHOyZaumHm0Wt}
API_IP_HEADERS: ${API_IP_HEADERS:-X-Forwarded-For,X-Real-IP}
API_CORS: ${API_CORS:-true}
DEMO_MODE: ${DEMO_MODE:-false}
volumes:

View File

@@ -7,7 +7,7 @@ set -e
source ./project-build.sh "$@"
# 版本号
version=2.5.3
version=2.5.5
# 是否推送镜像
push_image=false
# 是否构建 latest

View File

@@ -4,7 +4,7 @@ set -e
# DockerContext: orion-visor
# 版本号
version=2.5.3
version=2.5.5
# 是否构建 service
export build_service=false
# 是否构建 ui

View File

@@ -1,4 +1,4 @@
FROM --platform=$TARGETPLATFORM openjdk:8-jdk-alpine
FROM --platform=$TARGETPLATFORM openjdk:8u171-jdk-alpine3.7
USER root

View File

@@ -0,0 +1,55 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.common.configuration;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.common.utils.IpUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
/**
* 公共配置类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/6/20 10:34
*/
@Slf4j
@Configuration
public class CommonConfiguration {
@Value("${orion.api.ip-headers}")
private String[] ipHeaders;
/**
* 设置 IP 请求头
*/
@PostConstruct
public void setIpHeader() {
IpUtils.setIpHeader(ipHeaders);
log.info("IpUtils.setIpHeader {}", String.join(",", ipHeaders));
}
}

View File

@@ -36,7 +36,7 @@ public interface AppConst extends OrionConst {
/**
* 同 ${orion.version} 迭代时候需要手动更改
*/
String VERSION = "2.5.3";
String VERSION = "2.5.5";
/**
* 同 ${spring.application.name}

View File

@@ -34,116 +34,116 @@ public interface ConfigKeys {
/**
* SFTP 文件预览大小
*/
String SFTP_PREVIEW_SIZE = "sftp_previewSize";
String SFTP_PREVIEW_SIZE = "sftp.preview-size";
/**
* SFTP 重复文件备份
*/
String SFTP_UPLOAD_PRESENT_BACKUP = "sftp_uploadPresentBackup";
String SFTP_UPLOAD_PRESENT_BACKUP = "sftp.upload-present-backup";
/**
* SFTP 备份文件名称
*/
String SFTP_UPLOAD_BACKUP_FILE_NAME = "sftp_uploadBackupFileName";
String SFTP_UPLOAD_BACKUP_FILE_NAME = "sftp.upload-backup-file-name";
/**
* 加密公钥
*/
String ENCRYPT_PUBLIC_KEY = "encrypt_publicKey";
String ENCRYPT_PUBLIC_KEY = "encrypt.public-key";
/**
* 加密私钥
*/
String ENCRYPT_PRIVATE_KEY = "encrypt_privateKey";
String ENCRYPT_PRIVATE_KEY = "encrypt.private-key";
/**
* 日志前端显示行数
*/
String LOG_WEB_SCROLL_LINES = "log_webScrollLines";
String LOG_WEB_SCROLL_LINES = "log.web-scroll-lines";
/**
* 日志加载偏移行
*/
String LOG_TRACKER_LOAD_LINES = "log_trackerLoadLines";
String LOG_TRACKER_LOAD_LINES = "log.tracker-load-lines";
/**
* 日志加载间隔毫秒
*/
String LOG_TRACKER_LOAD_INTERVAL = "log_trackerLoadInterval";
String LOG_TRACKER_LOAD_INTERVAL = "log.tracker-load-interval";
/**
* 是否生成详细的执行日志
*/
String LOG_EXEC_DETAIL_LOG = "log_execDetailLog";
String LOG_EXEC_DETAIL_ENABLED = "log.exec-detail.enabled";
/**
* 凭证有效期分
* 凭证有效期
*/
String LOGIN_LOGIN_SESSION_TIME = "login_loginSessionTime";
String LOGIN_LOGIN_SESSION_TIME = "login.login-session-time";
/**
* 允许多端登录
*/
String LOGIN_ALLOW_MULTI_DEVICE = "login_allowMultiDevice";
String LOGIN_ALLOW_MULTI_DEVICE = "login.allow-multi-device";
/**
* 允许凭证续签
*/
String LOGIN_ALLOW_REFRESH = "login_allowRefresh";
String LOGIN_ALLOW_REFRESH = "login.allow-refresh";
/**
* 凭证续签最大次数
*/
String LOGIN_MAX_REFRESH_COUNT = "login_maxRefreshCount";
String LOGIN_MAX_REFRESH_COUNT = "login.max-refresh-count";
/**
* 凭证续签间隔分
*/
String LOGIN_REFRESH_INTERVAL = "login_refreshInterval";
String LOGIN_REFRESH_INTERVAL = "login.refresh-interval";
/**
* 登录失败锁定
*/
String LOGIN_LOGIN_FAILED_LOCK = "login_loginFailedLock";
String LOGIN_LOGIN_FAILED_LOCK = "login.login-failed-lock";
/**
* 登录失败锁定阈值
* 登录失败锁定阈值
*/
String LOGIN_LOGIN_FAILED_LOCK_THRESHOLD = "login_loginFailedLockThreshold";
String LOGIN_LOGIN_FAILED_LOCK_THRESHOLD = "login.login-failed-lock-threshold";
/**
* 登录失败锁定时间分
* 登录失败锁定时间
*/
String LOGIN_LOGIN_FAILED_LOCK_TIME = "login_loginFailedLockTime";
String LOGIN_LOGIN_FAILED_LOCK_TIME = "login.login-failed-lock-time";
/**
* 登录失败发信
*/
String LOGIN_LOGIN_FAILED_SEND = "login_loginFailedSend";
String LOGIN_LOGIN_FAILED_SEND = "login.login-failed-send";
/**
* 登录失败发信阈值
*/
String LOGIN_LOGIN_FAILED_SEND_THRESHOLD = "login_loginFailedSendThreshold";
String LOGIN_LOGIN_FAILED_SEND_THRESHOLD = "login.login-failed-send-threshold";
/**
* 是否开启自动清理命令记录
*/
String AUTO_CLEAR_EXEC_LOG_ENABLED = "autoClear_execLogEnabled";
String AUTO_CLEAR_EXEC_LOG_ENABLED = "auto-clear.exec-log.enabled";
/**
* 自动清理命令记录保留天数
*/
String AUTO_CLEAR_EXEC_LOG_KEEP_DAYS = "autoClear_execLogKeepDays";
String AUTO_CLEAR_EXEC_LOG_KEEP_DAYS = "auto-clear.exec-log.keep-days";
/**
* 是否开启自动清理终端连接记录
*/
String AUTO_CLEAR_TERMINAL_LOG_ENABLED = "autoClear_terminalLogEnabled";
String AUTO_CLEAR_TERMINAL_LOG_ENABLED = "auto-clear.terminal-log.enabled";
/**
* 自动清理终端连接记录保留天数
*/
String AUTO_CLEAR_TERMINAL_LOG_KEEP_DAYS = "autoClear_terminalLogKeepDays";
String AUTO_CLEAR_TERMINAL_LOG_KEEP_DAYS = "auto-clear.terminal-log.keep-days";
}

View File

@@ -33,6 +33,20 @@ import java.io.Serializable;
*/
public interface RequestIdentity extends Serializable {
/**
* 获取请求时间戳
*
* @return timestamp
*/
Long getTimestamp();
/**
* 设置请求时间戳
*
* @param timestamp timestamp
*/
void setTimestamp(Long timestamp);
/**
* 获取请求地址
*

View File

@@ -23,7 +23,10 @@
package org.dromara.visor.common.entity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 请求留痕模型
@@ -33,9 +36,15 @@ import lombok.Data;
* @since 2023/12/29 11:57
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "RequestIdentityModel", description = "请求留痕模型")
public class RequestIdentityModel implements RequestIdentity {
@Schema(description = "时间戳")
private Long timestamp;
@Schema(description = "请求地址")
private String address;

View File

@@ -0,0 +1,59 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.common.mapstruct;
import java.util.Date;
/**
* date 转换器
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/3/7 17:43
*/
public class DateConversion {
private DateConversion() {
}
/**
* Long > Date
*
* @param timestamp timestamp
* @return Date
*/
public static Date longToDate(Long timestamp) {
return timestamp != null ? new Date(timestamp) : null;
}
/**
* Date > Long
*
* @param date date
* @return Long
*/
public static Long dateToLong(Date date) {
return date != null ? date.getTime() : null;
}
}

View File

@@ -24,7 +24,7 @@ package org.dromara.visor.common.utils;
import cn.orionsec.kit.ext.location.Region;
import cn.orionsec.kit.ext.location.region.LocationRegions;
import cn.orionsec.kit.web.servlet.web.Servlets;
import cn.orionsec.kit.lang.utils.net.IPs;
import org.dromara.visor.common.constant.Const;
import javax.servlet.http.HttpServletRequest;
@@ -40,6 +40,8 @@ import java.util.Map;
*/
public class IpUtils {
private static String[] IP_HEADER = new String[]{"X-Forwarded-For", "X-Real-IP"};
private static final Map<String, String> CACHE = new HashMap<>();
private IpUtils() {
@@ -52,13 +54,17 @@ public class IpUtils {
* @return addr
*/
public static String getRemoteAddr(HttpServletRequest request) {
// 获取实际地址 X_REAL_IP 在多代理情况下会有问题
// String realIp = request.getHeader(StandardHttpHeader.X_REAL_IP);
// if (!Strings.isBlank(realIp)) {
// return realIp;
// }
// 获取请求地址
return Servlets.getRemoteAddr(request);
if (request == null) {
return null;
} else {
for (String remoteAddrHeader : IP_HEADER) {
String addr = checkIpHeader(request.getHeader(remoteAddrHeader));
if (addr != null) {
return addr;
}
}
return checkIpHeader(request.getRemoteAddr());
}
}
/**
@@ -112,4 +118,23 @@ public class IpUtils {
return Const.CN_UNKNOWN;
}
/**
* 检查 ip 请求头
*
* @param headerValue headerValue
* @return header
*/
private static String checkIpHeader(String headerValue) {
if (headerValue == null) {
return null;
} else {
headerValue = headerValue.split(",")[0];
return IPs.checkIp(headerValue);
}
}
public static void setIpHeader(String[] ipHeader) {
IP_HEADER = ipHeader;
}
}

View File

@@ -64,10 +64,24 @@ public class Requests {
.map(ServletRequestAttributes::getRequest)
.ifPresent(request -> {
String address = IpUtils.getRemoteAddr(request);
identity.setTimestamp(System.currentTimeMillis());
identity.setAddress(address);
identity.setLocation(IpUtils.getLocation(address));
identity.setUserAgent(Servlets.getUserAgent(request));
});
}
/**
* 复制留痕信息
*
* @param source source
* @param target target
*/
public static void copyIdentity(RequestIdentityModel source, RequestIdentityModel target) {
target.setTimestamp(source.getTimestamp());
target.setAddress(source.getAddress());
target.setLocation(source.getLocation());
target.setUserAgent(source.getUserAgent());
}
}

View File

@@ -14,11 +14,11 @@
<url>https://github.com/dromara/orion-visor</url>
<properties>
<revision>2.5.3</revision>
<revision>2.5.5</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>
<orion.kit.version>2.0.4</orion.kit.version>
<orion.kit.version>2.0.5</orion.kit.version>
<aspectj.version>1.9.7</aspectj.version>
<lombok.version>1.18.26</lombok.version>
<springdoc.version>1.6.15</springdoc.version>

View File

@@ -123,8 +123,8 @@ public class OperatorLogModel implements RequestIdentity {
private Date endTime;
/**
* 创建时间
* 时间
*/
private Date createTime;
private Long timestamp;
}

View File

@@ -31,8 +31,8 @@ import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializeFilter;
import org.dromara.visor.common.entity.RequestIdentity;
import org.dromara.visor.common.enums.BooleanBit;
import org.dromara.visor.common.trace.TraceIdHolder;
import org.dromara.visor.common.security.LoginUser;
import org.dromara.visor.common.trace.TraceIdHolder;
import org.dromara.visor.common.utils.Requests;
import org.dromara.visor.framework.biz.operator.log.configuration.config.OperatorLogConfig;
import org.dromara.visor.framework.biz.operator.log.core.enums.ReturnType;
@@ -91,6 +91,7 @@ public class OperatorLogFiller implements Gettable<OperatorLogModel> {
*/
public OperatorLogFiller fillUsedTime(long start) {
long end = System.currentTimeMillis();
model.setTimestamp(start);
model.setDuration((int) (end - start));
model.setStartTime(new Date(start));
model.setEndTime(new Date(end));

View File

@@ -91,6 +91,7 @@
<a-table row-key="id"
#end
ref="tableRef"
class="table-resize"
:loading="loading"
:columns="tableColumns"
#if($vue.enableRowSelection)
@@ -99,6 +100,7 @@
:data="tableRenderData"
:pagination="pagination"
:bordered="false"
:column-resizable="true"
@page-change="(page: number) => fetchTableData(page, pagination.pageSize)"
@page-size-change="(size: number) => fetchTableData(1, size)">
#foreach($field in ${table.fields})

View File

@@ -24,6 +24,7 @@ package org.dromara.visor.framework.test.core.base;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
import org.dromara.visor.common.configuration.CommonConfiguration;
import org.dromara.visor.common.configuration.SpringConfiguration;
import org.dromara.visor.framework.datasource.configuration.OrionDataSourceAutoConfiguration;
import org.dromara.visor.framework.mybatis.configuration.OrionMybatisAutoConfiguration;
@@ -60,6 +61,7 @@ public class BaseUnitTest {
@Import({
// spring
SpringConfiguration.class,
CommonConfiguration.class,
// mock
OrionMockBeanTestConfiguration.class,
OrionMockRedisTestConfiguration.class,

View File

@@ -33,6 +33,11 @@
"type": "java.lang.Boolean",
"description": "是否开启 cors 过滤器."
},
{
"name": "orion.api.ip-headers",
"type": "java.lang.String",
"description": "获取 IP 的请求头."
},
{
"name": "orion.api.expose.header",
"type": "java.lang.String",

View File

@@ -14,7 +14,7 @@ spring:
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:Data@123456}
database: ${REDIS_DATABASE:1}
data-version: ${REDIS_DATA_VERSION:1}
data-version: ${REDIS_DATA_VERSION:2}
mock: false
redisson:
threads: 2

View File

@@ -25,7 +25,7 @@ spring:
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:Data@123456}
database: ${REDIS_DATABASE:0}
data-version: ${REDIS_DATA_VERSION:1}
data-version: ${REDIS_DATA_VERSION:2}
redisson:
threads: 4
netty-threads: 4
@@ -74,6 +74,9 @@ orion:
api:
# 是否允许跨域
cors: ${API_CORS:true}
# 获取 IP 的请求头
ip-headers: ${API_IP_HEADERS:X-Forwarded-For,X-Real-IP}
# 对外服务
expose:
# 暴露接口请求头值
token: ${API_EXPOSE_TOKEN:pmqeHOyZaumHm0Wt}

View File

@@ -175,6 +175,8 @@ orion:
prefix: ${orion.prefix}/api
# 是否允许跨域
cors: true
# 获取 IP 的请求头
ip-headers: X-Forwarded-For,X-Real-IP
# 对外服务
expose:
# 暴露接口请求头

View File

@@ -39,9 +39,9 @@ import java.util.function.Function;
*/
public class ReplaceVersion {
private static final String TARGET_VERSION = "2.5.2";
private static final String TARGET_VERSION = "2.5.4";
private static final String REPLACE_VERSION = "2.5.3";
private static final String REPLACE_VERSION = "2.5.5";
private static final String PATH = new File("").getAbsolutePath();

View File

@@ -29,6 +29,7 @@ import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
/**
* 主机探针状态 视图响应对象
@@ -61,4 +62,7 @@ public class HostAgentStatusVO implements Serializable {
@Schema(description = "探针在线状态")
private Integer agentOnlineStatus;
@Schema(description = "探针切换在线状态时间")
private Date agentOnlineChangeTime;
}

View File

@@ -23,6 +23,7 @@
package org.dromara.visor.module.asset.handler.agent.intstall;
import cn.orionsec.kit.lang.utils.Exceptions;
import cn.orionsec.kit.lang.utils.Strings;
import cn.orionsec.kit.net.host.ssh.command.CommandExecutors;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.common.constant.FileConst;
@@ -50,7 +51,7 @@ public class LinuxAgentInstaller extends AbstractAgentInstaller {
sftpExecutor.write(agentHomePath + FileConst.CONFIG_YAML, this.replaceContent(params.getConfigFilePath()));
log.info("写入配置文件成功");
// 写入启动脚本
sftpExecutor.write(agentHomePath + startScriptName, this.replaceContent(params.getStartScriptPath()));
sftpExecutor.write(agentHomePath + startScriptName, Strings.replaceCRLF(this.replaceContent(params.getStartScriptPath())));
log.info("写入启动脚本成功");
// 上传探针文件
sftpExecutor.uploadFile(agentHomePath + uploadAgentName, params.getAgentFilePath());

View File

@@ -51,6 +51,16 @@ public class HostSpecExtraModel implements GenericsDataModel {
*/
private String sn;
/**
* 制造商
*/
private String vendor;
/**
* 型号
*/
private String model;
/**
* 系统名称
*/

View File

@@ -119,7 +119,8 @@ public class HostAgentServiceImpl implements HostAgentService {
.select(HostDO::getId,
HostDO::getAgentVersion,
HostDO::getAgentInstallStatus,
HostDO::getAgentOnlineStatus)
HostDO::getAgentOnlineStatus,
HostDO::getAgentOnlineChangeTime)
.in(HostDO::getId, idList)
.then()
.stream()

View File

@@ -258,7 +258,7 @@ public class HostIdentityServiceImpl implements HostIdentityService {
.eq(HostIdentityDO::getName, domain.getName());
// 检查是否存在
boolean present = hostIdentityDAO.of(wrapper).present();
Assert.isFalse(present, ErrorMessage.DATA_PRESENT);
Assert.isFalse(present, ErrorMessage.NAME_PRESENT);
}
/**

View File

@@ -242,7 +242,7 @@ public class HostKeyServiceImpl implements HostKeyService {
.eq(HostKeyDO::getName, domain.getName());
// 检查是否存在
boolean present = hostKeyDAO.of(wrapper).present();
Assert.isFalse(present, ErrorMessage.DATA_PRESENT);
Assert.isFalse(present, ErrorMessage.NAME_PRESENT);
}
/**

View File

@@ -50,12 +50,12 @@ public class AppLogConfig {
/**
* 是否生成详细的执行日志
*/
private final ConfigRef<Boolean> execDetailLog;
private final ConfigRef<Boolean> execDetailEnabled;
public AppLogConfig(ConfigStore configStore) {
this.trackerLoadLines = configStore.int32(ConfigKeys.LOG_TRACKER_LOAD_LINES);
this.trackerLoadInterval = configStore.int32(ConfigKeys.LOG_TRACKER_LOAD_INTERVAL);
this.execDetailLog = configStore.bool(ConfigKeys.LOG_EXEC_DETAIL_LOG);
this.execDetailEnabled = configStore.bool(ConfigKeys.LOG_EXEC_DETAIL_ENABLED);
}
public Integer getTrackerLoadLines() {
@@ -66,8 +66,8 @@ public class AppLogConfig {
return trackerLoadInterval.value;
}
public Boolean getExecDetailLog() {
return execDetailLog.value;
public Boolean getExecDetailEnabled() {
return execDetailEnabled.value;
}
}

View File

@@ -193,7 +193,7 @@ public class ExecTaskHandler implements IExecTaskHandler {
* @return handler
*/
private IExecCommandHandler createCommandHandler(Long execHostId) {
if (Booleans.isTrue(appLogConfig.getExecDetailLog())) {
if (Booleans.isTrue(appLogConfig.getExecDetailEnabled())) {
// 详细日志
return new ExecCommandDetailHandler(execHostId, execLog, builtParams, timeoutChecker);
} else {

View File

@@ -408,7 +408,7 @@ public class ExecJobServiceImpl implements ExecJobService {
.eq(ExecJobDO::getName, domain.getName());
// 检查是否存在
boolean present = execJobDAO.of(wrapper).present();
Assert.isFalse(present, ErrorMessage.DATA_PRESENT);
Assert.isFalse(present, ErrorMessage.NAME_PRESENT);
}
/**

View File

@@ -208,7 +208,7 @@ public class ExecTemplateServiceImpl implements ExecTemplateService {
.eq(ExecTemplateDO::getName, domain.getName());
// 检查是否存在
boolean present = execTemplateDAO.of(wrapper).present();
Assert.isFalse(present, ErrorMessage.DATA_PRESENT);
Assert.isFalse(present, ErrorMessage.NAME_PRESENT);
}
}

View File

@@ -25,6 +25,7 @@ package org.dromara.visor.module.infra.api.impl;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.common.constant.ErrorMessage;
import org.dromara.visor.common.utils.Assert;
import org.dromara.visor.common.utils.Requests;
import org.dromara.visor.module.infra.api.AuthenticationApi;
import org.dromara.visor.module.infra.entity.domain.SystemUserDO;
import org.dromara.visor.module.infra.entity.dto.user.SystemUserAuthDTO;
@@ -57,7 +58,11 @@ public class AuthenticationApiImpl implements AuthenticationApi {
result.setUsername(user.getUsername());
result.setNickname(user.getNickname());
// 检查用户密码
boolean passRight = authenticationService.checkUserPassword(user, password, addFailedCount);
boolean passRight = authenticationService.checkUserPassword(user, password);
if (!passRight && addFailedCount) {
// 发送站内信
authenticationService.addLoginFailedCount(user.getUsername(), Requests.getIdentity());
}
result.setPassRight(passRight);
Assert.isTrue(passRight, ErrorMessage.USERNAME_PASSWORD_ERROR);
// 检查用户状态

View File

@@ -63,9 +63,8 @@ public class AuthenticationController {
@PermitAll
@Operation(summary = "登录")
@PostMapping("/login")
public UserLoginVO login(@Validated @RequestBody UserLoginRequest request,
HttpServletRequest servletRequest) {
return authenticationService.login(request, servletRequest);
public UserLoginVO login(@Validated @RequestBody UserLoginRequest request) {
return authenticationService.login(request);
}
@OperatorLog(AuthenticationOperatorType.LOGOUT)

View File

@@ -38,6 +38,7 @@ import org.dromara.visor.module.infra.define.operator.SystemUserOperatorType;
import org.dromara.visor.module.infra.entity.request.user.*;
import org.dromara.visor.module.infra.entity.vo.LoginHistoryVO;
import org.dromara.visor.module.infra.entity.vo.SystemUserVO;
import org.dromara.visor.module.infra.entity.vo.UserLockedVO;
import org.dromara.visor.module.infra.entity.vo.UserSessionVO;
import org.dromara.visor.module.infra.service.OperatorLogService;
import org.dromara.visor.module.infra.service.SystemUserManagementService;
@@ -190,7 +191,33 @@ public class SystemUserController {
}
@IgnoreLog(IgnoreLogMode.RET)
@GetMapping("/session/list")
@GetMapping("/locked/list")
@Operation(summary = "获取锁定的用户列表")
@PreAuthorize("@ss.hasPermission('infra:system-user:query-lock')")
public List<UserLockedVO> getLockedUserList() {
return systemUserManagementService.getLockedUserList();
}
@OperatorLog(SystemUserOperatorType.UNLOCK)
@IgnoreLog(IgnoreLogMode.RET)
@PutMapping("/locked/unlock")
@Operation(summary = "解锁用户")
@PreAuthorize("@ss.hasPermission('infra:system-user:management:unlock')")
public Boolean unlockLockedUser(@RequestBody UserUnlockRequest request) {
systemUserManagementService.unlockLockedUser(request);
return true;
}
@IgnoreLog(IgnoreLogMode.RET)
@GetMapping("/session/users/list")
@Operation(summary = "获取全部用户会话列表")
@PreAuthorize("@ss.hasPermission('infra:system-user:query-session')")
public List<UserSessionVO> getUsersSessionList() {
return systemUserManagementService.getUsersSessionList();
}
@IgnoreLog(IgnoreLogMode.RET)
@GetMapping("/session/user/list")
@Operation(summary = "获取用户会话列表")
@PreAuthorize("@ss.hasPermission('infra:system-user:query-session')")
public List<UserSessionVO> getUserSessionList(@RequestParam("id") Long id) {

View File

@@ -22,16 +22,24 @@
*/
package org.dromara.visor.module.infra.controller;
import cn.orionsec.kit.lang.define.wrapper.DataGrid;
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.dromara.visor.common.validator.group.Page;
import org.dromara.visor.framework.biz.operator.log.core.annotation.OperatorLog;
import org.dromara.visor.framework.log.core.annotation.IgnoreLog;
import org.dromara.visor.framework.log.core.enums.IgnoreLogMode;
import org.dromara.visor.framework.web.core.annotation.DemoDisableApi;
import org.dromara.visor.framework.web.core.annotation.RestWrapper;
import org.dromara.visor.module.infra.define.operator.TagOperatorType;
import org.dromara.visor.module.infra.entity.request.tag.TagCreateRequest;
import org.dromara.visor.module.infra.entity.request.tag.TagQueryRequest;
import org.dromara.visor.module.infra.entity.request.tag.TagUpdateRequest;
import org.dromara.visor.module.infra.entity.vo.TagVO;
import org.dromara.visor.module.infra.service.TagService;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@@ -39,13 +47,13 @@ import javax.annotation.Resource;
import java.util.List;
/**
* 标签枚举 api
* 数据标签 api
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-9-5 11:58
*/
@Tag(name = "infra - 标签枚举服务")
@Tag(name = "infra - 数据标签服务")
@Slf4j
@Validated
@RestWrapper
@@ -56,12 +64,32 @@ public class TagController {
@Resource
private TagService tagService;
@DemoDisableApi
@OperatorLog(TagOperatorType.CREATE)
@PostMapping("/create")
@Operation(summary = "创建标签")
@PreAuthorize("@ss.hasPermission('infra:tag:create')")
public Long createTag(@Validated @RequestBody TagCreateRequest request) {
return tagService.createTag(request);
}
@DemoDisableApi
@OperatorLog(TagOperatorType.UPDATE)
@PutMapping("/update")
@Operation(summary = "修改标签")
@PreAuthorize("@ss.hasPermission('infra:tag:update')")
public Integer updateTag(@Validated @RequestBody TagUpdateRequest request) {
return tagService.updateTag(request);
}
@IgnoreLog(IgnoreLogMode.RET)
@PostMapping("/query")
@Operation(summary = "分页查询标签")
@PreAuthorize("@ss.hasPermission('infra:tag:query')")
public DataGrid<TagVO> getTagPage(@Validated(Page.class) @RequestBody TagQueryRequest request) {
return tagService.getTagPage(request);
}
@IgnoreLog(IgnoreLogMode.RET)
@GetMapping("/list")
@Operation(summary = "查询标签")
@@ -70,12 +98,14 @@ public class TagController {
return tagService.getTagList(type);
}
@DemoDisableApi
@OperatorLog(TagOperatorType.DELETE)
@DeleteMapping("/delete")
@Operation(summary = "通过 id 删除标签")
@Parameter(name = "id", description = "id", required = true)
@PreAuthorize("@ss.hasPermission('infra:tag:delete')")
public Integer deleteTag(@RequestParam("id") Long id) {
return tagService.deleteTagById(id);
}
}

View File

@@ -22,12 +22,14 @@
*/
package org.dromara.visor.module.infra.convert;
import org.dromara.visor.common.mapstruct.DateConversion;
import org.dromara.visor.framework.biz.operator.log.core.model.OperatorLogModel;
import org.dromara.visor.module.infra.entity.domain.OperatorLogDO;
import org.dromara.visor.module.infra.entity.request.operator.OperatorLogQueryRequest;
import org.dromara.visor.module.infra.entity.vo.LoginHistoryVO;
import org.dromara.visor.module.infra.entity.vo.OperatorLogVO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
/**
@@ -37,11 +39,12 @@ import org.mapstruct.factory.Mappers;
* @version 1.0.0
* @since 2023-10-10 17:08
*/
@Mapper
@Mapper(uses = DateConversion.class)
public interface OperatorLogConvert {
OperatorLogConvert MAPPER = Mappers.getMapper(OperatorLogConvert.class);
@Mapping(source = "timestamp", target = "createTime")
OperatorLogDO to(OperatorLogModel model);
OperatorLogDO to(OperatorLogQueryRequest request);

View File

@@ -26,6 +26,7 @@ import org.dromara.visor.module.infra.entity.domain.TagDO;
import org.dromara.visor.module.infra.entity.dto.TagCacheDTO;
import org.dromara.visor.module.infra.entity.request.tag.TagCreateRequest;
import org.dromara.visor.module.infra.entity.request.tag.TagQueryRequest;
import org.dromara.visor.module.infra.entity.request.tag.TagUpdateRequest;
import org.dromara.visor.module.infra.entity.vo.TagVO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@@ -33,7 +34,7 @@ import org.mapstruct.factory.Mappers;
import java.util.List;
/**
* 标签枚举 内部对象转换器
* 数据标签 内部对象转换器
*
* @author Jiahang Li
* @version 1.0.0
@@ -46,6 +47,8 @@ public interface TagConvert {
TagDO to(TagCreateRequest request);
TagDO to(TagUpdateRequest request);
TagDO to(TagQueryRequest request);
TagVO to(TagDO domain);

View File

@@ -30,7 +30,7 @@ import org.mapstruct.factory.Mappers;
import java.util.List;
/**
* 标签枚举 对外对象转换器
* 数据标签 对外对象转换器
*
* @author Jiahang Li
* @version 1.0.0

View File

@@ -45,20 +45,27 @@ public interface DataGroupDAO extends IMapper<DataGroupDO> {
*
* @param parentId parentId
* @param type type
* @param userId userId
* @return max(sort)
*/
Integer selectMaxSort(@Param("parentId") Long parentId, @Param("type") String type);
Integer selectMaxSort(@Param("parentId") Long parentId,
@Param("type") String type,
@Param("userId") Long userId);
/**
* 修改排序
*
* @param parentId parentId
* @param type type
* @param userId userId
* @param condition 条件
* @param referSort 对比值
* @param addition 自增步长
* @return effect
*/
Integer updateSort(@Param("parentId") Long parentId,
@Param("type") String type,
@Param("userId") Long userId,
@Param("condition") String condition,
@Param("referSort") Integer referSort,
@Param("addition") Integer addition);

View File

@@ -30,7 +30,7 @@ import org.dromara.visor.module.infra.entity.domain.TagDO;
import java.util.List;
/**
* 标签枚举 Mapper 接口
* 数据标签 Mapper 接口
*
* @author Jiahang Li
* @version 1.0.0

View File

@@ -24,8 +24,10 @@ package org.dromara.visor.module.infra.dao;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.dromara.visor.framework.mybatis.core.mapper.IMapper;
import org.dromara.visor.module.infra.entity.domain.TagRelDO;
import org.dromara.visor.module.infra.entity.po.TagRelCountPO;
import java.util.List;
import java.util.stream.Collectors;
@@ -72,4 +74,12 @@ public interface TagRelDAO extends IMapper<TagRelDO> {
.collect(Collectors.toList());
}
/**
* 查询标签引用数量
*
* @param tagIdList tagIdList
* @return count
*/
List<TagRelCountPO> selectTagRelCount(@Param("tagIdList") List<Long> tagIdList);
}

View File

@@ -26,6 +26,7 @@ import cn.orionsec.kit.lang.define.cache.key.CacheKeyBuilder;
import cn.orionsec.kit.lang.define.cache.key.CacheKeyDefine;
import cn.orionsec.kit.lang.define.cache.key.struct.RedisCacheStruct;
import org.dromara.visor.common.security.LoginUser;
import org.dromara.visor.module.infra.entity.dto.LoginFailedDTO;
import org.dromara.visor.module.infra.entity.dto.LoginTokenDTO;
import org.dromara.visor.module.infra.entity.dto.UserInfoDTO;
@@ -56,11 +57,13 @@ public interface UserCacheKeyDefine {
.timeout(8, TimeUnit.HOURS)
.build();
CacheKeyDefine LOGIN_FAILED_COUNT = new CacheKeyBuilder()
.key("user:login-failed:{}")
.desc("用户登录失败次数 ${username}")
.type(Integer.class)
CacheKeyDefine LOGIN_FAILED = new CacheKeyBuilder()
.key("user:login-failed-info:{}")
.desc("用户登录失败信息 ${username}")
.noPrefix()
.type(LoginFailedDTO.class)
.struct(RedisCacheStruct.STRING)
.timeout(24 * 60, TimeUnit.MINUTES)
.build();
CacheKeyDefine LOGIN_TOKEN = new CacheKeyBuilder()

View File

@@ -50,6 +50,8 @@ public class SystemUserOperatorType extends InitializingOperatorTypes {
public static final String DELETE = "system-user:delete";
public static final String UNLOCK = "system-user:unlock";
public static final String OFFLINE = "system-user:offline";
@Override
@@ -61,6 +63,7 @@ public class SystemUserOperatorType extends InitializingOperatorTypes {
new OperatorType(M, GRANT_ROLE, "分配用户角色 <sb>${username}</sb>"),
new OperatorType(H, RESET_PASSWORD, "重置用户密码 <sb>${username}</sb>"),
new OperatorType(H, DELETE, "删除用户 <sb>${username}</sb>"),
new OperatorType(M, UNLOCK, "解锁用户 <sb>${username}</sb>"),
new OperatorType(M, OFFLINE, "下线用户会话 <sb>${username}</sb>"),
};
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.infra.define.operator;
import org.dromara.visor.framework.biz.operator.log.core.annotation.Module;
import org.dromara.visor.framework.biz.operator.log.core.factory.InitializingOperatorTypes;
import org.dromara.visor.framework.biz.operator.log.core.model.OperatorType;
import static org.dromara.visor.framework.biz.operator.log.core.enums.OperatorRiskLevel.L;
import static org.dromara.visor.framework.biz.operator.log.core.enums.OperatorRiskLevel.M;
/**
* 数据标签 操作日志类型
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/10/10 17:30
*/
@Module("infra:tag")
public class TagOperatorType extends InitializingOperatorTypes {
public static final String CREATE = "tag:create";
public static final String UPDATE = "tag:update";
public static final String DELETE = "tag:delete";
@Override
public OperatorType[] types() {
return new OperatorType[]{
new OperatorType(L, CREATE, "创建数据标签 <sb>${name}</sb>"),
new OperatorType(L, UPDATE, "修改数据标签 <sb>${name}</sb>"),
new OperatorType(M, DELETE, "删除数据标签 <sb>${name}</sb>"),
};
}
}

View File

@@ -33,7 +33,7 @@ import lombok.experimental.SuperBuilder;
import org.dromara.visor.framework.mybatis.core.domain.BaseDO;
/**
* 标签枚举 实体对象
* 数据标签 实体对象
*
* @author Jiahang Li
* @version 1.0.0
@@ -45,7 +45,7 @@ import org.dromara.visor.framework.mybatis.core.domain.BaseDO;
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@TableName(value = "tag", autoResultMap = true)
@Schema(name = "TagDO", description = "标签枚举 实体对象")
@Schema(name = "TagDO", description = "数据标签 实体对象")
public class TagDO extends BaseDO {
private static final long serialVersionUID = 1L;

View File

@@ -0,0 +1,42 @@
package org.dromara.visor.module.infra.entity.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.dromara.visor.common.entity.RequestIdentityModel;
/**
* 登录失败信息
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/10/8 15:44
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class LoginFailedDTO {
/**
* 用户名
*/
private String username;
/**
* 失败次数
*/
private Integer failedCount;
/**
* 失效时间
*/
private Long expireTime;
/**
* 原始登录留痕信息
*/
private RequestIdentityModel origin;
}

View File

@@ -26,7 +26,7 @@ import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.dromara.visor.module.infra.enums.LoginTokenStatusEnum;
import org.dromara.visor.common.entity.RequestIdentityModel;
/**
* 登录 token 缓存
@@ -42,14 +42,19 @@ import org.dromara.visor.module.infra.enums.LoginTokenStatusEnum;
public class LoginTokenDTO {
/**
* 用户id
* userId
*/
private Long id;
/**
* 用户名
*/
private String username;
/**
* token 状态
*
* @see LoginTokenStatusEnum
* @see org.dromara.visor.module.infra.enums.LoginTokenStatusEnum
*/
private Integer status;
@@ -59,13 +64,13 @@ public class LoginTokenDTO {
private Integer refreshCount;
/**
* 原始登录身份
* 原始登录留痕信息
*/
private LoginTokenIdentityDTO origin;
private RequestIdentityModel origin;
/**
* 覆盖登录身份
* 覆盖登录刘海信息
*/
private LoginTokenIdentityDTO override;
private RequestIdentityModel override;
}

View File

@@ -20,40 +20,36 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.monitor.engine;
package org.dromara.visor.module.infra.entity.po;
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.0
* @since 2024/6/3 18:00
* @since 2024/12/23 16:24
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AlarmTriggerState {
@Schema(name = "TagRelCountPO", description = "标签关联数量")
public class TagRelCountPO implements Serializable {
/**
* 时间戳
*/
private Long timestamp;
private static final long serialVersionUID = 1L;
/**
* 是否触发告警
*/
private Boolean triggered;
@Schema(description = "tagId")
private Long tagId;
/**
* 规则键
*/
private String ruleKey;
@Schema(description = "数量")
private Integer count;
}
}

View File

@@ -33,7 +33,7 @@ import javax.validation.constraints.Size;
import java.io.Serializable;
/**
* 标签枚举 创建请求对象
* 数据标签 创建请求对象
*
* @author Jiahang Li
* @version 1.0.0
@@ -43,17 +43,17 @@ import java.io.Serializable;
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "TagCreateRequest", description = "标签枚举 创建请求对象")
@Schema(name = "TagCreateRequest", description = "数据标签 创建请求对象")
public class TagCreateRequest implements Serializable {
@NotBlank
@Size(max = 32)
@Schema(description = "标签名称")
private String name;
@NotBlank
@Size(max = 12)
@Schema(description = "标签类型")
private String type;
@NotBlank
@Size(max = 32)
@Schema(description = "标签名称")
private String name;
}

View File

@@ -29,7 +29,7 @@ import org.dromara.visor.common.entity.BaseQueryRequest;
import javax.validation.constraints.Size;
/**
* 标签枚举 查询请求对象
* 数据标签 查询请求对象
*
* @author Jiahang Li
* @version 1.0.0
@@ -40,11 +40,15 @@ import javax.validation.constraints.Size;
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Schema(name = "TagQueryRequest", description = "标签枚举 查询请求对象")
@Schema(name = "TagQueryRequest", description = "数据标签 查询请求对象")
public class TagQueryRequest extends BaseQueryRequest {
@Size(max = 12)
@Schema(description = "标签类型")
private String type;
@Size(max = 32)
@Schema(description = "标签名称")
private String name;
}

View File

@@ -64,4 +64,7 @@ public class TagRelQueryRequest extends BaseQueryRequest {
@Schema(description = "关联id")
private Collection<Long> relIdList;
@Schema(description = "tagId")
private Collection<Long> tagIdList;
}

View File

@@ -34,7 +34,7 @@ import javax.validation.constraints.Size;
import java.io.Serializable;
/**
* 标签枚举 更新请求对象
* 数据标签 更新请求对象
*
* @author Jiahang Li
* @version 1.0.0
@@ -44,7 +44,7 @@ import java.io.Serializable;
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "TagUpdateRequest", description = "标签枚举 更新请求对象")
@Schema(name = "TagUpdateRequest", description = "数据标签 更新请求对象")
public class TagUpdateRequest implements Serializable {
@NotNull
@@ -56,9 +56,4 @@ public class TagUpdateRequest implements Serializable {
@Schema(description = "标签名称")
private String name;
@NotBlank
@Size(max = 12)
@Schema(description = "标签类型")
private String type;
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.infra.entity.request.user;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* 用户解锁请求
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/7/17 12:19
*/
@Data
@Schema(name = "UserUnlockRequest", description = "用户解锁请求")
public class UserUnlockRequest {
@NotBlank
@Schema(description = "用户名")
private String username;
}

View File

@@ -29,9 +29,10 @@ import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
/**
* 标签枚举 视图响应对象
* 数据标签 视图响应对象
*
* @author Jiahang Li
* @version 1.0.0
@@ -41,7 +42,7 @@ import java.io.Serializable;
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "TagVO", description = "标签枚举 视图响应对象")
@Schema(name = "TagVO", description = "数据标签 视图响应对象")
public class TagVO implements Serializable {
private static final long serialVersionUID = 1L;
@@ -49,7 +50,25 @@ public class TagVO implements Serializable {
@Schema(description = "id")
private Long id;
@Schema(description = "标签类型")
private String type;
@Schema(description = "标签名称")
private String name;
@Schema(description = "关联数量")
private Integer relCount;
@Schema(description = "创建时间")
private Date createTime;
@Schema(description = "修改时间")
private Date updateTime;
@Schema(description = "创建人")
private String creator;
@Schema(description = "修改人")
private String updater;
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.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.0
* @since 2023-7-13 18:42
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "UserLockedVO", description = "用户锁定 响应对象")
public class UserLockedVO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "用户名")
private String username;
@Schema(description = "失效时间")
private Long expireTime;
@Schema(description = "请求ip")
private String address;
@Schema(description = "请求地址")
private String location;
@Schema(description = "userAgent")
private String userAgent;
@Schema(description = "登录时间")
private Date loginTime;
}

View File

@@ -47,6 +47,12 @@ public class UserSessionVO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "id")
private Long id;
@Schema(description = "用户名")
private String username;
@Schema(description = "是否为当前会话")
private Boolean current;

View File

@@ -26,8 +26,8 @@ import cn.orionsec.kit.lang.utils.time.Dates;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.dromara.visor.common.constant.ErrorCode;
import org.dromara.visor.common.entity.RequestIdentityModel;
import org.dromara.visor.module.infra.entity.dto.LoginTokenDTO;
import org.dromara.visor.module.infra.entity.dto.LoginTokenIdentityDTO;
import java.util.Date;
@@ -53,9 +53,9 @@ public enum LoginTokenStatusEnum {
OTHER_DEVICE(1) {
@Override
public RuntimeException toException(LoginTokenDTO token) {
LoginTokenIdentityDTO override = token.getOverride();
RequestIdentityModel override = token.getOverride();
return ErrorCode.USER_OTHER_DEVICE_LOGIN.exception(
Dates.format(new Date(override.getLoginTime()), Dates.MD_HM),
Dates.format(new Date(override.getTimestamp()), Dates.MD_HM),
override.getAddress(),
override.getLocation());
}
@@ -68,9 +68,9 @@ public enum LoginTokenStatusEnum {
SESSION_OFFLINE(2) {
@Override
public RuntimeException toException(LoginTokenDTO token) {
LoginTokenIdentityDTO override = token.getOverride();
RequestIdentityModel override = token.getOverride();
return ErrorCode.USER_OFFLINE.exception(
Dates.format(new Date(override.getLoginTime()), Dates.MD_HM),
Dates.format(new Date(override.getTimestamp()), Dates.MD_HM),
override.getAddress(),
override.getLocation());
}

View File

@@ -82,12 +82,13 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService {
if (tokenInfo == null) {
return null;
}
Long loginTime = tokenInfo.getOrigin().getTimestamp();
try {
// 检查 token 状态
this.checkTokenStatus(tokenInfo);
} catch (Exception e) {
// token 失效则删除
RedisUtils.delete(UserCacheKeyDefine.LOGIN_TOKEN.format(tokenInfo.getId(), tokenInfo.getOrigin().getLoginTime()));
RedisUtils.delete(UserCacheKeyDefine.LOGIN_TOKEN.format(tokenInfo.getId(), loginTime));
throw e;
}
// 获取登录信息
@@ -98,7 +99,7 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService {
// 检查用户状态
UserStatusEnum.checkUserStatus(user.getStatus());
// 设置登录时间戳
user.setTimestamp(tokenInfo.getOrigin().getLoginTime());
user.setTimestamp(loginTime);
return user;
}

View File

@@ -22,6 +22,7 @@
*/
package org.dromara.visor.module.infra.service;
import org.dromara.visor.common.entity.RequestIdentityModel;
import org.dromara.visor.common.security.LoginUser;
import org.dromara.visor.module.infra.entity.domain.SystemUserDO;
import org.dromara.visor.module.infra.entity.dto.LoginTokenDTO;
@@ -42,11 +43,10 @@ public interface AuthenticationService {
/**
* 登录
*
* @param request request
* @param servletRequest servletRequest
* @param request request
* @return login
*/
UserLoginVO login(UserLoginRequest request, HttpServletRequest servletRequest);
UserLoginVO login(UserLoginRequest request);
/**
* 登出
@@ -83,12 +83,19 @@ public interface AuthenticationService {
/**
* 检查用户密码
*
* @param user user
* @param password password
* @param addFailedCount addFailedCount
* @param user user
* @param password password
* @return passRight
*/
boolean checkUserPassword(SystemUserDO user, String password, boolean addFailedCount);
boolean checkUserPassword(SystemUserDO user, String password);
/**
* 添加登录失败次数
*
* @param username username
* @param identity identity
*/
void addLoginFailedCount(String username, RequestIdentityModel identity);
/**
* 检查用户状态

View File

@@ -23,6 +23,8 @@
package org.dromara.visor.module.infra.service;
import org.dromara.visor.module.infra.entity.request.user.UserSessionOfflineRequest;
import org.dromara.visor.module.infra.entity.request.user.UserUnlockRequest;
import org.dromara.visor.module.infra.entity.vo.UserLockedVO;
import org.dromara.visor.module.infra.entity.vo.UserSessionVO;
import java.util.List;
@@ -44,6 +46,27 @@ public interface SystemUserManagementService {
*/
Integer getUserSessionCount(Long userId);
/**
* 获取锁定的用户列表
*
* @return list
*/
List<UserLockedVO> getLockedUserList();
/**
* 解锁用户
*
* @param request request
*/
void unlockLockedUser(UserUnlockRequest request);
/**
* 获取全部用户会话列表
*
* @return list
*/
List<UserSessionVO> getUsersSessionList();
/**
* 获取用户会话列表
*

View File

@@ -114,4 +114,20 @@ public interface TagRelService {
*/
Integer deleteRelIdList(String type, List<Long> relIdList);
/**
* 通过 tagId 删除
*
* @param tagId tagId
* @return effect
*/
Integer deleteTagId(Long tagId);
/**
* 通过 tagId 删除
*
* @param tagIdList tagIdList
* @return effect
*/
Integer deleteTagIdList(List<Long> tagIdList);
}

View File

@@ -22,13 +22,16 @@
*/
package org.dromara.visor.module.infra.service;
import cn.orionsec.kit.lang.define.wrapper.DataGrid;
import org.dromara.visor.module.infra.entity.request.tag.TagCreateRequest;
import org.dromara.visor.module.infra.entity.request.tag.TagQueryRequest;
import org.dromara.visor.module.infra.entity.request.tag.TagUpdateRequest;
import org.dromara.visor.module.infra.entity.vo.TagVO;
import java.util.List;
/**
* 标签枚举 服务类
* 数据标签 服务类
*
* @author Jiahang Li
* @version 1.0.0
@@ -37,7 +40,7 @@ import java.util.List;
public interface TagService {
/**
* 创建标签枚举
* 创建数据标签
*
* @param request request
* @return id
@@ -45,7 +48,23 @@ public interface TagService {
Long createTag(TagCreateRequest request);
/**
* 查询标签枚举
* 更新数据标签
*
* @param request request
* @return effect
*/
Integer updateTag(TagUpdateRequest request);
/**
* 查询数据标签
*
* @param request request
* @return dataGrid
*/
DataGrid<TagVO> getTagPage(TagQueryRequest request);
/**
* 查询数据标签
*
* @param type type
* @return rows
@@ -53,7 +72,7 @@ public interface TagService {
List<TagVO> getTagList(String type);
/**
* 通过 id 删除标签枚举
* 通过 id 删除数据标签
*
* @param id id
* @return effect
@@ -61,7 +80,7 @@ public interface TagService {
Integer deleteTagById(Long id);
/**
* 通过 id 删除标签枚举
* 通过 id 删除数据标签
*
* @param idList idList
* @return effect
@@ -70,7 +89,9 @@ public interface TagService {
/**
* 清理未使用的 tag
*
* @param days days
*/
void clearUnusedTag();
void clearUnusedTag(Integer days);
}

View File

@@ -22,29 +22,26 @@
*/
package org.dromara.visor.module.infra.service.impl;
import cn.orionsec.kit.lang.annotation.Keep;
import cn.orionsec.kit.lang.define.wrapper.Pair;
import cn.orionsec.kit.lang.utils.Booleans;
import cn.orionsec.kit.lang.utils.Exceptions;
import cn.orionsec.kit.lang.utils.Strings;
import cn.orionsec.kit.lang.utils.collect.Lists;
import cn.orionsec.kit.lang.utils.crypto.Signatures;
import cn.orionsec.kit.lang.utils.time.Dates;
import cn.orionsec.kit.web.servlet.web.Servlets;
import com.alibaba.fastjson.JSON;
import org.dromara.visor.common.config.ConfigStore;
import org.dromara.visor.common.constant.ConfigKeys;
import org.dromara.visor.common.constant.Const;
import org.dromara.visor.common.constant.ErrorMessage;
import org.dromara.visor.common.constant.ExtraFieldConst;
import org.dromara.visor.common.entity.RequestIdentity;
import org.dromara.visor.common.entity.RequestIdentityModel;
import org.dromara.visor.common.security.LoginUser;
import org.dromara.visor.common.security.UserRole;
import org.dromara.visor.common.utils.AesEncryptUtils;
import org.dromara.visor.common.utils.Assert;
import org.dromara.visor.common.utils.IpUtils;
import org.dromara.visor.common.utils.Requests;
import org.dromara.visor.framework.biz.operator.log.core.utils.OperatorLogs;
import org.dromara.visor.framework.redis.core.utils.RedisStrings;
import org.dromara.visor.framework.redis.core.utils.RedisUtils;
import org.dromara.visor.framework.security.core.utils.SecurityUtils;
import org.dromara.visor.module.common.config.AppLoginConfig;
import org.dromara.visor.module.infra.api.SystemMessageApi;
@@ -54,8 +51,8 @@ import org.dromara.visor.module.infra.dao.SystemUserRoleDAO;
import org.dromara.visor.module.infra.define.cache.UserCacheKeyDefine;
import org.dromara.visor.module.infra.define.message.SystemUserMessageDefine;
import org.dromara.visor.module.infra.entity.domain.SystemUserDO;
import org.dromara.visor.module.infra.entity.dto.LoginFailedDTO;
import org.dromara.visor.module.infra.entity.dto.LoginTokenDTO;
import org.dromara.visor.module.infra.entity.dto.LoginTokenIdentityDTO;
import org.dromara.visor.module.infra.entity.dto.message.SystemMessageDTO;
import org.dromara.visor.module.infra.entity.request.user.UserLoginRequest;
import org.dromara.visor.module.infra.entity.vo.UserLoginVO;
@@ -63,7 +60,6 @@ import org.dromara.visor.module.infra.enums.LoginTokenStatusEnum;
import org.dromara.visor.module.infra.enums.UserStatusEnum;
import org.dromara.visor.module.infra.service.AuthenticationService;
import org.dromara.visor.module.infra.service.UserPermissionService;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
@@ -98,10 +94,6 @@ public class AuthenticationServiceImpl implements AuthenticationService {
@Resource
private SystemMessageApi systemMessageApi;
@Keep
@Resource
private RedisTemplate<String, String> redisTemplate;
@Resource
private ConfigStore configStore;
@@ -110,25 +102,29 @@ public class AuthenticationServiceImpl implements AuthenticationService {
// 监听并且设置缓存过期时间
configStore.int32(ConfigKeys.LOGIN_LOGIN_SESSION_TIME).onChange((v, b) -> this.setCacheExpireTime());
configStore.int32(ConfigKeys.LOGIN_REFRESH_INTERVAL).onChange((v, b) -> this.setCacheExpireTime());
configStore.int32(ConfigKeys.LOGIN_LOGIN_FAILED_LOCK_TIME).onChange((v, b) -> this.setCacheExpireTime());
this.setCacheExpireTime();
}
@Override
public UserLoginVO login(UserLoginRequest request, HttpServletRequest servletRequest) {
// 获取登录信息
String remoteAddr = IpUtils.getRemoteAddr(servletRequest);
String location = IpUtils.getLocation(remoteAddr);
String userAgent = Servlets.getUserAgent(servletRequest);
public UserLoginVO login(UserLoginRequest request) {
// 获取登录痕迹
String username = request.getUsername();
RequestIdentityModel identity = Requests.getIdentity();
// 设置日志上下文的用户 否则登录失败不会记录日志
OperatorLogs.setUser(SystemUserConvert.MAPPER.toLoginUser(request));
// 登录前检查
SystemUserDO user = this.preCheckLogin(request.getUsername(), request.getPassword());
SystemUserDO user = this.preCheckLogin(username, request.getPassword());
// 重新设置日志上下文
OperatorLogs.setUser(SystemUserConvert.MAPPER.toLoginUser(user));
// 用户密码校验
boolean passRight = this.checkUserPassword(user, request.getPassword(), true);
// 发送站内信
this.sendLoginFailedErrorMessage(passRight, user, remoteAddr, location);
boolean passRight = this.checkUserPassword(user, request.getPassword());
if (!passRight) {
// 增加登录失败次数
this.addLoginFailedCount(username, identity);
// 登录失败发送站内信
this.sendLoginFailedErrorMessage(user, identity);
}
Assert.isTrue(passRight, ErrorMessage.USERNAME_PASSWORD_ERROR);
// 用户状态校验
this.checkUserStatus(user);
@@ -139,14 +135,13 @@ public class AuthenticationServiceImpl implements AuthenticationService {
this.deleteUserCache(user);
// 重设用户缓存
this.setUserCache(user);
long current = System.currentTimeMillis();
// 不允许多端登录
if (Booleans.isFalse(appLoginConfig.getAllowMultiDevice())) {
// 无效化其他缓存
this.invalidOtherDeviceToken(id, current, remoteAddr, location, userAgent);
this.invalidOtherDeviceToken(id, identity);
}
// 生成 loginToken
String token = this.generatorLoginToken(user, current, remoteAddr, location, userAgent);
String token = this.generatorLoginToken(user, identity);
return UserLoginVO.builder()
.token(token)
.build();
@@ -169,16 +164,16 @@ public class AuthenticationServiceImpl implements AuthenticationService {
// 删除 loginToken & refreshToken
String loginKey = UserCacheKeyDefine.LOGIN_TOKEN.format(id, current);
String refreshKey = UserCacheKeyDefine.LOGIN_REFRESH.format(id, current);
redisTemplate.delete(Lists.of(loginKey, refreshKey));
RedisStrings.delete(loginKey, refreshKey);
}
@Override
public LoginUser getLoginUser(Long id) {
// 查询缓存用户信息
String userInfoKey = UserCacheKeyDefine.USER_INFO.format(id);
String userInfoCache = redisTemplate.opsForValue().get(userInfoKey);
// 缓存存在
if (userInfoCache != null) {
return JSON.parseObject(userInfoCache, LoginUser.class);
LoginUser loginUser = RedisStrings.getJson(userInfoKey, UserCacheKeyDefine.USER_INFO);
if (loginUser != null) {
return loginUser;
}
// 查询用户信息
SystemUserDO user = systemUserDAO.selectById(id);
@@ -198,22 +193,21 @@ public class AuthenticationServiceImpl implements AuthenticationService {
}
// 获取登录 key value
String loginKey = UserCacheKeyDefine.LOGIN_TOKEN.format(pair.getKey(), pair.getValue());
String loginCache = redisTemplate.opsForValue().get(loginKey);
LoginTokenDTO loginCache = RedisStrings.getJson(loginKey, UserCacheKeyDefine.LOGIN_TOKEN);
if (loginCache != null) {
return JSON.parseObject(loginCache, LoginTokenDTO.class);
return loginCache;
}
// loginToken 不存在 需要查询 refreshToken
if (Booleans.isFalse(appLoginConfig.getAllowRefresh())) {
return null;
}
String refreshKey = UserCacheKeyDefine.LOGIN_REFRESH.format(pair.getKey(), pair.getValue());
String refreshCache = redisTemplate.opsForValue().get(refreshKey);
// 未查询到刷新key直接返回
if (refreshCache == null) {
LoginTokenDTO refresh = RedisStrings.getJson(refreshKey, UserCacheKeyDefine.LOGIN_REFRESH);
// 未查询到 refreshToken 直接返回
if (refresh == null) {
return null;
}
// 执行续签操作
LoginTokenDTO refresh = JSON.parseObject(refreshCache, LoginTokenDTO.class);
int refreshCount = refresh.getRefreshCount() + 1;
refresh.setRefreshCount(refreshCount);
// 设置登录缓存
@@ -223,7 +217,7 @@ public class AuthenticationServiceImpl implements AuthenticationService {
RedisStrings.setJson(refreshKey, UserCacheKeyDefine.LOGIN_REFRESH, refresh);
} else {
// 大于等于续签最大次数 则删除
redisTemplate.delete(refreshKey);
RedisStrings.delete(refreshKey);
}
return refresh;
}
@@ -236,11 +230,14 @@ public class AuthenticationServiceImpl implements AuthenticationService {
}
// 检查登录失败次数锁定
if (Booleans.isTrue(appLoginConfig.getLoginFailedLock())) {
String failedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(username);
String failedCount = redisTemplate.opsForValue().get(failedCountKey);
if (failedCount != null
&& Integer.parseInt(failedCount) >= appLoginConfig.getLoginFailedLockThreshold()) {
throw Exceptions.argument(ErrorMessage.MAX_LOGIN_FAILED);
String loginFailedKey = UserCacheKeyDefine.LOGIN_FAILED.format(username);
LoginFailedDTO loginFailed = RedisStrings.getJson(loginFailedKey, UserCacheKeyDefine.LOGIN_FAILED);
Integer failedCount = Optional.ofNullable(loginFailed)
.map(LoginFailedDTO::getFailedCount)
.orElse(null);
// 检查是否超过失败次数
if (failedCount != null && failedCount >= appLoginConfig.getLoginFailedLockThreshold()) {
Assert.lt(failedCount, appLoginConfig.getLoginFailedLockThreshold(), ErrorMessage.MAX_LOGIN_FAILED);
}
}
// 获取登录用户
@@ -254,16 +251,32 @@ public class AuthenticationServiceImpl implements AuthenticationService {
}
@Override
public boolean checkUserPassword(SystemUserDO user, String password, boolean addFailedCount) {
// 检查密码
boolean passRight = user.getPassword().equals(Signatures.md5(password));
if (!passRight && addFailedCount) {
// 刷新登录失败缓存
String failedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(user.getUsername());
redisTemplate.opsForValue().increment(failedCountKey);
RedisUtils.setExpire(failedCountKey, appLoginConfig.getLoginFailedLockTime(), TimeUnit.MINUTES);
public boolean checkUserPassword(SystemUserDO user, String password) {
return user.getPassword().equals(Signatures.md5(password));
}
@Override
public void addLoginFailedCount(String username, RequestIdentityModel identity) {
// 过期时间
long expireTime = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(UserCacheKeyDefine.LOGIN_FAILED.getTimeout());
// 刷新登录失败缓存
String loginFailedKey = UserCacheKeyDefine.LOGIN_FAILED.format(username);
LoginFailedDTO loginFailed = RedisStrings.getJson(loginFailedKey, UserCacheKeyDefine.LOGIN_FAILED);
if (loginFailed == null) {
// 首次登录失败
loginFailed = LoginFailedDTO.builder()
.username(username)
.failedCount(1)
.expireTime(expireTime)
.origin(identity)
.build();
} else {
// 非首次登录失败
loginFailed.setExpireTime(expireTime);
loginFailed.setFailedCount(loginFailed.getFailedCount() + 1);
}
return passRight;
// 重新设置缓存
RedisStrings.setJson(loginFailedKey, UserCacheKeyDefine.LOGIN_FAILED, loginFailed);
}
@Override
@@ -275,33 +288,30 @@ public class AuthenticationServiceImpl implements AuthenticationService {
/**
* 发送登录失败错误消息
*
* @param passRight passRight
* @param user user
* @param remoteAddr remoteAddr
* @param location location
* @param user user
* @param identity identity
*/
private void sendLoginFailedErrorMessage(boolean passRight, SystemUserDO user,
String remoteAddr, String location) {
if (passRight) {
return;
}
private void sendLoginFailedErrorMessage(SystemUserDO user, RequestIdentity identity) {
// 检查是否开启登录失败发信
if (!Booleans.isTrue(appLoginConfig.getLoginFailedSend())) {
return;
}
String failedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(user.getUsername());
String failedCountStr = redisTemplate.opsForValue().get(failedCountKey);
if (failedCountStr == null || !Strings.isInteger(failedCountStr)) {
String loginFailedKey = UserCacheKeyDefine.LOGIN_FAILED.format(user.getUsername());
LoginFailedDTO loginFailed = RedisStrings.getJson(loginFailedKey, UserCacheKeyDefine.LOGIN_FAILED);
Integer failedCount = Optional.ofNullable(loginFailed)
.map(LoginFailedDTO::getFailedCount)
.orElse(null);
if (failedCount == null) {
return;
}
// 直接用相等 因为只触发一次
if (!Integer.valueOf(failedCountStr).equals(appLoginConfig.getLoginFailedSendThreshold())) {
if (!failedCount.equals(appLoginConfig.getLoginFailedSendThreshold())) {
return;
}
// 发送站内信
Map<String, Object> params = new HashMap<>();
params.put(ExtraFieldConst.ADDRESS, remoteAddr);
params.put(ExtraFieldConst.LOCATION, location);
params.put(ExtraFieldConst.ADDRESS, identity.getAddress());
params.put(ExtraFieldConst.LOCATION, identity.getLocation());
params.put(ExtraFieldConst.TIME, Dates.current());
SystemMessageDTO message = SystemMessageDTO.builder()
.receiverId(user.getId())
@@ -334,9 +344,9 @@ public class AuthenticationServiceImpl implements AuthenticationService {
// 用户信息缓存
String userInfoKey = UserCacheKeyDefine.USER_INFO.format(user.getId());
// 登录失败次数缓存
String loginFailedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(user.getUsername());
String loginFailedCountKey = UserCacheKeyDefine.LOGIN_FAILED.format(user.getUsername());
// 删除缓存
redisTemplate.delete(Lists.of(userInfoKey, loginFailedCountKey));
RedisStrings.delete(userInfoKey, loginFailedCountKey);
}
/**
@@ -385,21 +395,16 @@ public class AuthenticationServiceImpl implements AuthenticationService {
/**
* 无效化其他登录信息
*
* @param id id
* @param loginTime loginTime
* @param remoteAddr remoteAddr
* @param location location
* @param userAgent userAgent
* @param id id
* @param identity identity
*/
@SuppressWarnings("ALL")
private void invalidOtherDeviceToken(Long id, long loginTime,
String remoteAddr, String location, String userAgent) {
private void invalidOtherDeviceToken(Long id, RequestIdentityModel identity) {
// 获取登录信息
Set<String> loginKeyList = RedisUtils.scanKeys(UserCacheKeyDefine.LOGIN_TOKEN.format(id, "*"));
Set<String> loginKeyList = RedisStrings.scanKeys(UserCacheKeyDefine.LOGIN_TOKEN.format(id, "*"));
if (!loginKeyList.isEmpty()) {
// 获取有效登录信息
List<LoginTokenDTO> loginTokenInfoList = redisTemplate.opsForValue()
.multiGet(loginKeyList)
List<LoginTokenDTO> loginTokenInfoList = RedisStrings.getList(loginKeyList)
.stream()
.filter(Objects::nonNull)
.map(s -> JSON.parseObject(s, LoginTokenDTO.class))
@@ -407,47 +412,45 @@ public class AuthenticationServiceImpl implements AuthenticationService {
.collect(Collectors.toList());
// 修改登录信息
for (LoginTokenDTO loginTokenInfo : loginTokenInfoList) {
String deviceLoginKey = UserCacheKeyDefine.LOGIN_TOKEN.format(id, loginTokenInfo.getOrigin().getLoginTime());
String deviceLoginKey = UserCacheKeyDefine.LOGIN_TOKEN.format(id, loginTokenInfo.getOrigin().getTimestamp());
loginTokenInfo.setStatus(LoginTokenStatusEnum.OTHER_DEVICE.getStatus());
loginTokenInfo.setOverride(new LoginTokenIdentityDTO(loginTime, remoteAddr, location, userAgent));
loginTokenInfo.setOverride(identity);
RedisStrings.setJson(deviceLoginKey, UserCacheKeyDefine.LOGIN_TOKEN, loginTokenInfo);
}
}
// 删除续签信息
if (Booleans.isTrue(appLoginConfig.getAllowRefresh())) {
RedisUtils.scanKeysDelete(UserCacheKeyDefine.LOGIN_REFRESH.format(id, "*"));
RedisStrings.scanKeysDelete(UserCacheKeyDefine.LOGIN_REFRESH.format(id, "*"));
}
}
/**
* 生成 loginToken
*
* @param user user
* @param loginTime loginTime
* @param remoteAddr remoteAddr
* @param location location
* @param userAgent userAgent
* @param user user
* @param identity identity
* @return loginToken
*/
private String generatorLoginToken(SystemUserDO user, long loginTime,
String remoteAddr, String location, String userAgent) {
private String generatorLoginToken(SystemUserDO user, RequestIdentityModel identity) {
Long id = user.getId();
Long timestamp = identity.getTimestamp();
// 生成 loginToken
String loginKey = UserCacheKeyDefine.LOGIN_TOKEN.format(id, loginTime);
String loginKey = UserCacheKeyDefine.LOGIN_TOKEN.format(id, timestamp);
LoginTokenDTO loginValue = LoginTokenDTO.builder()
.id(id)
.username(user.getUsername())
.status(LoginTokenStatusEnum.OK.getStatus())
.refreshCount(0)
.origin(new LoginTokenIdentityDTO(loginTime, remoteAddr, location, userAgent))
.origin(new RequestIdentityModel(timestamp, identity.getAddress(), identity.getLocation(), identity.getUserAgent()))
.build();
RedisStrings.setJson(loginKey, UserCacheKeyDefine.LOGIN_TOKEN, loginValue);
// 生成 refreshToken
if (Booleans.isTrue(appLoginConfig.getAllowRefresh())) {
String refreshKey = UserCacheKeyDefine.LOGIN_REFRESH.format(id, loginTime);
String refreshKey = UserCacheKeyDefine.LOGIN_REFRESH.format(id, timestamp);
RedisStrings.setJson(refreshKey, UserCacheKeyDefine.LOGIN_REFRESH, loginValue);
}
// 返回token
return AesEncryptUtils.encryptBase62(id + ":" + loginTime);
return AesEncryptUtils.encryptBase62(id + ":" + timestamp);
}
/**
@@ -459,6 +462,10 @@ public class AuthenticationServiceImpl implements AuthenticationService {
UserCacheKeyDefine.LOGIN_TOKEN.setTimeout(loginSessionTime);
UserCacheKeyDefine.LOGIN_REFRESH.setTimeout(loginSessionTime + appLoginConfig.getRefreshInterval());
}
Integer loginFailedLockTime = appLoginConfig.getLoginFailedLockTime();
if (loginFailedLockTime != null) {
UserCacheKeyDefine.LOGIN_FAILED.setTimeout(loginFailedLockTime);
}
}
}

View File

@@ -82,7 +82,7 @@ public class DataGroupServiceImpl implements DataGroupService {
// 查询数据是否冲突
this.checkDataGroupPresent(record);
// 查询最大排序
Integer sort = dataGroupDAO.selectMaxSort(request.getParentId(), request.getType());
Integer sort = dataGroupDAO.selectMaxSort(request.getParentId(), request.getType(), request.getUserId());
record.setSort(sort + Const.DEFAULT_SORT);
// 插入
int effect = dataGroupDAO.insert(record);
@@ -130,14 +130,19 @@ public class DataGroupServiceImpl implements DataGroupService {
Assert.notNull(targetRecord, ErrorMessage.GROUP_ABSENT);
// 更新
String type = moveRecord.getType();
Long userId = moveRecord.getUserId();
Long targetParentId = targetRecord.getParentId();
int effect = 0;
// 修改排序
if (MovePosition.TOP.equals(position)) {
// 移动到元素上 将大于等于 targetRecord 的排序都加 10
dataGroupDAO.updateSort(targetParentId, ">=",
targetRecord.getSort(), Const.DEFAULT_SORT);
// 修改 parentId sort
dataGroupDAO.updateSort(targetParentId,
type,
userId,
">=",
targetRecord.getSort(),
Const.DEFAULT_SORT);
// 修改关联以及排序
DataGroupDO update = DataGroupDO.builder()
.id(id)
.parentId(targetParentId)
@@ -146,8 +151,8 @@ public class DataGroupServiceImpl implements DataGroupService {
effect = dataGroupDAO.updateById(update);
} else if (MovePosition.IN.equals(position)) {
// 移动到元素中 获取最大排序
Integer newSort = dataGroupDAO.selectMaxSort(targetId, type) + Const.DEFAULT_SORT;
// 修改 parentId sort
Integer newSort = dataGroupDAO.selectMaxSort(targetId, type, userId) + Const.DEFAULT_SORT;
// 修改关联以及排序
DataGroupDO update = DataGroupDO.builder()
.id(id)
.parentId(targetId)
@@ -156,9 +161,13 @@ public class DataGroupServiceImpl implements DataGroupService {
effect = dataGroupDAO.updateById(update);
} else if (MovePosition.BOTTOM.equals(position)) {
// 移动到元素下 将大于 targetRecord 的排序都加 10
dataGroupDAO.updateSort(targetParentId, ">",
targetRecord.getSort(), Const.DEFAULT_SORT);
// 修改 parentId sort
dataGroupDAO.updateSort(targetParentId,
type,
userId,
">",
targetRecord.getSort(),
Const.DEFAULT_SORT);
// 修改关联以及排序
DataGroupDO update = DataGroupDO.builder()
.id(id)
.parentId(targetParentId)
@@ -167,7 +176,7 @@ public class DataGroupServiceImpl implements DataGroupService {
effect = dataGroupDAO.updateById(update);
}
// 删除缓存
this.deleteCache(type, moveRecord.getUserId());
this.deleteCache(type, userId);
// 添加日志参数
OperatorLogs.add(OperatorLogs.SOURCE, moveRecord.getName());
OperatorLogs.add(OperatorLogs.TARGET, targetRecord.getName());
@@ -334,7 +343,7 @@ public class DataGroupServiceImpl implements DataGroupService {
.eq(DataGroupDO::getName, domain.getName());
// 检查是否存在
boolean present = dataGroupDAO.of(wrapper).present();
Assert.isFalse(present, ErrorMessage.DATA_PRESENT);
Assert.isFalse(present, ErrorMessage.NAME_PRESENT);
}
/**

View File

@@ -248,7 +248,7 @@ public class DictKeyServiceImpl implements DictKeyService {
.eq(DictKeyDO::getKeyName, domain.getKeyName());
// 检查是否存在
boolean present = dictKeyDAO.of(wrapper).present();
Assert.isFalse(present, ErrorMessage.DATA_PRESENT);
Assert.isFalse(present, ErrorMessage.NAME_PRESENT);
}
/**

View File

@@ -226,7 +226,7 @@ public class NotifyTemplateServiceImpl implements NotifyTemplateService {
.eq(NotifyTemplateDO::getBizType, domain.getBizType());
// 检查是否存在
boolean present = notifyTemplateDAO.of(wrapper).present();
Assert.isFalse(present, ErrorMessage.DATA_PRESENT);
Assert.isFalse(present, ErrorMessage.NAME_PRESENT);
}
}

View File

@@ -29,7 +29,6 @@ import cn.orionsec.kit.lang.utils.collect.Maps;
import cn.orionsec.kit.lang.utils.crypto.Keys;
import cn.orionsec.kit.lang.utils.crypto.RSA;
import cn.orionsec.kit.spring.SpringHolder;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.common.constant.AppConst;
import org.dromara.visor.common.constant.ConfigKeys;
@@ -124,28 +123,12 @@ public class SystemSettingServiceImpl implements SystemSettingService {
@Override
public void updateSystemSetting(SystemSettingUpdateRequest request) {
String type = request.getType();
String configKey = request.getConfigKey();
String value = request.getValue();
// 删除
systemSettingDAO.delete(Conditions.eq(SystemSettingDO::getConfigKey, configKey));
// 插入
SystemSettingDO insert = SystemSettingDO.builder()
.type(type)
.configKey(configKey)
.value(Strings.def(value))
SystemSettingUpdateBatchRequest params = SystemSettingUpdateBatchRequest.builder()
.type(request.getType())
.settings(Maps.of(request.getConfigKey(), request.getValue()))
.build();
systemSettingDAO.insert(insert);
// 更新
SystemSettingDO update = new SystemSettingDO();
update.setValue(value);
LambdaQueryWrapper<SystemSettingDO> wrapper = systemSettingDAO.lambda()
.eq(SystemSettingDO::getConfigKey, configKey);
systemSettingDAO.update(update, wrapper);
// 删除缓存
RedisUtils.delete(SystemSettingKeyDefine.SETTING);
// 触发修改事件
SpringHolder.publishEvent(ConfigUpdateEvent.of(configKey, value));
this.updateSystemSettingBatch(params);
}
@Override

View File

@@ -22,30 +22,32 @@
*/
package org.dromara.visor.module.infra.service.impl;
import cn.orionsec.kit.lang.utils.Objects1;
import cn.orionsec.kit.lang.utils.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.common.constant.ErrorMessage;
import org.dromara.visor.common.entity.RequestIdentityModel;
import org.dromara.visor.common.utils.Assert;
import org.dromara.visor.common.utils.Requests;
import org.dromara.visor.framework.biz.operator.log.core.utils.OperatorLogs;
import org.dromara.visor.framework.redis.core.utils.RedisStrings;
import org.dromara.visor.framework.security.core.utils.SecurityUtils;
import org.dromara.visor.module.common.config.AppLoginConfig;
import org.dromara.visor.module.infra.dao.SystemUserDAO;
import org.dromara.visor.module.infra.define.cache.UserCacheKeyDefine;
import org.dromara.visor.module.infra.entity.domain.SystemUserDO;
import org.dromara.visor.module.infra.entity.dto.LoginFailedDTO;
import org.dromara.visor.module.infra.entity.dto.LoginTokenDTO;
import org.dromara.visor.module.infra.entity.dto.LoginTokenIdentityDTO;
import org.dromara.visor.module.infra.entity.request.user.UserSessionOfflineRequest;
import org.dromara.visor.module.infra.entity.request.user.UserUnlockRequest;
import org.dromara.visor.module.infra.entity.vo.UserLockedVO;
import org.dromara.visor.module.infra.entity.vo.UserSessionVO;
import org.dromara.visor.module.infra.enums.LoginTokenStatusEnum;
import org.dromara.visor.module.infra.service.SystemUserManagementService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.*;
import java.util.stream.Collectors;
/**
@@ -59,6 +61,9 @@ import java.util.stream.Collectors;
@Service
public class SystemUserManagementServiceImpl implements SystemUserManagementService {
@Resource
private AppLoginConfig appLoginConfig;
@Resource
private SystemUserDAO systemUserDAO;
@@ -69,6 +74,53 @@ public class SystemUserManagementServiceImpl implements SystemUserManagementServ
return Lists.size(keys);
}
@Override
public List<UserLockedVO> getLockedUserList() {
// 扫描缓存
Set<String> keys = RedisStrings.scanKeys(UserCacheKeyDefine.LOGIN_FAILED.format("*"));
if (Lists.isEmpty(keys)) {
return Lists.empty();
}
// 查询缓存
List<LoginFailedDTO> loginFailedList = RedisStrings.getJsonList(keys, UserCacheKeyDefine.LOGIN_FAILED);
if (Lists.isEmpty(loginFailedList)) {
return Lists.empty();
}
// 返回
return loginFailedList.stream()
.filter(Objects::nonNull)
.filter(s -> s.getFailedCount() >= appLoginConfig.getLoginFailedLockThreshold())
.map(s -> {
RequestIdentityModel origin = s.getOrigin();
return UserLockedVO.builder()
.username(s.getUsername())
.expireTime(s.getExpireTime())
.address(origin.getAddress())
.location(origin.getLocation())
.userAgent(origin.getUserAgent())
.loginTime(new Date(origin.getTimestamp()))
.build();
})
.sorted(Comparator.comparing(UserLockedVO::getLoginTime).reversed())
.collect(Collectors.toList());
}
@Override
public void unlockLockedUser(UserUnlockRequest request) {
RedisStrings.delete(UserCacheKeyDefine.LOGIN_FAILED.format(request.getUsername()));
}
@Override
public List<UserSessionVO> getUsersSessionList() {
// 扫描缓存
Set<String> keys = RedisStrings.scanKeys(UserCacheKeyDefine.LOGIN_TOKEN.format("*", "*"));
if (Lists.isEmpty(keys)) {
return Lists.empty();
}
// 获取用户会话列表
return this.getUserSessionList(keys);
}
@Override
public List<UserSessionVO> getUserSessionList(Long userId) {
// 扫描缓存
@@ -76,23 +128,39 @@ public class SystemUserManagementServiceImpl implements SystemUserManagementServ
if (Lists.isEmpty(keys)) {
return Lists.empty();
}
// 获取用户会话列表
return this.getUserSessionList(keys);
}
/**
* 获取用户会话列表
*
* @param keys keys
* @return rows
*/
private List<UserSessionVO> getUserSessionList(Set<String> keys) {
Long loginUserId = SecurityUtils.getLoginUserId();
// 查询缓存
List<LoginTokenDTO> tokens = RedisStrings.getJsonList(keys, UserCacheKeyDefine.LOGIN_TOKEN);
if (Lists.isEmpty(tokens)) {
return Lists.empty();
}
final boolean isCurrentUser = userId.equals(SecurityUtils.getLoginUserId());
// 返回
return tokens.stream()
.filter(Objects::nonNull)
.filter(s -> LoginTokenStatusEnum.OK.getStatus().equals(s.getStatus()))
.map(LoginTokenDTO::getOrigin)
.map(s -> UserSessionVO.builder()
.current(isCurrentUser && s.getLoginTime().equals(SecurityUtils.getLoginTimestamp()))
.address(s.getAddress())
.location(s.getLocation())
.userAgent(s.getUserAgent())
.loginTime(new Date(s.getLoginTime()))
.build())
.map(s -> {
RequestIdentityModel origin = s.getOrigin();
return UserSessionVO.builder()
.id(s.getId())
.username(s.getUsername())
.current(Objects1.eq(loginUserId, s.getId()) && origin.getTimestamp().equals(SecurityUtils.getLoginTimestamp()))
.address(origin.getAddress())
.location(origin.getLocation())
.userAgent(origin.getUserAgent())
.loginTime(new Date(origin.getTimestamp()))
.build();
})
.sorted(Comparator.comparing(UserSessionVO::getCurrent).reversed()
.thenComparing(Comparator.comparing(UserSessionVO::getLoginTime).reversed()))
.collect(Collectors.toList());
@@ -115,11 +183,8 @@ public class SystemUserManagementServiceImpl implements SystemUserManagementServ
LoginTokenDTO tokenInfo = RedisStrings.getJson(tokenKey, UserCacheKeyDefine.LOGIN_TOKEN);
if (tokenInfo != null) {
tokenInfo.setStatus(LoginTokenStatusEnum.SESSION_OFFLINE.getStatus());
LoginTokenIdentityDTO override = new LoginTokenIdentityDTO();
override.setLoginTime(System.currentTimeMillis());
// 设置请求信息
Requests.fillIdentity(override);
tokenInfo.setOverride(override);
// 设置留痕信息
tokenInfo.setOverride(Requests.getIdentity());
// 更新 token
RedisStrings.setJson(tokenKey, UserCacheKeyDefine.LOGIN_TOKEN, tokenInfo);
}

View File

@@ -130,7 +130,7 @@ public class SystemUserServiceImpl implements SystemUserService {
// 用户列表
UserCacheKeyDefine.USER_LIST.getKey(),
// 登录失败次数
UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(request.getUsername())
UserCacheKeyDefine.LOGIN_FAILED.format(request.getUsername())
);
return record.getId();
}
@@ -179,7 +179,7 @@ public class SystemUserServiceImpl implements SystemUserService {
log.info("SystemUserService-updateUserStatus effect: {}, updateRecord: {}", effect, JSON.toJSONString(updateRecord));
// 改为启用则删除登录失败次数缓存
if (UserStatusEnum.ENABLED.equals(status)) {
RedisUtils.delete(UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(record.getUsername()));
RedisUtils.delete(UserCacheKeyDefine.LOGIN_FAILED.format(record.getUsername()));
}
// 更新用户缓存中的 status
RedisStrings.<LoginUser>processSetJson(UserCacheKeyDefine.USER_INFO, s -> {
@@ -320,7 +320,7 @@ public class SystemUserServiceImpl implements SystemUserService {
int effect = systemUserDAO.updateById(update);
log.info("SystemUserService-resetPassword record: {}, effect: {}", JSON.toJSONString(update), effect);
// 删除登录失败次数缓存
RedisUtils.delete(UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(record.getUsername()));
RedisUtils.delete(UserCacheKeyDefine.LOGIN_FAILED.format(record.getUsername()));
// 删除登录缓存
RedisUtils.scanKeysDelete(UserCacheKeyDefine.LOGIN_TOKEN.format(id, "*"));
// 删除续签信息
@@ -375,7 +375,7 @@ public class SystemUserServiceImpl implements SystemUserService {
// 用户信息缓存
deleteKeys.add(UserCacheKeyDefine.USER_INFO.format(id));
// 登录失败次数
deleteKeys.add(UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(s.getUsername()));
deleteKeys.add(UserCacheKeyDefine.LOGIN_FAILED.format(s.getUsername()));
// 登录 token
deleteKeys.addAll(RedisUtils.scanKeys(UserCacheKeyDefine.LOGIN_TOKEN.format(id, "*")));
// 刷新 token

View File

@@ -27,6 +27,7 @@ import cn.orionsec.kit.lang.utils.collect.Lists;
import cn.orionsec.kit.lang.utils.collect.Maps;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.dromara.visor.common.constant.Const;
import org.dromara.visor.framework.redis.core.utils.RedisStrings;
import org.dromara.visor.module.infra.convert.TagRelConvert;
import org.dromara.visor.module.infra.dao.TagDAO;
@@ -204,16 +205,7 @@ public class TagRelServiceImpl implements TagRelService {
@Override
public Integer deleteRelId(String type, Long relId) {
// 删除数据库
TagRelQueryRequest queryRequest = new TagRelQueryRequest();
queryRequest.setTagType(type);
queryRequest.setRelId(relId);
LambdaQueryWrapper<TagRelDO> wrapper = this.buildQueryWrapper(queryRequest);
int effect = tagRelDAO.delete(wrapper);
// 删除缓存
String cacheKey = TagCacheKeyDefine.TAG_REL.format(type, relId);
redisTemplate.delete(cacheKey);
return effect;
return this.deleteRelIdList(type, Lists.singleton(relId));
}
@Override
@@ -232,6 +224,36 @@ public class TagRelServiceImpl implements TagRelService {
return effect;
}
@Override
public Integer deleteTagId(Long tagId) {
return this.deleteTagIdList(Lists.singleton(tagId));
}
@Override
public Integer deleteTagIdList(List<Long> tagIdList) {
TagRelQueryRequest queryRequest = new TagRelQueryRequest();
queryRequest.setTagIdList(tagIdList);
LambdaQueryWrapper<TagRelDO> wrapper = this.buildQueryWrapper(queryRequest);
// 查询数据库
List<TagRelDO> tags = tagRelDAO.selectList(wrapper);
if (tags.isEmpty()) {
return Const.N_0;
}
// 删除数据库
int effect = tagRelDAO.delete(wrapper);
// 删除缓存
String tagType = tags.get(0).getTagType();
List<Long> relIdList = tags.stream()
.map(TagRelDO::getRelId)
.distinct()
.collect(Collectors.toList());
List<String> cacheKeyList = relIdList.stream()
.map(relId -> TagCacheKeyDefine.TAG_REL.format(tagType, relId))
.collect(Collectors.toList());
redisTemplate.delete(cacheKeyList);
return effect;
}
/**
* 构建查询 wrapper
*
@@ -242,6 +264,7 @@ public class TagRelServiceImpl implements TagRelService {
return tagRelDAO.wrapper()
.eq(TagRelDO::getId, request.getId())
.eq(TagRelDO::getTagId, request.getTagId())
.in(TagRelDO::getTagId, request.getTagIdList())
.eq(TagRelDO::getTagName, request.getTagName())
.eq(TagRelDO::getTagType, request.getTagType())
.eq(TagRelDO::getRelId, request.getRelId())

View File

@@ -22,29 +22,42 @@
*/
package org.dromara.visor.module.infra.service.impl;
import cn.orionsec.kit.lang.define.wrapper.DataGrid;
import cn.orionsec.kit.lang.utils.Objects1;
import cn.orionsec.kit.lang.utils.collect.Lists;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.common.constant.Const;
import org.dromara.visor.common.constant.ErrorMessage;
import org.dromara.visor.common.utils.Assert;
import org.dromara.visor.framework.biz.operator.log.core.utils.OperatorLogs;
import org.dromara.visor.framework.mybatis.core.query.Conditions;
import org.dromara.visor.framework.redis.core.utils.RedisLists;
import org.dromara.visor.framework.redis.core.utils.barrier.CacheBarriers;
import org.dromara.visor.module.infra.convert.TagConvert;
import org.dromara.visor.module.infra.dao.TagDAO;
import org.dromara.visor.module.infra.dao.TagRelDAO;
import org.dromara.visor.module.infra.define.cache.TagCacheKeyDefine;
import org.dromara.visor.module.infra.entity.domain.TagDO;
import org.dromara.visor.module.infra.entity.dto.TagCacheDTO;
import org.dromara.visor.module.infra.entity.po.TagRelCountPO;
import org.dromara.visor.module.infra.entity.request.tag.TagCreateRequest;
import org.dromara.visor.module.infra.entity.request.tag.TagQueryRequest;
import org.dromara.visor.module.infra.entity.request.tag.TagUpdateRequest;
import org.dromara.visor.module.infra.entity.vo.TagVO;
import org.dromara.visor.module.infra.service.TagRelService;
import org.dromara.visor.module.infra.service.TagService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 标签枚举 服务实现类
* 数据标签 服务实现类
*
* @author Jiahang Li
* @version 1.0.0
@@ -54,14 +67,15 @@ import java.util.stream.Collectors;
@Service
public class TagServiceImpl implements TagService {
/**
* 未使用的天数
*/
private static final Integer UN_USED_DAYS = 3;
@Resource
private TagDAO tagDAO;
@Resource
private TagRelDAO tagRelDAO;
@Resource
private TagRelService tagRelService;
@Override
public Long createTag(TagCreateRequest request) {
// 转换
@@ -86,6 +100,26 @@ public class TagServiceImpl implements TagService {
return record.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public Integer updateTag(TagUpdateRequest request) {
Long id = request.getId();
log.info("TagService-updateTag id: {}, request: {}", id, JSON.toJSONString(request));
// 查询标签是否存在
TagDO record = tagDAO.selectById(id);
Assert.notNull(record, ErrorMessage.DATA_ABSENT);
// 转换
TagDO updateRecord = TagConvert.MAPPER.to(request);
// 检查数据是否存在
this.checkTagPresent(updateRecord);
// 更新
int effect = tagDAO.updateById(updateRecord);
log.info("HostProxyService-updateHostProxyById effect: {}", effect);
// 删除缓存
RedisLists.delete(TagCacheKeyDefine.TAG_NAME.format(record.getType()));
return effect;
}
@Override
public List<TagVO> getTagList(String type) {
// 查询缓存
@@ -109,41 +143,72 @@ public class TagServiceImpl implements TagService {
}
@Override
public Integer deleteTagById(Long id) {
TagDO tag = tagDAO.selectById(id);
if (tag == null) {
return Const.N_0;
public DataGrid<TagVO> getTagPage(TagQueryRequest request) {
// 查询标签
DataGrid<TagVO> dataGrid = tagDAO.of()
.createValidateWrapper()
.eq(TagDO::getType, request.getType())
.like(TagDO::getName, request.getName())
.then()
.page(request)
.order(request, TagDO::getId)
.dataGrid(TagConvert.MAPPER::to);
// 查询数量
if (!dataGrid.isEmpty()) {
List<Long> tagIdList = dataGrid.stream()
.map(TagVO::getId)
.collect(Collectors.toList());
// 查询数量
Map<Long, Integer> tagRelCountMap = tagRelDAO.selectTagRelCount(tagIdList)
.stream()
.collect(Collectors.toMap(TagRelCountPO::getTagId,
s -> Objects1.def(s.getCount(), Const.N_0)));
// 设置数量
for (TagVO tag : dataGrid) {
Integer count = tagRelCountMap.getOrDefault(tag.getId(), Const.N_0);
tag.setRelCount(count);
}
}
// 删除数据库
int effect = tagDAO.deleteById(id);
log.info("TagService-deleteTagById id: {}, effect: {}", id, effect);
// 删除缓存
String cacheKey = TagCacheKeyDefine.TAG_NAME.format(tag.getType());
RedisLists.removeJson(cacheKey, TagConvert.MAPPER.toCache(tag));
return effect;
return dataGrid;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Integer deleteTagById(Long id) {
return this.deleteTagByIdList(Lists.singleton(id));
}
@Override
@Transactional(rollbackFor = Exception.class)
public Integer deleteTagByIdList(List<Long> idList) {
List<TagDO> tagList = tagDAO.selectBatchIds(idList);
if (tagList.isEmpty()) {
return Const.N_0;
}
// 删除数据库
// 设置日志参数
String name = tagList.stream()
.map(TagDO::getName)
.collect(Collectors.joining(Const.COMMA));
OperatorLogs.add(OperatorLogs.NAME, name);
// 删除标签
int effect = tagDAO.deleteBatchIds(idList);
// 删除关联
tagRelService.deleteTagIdList(idList);
log.info("TagService-deleteTagByIdList idList: {}, effect: {}", idList, effect);
// 删除缓存
for (TagDO tag : tagList) {
String cacheKey = TagCacheKeyDefine.TAG_NAME.format(tag.getType());
RedisLists.removeJson(cacheKey, TagConvert.MAPPER.toCache(tag));
}
List<String> deleteKeys = tagList.stream()
.map(TagDO::getType)
.distinct()
.map(TagCacheKeyDefine.TAG_NAME::format)
.collect(Collectors.toList());
RedisLists.delete(deleteKeys);
return effect;
}
@Override
public void clearUnusedTag() {
public void clearUnusedTag(Integer days) {
// 查询
List<TagDO> tagList = tagDAO.selectUnusedTag(UN_USED_DAYS);
List<TagDO> tagList = tagDAO.selectUnusedTag(days);
if (tagList.isEmpty()) {
log.info("TagService.clearUnusedTag isEmpty");
return;
@@ -163,4 +228,22 @@ public class TagServiceImpl implements TagService {
RedisLists.delete(cacheKeys);
}
/**
* 检查数据是否存在
*
* @param domain domain
*/
private void checkTagPresent(TagDO domain) {
// 构造条件
LambdaQueryWrapper<TagDO> wrapper = tagDAO.wrapper()
// 更新时忽略当前记录
.ne(TagDO::getId, domain.getId())
// 用其他字段做重复校验
.eq(TagDO::getType, domain.getType())
.eq(TagDO::getName, domain.getName());
// 检查是否存在
boolean present = tagDAO.of(wrapper).present();
Assert.isFalse(present, ErrorMessage.NAME_PRESENT);
}
}

View File

@@ -26,19 +26,20 @@ import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.common.utils.LockerUtils;
import org.dromara.visor.module.infra.service.TagService;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* tag 定时清理任务
* <p>
* 提供了 web 管理删除, 所以不需要定时清理
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/4/15 23:50
*/
@Slf4j
@Component
// @Component
public class TagAutoClearTask {
/**
@@ -46,6 +47,11 @@ public class TagAutoClearTask {
*/
private static final String LOCK_KEY = "clear:tag:lock";
/**
* 未使用的天数
*/
private static final Integer UN_USED_DAYS = 3;
@Resource
private TagService tagService;
@@ -56,7 +62,7 @@ public class TagAutoClearTask {
public void clear() {
log.info("TagAutoClearTask.clear start");
// 获取锁并执行
LockerUtils.tryLockExecute(LOCK_KEY, tagService::clearUnusedTag);
LockerUtils.tryLockExecute(LOCK_KEY, () -> tagService.clearUnusedTag(UN_USED_DAYS));
log.info("TagAutoClearTask.clear finish");
}

View File

@@ -22,19 +22,27 @@
id, parent_id, type, user_id, name, sort, create_time, update_time, creator, updater, deleted
</sql>
<update id="updateSort">
UPDATE data_group
SET sort = sort + #{addition}
WHERE parent_id = #{parentId}
AND sort ${condition} #{referSort}
</update>
<select id="selectMaxSort" resultType="java.lang.Integer">
SELECT IFNULL(MAX(sort), 0)
FROM data_group
WHERE deleted = 0
AND type = #{type}
AND parent_id = #{parentId}
AND type = #{type}
AND parent_id = #{parentId}
<if test="userId != null">
AND user_id = #{userId}
</if>
</select>
<update id="updateSort">
UPDATE data_group
SET sort = sort + #{addition}
WHERE deleted = 0
AND type = #{type}
AND parent_id = #{parentId}
AND sort ${condition} #{referSort}
<if test="userId != null">
AND user_id = #{userId}
</if>
</update>
</mapper>

View File

@@ -16,9 +16,28 @@
<result column="rel_id" property="relId"/>
</resultMap>
<!-- 数量查询映射结果 -->
<resultMap id="CountResultMap" type="org.dromara.visor.module.infra.entity.po.TagRelCountPO">
<result column="tag_id" property="tagId"/>
<result column="total_count" property="count"/>
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, tag_id, tag_name, tag_type, rel_id, create_time, update_time, creator, updater, deleted
</sql>
<select id="selectTagRelCount" resultMap="CountResultMap">
SELECT tag_id, count(1) total_count
FROM tag_rel
WHERE deleted = 0
<if test="tagIdList != null and tagIdList.size() > 0">
AND tag_id IN
<foreach collection="tagIdList" item="item" open=" (" separator="," close=")">
#{item}
</foreach>
</if>
GROUP BY tag_id
</select>
</mapper>

View File

@@ -20,7 +20,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.monitor.engine;
package org.dromara.visor.module.monitor.context;
import cn.orionsec.kit.lang.define.cache.TimedCache;
import cn.orionsec.kit.lang.define.cache.TimedCacheBuilder;
@@ -29,15 +29,11 @@ import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.common.constant.Const;
import org.dromara.visor.module.monitor.convert.MonitorHostConvert;
import org.dromara.visor.module.monitor.convert.MonitorMetricsConvert;
import org.dromara.visor.module.monitor.dao.MonitorHostDAO;
import org.dromara.visor.module.monitor.dao.MonitorMetricsDAO;
import org.dromara.visor.module.monitor.entity.domain.MonitorHostDO;
import org.dromara.visor.module.monitor.entity.domain.MonitorMetricsDO;
import org.dromara.visor.module.monitor.entity.dto.AgentMetricsDataDTO;
import org.dromara.visor.module.monitor.entity.dto.MonitorHostConfigDTO;
import org.dromara.visor.module.monitor.entity.dto.MonitorHostContextDTO;
import org.dromara.visor.module.monitor.entity.dto.MonitorMetricsContextDTO;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@@ -47,7 +43,7 @@ import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
/**
* 监控上下文
* 监控探针上下文
*
* @author Jiahang Li
* @version 1.0.0
@@ -55,23 +51,13 @@ import java.util.concurrent.ConcurrentHashMap;
*/
@Slf4j
@Component
public class MonitorContext {
public class MonitorAgentContext {
/**
* 监控主机缓存
*/
private static final ConcurrentHashMap<String, MonitorHostContextDTO> MONITOR_HOST_CACHE = new ConcurrentHashMap<>();
/**
* 监控指标缓存
*/
private static final ConcurrentHashMap<Long, MonitorMetricsContextDTO> MONITOR_METRICS_CACHE = new ConcurrentHashMap<>();
/**
* 监控指标引用缓存
*/
private static final ConcurrentHashMap<String, Long> MONITOR_METRICS_KEY_REL = new ConcurrentHashMap<>();
/**
* 最后心跳时间缓存 3min
*/
@@ -88,9 +74,6 @@ public class MonitorContext {
.checkInterval(Const.MS_S_60)
.build();
@Resource
private MonitorMetricsDAO monitorMetricsDAO;
@Resource
private MonitorHostDAO monitorHostDAO;
@@ -103,10 +86,6 @@ public class MonitorContext {
log.info("MonitorContext-init hosts start.");
this.loadMonitorHost();
log.info("MonitorContext-init hosts end.");
// 初始化监控指标
log.info("MonitorContext-init metrics start.");
this.loadMonitorMetrics();
log.info("MonitorContext-init metrics end.");
}
@PreDestroy
@@ -128,17 +107,6 @@ public class MonitorContext {
}
}
/**
* 加载监控指标
*/
public void loadMonitorMetrics() {
MONITOR_METRICS_CACHE.clear();
// 查询全部指标
List<MonitorMetricsDO> metrics = monitorMetricsDAO.selectList(null);
metrics.forEach(s -> MONITOR_METRICS_CACHE.put(s.getId(), MonitorMetricsConvert.MAPPER.toContext(s)));
metrics.forEach(s -> MONITOR_METRICS_KEY_REL.put(this.getMonitorMetricsKey(s.getMeasurement(), s.getValue()), s.getId()));
}
// ----------------------- 监控主机 ----------------------
/**
@@ -194,7 +162,7 @@ public class MonitorContext {
* @param metrics 指标
*/
public void setAgentMetrics(String agentKey, AgentMetricsDataDTO metrics) {
// 设置指标数据
// 设置指标数据
LATEST_METRICS_CACHE.put(agentKey, metrics);
// 更新心跳时间
AGENT_LAST_ACTIVE_TIME.put(agentKey, System.currentTimeMillis());
@@ -237,72 +205,4 @@ public class MonitorContext {
return AGENT_LAST_ACTIVE_TIME.get(agentKey) != null;
}
// ----------------------- 监控指标 ----------------------
/**
* 重新加载监控指标
*
* @param id id
*/
public void reloadMonitorMetrics(Long id) {
// 删除指标缓存
MONITOR_METRICS_CACHE.remove(id);
// 删除指标引用
MONITOR_METRICS_KEY_REL.entrySet().removeIf(entry -> entry.getValue().equals(id));
// 重新加载指标
MonitorMetricsDO metrics = monitorMetricsDAO.selectById(id);
if (metrics == null) {
return;
}
MONITOR_METRICS_CACHE.put(metrics.getId(), MonitorMetricsConvert.MAPPER.toContext(metrics));
MONITOR_METRICS_KEY_REL.put(this.getMonitorMetricsKey(metrics.getMeasurement(), metrics.getValue()), metrics.getId());
}
/**
* 获取监控指标
*
* @param id id
* @return cache
*/
public MonitorMetricsContextDTO getMonitorMetrics(Long id) {
return MONITOR_METRICS_CACHE.get(id);
}
/**
* 获取监控指标
*
* @param measurement measurement
* @param field field
* @return cache
*/
public MonitorMetricsContextDTO getMonitorMetrics(String measurement, String field) {
Long id = MONITOR_METRICS_KEY_REL.get(this.getMonitorMetricsKey(measurement, field));
if (id == null) {
return null;
}
return MONITOR_METRICS_CACHE.get(id);
}
/**
* 获取监控指标 id
*
* @param measurement measurement
* @param field field
* @return id
*/
public Long getMonitorMetricsId(String measurement, String field) {
return MONITOR_METRICS_KEY_REL.get(this.getMonitorMetricsKey(measurement, field));
}
/**
* 获取监控指标 key
*
* @param measurement measurement
* @param field field
* @return key
*/
private String getMonitorMetricsKey(String measurement, String field) {
return measurement + "_" + field;
}
}

View File

@@ -0,0 +1,152 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.monitor.context;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.module.monitor.convert.MonitorMetricsConvert;
import org.dromara.visor.module.monitor.dao.MonitorMetricsDAO;
import org.dromara.visor.module.monitor.entity.domain.MonitorMetricsDO;
import org.dromara.visor.module.monitor.entity.dto.MonitorMetricsContextDTO;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
/**
* 监控指标上下文
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/10/12 19:39
*/
@Slf4j
@Component
public class MonitorMetricsContext {
/**
* 监控指标缓存
*/
private static final ConcurrentHashMap<Long, MonitorMetricsContextDTO> MONITOR_METRICS_CACHE = new ConcurrentHashMap<>();
/**
* 监控指标引用缓存
*/
private static final ConcurrentHashMap<String, Long> MONITOR_METRICS_KEY_REL = new ConcurrentHashMap<>();
@Resource
private MonitorMetricsDAO monitorMetricsDAO;
/**
* 初始化监控上下文
*/
@PostConstruct
public void initMonitorContext() {
// 初始化监控指标
log.info("MetricsContext-init start.");
this.loadMonitorMetrics();
log.info("MetricsContext-init end.");
}
/**
* 加载监控指标
*/
public void loadMonitorMetrics() {
MONITOR_METRICS_CACHE.clear();
// 查询全部指标
List<MonitorMetricsDO> metrics = monitorMetricsDAO.selectList(null);
metrics.forEach(s -> MONITOR_METRICS_CACHE.put(s.getId(), MonitorMetricsConvert.MAPPER.toContext(s)));
metrics.forEach(s -> MONITOR_METRICS_KEY_REL.put(this.getMonitorMetricsKey(s.getMeasurement(), s.getValue()), s.getId()));
}
// ----------------------- 监控指标 ----------------------
/**
* 重新加载监控指标
*
* @param id id
*/
public void reloadMonitorMetrics(Long id) {
// 删除指标缓存
MONITOR_METRICS_CACHE.remove(id);
// 删除指标引用
MONITOR_METRICS_KEY_REL.entrySet().removeIf(entry -> entry.getValue().equals(id));
// 重新加载指标
MonitorMetricsDO metrics = monitorMetricsDAO.selectById(id);
if (metrics == null) {
return;
}
MONITOR_METRICS_CACHE.put(metrics.getId(), MonitorMetricsConvert.MAPPER.toContext(metrics));
MONITOR_METRICS_KEY_REL.put(this.getMonitorMetricsKey(metrics.getMeasurement(), metrics.getValue()), metrics.getId());
}
/**
* 获取监控指标
*
* @param id id
* @return cache
*/
public MonitorMetricsContextDTO getMonitorMetrics(Long id) {
return MONITOR_METRICS_CACHE.get(id);
}
/**
* 获取监控指标
*
* @param measurement measurement
* @param field field
* @return cache
*/
public MonitorMetricsContextDTO getMonitorMetrics(String measurement, String field) {
Long id = MONITOR_METRICS_KEY_REL.get(this.getMonitorMetricsKey(measurement, field));
if (id == null) {
return null;
}
return MONITOR_METRICS_CACHE.get(id);
}
/**
* 获取监控指标 id
*
* @param measurement measurement
* @param field field
* @return id
*/
public Long getMonitorMetricsId(String measurement, String field) {
return MONITOR_METRICS_KEY_REL.get(this.getMonitorMetricsKey(measurement, field));
}
/**
* 获取监控指标 key
*
* @param measurement measurement
* @param field field
* @return key
*/
private String getMonitorMetricsKey(String measurement, String field) {
return measurement + "_" + field;
}
}

View File

@@ -104,9 +104,10 @@ public class AlarmPolicyController {
@IgnoreLog(IgnoreLogMode.RET)
@GetMapping("/list")
@Operation(summary = "查询全部监控告警策略")
@Parameter(name = "type", description = "type", required = true)
@PreAuthorize("@ss.hasPermission('monitor:alarm-policy:query')")
public List<AlarmPolicyVO> getAlarmPolicyList() {
return alarmPolicyService.getAlarmPolicyListByCache();
public List<AlarmPolicyVO> getAlarmPolicyList(@RequestParam("type") String type) {
return alarmPolicyService.getAlarmPolicyListByCache(type);
}
@IgnoreLog(IgnoreLogMode.RET)

View File

@@ -35,8 +35,8 @@ import org.dromara.visor.framework.log.core.annotation.IgnoreLog;
import org.dromara.visor.framework.log.core.enums.IgnoreLogMode;
import org.dromara.visor.framework.web.core.annotation.DemoDisableApi;
import org.dromara.visor.framework.web.core.annotation.RestWrapper;
import org.dromara.visor.module.monitor.context.MonitorAgentContext;
import org.dromara.visor.module.monitor.define.operator.MonitorHostOperatorType;
import org.dromara.visor.module.monitor.engine.MonitorContext;
import org.dromara.visor.module.monitor.entity.dto.AgentMetricsDataDTO;
import org.dromara.visor.module.monitor.entity.request.host.*;
import org.dromara.visor.module.monitor.entity.vo.MonitorHostMetricsDataVO;
@@ -68,7 +68,7 @@ public class MonitorHostController {
private MonitorHostService monitorHostService;
@Resource
private MonitorContext monitorContext;
private MonitorAgentContext monitorAgentContext;
@IgnoreLog(IgnoreLogMode.RET)
@PostMapping("/query")
@@ -84,7 +84,7 @@ public class MonitorHostController {
@Parameter(name = "agentKey", description = "agentKey", required = true)
@PreAuthorize("@ss.hasPermission('monitor:monitor-host:query')")
public AgentMetricsDataDTO getMonitorHostOverride(@RequestParam("agentKey") String agentKey) {
return monitorContext.getAgentMetrics(agentKey);
return monitorAgentContext.getAgentMetrics(agentKey);
}
@IgnoreLog(IgnoreLogMode.RET)

View File

@@ -22,6 +22,7 @@
*/
package org.dromara.visor.module.monitor.convert;
import org.dromara.visor.common.mapstruct.JsonConversion;
import org.dromara.visor.module.monitor.entity.domain.AlarmEventDO;
import org.dromara.visor.module.monitor.entity.dto.AlarmEventTriggerDTO;
import org.dromara.visor.module.monitor.entity.dto.AlarmPolicyAlarmCountDTO;
@@ -40,7 +41,7 @@ import java.util.List;
* @version 1.0.0
* @since 2025-9-17 21:31
*/
@Mapper
@Mapper(uses = JsonConversion.class)
public interface AlarmEventConvert {
AlarmEventConvert MAPPER = Mappers.getMapper(AlarmEventConvert.class);

View File

@@ -22,11 +22,11 @@
*/
package org.dromara.visor.module.monitor.convert;
import org.dromara.visor.module.monitor.engine.AlarmEngineRule;
import org.dromara.visor.module.monitor.entity.domain.AlarmPolicyRuleDO;
import org.dromara.visor.module.monitor.entity.request.alarm.AlarmPolicyRuleCreateRequest;
import org.dromara.visor.module.monitor.entity.request.alarm.AlarmPolicyRuleUpdateRequest;
import org.dromara.visor.module.monitor.entity.vo.AlarmPolicyRuleVO;
import org.dromara.visor.module.monitor.handler.alarm.model.AlarmEngineRule;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

View File

@@ -22,6 +22,7 @@
*/
package org.dromara.visor.module.monitor.convert;
import org.dromara.visor.common.mapstruct.StringConversion;
import org.dromara.visor.module.asset.entity.dto.host.HostDTO;
import org.dromara.visor.module.asset.entity.dto.host.HostQueryDTO;
import org.dromara.visor.module.monitor.entity.domain.MonitorHostDO;
@@ -40,7 +41,7 @@ import org.mapstruct.factory.Mappers;
* @version 1.0.0
* @since 2025-8-14 16:27
*/
@Mapper
@Mapper(uses = StringConversion.class)
public interface MonitorHostConvert {
MonitorHostConvert MAPPER = Mappers.getMapper(MonitorHostConvert.class);

View File

@@ -59,4 +59,13 @@ public interface AlarmPolicyRuleDAO extends IMapper<AlarmPolicyRuleDO> {
return this.selectList(Conditions.eq(AlarmPolicyRuleDO::getPolicyId, policyId));
}
/**
* 通过 policyId 删除
*
* @param policyId policyId
*/
default void deleteByPolicyId(Long policyId) {
this.delete(Conditions.eq(AlarmPolicyRuleDO::getPolicyId, policyId));
}
}

View File

@@ -39,8 +39,8 @@ import java.util.concurrent.TimeUnit;
public interface AlarmPolicyCacheKeyDefine {
CacheKeyDefine ALARM_POLICY = new CacheKeyBuilder()
.key("alarm:policy:list")
.desc("告警策略")
.key("alarm:policy:list:{}")
.desc("告警策略 ${type}")
.type(AlarmPolicyCacheDTO.class)
.struct(RedisCacheStruct.HASH)
.timeout(8, TimeUnit.HOURS)
@@ -49,6 +49,7 @@ public interface AlarmPolicyCacheKeyDefine {
CacheKeyDefine ALARM_RULE_SILENCE = new CacheKeyBuilder()
.key("alarm:silence:{}:{}")
.desc("告警规则沉默标志 ${agentKey} ${ruleId}")
.noPrefix()
.type(Long.class)
.struct(RedisCacheStruct.STRING)
.build();

View File

@@ -57,17 +57,17 @@ public class AlarmEventDO extends BaseDO {
@TableField("agent_key")
private String agentKey;
@Schema(description = "主机id")
@TableField("host_id")
private Long hostId;
@Schema(description = "事件来源")
@TableField("source_type")
private String sourceType;
@Schema(description = "主机名称")
@TableField("host_name")
private String hostName;
@Schema(description = "事件来源id")
@TableField("source_id")
private Long sourceId;
@Schema(description = "主机地址")
@TableField("host_address")
private String hostAddress;
@Schema(description = "事件来源id")
@TableField("source_info")
private String sourceInfo;
@Schema(description = "策略id")
@TableField("policy_id")

View File

@@ -50,6 +50,10 @@ public class AlarmPolicyDO extends BaseDO {
private static final long serialVersionUID = 1L;
@Schema(description = "策略类型")
@TableField("type")
private String type;
@Schema(description = "策略名称")
@TableField("name")
private String name;

View File

@@ -56,14 +56,14 @@ public class AlarmEventTriggerDTO extends BaseDO {
@Schema(description = "agentKey")
private String agentKey;
@Schema(description = "主机id")
private Long hostId;
@Schema(description = "事件来源")
private String sourceType;
@Schema(description = "主机名称")
private String hostName;
@Schema(description = "事件来源id")
private Long sourceId;
@Schema(description = "主机地址")
private String hostAddress;
@Schema(description = "事件来源id")
private String sourceInfo;
@Schema(description = "策略id")
private Long policyId;

View File

@@ -48,13 +48,16 @@ public class AlarmEventQueryRequest extends BaseQueryRequest {
@Schema(description = "id")
private Long id;
@Schema(description = "主机名称")
private Long hostId;
@Size(max = 32)
@Schema(description = "agentKey")
private String agentKey;
@Schema(description = "事件来源")
private String sourceType;
@Schema(description = "事件来源id")
private Long sourceId;
@Schema(description = "策略id")
private Long policyId;

View File

@@ -47,6 +47,10 @@ public class AlarmPolicyCreateRequest implements Serializable {
private static final long serialVersionUID = 1L;
@NotBlank
@Schema(description = "策略类型")
private String type;
@NotBlank
@Size(max = 64)
@Schema(description = "策略名称")

View File

@@ -26,6 +26,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import org.dromara.visor.common.entity.BaseQueryRequest;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
/**
@@ -46,6 +47,10 @@ public class AlarmPolicyQueryRequest extends BaseQueryRequest {
@Schema(description = "id")
private Long id;
@NotBlank
@Schema(description = "策略类型")
private String type;
@Size(max = 64)
@Schema(description = "策略名称")
private String name;

View File

@@ -22,6 +22,7 @@
*/
package org.dromara.visor.module.monitor.entity.vo;
import com.alibaba.fastjson.JSONObject;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
@@ -51,14 +52,14 @@ public class AlarmEventVO implements Serializable {
@Schema(description = "id")
private Long id;
@Schema(description = "主机名称")
private Long hostId;
@Schema(description = "事件来源")
private String sourceType;
@Schema(description = "主机名称")
private String hostName;
@Schema(description = "事件来源id")
private Long sourceId;
@Schema(description = "主机地址")
private String hostAddress;
@Schema(description = "事件来源id")
private JSONObject sourceInfo;
@Schema(description = "agentKey")
private String agentKey;

View File

@@ -51,6 +51,9 @@ public class AlarmPolicyVO implements Serializable {
@Schema(description = "id")
private Long id;
@Schema(description = "策略类型")
private String type;
@Schema(description = "策略名称")
private String name;

View File

@@ -76,6 +76,9 @@ public class MonitorHostVO implements Serializable {
@Schema(description = "主机地址")
private String address;
@Schema(description = "主机类型")
private List<String> types;
@Schema(description = "主机状态")
private String status;

View File

@@ -0,0 +1,58 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.monitor.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 告警事件来源
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/10/13 22:03
*/
@Getter
@AllArgsConstructor
public enum AlarmEventSourceTypeEnum {
/**
* 主机告警
*/
HOST,
;
public static AlarmEventSourceTypeEnum of(String value) {
if (value == null) {
return null;
}
for (AlarmEventSourceTypeEnum item : values()) {
if (item.name().equals(value)) {
return item;
}
}
return null;
}
}

View File

@@ -20,7 +20,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.monitor.engine;
package org.dromara.visor.module.monitor.handler.alarm;
import cn.orionsec.kit.lang.utils.Strings;
import cn.orionsec.kit.lang.utils.collect.Lists;
@@ -37,6 +37,8 @@ import org.dromara.visor.module.monitor.entity.domain.AlarmPolicyDO;
import org.dromara.visor.module.monitor.entity.domain.AlarmPolicyNotifyDO;
import org.dromara.visor.module.monitor.entity.domain.AlarmPolicyRuleDO;
import org.dromara.visor.module.monitor.enums.AlarmSwitchEnum;
import org.dromara.visor.module.monitor.handler.alarm.model.AlarmEnginePolicy;
import org.dromara.visor.module.monitor.handler.alarm.model.AlarmEngineRule;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;

View File

@@ -20,7 +20,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.monitor.engine;
package org.dromara.visor.module.monitor.handler.alarm;
import cn.orionsec.kit.lang.define.cache.TimedCache;
import cn.orionsec.kit.lang.define.cache.TimedCacheBuilder;
@@ -32,23 +32,23 @@ import cn.orionsec.kit.lang.utils.collect.Lists;
import cn.orionsec.kit.lang.utils.collect.Maps;
import cn.orionsec.kit.lang.utils.io.Streams;
import cn.orionsec.kit.lang.utils.time.Dates;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.common.constant.Const;
import org.dromara.visor.common.entity.PushUser;
import org.dromara.visor.common.enums.BooleanBit;
import org.dromara.visor.framework.biz.push.core.utils.PushUtils;
import org.dromara.visor.framework.redis.core.utils.RedisStrings;
import org.dromara.visor.module.asset.api.HostAgentApi;
import org.dromara.visor.module.asset.entity.dto.host.HostBaseDTO;
import org.dromara.visor.module.infra.api.SystemUserApi;
import org.dromara.visor.module.monitor.convert.AlarmEventConvert;
import org.dromara.visor.module.monitor.context.MonitorAgentContext;
import org.dromara.visor.module.monitor.context.MonitorMetricsContext;
import org.dromara.visor.module.monitor.define.cache.AlarmPolicyCacheKeyDefine;
import org.dromara.visor.module.monitor.entity.domain.AlarmEventDO;
import org.dromara.visor.module.monitor.entity.dto.*;
import org.dromara.visor.module.monitor.enums.*;
import org.dromara.visor.module.monitor.enums.AlarmLevelEnum;
import org.dromara.visor.module.monitor.enums.AlarmTriggerConditionEnum;
import org.dromara.visor.module.monitor.enums.MetricsUnitEnum;
import org.dromara.visor.module.monitor.handler.alarm.model.AlarmEnginePolicy;
import org.dromara.visor.module.monitor.handler.alarm.model.AlarmEngineRule;
import org.dromara.visor.module.monitor.service.AlarmEventService;
import org.springframework.stereotype.Component;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
@@ -57,62 +57,50 @@ import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* 告警引擎
* 告警引擎基类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/8/21 17:26
* @since 2025/10/13 10:12
*/
@Slf4j
@Component
public class AlarmEngine {
public abstract class BaseAlarmEngine implements IAlarmEngine {
/**
* 告警触发状态缓存 10min
*/
private static final TimedCache<AlarmTriggerStateDTO> TRIGGER_STATE_CACHE = TimedCacheBuilder.<AlarmTriggerStateDTO>create()
protected static final TimedCache<AlarmTriggerStateDTO> TRIGGER_STATE_CACHE = TimedCacheBuilder.<AlarmTriggerStateDTO>create()
.expireAfter(10 * Const.MS_S_60)
.checkInterval(Const.MS_S_60)
.build();
@Resource
private AlarmEngineContext alarmEngineContext;
protected AlarmEngineContext alarmEngineContext;
@Resource
private MonitorContext monitorContext;
protected MonitorAgentContext monitorAgentContext;
@Resource
private AlarmEventService alarmEventService;
protected MonitorMetricsContext monitorMetricsContext;
@Resource
private HostAgentApi hostAgentApi;
protected AlarmEventService alarmEventService;
@Resource
private SystemUserApi systemUserApi;
protected SystemUserApi systemUserApi;
@PreDestroy
public void destroyTimedCache() {
Streams.close(TRIGGER_STATE_CACHE);
}
/**
* 检查并且告警
*
* @param agentKey agentKey
* @param prevMetrics prevMetrics
* @param newMetrics newMetrics
*/
@Override
public void checkAndAlarm(String agentKey,
AgentMetricsDataDTO prevMetrics,
AgentMetricsDataDTO newMetrics) {
// 获取主机信息
MonitorHostContextDTO monitorHost = monitorContext.getMonitorHost(agentKey);
if (monitorHost == null) {
return;
}
// 检查策略是否开启
Long policyId = monitorHost.getPolicyId();
if (policyId == null || AlarmSwitchEnum.isOff(monitorHost.getAlarmSwitch())) {
// 获取告警策略
Long policyId = this.getAlarmPolicyId(agentKey);
if (policyId == null) {
return;
}
// 获取对应的策略
@@ -132,7 +120,7 @@ public class AlarmEngine {
// 检查指标
AlarmEventTriggerDTO event = this.checkAndAlarm(agentKey,
prevMetrics, newMetrics, agentMetrics, metricsField,
policy, monitorHost);
policy);
if (event != null) {
alarmEvents.add(event);
}
@@ -147,6 +135,14 @@ public class AlarmEngine {
}
}
/**
* 获取告警策略 id
*
* @param agentKey agentKey
* @return policyId
*/
protected abstract Long getAlarmPolicyId(String agentKey);
/**
* 检查并且告警
*
@@ -156,25 +152,22 @@ public class AlarmEngine {
* @param agentMetrics agentMetrics
* @param metricsField metricsField
* @param policy policy
* @param monitorHost monitorHost
* @return event
*/
private AlarmEventTriggerDTO checkAndAlarm(String agentKey,
AgentMetricsDataDTO prevMetrics,
AgentMetricsDataDTO newMetrics,
AgentMetricsDTO agentMetrics,
String metricsField,
AlarmEnginePolicy policy,
MonitorHostContextDTO monitorHost) {
Long timestamp = newMetrics.getTimestamp();
protected AlarmEventTriggerDTO checkAndAlarm(String agentKey,
AgentMetricsDataDTO prevMetrics,
AgentMetricsDataDTO newMetrics,
AgentMetricsDTO agentMetrics,
String metricsField,
AlarmEnginePolicy policy) {
Long alarmTimestamp = newMetrics.getTimestamp();
// 指标id
Long metricsId = monitorContext.getMonitorMetricsId(agentMetrics.getType(), metricsField);
Long metricsId = monitorMetricsContext.getMonitorMetricsId(agentMetrics.getType(), metricsField);
if (metricsId == null) {
return null;
}
// 指标值
BigDecimal metricsValue;
metricsValue = agentMetrics.getValues().getBigDecimal(metricsField);
BigDecimal metricsValue = agentMetrics.getValues().getBigDecimal(metricsField);
if (metricsValue == null) {
return null;
}
@@ -200,14 +193,30 @@ public class AlarmEngine {
return null;
}
// 检查是否在静默期
boolean inSilence = this.checkAndSetInSilencePeriod(agentKey, timestamp, matchedRule);
boolean inSilence = this.checkAndSetInSilencePeriod(agentKey, alarmTimestamp, matchedRule);
if (inSilence) {
return null;
}
// 创建告警事件
return this.createAlarmEvent(agentKey, monitorHost, timestamp, agentMetrics, metricsValue, matchedRule);
return this.createAlarmEvent(agentKey, alarmTimestamp, agentMetrics, metricsValue, matchedRule);
}
/**
* 创建告警事件
*
* @param agentKey agentKey
* @param alarmTimestamp alarmTimestamp
* @param agentMetrics agentMetrics
* @param metricsValue metricsValue
* @param rule rule
* @return event
*/
protected abstract AlarmEventTriggerDTO createAlarmEvent(String agentKey,
Long alarmTimestamp,
AgentMetricsDTO agentMetrics,
BigDecimal metricsValue,
AlarmEngineRule rule);
/**
* 获取到第一个匹配到达阈值的规则 包含 tag
*
@@ -216,9 +225,9 @@ public class AlarmEngine {
* @param metricsValue metricsValue
* @return rule
*/
private AlarmEngineRule matchTaggedAgentMetricsRule(List<AlarmEngineRule> rules,
Map<String, String> metricsTags,
BigDecimal metricsValue) {
protected AlarmEngineRule matchTaggedAgentMetricsRule(List<AlarmEngineRule> rules,
Map<String, String> metricsTags,
BigDecimal metricsValue) {
AlarmEngineRule matchedRule = null;
// context 根据 level 排序了
for (AlarmEngineRule rule : rules) {
@@ -265,7 +274,7 @@ public class AlarmEngine {
* @param metricsValue metricsValue
* @return rule
*/
private AlarmEngineRule matchAgentMetricsRule(List<AlarmEngineRule> rules, BigDecimal metricsValue) {
protected AlarmEngineRule matchAgentMetricsRule(List<AlarmEngineRule> rules, BigDecimal metricsValue) {
AlarmEngineRule matchedRule = null;
// context 根据 level 排序了
for (AlarmEngineRule rule : rules) {
@@ -289,13 +298,13 @@ public class AlarmEngine {
* @param rule rule
* @param metricValue metricValue
*/
private boolean checkAlarmCondition(AlarmEngineRule rule, BigDecimal metricValue) {
protected boolean checkAlarmCondition(AlarmEngineRule rule, BigDecimal metricValue) {
// 获取指标值
if (metricValue == null) {
return false;
}
// 获取指标单位
MonitorMetricsContextDTO metrics = monitorContext.getMonitorMetrics(rule.getMetricsId());
MonitorMetricsContextDTO metrics = monitorMetricsContext.getMonitorMetrics(rule.getMetricsId());
MetricsUnitEnum unit = Optional.ofNullable(metrics)
.map(MonitorMetricsContextDTO::getUnit)
.map(MetricsUnitEnum::of)
@@ -310,13 +319,8 @@ public class AlarmEngine {
}
// 将阈值转换为原始值
threshold = unit.getThresholdOriginalValue(threshold);
// 触发条件
AlarmTriggerConditionEnum condition = AlarmTriggerConditionEnum.of(rule.getTriggerCondition());
if (condition == null) {
return false;
}
// 判断是否达到触发条件
return this.evaluateCondition(condition, metricValue, threshold);
return this.evaluateCondition(rule.getTriggerCondition(), metricValue, threshold);
}
/**
@@ -327,10 +331,12 @@ public class AlarmEngine {
* @param threshold threshold
* @return eval
*/
private boolean evaluateCondition(AlarmTriggerConditionEnum condition,
BigDecimal metricValue,
BigDecimal threshold) {
switch (condition) {
protected boolean evaluateCondition(String condition,
BigDecimal metricValue,
BigDecimal threshold) {
// 触发条件
AlarmTriggerConditionEnum triggerCondition = AlarmTriggerConditionEnum.of(condition);
switch (triggerCondition) {
case GT:
return metricValue.compareTo(threshold) > 0;
case GE:
@@ -356,10 +362,10 @@ public class AlarmEngine {
* @param rule rule
* @return result
*/
private boolean checkConsecutiveCount(String agentKey,
AgentMetricsDataDTO prevMetrics,
AgentMetricsDataDTO newMetrics,
AlarmEngineRule rule) {
protected boolean checkConsecutiveCount(String agentKey,
AgentMetricsDataDTO prevMetrics,
AgentMetricsDataDTO newMetrics,
AlarmEngineRule rule) {
// 获取规则连续触发次数
Integer ruleConsecutiveCount = Objects1.def(rule.getConsecutiveCount(), 1);
// 获取指标连续触发次数
@@ -393,8 +399,8 @@ public class AlarmEngine {
* @param prevMetrics prevMetrics
* @return isConsecutiveTrigger
*/
private boolean isConsecutiveTrigger(AlarmTriggerStateDTO triggerState,
AgentMetricsDataDTO prevMetrics) {
protected boolean isConsecutiveTrigger(AlarmTriggerStateDTO triggerState,
AgentMetricsDataDTO prevMetrics) {
if (prevMetrics == null || triggerState == null) {
return false;
}
@@ -404,14 +410,14 @@ public class AlarmEngine {
/**
* 检查并且设置静默期
*
* @param agentKey agentKey
* @param timestamp timestamp
* @param rule rule
* @param agentKey agentKey
* @param alarmTimestamp alarmTimestamp
* @param rule rule
* @return inSilence
*/
private boolean checkAndSetInSilencePeriod(String agentKey,
Long timestamp,
AlarmEngineRule rule) {
protected boolean checkAndSetInSilencePeriod(String agentKey,
Long alarmTimestamp,
AlarmEngineRule rule) {
Integer silencePeriod = Objects1.def(rule.getSilencePeriod(), 0);
// 无静默期则触发
if (silencePeriod <= 0) {
@@ -427,75 +433,11 @@ public class AlarmEngine {
.noPrefix()
.timeout(silencePeriod, TimeUnit.MINUTES)
.build();
RedisStrings.set(key, timestamp);
RedisStrings.set(key, alarmTimestamp);
}
return inSilence;
}
/**
* 创建告警事件
*
* @param agentKey agentKey
* @param monitorHost monitorHost
* @param timestamp timestamp
* @param agentMetrics agentMetrics
* @param metricsValue metricsValue
* @param rule rule
* @return event
*/
private AlarmEventTriggerDTO createAlarmEvent(String agentKey,
MonitorHostContextDTO monitorHost,
Long timestamp,
AgentMetricsDTO agentMetrics,
BigDecimal metricsValue,
AlarmEngineRule rule) {
// 查询主机信息
HostBaseDTO host = hostAgentApi.getHostCacheByAgentKey(agentKey);
if (host == null) {
host = new HostBaseDTO();
}
// 获取指标
MonitorMetricsContextDTO metrics = monitorContext.getMonitorMetrics(rule.getMetricsId());
// 指标单位
MetricsUnitEnum unit = MetricsUnitEnum.of(metrics.getUnit());
// 获取连续触发次数
Integer consecutiveCount = Optional.ofNullable(TRIGGER_STATE_CACHE.get(this.getTriggerStateCacheKey(agentKey, rule)))
.map(AlarmTriggerStateDTO::getConsecutiveCount)
.orElse(1);
// 构建告警信息
String alarmInfo = this.buildAlarmInfo(metrics, rule, unit, metricsValue, consecutiveCount);
// 创建告警事件记录
Map<String, String> tags = agentMetrics.getTags();
AlarmEventDO alarmEvent = AlarmEventDO.builder()
.agentKey(agentKey)
.hostId(host.getId())
.hostName(host.getName())
.hostAddress(host.getAddress())
.policyId(rule.getPolicyId())
.policyRuleId(rule.getId())
.metricsId(rule.getMetricsId())
.metricsMeasurement(metrics.getMeasurement())
.alarmTags(tags == null ? Const.EMPTY_OBJECT : JSON.toJSONString(tags))
.alarmValue(metricsValue)
.alarmThreshold(unit.getThresholdOriginalValue(rule.getThreshold()))
.alarmInfo(alarmInfo)
.alarmLevel(rule.getLevel())
.triggerCondition(rule.getTriggerCondition())
.consecutiveCount(consecutiveCount)
.falseAlarm(BooleanBit.FALSE.getValue())
.handleStatus(AlarmHandleStatusEnum.NEW.name())
.handleUserId(monitorHost.getOwnerUserId())
.handleUsername(monitorHost.getOwnerUsername())
.createTime(new Date(timestamp))
.updateTime(new Date(timestamp))
.build();
// 保存告警事件
alarmEventService.createAlarmEvent(alarmEvent);
// 填充其他参数
return AlarmEventConvert.MAPPER.toTrigger(alarmEvent);
}
/**
* 构建告警信息
*
@@ -506,11 +448,11 @@ public class AlarmEngine {
* @param consecutiveCount consecutiveCount
* @return alarmInfo
*/
private String buildAlarmInfo(MonitorMetricsContextDTO metrics,
AlarmEngineRule rule,
MetricsUnitEnum unit,
BigDecimal metricsValue,
Integer consecutiveCount) {
protected String buildAlarmInfo(MonitorMetricsContextDTO metrics,
AlarmEngineRule rule,
MetricsUnitEnum unit,
BigDecimal metricsValue,
Integer consecutiveCount) {
return metrics.getName()
+ Const.SPACE + AlarmTriggerConditionEnum.of(rule.getTriggerCondition()).getCondition()
+ Const.SPACE + unit.format(rule.getThreshold(), new MetricsUnitEnum.FormatOptions(2, metrics.getSuffix()))
@@ -525,7 +467,7 @@ public class AlarmEngine {
* @param rule rule
* @return cacheKey
*/
private String getTriggerStateCacheKey(String agentKey, AlarmEngineRule rule) {
protected String getTriggerStateCacheKey(String agentKey, AlarmEngineRule rule) {
return agentKey + ":" + rule.getId();
}
@@ -535,7 +477,7 @@ public class AlarmEngine {
* @param policy policy
* @param alarmEvents alarmEvents
*/
private void notifyAlarmPolicyChannels(AlarmEnginePolicy policy, List<AlarmEventTriggerDTO> alarmEvents) {
protected void notifyAlarmPolicyChannels(AlarmEnginePolicy policy, List<AlarmEventTriggerDTO> alarmEvents) {
List<Long> notifyIdList = policy.getNotifyIdList();
if (Lists.isEmpty(notifyIdList)) {
return;
@@ -549,38 +491,41 @@ public class AlarmEngine {
// 构建参数
List<Map<String, Object>> paramsList = new ArrayList<>();
for (AlarmEventTriggerDTO event : alarmEvents) {
MonitorMetricsContextDTO metrics = monitorContext.getMonitorMetrics(event.getMetricsId());
MetricsUnitEnum unit = MetricsUnitEnum.of(metrics.getUnit());
AlarmLevelEnum level = AlarmLevelEnum.of(event.getAlarmLevel());
AlarmTriggerConditionEnum triggerCondition = AlarmTriggerConditionEnum.of(event.getTriggerCondition());
try {
MonitorMetricsContextDTO metrics = monitorMetricsContext.getMonitorMetrics(event.getMetricsId());
MetricsUnitEnum unit = MetricsUnitEnum.of(metrics.getUnit());
AlarmLevelEnum level = AlarmLevelEnum.of(event.getAlarmLevel());
AlarmTriggerConditionEnum triggerCondition = AlarmTriggerConditionEnum.of(event.getTriggerCondition());
// 告警事件参数
Map<String, Object> params = new HashMap<>();
params.put("id", event.getId());
params.put("relKey", event.getId());
params.put("policyId", policy.getId());
params.put("policyName", policy.getName());
params.put("ruleId", event.getPolicyRuleId());
params.put("hostId", event.getHostId());
params.put("hostName", event.getHostName());
params.put("hostAddress", event.getHostAddress());
params.put("metrics", metrics.getMeasurement() + "." + metrics.getValue());
params.put("metricsId", metrics.getId());
params.put("metricsName", metrics.getName());
params.put("metricsField", metrics.getValue());
params.put("metricsMeasurement", metrics.getMeasurement());
params.put("tags", event.getAlarmTags());
params.put("level", level.name());
params.put("levelLabel", level.getLabel());
params.put("levelSeverity", level.getSeverity());
params.put("levelColor", level.getColor());
params.put("consecutiveCount", event.getConsecutiveCount());
params.put("triggerCondition", triggerCondition.getCondition());
params.put("alarmInfo", event.getAlarmInfo());
params.put("alarmValue", unit.format(event.getAlarmValue(), new MetricsUnitEnum.FormatOptions(2, metrics.getSuffix())));
params.put("alarmThreshold", unit.format(event.getAlarmThreshold(), new MetricsUnitEnum.FormatOptions(4, metrics.getSuffix())));
params.put("alarmTime", Dates.format(event.getCreateTime()));
paramsList.add(params);
// 告警事件参数
Map<String, Object> params = new HashMap<>();
params.put("id", event.getId());
params.put("relKey", event.getId());
params.put("policyId", policy.getId());
params.put("policyName", policy.getName());
params.put("ruleId", event.getPolicyRuleId());
params.put("metrics", metrics.getMeasurement() + "." + metrics.getValue());
params.put("metricsId", metrics.getId());
params.put("metricsName", metrics.getName());
params.put("metricsField", metrics.getValue());
params.put("metricsMeasurement", metrics.getMeasurement());
params.put("tags", event.getAlarmTags());
params.put("level", level.name());
params.put("levelLabel", level.getLabel());
params.put("levelSeverity", level.getSeverity());
params.put("levelColor", level.getColor());
params.put("consecutiveCount", event.getConsecutiveCount());
params.put("triggerCondition", triggerCondition.getCondition());
params.put("alarmInfo", event.getAlarmInfo());
params.put("alarmValue", unit.format(event.getAlarmValue(), new MetricsUnitEnum.FormatOptions(2, metrics.getSuffix())));
params.put("alarmThreshold", unit.format(event.getAlarmThreshold(), new MetricsUnitEnum.FormatOptions(4, metrics.getSuffix())));
params.put("alarmTime", Dates.format(event.getCreateTime()));
// 设置额外告警推送参数
this.setExtraAlarmPushParams(params, event);
paramsList.add(params);
} catch (Exception e) {
log.info("AlarmEngine-setAlarmParams error", e);
}
}
// 推送消息
for (Map<String, Object> params : paramsList) {
@@ -590,4 +535,12 @@ public class AlarmEngine {
}
}
}
/**
* 设置告警推送参数
*
* @param params params
* @param event event
*/
protected abstract void setExtraAlarmPushParams(Map<String, Object> params, AlarmEventTriggerDTO event);
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.monitor.handler.alarm;
import org.dromara.visor.module.monitor.entity.dto.AgentMetricsDataDTO;
/**
* 告警引擎
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/10/13 10:22
*/
public interface IAlarmEngine {
/**
* 检查并且告警
*
* @param agentKey agentKey
* @param prevMetrics prevMetrics
* @param newMetrics newMetrics
*/
void checkAndAlarm(String agentKey,
AgentMetricsDataDTO prevMetrics,
AgentMetricsDataDTO newMetrics);
}

View File

@@ -0,0 +1,145 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.monitor.handler.alarm;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.common.constant.Const;
import org.dromara.visor.common.enums.BooleanBit;
import org.dromara.visor.module.asset.api.HostAgentApi;
import org.dromara.visor.module.asset.entity.dto.host.HostBaseDTO;
import org.dromara.visor.module.monitor.convert.AlarmEventConvert;
import org.dromara.visor.module.monitor.entity.domain.AlarmEventDO;
import org.dromara.visor.module.monitor.entity.dto.*;
import org.dromara.visor.module.monitor.enums.AlarmEventSourceTypeEnum;
import org.dromara.visor.module.monitor.enums.AlarmHandleStatusEnum;
import org.dromara.visor.module.monitor.enums.AlarmSwitchEnum;
import org.dromara.visor.module.monitor.enums.MetricsUnitEnum;
import org.dromara.visor.module.monitor.handler.alarm.model.AlarmEngineRule;
import org.dromara.visor.module.monitor.handler.alarm.model.HostAlarmSourceInfo;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Date;
import java.util.Map;
import java.util.Optional;
/**
* 告警引擎
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/8/21 17:26
*/
@Slf4j
@Component("metricsAlarmEngine")
public class MetricsAlarmEngine extends BaseAlarmEngine {
@Resource
protected HostAgentApi hostAgentApi;
@Override
protected Long getAlarmPolicyId(String agentKey) {
// 获取主机信息
MonitorHostContextDTO monitorHost = monitorAgentContext.getMonitorHost(agentKey);
if (monitorHost == null) {
return null;
}
// 检查策略是否开启
Long policyId = monitorHost.getPolicyId();
if (policyId == null || AlarmSwitchEnum.isOff(monitorHost.getAlarmSwitch())) {
return null;
}
return policyId;
}
@Override
protected AlarmEventTriggerDTO createAlarmEvent(String agentKey,
Long alarmTimestamp,
AgentMetricsDTO agentMetrics,
BigDecimal metricsValue,
AlarmEngineRule rule) {
MonitorHostContextDTO monitorHost = monitorAgentContext.getMonitorHost(agentKey);
// 查询主机信息
HostBaseDTO host = hostAgentApi.getHostCacheByAgentKey(agentKey);
if (host == null) {
host = new HostBaseDTO();
}
// 获取指标
MonitorMetricsContextDTO metrics = monitorMetricsContext.getMonitorMetrics(rule.getMetricsId());
// 指标单位
MetricsUnitEnum unit = MetricsUnitEnum.of(metrics.getUnit());
// 获取连续触发次数
Integer consecutiveCount = Optional.ofNullable(TRIGGER_STATE_CACHE.get(this.getTriggerStateCacheKey(agentKey, rule)))
.map(AlarmTriggerStateDTO::getConsecutiveCount)
.orElse(1);
// 构建告警信息
String alarmInfo = this.buildAlarmInfo(metrics, rule, unit, metricsValue, consecutiveCount);
// 创建告警事件记录
Map<String, String> tags = agentMetrics.getTags();
AlarmEventDO alarmEvent = AlarmEventDO.builder()
.agentKey(agentKey)
.sourceType(AlarmEventSourceTypeEnum.HOST.name())
.sourceId(host.getId())
.sourceInfo(HostAlarmSourceInfo.builder()
.name(host.getName())
.code(host.getCode())
.address(host.getAddress())
.build()
.toJsonString())
.policyId(rule.getPolicyId())
.policyRuleId(rule.getId())
.metricsId(rule.getMetricsId())
.metricsMeasurement(metrics.getMeasurement())
.alarmTags(tags == null ? Const.EMPTY_OBJECT : JSON.toJSONString(tags))
.alarmValue(metricsValue)
.alarmThreshold(unit.getThresholdOriginalValue(rule.getThreshold()))
.alarmInfo(alarmInfo)
.alarmLevel(rule.getLevel())
.triggerCondition(rule.getTriggerCondition())
.consecutiveCount(consecutiveCount)
.falseAlarm(BooleanBit.FALSE.getValue())
.handleStatus(AlarmHandleStatusEnum.NEW.name())
.handleUserId(monitorHost.getOwnerUserId())
.handleUsername(monitorHost.getOwnerUsername())
.createTime(new Date(alarmTimestamp))
.updateTime(new Date(alarmTimestamp))
.build();
// 保存告警事件
alarmEventService.createAlarmEvent(alarmEvent);
// 填充其他参数
return AlarmEventConvert.MAPPER.toTrigger(alarmEvent);
}
@Override
protected void setExtraAlarmPushParams(Map<String, Object> params, AlarmEventTriggerDTO event) {
HostAlarmSourceInfo sourceInfo = JSON.parseObject(event.getSourceInfo(), HostAlarmSourceInfo.class);
params.put("hostId", event.getSourceId());
params.put("hostName", sourceInfo.getName());
params.put("hostCode", sourceInfo.getCode());
params.put("hostAddress", sourceInfo.getAddress());
}
}

View File

@@ -20,7 +20,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.monitor.engine;
package org.dromara.visor.module.monitor.handler.alarm.model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
@@ -48,6 +48,9 @@ public class AlarmEnginePolicy {
@Schema(description = "策略id")
private Long id;
@Schema(description = "策略类型")
private String type;
@Schema(description = "策略名称")
private String name;

Some files were not shown because too many files have changed in this diff Show More