分等级和超时时间段推送企业微信

分等级和超时时间段推送企业微信

场景:

系统检测的报警,需要分职位和时间段推送给对应的负责人的企业微信,具体流程如下:

推送规则:

需求:

  1. 目前流程节点是以时间点往下增加的,而且可时间点时间可配置,
  2. 分组-人员-类别等均可配置
  3. 时间节点有可能会增加

分析:

  1. 需要新增加标志信息表,来记录告警的多阶段推送标记,如果每个时间点都用一个标志位的话,如果将来时间判断节点增加了或则是减少了怎么办呢,是修改数据库吗?显然不符合设计理念。 > 1. 我是通过一个字段来表示三个节点在未处理报警和处理报警的状态: > 1. 初始化状态:标志位的值为 0 (000) 2. 情况一:已处理报警:标志位的值为 7 (111) 3. 情况二:未处理报警 & 刚超过8小时 :标志位的值为 1 (001) 4. 情况三:未处理报警 & 刚超过16小时 :标志位的值为 3 (011) 5. 情况三:未处理报警 & 刚超过24小时 :标志位的值为 7 (111) 2. 同理在增加一个字段来表示三个节点在未整改报警和整改报警的状态: > 1. 略 3. 这样即便将来再增加时间节点依次在添加一个二进制标志位即可,就能灵活应对时间节点的变化了

  2. 是通过定时任务定时扫描数据,还是通过rabbitmq的延迟消息对来来完成呢? > 1. 如果是定时任务定时扫描:消息可能会有些许延迟;(采用该方式) 2. 如果是使用rabbitmq延迟消息:当产生了一条报警后,就让延迟队列中新增加一条数据,当时间到的时候,通过查询数据库标志位,查看是否满足条件,再决定是否发送企业微信消息,在新增下一个时间段到延迟队列中,同样的问题,如果将来时间点变化,怎么办? 3. 其他方式(我暂时没想到)

数据库设计

drop table if exists wechat_push_alarm_statistics;
create table wechat_push_alarm_statistics(
    id bigint auto_increment primary key comment 'id',
    alarm_info_manager_id bigint null comment '告警ID',
    potential_safety_hazard_id bigint null comment '整改ID',
    alarm_time datetime null comment '告警时间',
    deal_with_time datetime null comment '处理告警时间',
    potential_time datetime null comment '整改时间',
    # 0 1 3 7 15 .....
    # 1 = 0 + 1 << 0
    # 3 = 1 + 1 << 1
    # 7 = 3 + 1 << 2
    deal_with_flag int not null default 0 comment '是否处理报警:0-未处理;1-已处理',
    potential_flag int not null default 0 comment '是否整改:0-未整改;1-已整改'
)comment '微信推送告警统计信息表';
drop index idx_dwy on wechat_push_alarm_statistics;
create index idx_dwy on wechat_push_alarm_statistics(deal_with_flag);
drop index idx_py on wechat_push_alarm_statistics;
create index idx_py on wechat_push_alarm_statistics(potential_flag);
drop index idx_dwy_py on wechat_push_alarm_statistics;
create index idx_dwy_py on wechat_push_alarm_statistics(deal_with_flag, potential_flag);

根据需求设计配置属性类

@Data
@ConfigurationProperties(prefix = "sin.var.push.alarm.wechat")
public class SinvarProperties {

    private String fdfsHttp;

    private String msgTemplate;

    private List<Group> groupList;

    /**
     * 用于时间段配置[480,960,1440]
     */
    private List<Integer> overTimeList;

    @Data
    public  static class Group {
        private String id;

        /**
         * 超时时间配置:(单位分钟)
         */
        private Integer overTime;

        /**
         * 等级
         */
        private Integer level;

        private List<String> userPhoneList;

        private List<String> alarmTypeList;
    }

}

设计技巧:

  1. private List overTimeList; 数组中的时间顺序一定要和group中的id匹配; 后面通过 (id - 1) 来当做是 overTimeList 数组的下标,
  2. 通过 level 来表示部门分组,
  3. 通过两个数组userPhoneList,alarmTypeList 来分别存放人员和推送类别

通过设计模式-过滤器链模式来解决

实体类,Mapper类:WechatPushAlarmStatistics, WechatPushAlarmStatisticsMapper

下面的方法中,mapper的方法比较古老,不是最新的mybatis-plus框架,所以。。。。。。

接口:IWeChatPushAlarmHandle

public interface IWeChatPushAlarmHandle {
    void handle(List<WechatPushAlarmStatistics> wechatPushAlarmStatisticsList);

}

抽象类:AbstractWeChatPushAlarmHandleImpl

@RequiredArgsConstructor
public abstract class AbstractWeChatPushAlarmHandleImpl implements IWeChatPushAlarmHandle{
    public final SinvarProperties sinvarProperties;
    public final WechatPushAlarmStatisticsMapper wechatPushAlarmStatisticsMapper;
    @Override
    public void handle(List<WechatPushAlarmStatistics> wechatPushAlarmStatisticsList) {
        this.business(wechatPushAlarmStatisticsList);
    }

    abstract protected void business(List<WechatPushAlarmStatistics> wechatPushAlarmStatisticsList);

    /**
     * @param wechatPushAlarmStatistics
     * @param flageSupplier
     * @param dateTime
     * @param handleCode 表示:1-处理;2-整改
     */
    protected final void timePhaseJudgment(WechatPushAlarmStatistics wechatPushAlarmStatistics, Supplier<Integer> flageSupplier, Supplier<Date> dateTime, Integer handleCode){
        // 获取时间段配置
        List<Integer> overTimeList = sinvarProperties.getOverTimeList();
        Integer index = this.getPhase(flageSupplier);
        // 通过index来标识阶段,获取对应的超时时间
        if(index != null && index < overTimeList.size()){
            if(dateTime.get() != null){
                // 判断时间 + 超时时间
                Date endTime = DateUtils.addMinute(dateTime.get(), overTimeList.get(index));
                Date nowTime = new Date();
                // 如果当前时间比endTime小,则已超时
                if(nowTime.before(endTime)){
                    // 发送企业微信 修改数据库标志位数据
                    this.sendWeChatMessage(handleCode, index);
                    if(1 == handleCode){
                        wechatPushAlarmStatistics.setDealWithFlag(flageSupplier.get() + (WeChatPushAlarmConstant.FLAG_CONSTANT << index));
                        wechatPushAlarmStatisticsMapper.updateByPrimaryKeySelective(wechatPushAlarmStatistics);
                    }else{
                        wechatPushAlarmStatistics.setPotentialFlag(flageSupplier.get() + (WeChatPushAlarmConstant.FLAG_CONSTANT << index));
                        wechatPushAlarmStatisticsMapper.updateByPrimaryKeySelective(wechatPushAlarmStatistics);
                    }
                }
            }
        }

    }

    protected final Integer getPhase(Supplier<Integer> supplier){
        if(WeChatPushAlarmConstant.ZERO.equals(supplier.get())){
            return WeChatPushAlarmConstant.ZERO;
        }else if(supplier.get().equals(WeChatPushAlarmConstant.FLAG_CONSTANT)){
            return 1;
        }else if(supplier.get().equals(WeChatPushAlarmConstant.FLAG_CONSTANT + WeChatPushAlarmConstant.FLAG_CONSTANT << 1)){
            return 2;
        }
        return null;
    }

    protected final void sendWeChatMessage(Integer handleCode, Integer index){
        List<SinvarProperties.Group> groupList = sinvarProperties.getGroupList();
        groupList.stream().filter(item-> String.valueOf(handleCode).equals(item.getId()) && item.getLevel().equals(index + 1))
                .forEach(item->{
                    // TODO 发送企业微信的具体逻辑
                });
    }
}

方法:protected final Integer getPhase(Supplier<Integer> supplier)

protected final Integer getPhase(Supplier<Integer> supplier){
        if(WeChatPushAlarmConstant.ZERO.equals(supplier.get())){
            return WeChatPushAlarmConstant.ZERO;
        }else if(supplier.get().equals(WeChatPushAlarmConstant.FLAG_CONSTANT)){
            return 1;
        }else if(supplier.get().equals(WeChatPushAlarmConstant.FLAG_CONSTANT + WeChatPushAlarmConstant.FLAG_CONSTANT << 1)){
            return 2;
        }
        return null;
    }
  1. 根据标记为的值来判断当前处于哪个阶段,因为这三个节点之间是互斥,这里通过返回 0,1,2 来当做 overTimeList 数组的下标。

方法:protected final void timePhaseJudgment(WechatPushAlarmStatistics wechatPushAlarmStatistics, Supplier flageSupplier, Supplier dateTime, Integer handleCode)

protected final void timePhaseJudgment(WechatPushAlarmStatistics wechatPushAlarmStatistics, Supplier<Integer> flageSupplier, Supplier<Date> dateTime, Integer handleCode){
        // 获取时间段配置
        List<Integer> overTimeList = sinvarProperties.getOverTimeList();
        Integer index = this.getPhase(flageSupplier);
        // 通过index来标识阶段,获取对应的超时时间
        if(index != null && index < overTimeList.size()){
            if(dateTime.get() != null){
                // 判断时间 + 超时时间
                Date endTime = DateUtils.addMinute(dateTime.get(), overTimeList.get(index));
                Date nowTime = new Date();
                // 如果当前时间比endTime小,则已超时
                if(nowTime.before(endTime)){
                    // 发送企业微信 修改数据库标志位数据
                    this.sendWeChatMessage(handleCode, index);
                    if(1 == handleCode){
                        wechatPushAlarmStatistics.setDealWithFlag(flageSupplier.get() + (WeChatPushAlarmConstant.FLAG_CONSTANT << index));
                        wechatPushAlarmStatisticsMapper.updateByPrimaryKeySelective(wechatPushAlarmStatistics);
                    }else{
                        wechatPushAlarmStatistics.setPotentialFlag(flageSupplier.get() + (WeChatPushAlarmConstant.FLAG_CONSTANT << index));
                        wechatPushAlarmStatisticsMapper.updateByPrimaryKeySelective(wechatPushAlarmStatistics);
                    }
                }
            }
        }

    }
  1. 形参:Integer handleCode: 用来标识是处理报警流程还是整改报警流程,决定了下面修改哪个标志位

  2. 在修改标志位的时候,没有设置具体的值,而是通过位运算来站位计算的

    1. 分析:

      wechatPushAlarmStatistics.setDealWithFlag(flageSupplier.get() + (WeChatPushAlarmConstant.FLAG_CONSTANT << index))
    2. 情况一:0 -> 1: 原值:0 ;index: 0; 所以上面表达式:0 + 1 « 0 = 1

    3. 情况二:1 -> 3: 原值:1 ;index: 1; 所以上面表达式:1 + 1 « 1 = 3

    4. 情况三:3 -> 7: 原值:3 ;index: 2; 所以上面表达式:3 + 1 « 2 = 7

方法:protected final void sendWeChatMessage(Integer handleCode, Integer index)

protected final void sendWeChatMessage(Integer handleCode, Integer index){
    List<SinvarProperties.Group> groupList = sinvarProperties.getGroupList();
    groupList.stream().filter(item-> String.valueOf(handleCode).equals(item.getId()) && item.getLevel().equals(index + 1))
            .forEach(item->{
                // TODO 发送企业微信的具体逻辑
            });
}
  1. 通过配置类group中的 id + level 两个字段来匹配当前告警需要发送到哪些人的手机上,然后在通过报警类型过滤,最后获取人员手机号发送短信即可。

实现类:DealWithWeChatPushAlarmHandleImpl

@Order(1)
@Slf4j
@Service
public class DealWithWeChatPushAlarmHandleImpl extends AbstractWeChatPushAlarmHandleImpl{


    public DealWithWeChatPushAlarmHandleImpl(SinvarProperties sinvarProperties, WechatPushAlarmStatisticsMapper wechatPushAlarmStatisticsMapper) {
        super(sinvarProperties, wechatPushAlarmStatisticsMapper);
    }

    @Override
    protected void business(List<WechatPushAlarmStatistics> wechatPushAlarmStatisticsList) {
        for(WechatPushAlarmStatistics wechatPushAlarmStatistics : wechatPushAlarmStatisticsList){
            this.timePhaseJudgment(wechatPushAlarmStatistics, wechatPushAlarmStatistics::getDealWithFlag, wechatPushAlarmStatistics::getAlarmTime, 1);
        }
    }
}

实现类:PotentialWeChatPushAlarmHandleImpl

@Order(2)
@Slf4j
@Service
public class PotentialWeChatPushAlarmHandleImpl extends AbstractWeChatPushAlarmHandleImpl{


    public PotentialWeChatPushAlarmHandleImpl(SinvarProperties sinvarProperties, WechatPushAlarmStatisticsMapper wechatPushAlarmStatisticsMapper) {
        super(sinvarProperties, wechatPushAlarmStatisticsMapper);
    }

    @Override
    protected void business(List<WechatPushAlarmStatistics> wechatPushAlarmStatisticsList) {
        for(WechatPushAlarmStatistics wechatPushAlarmStatistics : wechatPushAlarmStatisticsList){
            this.timePhaseJudgment(wechatPushAlarmStatistics, wechatPushAlarmStatistics::getPotentialFlag, wechatPushAlarmStatistics::getDealWithTime, 2);
        }
    }
}

定时任务类

@Slf4j
@Component
@RequiredArgsConstructor
public class WeChatPushAlarmInfoSchedule {

    private final WechatPushAlarmStatisticsMapper wechatPushAlarmStatisticsMapper;

    private final List<IWeChatPushAlarmHandle> handleList;
    @Scheduled(cron = "* 0/5 * * * ?")
    public void business(){
        Integer dealWithFlag = WeChatPushAlarmConstant.FLAG_CONSTANT  + WeChatPushAlarmConstant.FLAG_CONSTANT << 1 + WeChatPushAlarmConstant.FLAG_CONSTANT << 2;
        Integer potentialFlag = WeChatPushAlarmConstant.FLAG_CONSTANT  + WeChatPushAlarmConstant.FLAG_CONSTANT << 1 + WeChatPushAlarmConstant.FLAG_CONSTANT << 2;
        // 获取当前时间前3天的日期
        Date startTime = DateUtils.getBeforeDay(3);
      // 过滤掉两个标志位值为7的数据
        List<WechatPushAlarmStatistics> list = wechatPushAlarmStatisticsMapper.getList(dealWithFlag, potentialFlag, startTime, new Date());
        for(IWeChatPushAlarmHandle handle : handleList){
            handle.handle(list);
        }
    }
}
0%