数据预警第三方渠道添加示例参考AlarmPushChannel.zip


项目相关文件说明


# 整体说明

预警推送有两种,一种是常规消息推送,一种是含动态接收人的消息推送。由于动态接收人的相关逻辑比较复杂,为了方便后续有项目只需要常规推送,准备了两个demo,分是常规推送渠道demo和包含动态接收人的渠道demo

# 前端二开运行

详见 仪表盘 前端二开开发文档说明 [自助仪表盘开发快速入门](https://smartbi.feishu.cn/docs/doccnY0Tuaqlq9WPLksPflegeUh)  
1. cd进webpack目录
2. 执行npm install smartbi-ext-env
3. npm run dev (开发调试)  npm run build (打包命令)

` 这个项目运行直接使用 npm run dev 即可, 已将 node_modules 一起打包`

# 整体结构

## 前端文件

前端文件在 src\webpack\src\plugins\modifyExtenders\alarm-push 目录下



1. src\webpack\src\plugins\modifyExtenders\alarm-push\index.js  
前端整体入口文件,供X二开扫描引用
2. src\webpack\src\plugins\modifyExtenders\alarm-push\AlarmPushExtender.js  
渠道扩展点插入定义入口,包含常规渠道`DEMO`, 和 动态接收人 `DYNAMIC_DEMO` 两个渠道
```
import SmartBIExt from  'smartbi-ext'
import SxAlarmPushChannel from './AlarmPushChannel.vue'
import SxAlarmDynamicPushChannel from './AlarmDynamicPushChannel.vue'

let {
  AlarmModule: {
    AlarmEventEnum: {
      ALARM_PUSH_ON_INIT
    },
    BaseAlarmExtender
  },
  Lang
} = SmartBIExt

class AlarmPushExtender extends BaseAlarmExtender {
  
  constructor() {
    super()
  }

  install () {
    // 预警推送界面初始化接口
    this.on(ALARM_PUSH_ON_INIT, iAlarmPush => {
      const channel = {
        id: 'DEMO', // id 即是渠道的类型
        icon: 'sx-icon-push-textmessage icon-16', // 图标
        label: Lang.$t('pushChannel.pushDemo'), // 标签
        tooltip: Lang.$t('pushChannel.pushDemo'), // 提示
        comp: SxAlarmPushChannel, // 面板实现Vue,具体的预警面板内容
        getDefaultData () { // 添加新的渠道时,初始化的默认数据结构
          return {
            config: {
              receiverType: 'USER',
              receivers: [],
              custom: ''
            }
          }
        },
        getDefaultTitle () { // 默认标题
          return Lang.$t('pushChannel.pushDemo')
        }
      }
      iAlarmPush.insertChannel(-1, channel) // 插入渠道,-1 标识插入到末尾
    })
  }
}
```
3. src\webpack\src\plugins\lang.js  
前端多语言文件
4. src\webpack\src\assets\alarmpush.css  
前端CSS样式文件

5. SmartbiX封装好的相关控件
```
const {
  SxAlarmRecipientInput,
  SxAlarmDynamicRecipient,
  SettingPanel: {
    SxInputTextWidget,
    SxSelectWidget
  }
} = SmartUI
```
    a. SxAlarmRecipientInput 预警接收用户控件,封装了打开用户弹窗,选择用户的逻辑
    b. SxAlarmDynamicRecipient 预警动态接收人控件,封装了动态接收人的控件实现
    c. SxInputTextWidget 符合礼显哥UI规范的输入框控件
    d. SxSelectWidget 符合礼显哥UI规范的下拉选择框控件

## 后端文件

后端文件在 src\java\smartbi\demo 目录下

1. src\java\smartbi\demo\AlarmPushDemoModule.java   
后端逻辑注册主文件,注册 渠道订阅器、预警渠道实现代理
```
        // 某些服务器异常情况下,可能会重复注册subscriber,导致接到的消息发送两遍,复现原因场景未知
		// 先取消注册,再重新注册
		// 通过重写subscriber的equals方法使subscriber移除注册成功
		CommonMessageService.getInstance().unsubscribe(demoType, subscriber);
		CommonMessageService.getInstance().subscribe(demoType, subscriber);
		AlarmModule.getInstance().addAlarmChannelDelegate("DEMO", new AlarmDemoDelegate());
		AlarmModule.getInstance().addAlarmChannelDelegate("DYNAMIC_DEMO", new AlarmDemoDynamicDelegate());
		AlarmModule.getInstance().registerAlarmChannelClz("DYNAMIC_DEMO", AlarmDemoChannel.class);
```
2. src\java\smartbi\demo\errorcode\DemoErrorCode.java  
错误码定义文件,用来规范渠道的异常信息,对应多语言文件为 src\web\META-INF\classes\smartbi\demo\errorcode\DemoErrorCode_en.properties、src\web\META-INF\extension_lang_zh_CN.properties、src\web\META-INF\extension_lang_zh_TW.properties

# 渠道推送

## 渠道subscriber
由消息推送模块 CommonMessageService 统一管理接收从预警渠道发出的消息,每个渠道的subscriber自己接收处理  
相关文件在 src\java\smartbi\demo\channel\config 目录下

1. src\java\smartbi\demo\channel\subscriber\AlarmPushDemoSubscriber.java
自定义渠道推送的主文件,主要方法为 onMessage。接收的参数应为自己渠道接收的指类型,如IDemoConfig等。该文件在执行时,自己对相关参数进行类型转换,来处理后续逻辑

```
public class AlarmPushDemoSubscriber implements ISubscriber {

	@Override
	public void onMessage(IMessageBody messageBody, IChannelConfig channelConfig, IPublisher publisher) {
		if (channelConfig == null) {
			return;
		}
		// 类型强转
		IDemoConfig demoConfig = (IDemoConfig) channelConfig;
		try {
			send(messageBody, demoConfig);
		} catch (Exception e) {
			LogManager.getLogger(AlarmPushDemoSubscriber.class).error(e.getMessage(), e);
			futureException(channelConfig, e);
		}
	}
}
```
2. src\java\smartbi\demo\channel\config\DemoConfig.java
AlarmPushDemoSubscriber 接收的主要参数,含有渠道推送需要的相关定义,主要由于该类没有实现ICompletableConfig接口,只能能接收处理预警推送消息,无法将处理后的消息结果进行回调处理,返回给预警

3. src\java\smartbi\demo\channel\config\DemoCompletableConfig.java  
AlarmPushDemoSubscriber 接收的主要参数,含有渠道推送需要的相关定义有实现ICompletableConfig接口,可以通过config.getFuture().complete(DemoResult)将相关推动消息结果进行回调处理,返回给预警

4. src\java\smartbi\demo\channel\config\DemoReceiverType.java  
渠道可能会接收多种推送方式,不同的推送方式,处理不同,使用枚举类区别规范化

5. src\java\smartbi\demo\channel\config\DemoMessageBodyExtend.java  
IMessageBody messageBody 的相关接口提供了基本的推送消息内容。但是不同的渠道对信息的需求不同,这些信息统一xiezaile messageBody exntended中,DemoMessageBodyExtend为和exntended数据结构对应的实体bean

6. src\java\smartbi\demo\channel\config\DemoResult.java  
通过IDemoCompletableConfig接口回调给预警的结果,预警接到结果后,对结果中的信息进行解析,将详细信息和错误信息写入预警日志

7. src\java\smartbi\demo\channel\config\DemoResultType.java  
渠道推送处理的结果类型,方便预警根据结果类型进行归类处理相信信息和错误信息

8. 将AlarmPushDemoSubscriber 注册给渠道  
在 AlarmPushDemoModule中将AlarmPushDemoSubscriber注册给系统,以便能够由系统统一管理渠道的推送消息接收
```
// 某些服务器异常情况下,可能会重复注册subscriber,导致接到的消息发送两遍,复现原因场景未知
		// 先取消注册,再重新注册
		// 通过重写subscriber的equals方法使subscriber移除注册成功
		CommonMessageService.getInstance().unsubscribe(demoType, subscriber);
		CommonMessageService.getInstance().subscribe(demoType, subscriber);
```

## 常规渠道推送

### 前端实现文件

1. src\webpack\src\plugins\modifyExtenders\alarm-push\AlarmPushChannel.vue
定义常规渠道推送的文件,通过实现主要接口,将结果从预警读取和写入预警
```
export default {
  name: 'SxAlarmPushChannel',
  components: { SxAlarmRecipientInput, SxInputTextWidget },
  props: {
    channel: {
      type: Object, // 渠道定义
    },
  },
  data () {
    return {
      config: {
        // 接收人相关的配置项
        receiverType: 'USER',
        receivers: [],
        custom: ''
      },
      ...
    }
  },
  ...
  created () {
    this.intConfig()
  },
  methods: {
    intConfig () {
      // 解析传入的channel,初始化
      let config = this.channel ? this.channel.config : {}
      this.config = {
        receiverType: config.receiverType,
        receivers: config.receivers || [],
        custom: config.custom
      }
    },
    ...
    // 校验方法,校验当前页面的输入是否合法,由预警系统整体调用
    validate () {
      return new Promise((resolve, reject) => {
        this.$refs.form.validate((valid) => resolve(valid))
      })
    },
    // 将被父组件 AlarmPushChannel.vue 调用
    getContent () {
      // 返回定义主体内容
      return {
        // config 属性,返回时需要写在自己定义的属性名下
        // 返回的结构和在AlarmPushExtender  getDefaultData方法的对象结构整体一致
        // AlarmPushChannel.vue 会将属性定义复制到 channel 定义中
        config: {
          // 接收人相关的配置项
          receiverType: this.config.receiverType,
          receivers: this.config.receiverType === 'USER' ? this.config.receivers : [],
          custom: this.config.receiverType === 'CUSTOM' ? this.config.custom : ''
        }
      }
    },
    async sync () {
      // 预警定义变更时,做一些同步动作,主要动态接收人时需要
    }
  }
}
```
### 后端实现文件

后端预警渠道实现文件在 src\java\smartbi\demo\alarm 目录先

#### 渠道定义相关文件

将JavaBean和前端渠道定义channel 的结构一一对应,方便delegate中的逻辑处理

1. src\java\smartbi\demo\alarm\AlarmDemoChannel.java  
渠道定义文件,其结构和预警渠道定义一致

2. src\java\smartbi\demo\alarm\AlarmDemoChannelConfig.java  
渠道Config文件,其结构和前端config界面文件一致

### 渠道delegate文件

对接预警渠道处理逻辑的文件,通过实现相关接口逻辑,使得预警系统那边能够根据相关信息进行消息推送

1. src\java\smartbi\demo\alarm\delegate\AlarmDemoDelegate.java
```
/**
 * 常规的渠道推送delegate
 */
public class AlarmDemoDelegate implements IAlarmChannelDelegate {

	@Override
	public IAlarmConvertor getContentConvertor() {
		// html 转其他内容的转换器,以预警系统做整体上的转换
		// 可以自己在channel的 subsriber中自己做转换,以更好的切合实际
		return null;
	}

	// 返回渠道接受的config
    // 系统接收到config后,组装拼接成CommonMessageService需要消息体等,由预警统一进行消息推送处理
	@Override
	public IChannelConfig getChannelConfig(AlarmBO alarmBO, IAlarmChannel channel) {
		// 转成对应的实体bean,方便后续读取
        // channel.toJSON() 为将渠道转成JSON,主要针对没有将渠道对应的JavaBean如AlarmDemoChannel注册给预警系统的情况,如果注册后,可以直接进行类型转换
		AlarmDemoChannel demoChannel = JSONUtil.toBean(channel.toJSON(), AlarmDemoChannel.class);
		DemoCompletableConfig config = new DemoCompletableConfig();
		...
		return config;
	}

    // 对推送后的结构进行包装成IAlarmChannelResult接口对象,供预警统一执行判读推送结果和记录日志
	@Override
	public IAlarmChannelResult getAlarmSendResult(AlarmBO alarmBO, IAlarmChannelConfig config) {
		IChannelConfig channelConfig = config.getChannelConfig();
        // AlarmChannelResult 为产品定义的 IAlarmChannelResult
        // 渠道有需要也可以自己实现
        // 具体处理逻辑见 AlarmDemoDelegate
		AlarmChannelResult result = new AlarmChannelResult();
		...
		return result;
	}
	
	...
}

```
2. 在 AlarmPushDemoModule 中  注册Delegate

```
// 第一个参数 "DEMO" 和在AlarmPushExtender中的渠道ID 一致
AlarmModule.getInstance().addAlarmChannelDelegate("DEMO", new AlarmDemoDelegate());
```

## 含动态接收人逻辑的渠道推送

### 前端实现文件

1. src\webpack\src\plugins\modifyExtenders\alarm-push\AlarmDynamicPushChannel.vue
```
export default {
  name: 'SxAlarmDynamicPushChannel',
  components: { SxAlarmRecipientInput, SxInputTextWidget, SxSelectWidget, SxAlarmDynamicRecipient },
  props: {
    channel: {
      type: Object,
    },
    alarmBusiness: Object
  },
  data () {
    return {
      config: {
        // 接收人相关的配置项
        receiverType: 'USER',
        receivers: [],
        custom: '',
        dynamic: {
          id: '',
          type: null
        }
      },
      ...
  },
  computed: {
    ...
    fields () {
      return (this.alarmBusiness.getDataProvider().getFields() || []).map(({ uniqueId: value, label }) => ({ value, label }))
    }
  },
  watch: {
  },
  created () {
    this.intConfig()
  },
  methods: {
    intConfig () {
      // 解析输入的channel,初始化
      let config = this.channel ? this.channel.config : {}
      let dynamic = config.dynamic || { id: '', type: null }
      this.config = {
        receiverType: config.receiverType,
        receivers: config.receivers || [],
        custom: config.custom,
        dynamic: {
          id: dynamic.id,
          type: dynamic.type
        }
      }
    },
    ...
    validate () {
      return new Promise((resolve, reject) => {
        this.$refs.form.validate((valid) => resolve(valid))
      })
    },
    // 将被父组件 AlarmPushChannel.vue 调用
    getContent () {
      // 返回定义主体内容
      return {
        // config 属性,返回时需要写在自己定义的属性名下
        // AlarmPushChannel.vue 会将属性定义复制到 channel 定义中
        config: {
          // 接收人相关的配置项
          receiverType: this.config.receiverType,
          receivers: this.config.receiverType === 'USER' ? this.config.receivers : [],
          custom: this.config.receiverType === 'CUSTOM' ? this.config.custom : '',
          dynamic: this.config.receiverType === 'DYNAMIC' ? this.config.dynamic : {},
        }
      }
    },
    ... 
    // 将被父组件 AlarmPushChannel.vue 调用
    async sync () {
      // 前面数据范围字段变更后,可能动态接收人选择的字段已经不存在了
      // 这个时候需要清空动态接收人的字段定义
      if (this.checkFieldId()) {
        return
      }
      this.$set(this.config.dynamic, 'id', null)
    },
    checkFieldId () {
      let id = this.config.dynamic.id
      return this.fields.some(f => f.value === id)
    }
  }
```

### 后端实现文件

对接预警渠道处理逻辑的文件,通过实现相关接口逻辑,使得预警系统那边能够根据相关信息进行消息推送

1. src\java\smartbi\demo\alarm\delegate\AlarmDemoDynamicDelegate.java
```
/**
 * 
 * 包含动态接收人处理逻辑的delegate
 */
public class AlarmDemoDynamicDelegate implements IAlarmChannelDelegate, IAlarmChannelDynamicDelegate { // 一般即实现常规渠道推送接口,也实现动态接收人渠道推送接口
    

	@Override
	public IAlarmConvertor getContentConvertor() {
		// html 转其他内容的转换器,以预警系统做整体上的转换
		// 可以自己在channel的 subsriber中自己做转换,以更好的切合实际
		return null;
	}

	// 返回渠道接受的config
	@Override
	public IChannelConfig getChannelConfig(AlarmBO alarmBO, IAlarmChannel channel) {
		AlarmDemoChannel demoChannel = (AlarmDemoChannel) channel;
		DemoCompletableConfig config = new DemoCompletableConfig();
		config.setType(ChannelType.valueOf(demoChannel.getType().name()));
		config.setCustom(demoChannel.getConfig().getCustom());
		config.setHtml(true);
		config.setReceiverType(demoChannel.getConfig().getReceiverType());
		config.setRecipients(demoChannel.getConfig().getReceivers().stream().map(reciver -> 
			new AlarmChannelRecipient(RecipientType.valueOf(reciver.getTargettype()), reciver.getTargetid(), reciver.getAlias()))
				.collect(Collectors.toList()));
		return config;
	}

	@Override
	public IAlarmChannelResult getAlarmSendResult(AlarmBO alarmBO, IAlarmChannelConfig config) {
		return new AlarmDemoResultGenerator().generate(config);
	}
	
	// ------------- 下面是动态接收人的处理逻辑  ----------------------

	/**
	 * 是否是动态接收人的方式
     * 是动态接收人时,由预警系统管理相关逻辑走动态接收人的逻辑
	 */
	@Override
	public boolean isDynamic(AlarmBO alarmBO, IAlarmChannel channel) {
        // AlarmDemoChannel 注册给了预警系统,有预警系统统一转成对应的JavaBean,和前端属性一一对应,可以直接进行类型转义
		AlarmDemoChannel demoChannel = (AlarmDemoChannel) channel;
		return DemoReceiverType.DYNAMIC == demoChannel.getConfig().getReceiverType();
	}

	@Override
	public IAlarmDynamicRecipient getDynamicRecipient(AlarmBO alarmBO, IAlarmChannel channel) {
		// 返回动态接收人对象
		return ((AlarmDemoChannel) channel).getConfig().getDynamic();
	}

	// 动态接收人接口生成config
	@Override
	public IChannelConfig getChannelConfig(AlarmBO alarmBO, IAlarmChannel channel, List<IRecipient> recipients) {
        // 对于动态接收人,实际走的时User类型的渠道退丝攻
        // 只是接收人是由预警系统根据执行结果生成的

		// 转成对应的实体bean,方便后续读取
		AlarmDemoChannel demoChannel = (AlarmDemoChannel) channel;
		DemoCompletableConfig config = new DemoCompletableConfig();
		config.setType(ChannelType.valueOf("DEMO"));
		config.setCustom(demoChannel.getConfig().getCustom());
		config.setHtml(true);
		// 动态接收人实际走的用户逻辑
		config.setReceiverType(DemoReceiverType.USER);
		// 将接收人设置为本次的接收人
		config.setRecipients(recipients);
		return config;		
	}
	
	@Override
	public IAlarmChannelResult getAlarmDynamicSendResult(AlarmBO alarmBO, IAlarmChannel channel,
			List<IAlarmChannelDynamicConfig> dynamicConfigs) {
        // dynamicConfigs 是动态接收人结果config列表
        // 一次推送动态接收人,由于动态接收人会触发多次推送,需要将多次推送的结果合并处理后,才是这次渠道的推送结果
		return new AlarmDemoResultGenerator().generateDynamicResult(channel, dynamicConfigs);
	}
```

2. src\java\smartbi\demo\alarm\delegate\AlarmDemoResultGenerator.java  
预警渠道结果生成器,将渠道的返回信息进行解析整理,返回给预警系统

3. 在 AlarmPushDemoModule 中  注册Delegate

```
// 第一个参数 "DYNAMIC_DEMO" 和在AlarmPushExtender中的渠道ID 一致
AlarmModule.getInstance().addAlarmChannelDelegate("DYNAMIC_DEMO", new AlarmDemoDynamicDelegate());
// 将渠道数据结构对应的JavaBean注册给预警,以便后续delegate处理时,不需要每次都使用JSONUtil转成JavaBean。
// 如果想要在delegate直接操作ObjectNode,也可以不用注册
// 操作ObjectNode的工具类 smarbix.util.JSONUtil.java
		AlarmModule.getInstance().registerAlarmChannelClz("DYNAMIC_DEMO", AlarmDemoChannel.class);
```




预警推送有两种,一种是常规消息推送,一种是含动态接收人的消息推送。由于动态接收人的相关逻辑比较复杂,为了方便后续有项目只需要常规推送,准备了两个demo,分是常规推送渠道demo和包含动态接收人的渠道demo

# 前端二开运行

详见 仪表盘 前端二开开发文档说明 [自助仪表盘开发快速入门](https://smartbi.feishu.cn/docs/doccnY0Tuaqlq9WPLksPflegeUh)  
1. cd进webpack目录
2. 执行npm install smartbi-ext-env
3. npm run dev (开发调试)  npm run build (打包命令)

` 这个项目运行直接使用 npm run dev 即可, 已将 node_modules 一起打包`

# 整体结构

## 前端文件

前端文件在 src\webpack\src\plugins\modifyExtenders\alarm-push 目录下



1. src\webpack\src\plugins\modifyExtenders\alarm-push\index.js  
前端整体入口文件,供X二开扫描引用
2. src\webpack\src\plugins\modifyExtenders\alarm-push\AlarmPushExtender.js  
渠道扩展点插入定义入口,包含常规渠道`DEMO`, 和 动态接收人 `DYNAMIC_DEMO` 两个渠道
```
import SmartBIExt from  'smartbi-ext'
import SxAlarmPushChannel from './AlarmPushChannel.vue'
import SxAlarmDynamicPushChannel from './AlarmDynamicPushChannel.vue'

let {
  AlarmModule: {
    AlarmEventEnum: {
      ALARM_PUSH_ON_INIT
    },
    BaseAlarmExtender
  },
  Lang
} = SmartBIExt

class AlarmPushExtender extends BaseAlarmExtender {
  
  constructor() {
    super()
  }

  install () {
    // 预警推送界面初始化接口
    this.on(ALARM_PUSH_ON_INIT, iAlarmPush => {
      const channel = {
        id: 'DEMO', // id 即是渠道的类型
        icon: 'sx-icon-push-textmessage icon-16', // 图标
        label: Lang.$t('pushChannel.pushDemo'), // 标签
        tooltip: Lang.$t('pushChannel.pushDemo'), // 提示
        comp: SxAlarmPushChannel, // 面板实现Vue,具体的预警面板内容
        getDefaultData () { // 添加新的渠道时,初始化的默认数据结构
          return {
            config: {
              receiverType: 'USER',
              receivers: [],
              custom: ''
            }
          }
        },
        getDefaultTitle () { // 默认标题
          return Lang.$t('pushChannel.pushDemo')
        }
      }
      iAlarmPush.insertChannel(-1, channel) // 插入渠道,-1 标识插入到末尾
    })
  }
}
```
3. src\webpack\src\plugins\lang.js  
前端多语言文件
4. src\webpack\src\assets\alarmpush.css  
前端CSS样式文件

5. SmartbiX封装好的相关控件
```
const {
  SxAlarmRecipientInput,
  SxAlarmDynamicRecipient,
  SettingPanel: {
    SxInputTextWidget,
    SxSelectWidget
  }
} = SmartUI
```
    a. SxAlarmRecipientInput 预警接收用户控件,封装了打开用户弹窗,选择用户的逻辑
    b. SxAlarmDynamicRecipient 预警动态接收人控件,封装了动态接收人的控件实现
    c. SxInputTextWidget 符合礼显哥UI规范的输入框控件
    d. SxSelectWidget 符合礼显哥UI规范的下拉选择框控件

## 后端文件

后端文件在 src\java\smartbi\demo 目录下

1. src\java\smartbi\demo\AlarmPushDemoModule.java   
后端逻辑注册主文件,注册 渠道订阅器、预警渠道实现代理
```
        // 某些服务器异常情况下,可能会重复注册subscriber,导致接到的消息发送两遍,复现原因场景未知
		// 先取消注册,再重新注册
		// 通过重写subscriber的equals方法使subscriber移除注册成功
		CommonMessageService.getInstance().unsubscribe(demoType, subscriber);
		CommonMessageService.getInstance().subscribe(demoType, subscriber);
		AlarmModule.getInstance().addAlarmChannelDelegate("DEMO", new AlarmDemoDelegate());
		AlarmModule.getInstance().addAlarmChannelDelegate("DYNAMIC_DEMO", new AlarmDemoDynamicDelegate());
		AlarmModule.getInstance().registerAlarmChannelClz("DYNAMIC_DEMO", AlarmDemoChannel.class);
```
2. src\java\smartbi\demo\errorcode\DemoErrorCode.java  
错误码定义文件,用来规范渠道的异常信息,对应多语言文件为 src\web\META-INF\classes\smartbi\demo\errorcode\DemoErrorCode_en.properties、src\web\META-INF\extension_lang_zh_CN.properties、src\web\META-INF\extension_lang_zh_TW.properties

# 渠道推送

## 渠道subscriber
由消息推送模块 CommonMessageService 统一管理接收从预警渠道发出的消息,每个渠道的subscriber自己接收处理  
相关文件在 src\java\smartbi\demo\channel\config 目录下

1. src\java\smartbi\demo\channel\subscriber\AlarmPushDemoSubscriber.java
自定义渠道推送的主文件,主要方法为 onMessage。接收的参数应为自己渠道接收的指类型,如IDemoConfig等。该文件在执行时,自己对相关参数进行类型转换,来处理后续逻辑

```
public class AlarmPushDemoSubscriber implements ISubscriber {

	@Override
	public void onMessage(IMessageBody messageBody, IChannelConfig channelConfig, IPublisher publisher) {
		if (channelConfig == null) {
			return;
		}
		// 类型强转
		IDemoConfig demoConfig = (IDemoConfig) channelConfig;
		try {
			send(messageBody, demoConfig);
		} catch (Exception e) {
			LogManager.getLogger(AlarmPushDemoSubscriber.class).error(e.getMessage(), e);
			futureException(channelConfig, e);
		}
	}
}
```
2. src\java\smartbi\demo\channel\config\DemoConfig.java
AlarmPushDemoSubscriber 接收的主要参数,含有渠道推送需要的相关定义,主要由于该类没有实现ICompletableConfig接口,只能能接收处理预警推送消息,无法将处理后的消息结果进行回调处理,返回给预警

3. src\java\smartbi\demo\channel\config\DemoCompletableConfig.java  
AlarmPushDemoSubscriber 接收的主要参数,含有渠道推送需要的相关定义有实现ICompletableConfig接口,可以通过config.getFuture().complete(DemoResult)将相关推动消息结果进行回调处理,返回给预警

4. src\java\smartbi\demo\channel\config\DemoReceiverType.java  
渠道可能会接收多种推送方式,不同的推送方式,处理不同,使用枚举类区别规范化

5. src\java\smartbi\demo\channel\config\DemoMessageBodyExtend.java  
IMessageBody messageBody 的相关接口提供了基本的推送消息内容。但是不同的渠道对信息的需求不同,这些信息统一xiezaile messageBody exntended中,DemoMessageBodyExtend为和exntended数据结构对应的实体bean

6. src\java\smartbi\demo\channel\config\DemoResult.java  
通过IDemoCompletableConfig接口回调给预警的结果,预警接到结果后,对结果中的信息进行解析,将详细信息和错误信息写入预警日志

7. src\java\smartbi\demo\channel\config\DemoResultType.java  
渠道推送处理的结果类型,方便预警根据结果类型进行归类处理相信信息和错误信息

8. 将AlarmPushDemoSubscriber 注册给渠道  
在 AlarmPushDemoModule中将AlarmPushDemoSubscriber注册给系统,以便能够由系统统一管理渠道的推送消息接收
```
// 某些服务器异常情况下,可能会重复注册subscriber,导致接到的消息发送两遍,复现原因场景未知
		// 先取消注册,再重新注册
		// 通过重写subscriber的equals方法使subscriber移除注册成功
		CommonMessageService.getInstance().unsubscribe(demoType, subscriber);
		CommonMessageService.getInstance().subscribe(demoType, subscriber);
```

## 常规渠道推送

### 前端实现文件

1. src\webpack\src\plugins\modifyExtenders\alarm-push\AlarmPushChannel.vue
定义常规渠道推送的文件,通过实现主要接口,将结果从预警读取和写入预警
```
export default {
  name: 'SxAlarmPushChannel',
  components: { SxAlarmRecipientInput, SxInputTextWidget },
  props: {
    channel: {
      type: Object, // 渠道定义
    },
  },
  data () {
    return {
      config: {
        // 接收人相关的配置项
        receiverType: 'USER',
        receivers: [],
        custom: ''
      },
      ...
    }
  },
  ...
  created () {
    this.intConfig()
  },
  methods: {
    intConfig () {
      // 解析传入的channel,初始化
      let config = this.channel ? this.channel.config : {}
      this.config = {
        receiverType: config.receiverType,
        receivers: config.receivers || [],
        custom: config.custom
      }
    },
    ...
    // 校验方法,校验当前页面的输入是否合法,由预警系统整体调用
    validate () {
      return new Promise((resolve, reject) => {
        this.$refs.form.validate((valid) => resolve(valid))
      })
    },
    // 将被父组件 AlarmPushChannel.vue 调用
    getContent () {
      // 返回定义主体内容
      return {
        // config 属性,返回时需要写在自己定义的属性名下
        // 返回的结构和在AlarmPushExtender  getDefaultData方法的对象结构整体一致
        // AlarmPushChannel.vue 会将属性定义复制到 channel 定义中
        config: {
          // 接收人相关的配置项
          receiverType: this.config.receiverType,
          receivers: this.config.receiverType === 'USER' ? this.config.receivers : [],
          custom: this.config.receiverType === 'CUSTOM' ? this.config.custom : ''
        }
      }
    },
    async sync () {
      // 预警定义变更时,做一些同步动作,主要动态接收人时需要
    }
  }
}
```
### 后端实现文件

后端预警渠道实现文件在 src\java\smartbi\demo\alarm 目录先

#### 渠道定义相关文件

将JavaBean和前端渠道定义channel 的结构一一对应,方便delegate中的逻辑处理

1. src\java\smartbi\demo\alarm\AlarmDemoChannel.java  
渠道定义文件,其结构和预警渠道定义一致

2. src\java\smartbi\demo\alarm\AlarmDemoChannelConfig.java  
渠道Config文件,其结构和前端config界面文件一致

### 渠道delegate文件

对接预警渠道处理逻辑的文件,通过实现相关接口逻辑,使得预警系统那边能够根据相关信息进行消息推送

1. src\java\smartbi\demo\alarm\delegate\AlarmDemoDelegate.java
```
/**
 * 常规的渠道推送delegate
 */
public class AlarmDemoDelegate implements IAlarmChannelDelegate {

	@Override
	public IAlarmConvertor getContentConvertor() {
		// html 转其他内容的转换器,以预警系统做整体上的转换
		// 可以自己在channel的 subsriber中自己做转换,以更好的切合实际
		return null;
	}

	// 返回渠道接受的config
    // 系统接收到config后,组装拼接成CommonMessageService需要消息体等,由预警统一进行消息推送处理
	@Override
	public IChannelConfig getChannelConfig(AlarmBO alarmBO, IAlarmChannel channel) {
		// 转成对应的实体bean,方便后续读取
        // channel.toJSON() 为将渠道转成JSON,主要针对没有将渠道对应的JavaBean如AlarmDemoChannel注册给预警系统的情况,如果注册后,可以直接进行类型转换
		AlarmDemoChannel demoChannel = JSONUtil.toBean(channel.toJSON(), AlarmDemoChannel.class);
		DemoCompletableConfig config = new DemoCompletableConfig();
		...
		return config;
	}

    // 对推送后的结构进行包装成IAlarmChannelResult接口对象,供预警统一执行判读推送结果和记录日志
	@Override
	public IAlarmChannelResult getAlarmSendResult(AlarmBO alarmBO, IAlarmChannelConfig config) {
		IChannelConfig channelConfig = config.getChannelConfig();
        // AlarmChannelResult 为产品定义的 IAlarmChannelResult
        // 渠道有需要也可以自己实现
        // 具体处理逻辑见 AlarmDemoDelegate
		AlarmChannelResult result = new AlarmChannelResult();
		...
		return result;
	}
	
	...
}

```
2. 在 AlarmPushDemoModule 中  注册Delegate

```
// 第一个参数 "DEMO" 和在AlarmPushExtender中的渠道ID 一致
AlarmModule.getInstance().addAlarmChannelDelegate("DEMO", new AlarmDemoDelegate());
```

## 含动态接收人逻辑的渠道推送

### 前端实现文件

1. src\webpack\src\plugins\modifyExtenders\alarm-push\AlarmDynamicPushChannel.vue
```
export default {
  name: 'SxAlarmDynamicPushChannel',
  components: { SxAlarmRecipientInput, SxInputTextWidget, SxSelectWidget, SxAlarmDynamicRecipient },
  props: {
    channel: {
      type: Object,
    },
    alarmBusiness: Object
  },
  data () {
    return {
      config: {
        // 接收人相关的配置项
        receiverType: 'USER',
        receivers: [],
        custom: '',
        dynamic: {
          id: '',
          type: null
        }
      },
      ...
  },
  computed: {
    ...
    fields () {
      return (this.alarmBusiness.getDataProvider().getFields() || []).map(({ uniqueId: value, label }) => ({ value, label }))
    }
  },
  watch: {
  },
  created () {
    this.intConfig()
  },
  methods: {
    intConfig () {
      // 解析输入的channel,初始化
      let config = this.channel ? this.channel.config : {}
      let dynamic = config.dynamic || { id: '', type: null }
      this.config = {
        receiverType: config.receiverType,
        receivers: config.receivers || [],
        custom: config.custom,
        dynamic: {
          id: dynamic.id,
          type: dynamic.type
        }
      }
    },
    ...
    validate () {
      return new Promise((resolve, reject) => {
        this.$refs.form.validate((valid) => resolve(valid))
      })
    },
    // 将被父组件 AlarmPushChannel.vue 调用
    getContent () {
      // 返回定义主体内容
      return {
        // config 属性,返回时需要写在自己定义的属性名下
        // AlarmPushChannel.vue 会将属性定义复制到 channel 定义中
        config: {
          // 接收人相关的配置项
          receiverType: this.config.receiverType,
          receivers: this.config.receiverType === 'USER' ? this.config.receivers : [],
          custom: this.config.receiverType === 'CUSTOM' ? this.config.custom : '',
          dynamic: this.config.receiverType === 'DYNAMIC' ? this.config.dynamic : {},
        }
      }
    },
    ... 
    // 将被父组件 AlarmPushChannel.vue 调用
    async sync () {
      // 前面数据范围字段变更后,可能动态接收人选择的字段已经不存在了
      // 这个时候需要清空动态接收人的字段定义
      if (this.checkFieldId()) {
        return
      }
      this.$set(this.config.dynamic, 'id', null)
    },
    checkFieldId () {
      let id = this.config.dynamic.id
      return this.fields.some(f => f.value === id)
    }
  }
```

### 后端实现文件

对接预警渠道处理逻辑的文件,通过实现相关接口逻辑,使得预警系统那边能够根据相关信息进行消息推送

1. src\java\smartbi\demo\alarm\delegate\AlarmDemoDynamicDelegate.java
```
/**
 * 
 * 包含动态接收人处理逻辑的delegate
 */
public class AlarmDemoDynamicDelegate implements IAlarmChannelDelegate, IAlarmChannelDynamicDelegate { // 一般即实现常规渠道推送接口,也实现动态接收人渠道推送接口
    

	@Override
	public IAlarmConvertor getContentConvertor() {
		// html 转其他内容的转换器,以预警系统做整体上的转换
		// 可以自己在channel的 subsriber中自己做转换,以更好的切合实际
		return null;
	}

	// 返回渠道接受的config
	@Override
	public IChannelConfig getChannelConfig(AlarmBO alarmBO, IAlarmChannel channel) {
		AlarmDemoChannel demoChannel = (AlarmDemoChannel) channel;
		DemoCompletableConfig config = new DemoCompletableConfig();
		config.setType(ChannelType.valueOf(demoChannel.getType().name()));
		config.setCustom(demoChannel.getConfig().getCustom());
		config.setHtml(true);
		config.setReceiverType(demoChannel.getConfig().getReceiverType());
		config.setRecipients(demoChannel.getConfig().getReceivers().stream().map(reciver -> 
			new AlarmChannelRecipient(RecipientType.valueOf(reciver.getTargettype()), reciver.getTargetid(), reciver.getAlias()))
				.collect(Collectors.toList()));
		return config;
	}

	@Override
	public IAlarmChannelResult getAlarmSendResult(AlarmBO alarmBO, IAlarmChannelConfig config) {
		return new AlarmDemoResultGenerator().generate(config);
	}
	
	// ------------- 下面是动态接收人的处理逻辑  ----------------------

	/**
	 * 是否是动态接收人的方式
     * 是动态接收人时,由预警系统管理相关逻辑走动态接收人的逻辑
	 */
	@Override
	public boolean isDynamic(AlarmBO alarmBO, IAlarmChannel channel) {
        // AlarmDemoChannel 注册给了预警系统,有预警系统统一转成对应的JavaBean,和前端属性一一对应,可以直接进行类型转义
		AlarmDemoChannel demoChannel = (AlarmDemoChannel) channel;
		return DemoReceiverType.DYNAMIC == demoChannel.getConfig().getReceiverType();
	}

	@Override
	public IAlarmDynamicRecipient getDynamicRecipient(AlarmBO alarmBO, IAlarmChannel channel) {
		// 返回动态接收人对象
		return ((AlarmDemoChannel) channel).getConfig().getDynamic();
	}

	// 动态接收人接口生成config
	@Override
	public IChannelConfig getChannelConfig(AlarmBO alarmBO, IAlarmChannel channel, List<IRecipient> recipients) {
        // 对于动态接收人,实际走的时User类型的渠道退丝攻
        // 只是接收人是由预警系统根据执行结果生成的

		// 转成对应的实体bean,方便后续读取
		AlarmDemoChannel demoChannel = (AlarmDemoChannel) channel;
		DemoCompletableConfig config = new DemoCompletableConfig();
		config.setType(ChannelType.valueOf("DEMO"));
		config.setCustom(demoChannel.getConfig().getCustom());
		config.setHtml(true);
		// 动态接收人实际走的用户逻辑
		config.setReceiverType(DemoReceiverType.USER);
		// 将接收人设置为本次的接收人
		config.setRecipients(recipients);
		return config;		
	}
	
	@Override
	public IAlarmChannelResult getAlarmDynamicSendResult(AlarmBO alarmBO, IAlarmChannel channel,
			List<IAlarmChannelDynamicConfig> dynamicConfigs) {
        // dynamicConfigs 是动态接收人结果config列表
        // 一次推送动态接收人,由于动态接收人会触发多次推送,需要将多次推送的结果合并处理后,才是这次渠道的推送结果
		return new AlarmDemoResultGenerator().generateDynamicResult(channel, dynamicConfigs);
	}
```

2. src\java\smartbi\demo\alarm\delegate\AlarmDemoResultGenerator.java  
预警渠道结果生成器,将渠道的返回信息进行解析整理,返回给预警系统

3. 在 AlarmPushDemoModule 中  注册Delegate

```
// 第一个参数 "DYNAMIC_DEMO" 和在AlarmPushExtender中的渠道ID 一致
AlarmModule.getInstance().addAlarmChannelDelegate("DYNAMIC_DEMO", new AlarmDemoDynamicDelegate());
// 将渠道数据结构对应的JavaBean注册给预警,以便后续delegate处理时,不需要每次都使用JSONUtil转成JavaBean。
// 如果想要在delegate直接操作ObjectNode,也可以不用注册
// 操作ObjectNode的工具类 smarbix.util.JSONUtil.java
		AlarmModule.getInstance().registerAlarmChannelClz("DYNAMIC_DEMO", AlarmDemoChannel.class);
```