feat:alarm

This commit is contained in:
nieziyan 2023-07-24 09:47:40 +08:00
parent 82783edbcc
commit d08a34895b
19 changed files with 145 additions and 116 deletions

View File

@ -495,7 +495,7 @@ public interface CommonConstant {
/**
* Redis Stream Key
*/
String STREAM_KEY_WARN = "Warn:Stream_Key_Warn";
String STREAM_ALARM = "Stream:alarm";
/*
* Redis Stream 消费组名
@ -515,6 +515,11 @@ public interface CommonConstant {
*/
String PREFIX_RULE = "Rule:";
/*
预警规则沉默周期Key
*/
String PREFIX_SILENCE = "SilenceCycle:";
// 启用
Integer ENABLED = 1;

View File

@ -1,11 +1,10 @@
package org.jeecg.common.util;
import cn.hutool.core.collection.ListUtil;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Multimap;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.constant.SymbolConstant;
import org.jeecg.modules.base.dto.RuleDto;
import org.jeecg.modules.base.entity.AlarmRule;
import org.springframework.beans.factory.annotation.Autowired;
@ -160,14 +159,14 @@ public class RedisStreamUtil{
* @param ruleDto
*/
public String pushWarn(RuleDto ruleDto){
String warnKey = CommonConstant.STREAM_KEY_WARN;
String warnKey = CommonConstant.STREAM_ALARM;
ObjectRecord<String, RuleDto> record = StreamRecords.newRecord()
.in(warnKey).ofObject(ruleDto);
// 向Redis Stream中推送消息
return putRecord(record);
}
public void putRules(Multimap<String, AlarmRule> ruleMap){
public void setRules(Map<String, AlarmRule> ruleMap){
Jackson2JsonRedisSerializer jacksonSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
// 使Jackson支持Java8的新日期API
@ -175,19 +174,33 @@ public class RedisStreamUtil{
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jacksonSerializer.setObjectMapper(objectMapper);
// 返回批量命令的执行结果
// 返回批处理的执行结果
List<Object> execResult = redisTemplate.executePipelined((RedisConnection connection) -> {
Set<String> keySet = ruleMap.keySet();
for (String key : keySet) {
List<AlarmRule> rules = ListUtil.toList(ruleMap.get(key));
connection.set(key.getBytes(),jacksonSerializer.serialize(rules));
AlarmRule rule = ruleMap.get(key);
connection.set(key.getBytes(),jacksonSerializer.serialize(rule));
}
return null;
});
}
public void setSilence(Map<String, Long> silenceMap){
// 返回批处理的执行结果
List<Object> execResult = redisTemplate.executePipelined((RedisConnection connection) -> {
Set<String> keySet = silenceMap.keySet();
for (String key : keySet) {
Long silence = silenceMap.get(key);
byte[] value = String.valueOf(silence).getBytes();
connection.setEx(key.getBytes(),silence,value);
}
return null;
});
}
public Set<String> keys(String pattern) {
ScanOptions options = ScanOptions.scanOptions().match(pattern).build();
String asterisk = SymbolConstant.ASTERISK;
ScanOptions options = ScanOptions.scanOptions().match(pattern+asterisk).build();
return redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
Set<String> keys = new HashSet<>();
try (Cursor<byte[]> cursor = connection.scan(options)) {

View File

@ -164,7 +164,7 @@ public class TokenUtils {
// 模拟登录生成Token
String token = JwtUtil.sign(username, secret);
// 设置Token缓存有效时间为 5 分钟
redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, token, 5 * 60);
redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, token, 3 * 60);
return token;
}
}

View File

@ -1,4 +1,4 @@
package org.jeecg.modules.entity;
package org.jeecg.modules.base.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
@ -41,4 +41,10 @@ public class AlarmLog implements Serializable {
@TableField(value = "alarm_info")
private String alarmInfo;
/**
* 报警值
*/
@TableField(value = "alarm_value")
private String alarmValue;
}

View File

@ -38,7 +38,7 @@ public class AlarmRule implements Serializable {
* 沉默周期单位为秒
*/
@TableField(value = "silence_cycle")
private Integer silenceCycle;
private Long silenceCycle;
/**
* 报警通知方式 1:短信 2.邮件 3.站内

View File

@ -12,15 +12,6 @@ public class Rule {
private Double threshold; // 阈值
@JsonIgnore
private String ruleName; // 规则名称
@JsonIgnore
private Double current; // 当前值
@JsonIgnore
private String groupId; // 联系人组id
@JsonIgnore
private String notific; // 通知方式
}

View File

@ -2,20 +2,13 @@ package org.jeecg.modules.controller;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.jeecg.common.api.QueryRequest;
import org.jeecg.common.api.dto.message.MessageDTO;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.constant.enums.MessageTypeEnum;
import org.jeecg.common.system.api.ISysBaseAPI;
import org.jeecg.modules.entity.AlarmLog;
import org.jeecg.modules.base.entity.AlarmLog;
import org.jeecg.modules.service.IAlarmLogService;
import org.jeecg.modules.vo.AlarmVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("alarmLog")
@Api(value = "报警日志服务", tags = "报警日志服务")
@ -24,9 +17,6 @@ public class AlarmLogController {
@Autowired
private IAlarmLogService alarmLogService;
@Autowired
private ISysBaseAPI sysBaseAPI;
@ApiOperation("报警日志量总览-柱状图")
@PostMapping("viewAll")
public Result viewAll(@RequestBody AlarmVo alarmVo){ return alarmLogService.viewAll(alarmVo); }

View File

@ -5,6 +5,7 @@ import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
public class AlarmHistory {
@ -17,9 +18,9 @@ public class AlarmHistory {
/**
* 报警开始时间
*/
@DateTimeFormat(pattern = "yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate alarmStartDate;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime alarmStartDate;
/**
* 报警详情

View File

@ -1,14 +1,11 @@
package org.jeecg.modules.mapper;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.jeecg.modules.dto.TypeDto;
import org.jeecg.modules.entity.AlarmHistory;
import org.jeecg.modules.entity.AlarmLog;
import org.jeecg.modules.vo.AlarmVo;
import org.jeecg.modules.base.entity.AlarmLog;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.Map;

View File

@ -8,8 +8,11 @@
alarm_log l
INNER JOIN alarm_rule r ON l.rule_id = r.ID
<where>
<if test="type != null and type != ''">
r.source_type = #{type}
<if test="type != null and type.size() > 0">
r.source_type IN
<foreach collection="type" index="index" open="(" close=")" item="item" separator=",">
#{item}
</foreach>
</if>
<if test="startDate != null and startDate != ''">
AND l.alarm_start_date &gt;= #{startDate}
@ -31,19 +34,22 @@
CASE
r.source_type
WHEN 'Server' THEN
( SELECT NAME FROM sys_server comm WHERE ID = r.source_id )
( SELECT NAME FROM sys_server WHERE ID = r.source_id )
WHEN 'Database' THEN
( SELECT NAME FROM sys_database comm WHERE ID = r.source_id )
( SELECT NAME FROM sys_database WHERE ID = r.source_id )
WHEN 'Email' THEN
( SELECT NAME FROM sys_email WHERE ID = r.source_id )
( SELECT USERNAME FROM sys_email WHERE ID = r.source_id )
END AS NAME
FROM
alarm_log l
INNER JOIN alarm_rule r ON l.rule_id = r.ID
) AS res
<where>
<if test="type != null and type != ''">
source_type = #{type}
<if test="type != null and type.size() > 0">
source_type IN
<foreach collection="type" index="index" open="(" close=")" item="item" separator=",">
#{item}
</foreach>
</if>
<if test="name != null and name != ''">
AND NAME LIKE concat ( '%', #{name} , '%' )
@ -67,8 +73,11 @@
alarm_log l
INNER JOIN alarm_rule r ON l.rule_id = r.ID
<where>
<if test="type != null and type != ''">
r.source_type = #{type}
<if test="type != null and type.size() > 0">
r.source_type IN
<foreach collection="type" index="index" open="(" close=")" item="item" separator=",">
#{item}
</foreach>
</if>
<if test="startDate != null and startDate != ''">
AND l.alarm_start_date &gt;= #{startDate}
@ -102,8 +111,11 @@
) AS l
INNER JOIN alarm_rule r ON l.rule_id = r.id
<where>
<if test="type != null and type != ''">
r.source_type = #{type}
<if test="type != null and type.size() > 0">
r.source_type IN
<foreach collection="type" index="index" open="(" close=")" item="item" separator=",">
#{item}
</foreach>
</if>
</where>
ORDER BY l.ruleCount DESC

View File

@ -1,9 +1,8 @@
package org.jeecg.modules.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.jeecg.common.api.QueryRequest;
import org.jeecg.common.api.vo.Result;
import org.jeecg.modules.entity.AlarmLog;
import org.jeecg.modules.base.entity.AlarmLog;
import org.jeecg.modules.vo.AlarmVo;
public interface IAlarmLogService extends IService<AlarmLog> {

View File

@ -2,7 +2,6 @@ package org.jeecg.modules.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
@ -12,14 +11,12 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.jeecg.common.api.vo.Result;
import org.jeecg.modules.dto.TypeDto;
import org.jeecg.modules.entity.AlarmHistory;
import org.jeecg.modules.entity.AlarmLog;
import org.jeecg.modules.base.entity.AlarmLog;
import org.jeecg.modules.mapper.AlarmLogMapper;
import org.jeecg.modules.service.IAlarmLogService;
import org.jeecg.modules.vo.AlarmVo;
import org.springframework.stereotype.Service;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

View File

@ -1,5 +1,6 @@
package org.jeecg.modules.service.impl;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
@ -133,21 +134,27 @@ public class AlarmRuleServiceImpl extends ServiceImpl<AlarmRuleMapper, AlarmRule
**/
@PostConstruct
public void rule2Redis(){
String underline = SymbolConstant.UNDERLINE;
String colon = SymbolConstant.COLON;
String prefix = CommonConstant.PREFIX_RULE;
String prefixRule = CommonConstant.PREFIX_RULE;
String prefixSilence = CommonConstant.PREFIX_SILENCE;
LambdaQueryWrapper<AlarmRule> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(AlarmRule::getEnabled,1);
List<AlarmRule> alarmRules = this.list(wrapper);
Multimap<String, AlarmRule> ruleMap = ArrayListMultimap.create();
Map<String, AlarmRule> ruleMap = new HashMap<>();
Map<String, Long> silenceMap = new HashMap<>();
for (AlarmRule alarmRule : alarmRules) {
String sourceType = alarmRule.getSourceType();
String sourceId = alarmRule.getSourceId();
Integer itemId = alarmRule.getItemId();
String key = prefix + sourceType + colon;
key += sourceId + underline + itemId;
ruleMap.put(key,alarmRule);
String ruleId = alarmRule.getId();
Long silence = alarmRule.getSilenceCycle();
String ruleKey = prefixRule + sourceType + colon + ruleId;
ruleMap.put(ruleKey,alarmRule);
if (ObjectUtil.isNotNull(silence)){
String silenceKey = prefixSilence + ruleId;
silenceMap.put(silenceKey,silence);
}
}
redisStreamUtil.putRules(ruleMap);
redisStreamUtil.setRules(ruleMap);
redisStreamUtil.setSilence(silenceMap);
}
}

View File

@ -3,11 +3,12 @@ package org.jeecg.modules.vo;
import lombok.Data;
import org.jeecg.common.api.QueryRequest;
import java.io.Serializable;
import java.util.List;
@Data
public class AlarmVo extends QueryRequest implements Serializable {
private String name;
private String type;
private List<String> type;
private String startDate;
private String endDate;
private Integer pageStart;

View File

@ -1,10 +1,13 @@
package org.jeecg.modules.feignclient;
import org.jeecg.common.api.vo.Result;
import org.jeecg.modules.base.entity.AlarmLog;
import org.jeecg.modules.base.entity.SysEmail;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
@ -20,4 +23,8 @@ public interface AbnormalAlarmClient {
/* AlarmContactGroupMemberController下相关接口 */
@GetMapping("/alarmContactGroupMember/userIds")
Result<List<String>> userIds(@RequestParam String groupId);
/* AlarmLogController下相关接口 */
@PostMapping("/alarmLog/create")
Result create(@RequestBody AlarmLog alarmLog);
}

View File

@ -49,22 +49,15 @@ public class SendMessage {
* 根据联系人组id向用户推送消息
*
*/
public void send(Rule rule){
public void send(String message,String groupId,String notific){
// start:生成临时Token到线程中
UserTokenContext.setToken(getTempToken());
// 封装MessageDTO消息体
MessageDTO messageDTO = new MessageDTO();
messageDTO.setTitle("系统预警消息");
String ruleName = rule.getRuleName();
double current = rule.getCurrent();
String operator = rule.getOperator();
double threshold = rule.getThreshold();
String context = "您设定的预警规则:"+ruleName+",预警阈值为:["+operator+threshold+"]当前值为:"+current;
messageDTO.setContent(context);
messageDTO.setContent(message);
String groupId = rule.getGroupId();
String notific = rule.getNotific();
Map<String, String> contact = getContact(groupId);
if (StrUtil.isBlank(notific))return;
String[] ways = notific.split(",");

View File

@ -15,10 +15,12 @@ import org.jeecg.common.util.RedisStreamUtil;
import org.jeecg.common.util.SpringContextUtils;
import org.jeecg.modules.base.dto.ItemDto;
import org.jeecg.modules.base.dto.RuleDto;
import org.jeecg.modules.base.entity.AlarmLog;
import org.jeecg.modules.base.entity.AlarmRule;
import org.jeecg.modules.base.entity.Rule;
import org.jeecg.modules.base.enums.Item;
import org.jeecg.modules.base.enums.SourceType;
import org.jeecg.modules.feignclient.AbnormalAlarmClient;
import org.jeecg.modules.feignclient.MonitorClient;
import org.jeecg.modules.message.SendMessage;
import org.quartz.*;
@ -53,10 +55,15 @@ public class SysInfoJob implements Job {
RedisStreamUtil redisStreamUtil = SpringContextUtils.getBean(RedisStreamUtil.class);
MonitorClient monitorClient = SpringContextUtils.getBean(MonitorClient.class);
SendMessage sendMessage = SpringContextUtils.getBean(SendMessage.class);
AbnormalAlarmClient alarmClient = SpringContextUtils.getBean(AbnormalAlarmClient.class);
// 查询所有报警规则,根据报警规则查询监控项数据
String pattern = CommonConstant.PREFIX_RULE;
Set<String> keys = redisStreamUtil.keys(pattern);
if (CollUtil.isEmpty(keys)) return;
if (CollUtil.isEmpty(keys)) {
log.error("当前没有需要监控的预警规则!");
return;
}
// 时间间隔为每分钟
LocalDateTime now = LocalDateTime.now()
@ -68,42 +75,51 @@ public class SysInfoJob implements Job {
String start = beforeMin.format(formatter);
String end = now.format(formatter);
log.info("开始时间:"+start+",结束时间:"+end);
String prefixSilence = CommonConstant.PREFIX_SILENCE;
for (String ruleKey : keys) {
String operator = "";
try {
AlarmRule alarmRule = (AlarmRule) redisStreamUtil.get(ruleKey);
// 如果报警规则为空,或者在沉默周期内,跳过当前规则
operator = alarmRule.getOperator();
String silenceKey = prefixSilence + alarmRule.getId();
boolean hasKey = redisStreamUtil.hasKey(silenceKey);
boolean blank = StrUtil.isBlank(operator);
if (blank || hasKey)continue;
for (String key : keys) {
Double current = null;
List<AlarmRule> alarmRules = (List<AlarmRule>) redisStreamUtil.get(key);
for (int i = 0; i < alarmRules.size(); i++) {
AlarmRule alarmRule = alarmRules.get(i);
// 如果报警规则为空,跳过本次循环
String operator = alarmRule.getOperator();
if (StrUtil.isBlank(operator))continue;
// 向运管查询监控项数据
if (i == 0){
String itemId = alarmRule.getItemId().toString();
Result<ItemDto> result = monitorClient.itemHistory(itemId, 0, start, end);
current = result.getResult().getNow();
}
String itemId = alarmRule.getItemId().toString();
Result<ItemDto> result = monitorClient.itemHistory(itemId, 0, start, end);
Double current = result.getResult().getNow();
// 解析预警规则,判断是否需要报警
try {
ObjectMapper mapper = new ObjectMapper();
Rule rule = mapper.readValue(operator, Rule.class);
rule.setCurrent(current);
boolean needWarn = parse(rule);
// 发送预警信息
if (needWarn){
// 规则名称
rule.setRuleName(alarmRule.getName());
// 报警信息发送方式
rule.setNotific(alarmRule.getNotification());
// 联系人组id
rule.setGroupId(alarmRule.getContactId());
sendMessage.send(rule);
}
} catch (JsonProcessingException e) {
log.error("预警规则{}解析失败!",operator);
e.printStackTrace();
ObjectMapper mapper = new ObjectMapper();
Rule rule = mapper.readValue(operator, Rule.class);
rule.setCurrent(current);
boolean needWarn = parse(rule);
if (needWarn){
// 记录报警日志
AlarmLog alarmLog = new AlarmLog();
alarmLog.setAlarmStartDate(LocalDateTime.now());
alarmLog.setAlarmValue(current.toString());
String ruleName = alarmRule.getName();
Double threshold = rule.getThreshold();
String message = "您设定的预警规则:"+ruleName+"," + "预警阈值为:["+
operator+threshold+"],当前值为:"+current;
alarmLog.setAlarmInfo(message);
alarmClient.create(alarmLog);
// 发送报警信息
String groupId = alarmRule.getContactId();
String notific = alarmRule.getNotification();
sendMessage.send(message,groupId,notific);
}
} catch (JsonProcessingException e) {
log.error("预警规则{}解析失败!",operator);
e.printStackTrace();
}catch (RuntimeException e){
e.printStackTrace();
}
}
}

View File

@ -100,13 +100,7 @@ public class ConsumeA1 implements StreamListener<String, ObjectRecord<String, Ru
boolean needWarn = parse(rule);
// 发送预警信息
if (needWarn){
// 规则名称
rule.setRuleName(alarmRule.getName());
// 报警信息发送方式
rule.setNotific(alarmRule.getNotification());
// 联系人组id
rule.setGroupId(alarmRule.getContactId());
sendMessage.send(rule);
// sendMessage.send(rule);
}
}
}

View File

@ -33,7 +33,7 @@ public class RedisStreamConfig {
private final Integer maxMsg = 10;
// Stream Key
private final String warnKey = CommonConstant.STREAM_KEY_WARN;
private final String warnKey = CommonConstant.STREAM_ALARM;
// 消费组名
private final String groupWarnA = CommonConstant.GROUP_WARN_A;