xxl-job企微告警实现
前言
xxl-job默认提供了邮箱告警,添加企微告警要折腾一番,并不是像营销文说的那么简单加个实现类就行,本文提供xxl-job企微告警实现。
效果展现
■ 编辑UI展示
■ 告警推送展示
列出改动清单
开始敲代码。。。
■ 获取corpid和corpsecret
企业ID和应用的凭证密钥,按官方文档获取。上传个图片创建应用就行了,我当时是登错了地方,进了企业微信服务商的管理后台一直找不到入口,坑了不少时间。
■ Properties
corpid=XXXXXXXXXXXXXXXXX
corpsecret=XXXXXX_XXXXXXXXXXXXXXXXXXXXX-XXXXXXXXXXXXXX
■ POM
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.6</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.5</version>
</dependency>
■ 工具类准备
为了节省开发时间,几个企微工具类我也是从网上搜索过来改造,并不是我喜欢的方式。
① IacsUrlDataVo
package com.xxl.job.admin.core.alarm.WeiUtils;
public class IacsUrlDataVo {
String corpid;
String corpsecret;
String Get_Token_Url;
String SendMessage_Url;
public String getCorpid() {
return corpid;
}
public void setCorpid(String corpid) {
this.corpid = corpid;
}
public String getCorpsecret() {
return corpsecret;
}
public void setCorpsecret(String corpsecret) {
this.corpsecret = corpsecret;
}
public void setGet_Token_Url(String corpid,String corpsecret) {
this.Get_Token_Url ="https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid="+corpid+"&corpsecret="+corpsecret;
}
public String getGet_Token_Url() {
return Get_Token_Url;
}
public String getSendMessage_Url(){
SendMessage_Url = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=";
return SendMessage_Url;
}
}
② IacsWeChatDataVo
package com.xxl.job.admin.core.alarm.WeiUtils;
public class IacsWeChatDataVo {
String touser;
String toparty;
String msgtype;
int agentid;
Object text;//实际接收Map类型数据
Object markdown;//实际接收Map类型数据
public Object getText() {
return text;
}
public void setText(Object text) {
this.text = text;
}
public Object getMarkdown() {
return markdown;
}
public void setMarkdown(Object markdown) {
this.markdown = markdown;
}
public String getMsgtype() {
return msgtype;
}
public void setMsgtype(String msgtype) {
this.msgtype = msgtype;
}
public int getAgentid() {
return agentid;
}
public void setAgentid(int agentid) {
this.agentid = agentid;
}
public String getTouser() {
return touser;
}
public void setTouser(String touser) {
this.touser = touser;
}
public String getToparty() {
return toparty;
}
public void setToparty(String toparty) {
this.toparty = toparty;
}
}
③ SendWeChatUtils
package com.xxl.job.admin.core.alarm.WeiUtils;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class SendWeChatUtils {
private CloseableHttpClient httpClient;
private HttpPost httpPost;//用于提交登陆数据
private HttpGet httpGet;//用于获得登录后的页面
public static final String CONTENT_TYPE = "Content-Type";
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static String token = "";
private static Gson gson = new Gson();
// /**
// * 微信授权请求,GET类型,获取授权响应,用于其他方法截取token
// *
// * @param Get_Token_Url
// * @return String 授权响应内容
// * @throws IOException
// */
public String toAuth(String Get_Token_Url) throws IOException {
httpClient = HttpClients.createDefault();
httpGet = new HttpGet(Get_Token_Url);
CloseableHttpResponse response = httpClient.execute(httpGet);
String resp;
try {
HttpEntity entity = response.getEntity();
resp = EntityUtils.toString(entity, "utf-8");
EntityUtils.consume(entity);
} finally {
response.close();
}
LoggerFactory.getLogger(getClass()).info(" resp:{}", resp);
return resp;
}
/**
* 获取toAuth(String Get_Token_Url)返回结果中键值对中access_token键的值
*
* @param corpid 应用组织编号 corpsecret 应用秘钥
*/
public void refreshToken() throws IOException {
String corpid = Global.getConfig("corpid");
String corpsecret = Global.getConfig("corpsecret");
IacsUrlDataVo uData = new IacsUrlDataVo();
uData.setGet_Token_Url(corpid, corpsecret);
String resp = toAuth(uData.getGet_Token_Url());
Map<String, Object> map = gson.fromJson(resp,
new TypeToken<Map<String, Object>>() {
}.getType());
SendWeChatUtils.token = map.get("access_token").toString();
}
/**
* @param touser 发送消息接收者 ,msgtype消息类型(文本/图片等),
* @param application_id 应用编号。
* @return String
* @Title:创建微信发送请求post数据
*/
public String createpostdata(String touser, String toparty, String msgtype,
int application_id, String contentKey, String contentValue) {
IacsWeChatDataVo wcd = new IacsWeChatDataVo();
wcd.setTouser(touser);
wcd.setToparty(toparty);
wcd.setAgentid(application_id);
wcd.setMsgtype(msgtype);
Map<Object, Object> content = new HashMap<Object, Object>();
content.put(contentKey, contentValue + "\n--------\n" + df.format(new Date()));
if("markdown".equals(msgtype)) {
wcd.setMarkdown(content);
} else {
wcd.setText(content);
}
return gson.toJson(wcd);
}
/**
* @param charset 消息编码 ,contentType 消息体内容类型,
* @param url 微信消息发送请求地址,data为post数据,token鉴权token
* @return String
* @Title 创建微信发送请求post实体
*/
public String post(String charset, String contentType, String url,
String data, String token) throws IOException {
return post(charset, contentType, url, data, token, 3);
}
public String post(String charset, String contentType, String url,
String data, String token, int count) throws IOException {
CloseableHttpClient httpclient = HttpClients.createDefault();
httpPost = new HttpPost(url + token);
httpPost.setHeader(CONTENT_TYPE, contentType);
httpPost.setEntity(new StringEntity(data, charset));
CloseableHttpResponse response = httpclient.execute(httpPost);
String resp;
try {
HttpEntity entity = response.getEntity();
resp = EntityUtils.toString(entity, charset);
EntityUtils.consume(entity);
} finally {
response.close();
}
Map<String, Object> map = gson.fromJson(resp,
new TypeToken<Map<String, Object>>() {
}.getType());
String errcode = map.get("errcode").toString();
if (!"0.0".equals(errcode) && !"0".equals(errcode) && count > 0) {
refreshToken();
LoggerFactory.getLogger(getClass()).info("token失效");
return post(charset, contentType, url, data, SendWeChatUtils.token, --count);
}
LoggerFactory.getLogger(getClass()).info(
"call [{}], param:{}, resp:{}", url, data, resp);
return resp;
}
}
④ Global
获取配置工具类
/**
* Copyright © 2012-2014 <a href="https://github.com/thinkgem/jeesite">JeeSite</a> All rights reserved.
*/
package com.xxl.job.admin.core.alarm.WeiUtils;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
/**
* 全局配置类
*
* @author bbq
* @version 2021-05-13
*/
@Component
public class Global implements EnvironmentAware {
private static Environment environment;
public static String getConfig(String key) {
return environment.getProperty(key);
}
public static String getConfig(String key, String defaultValue) {
String value = getConfig(key);
return value != null ? value : defaultValue;
}
public static Integer getConfigInteger(String key) {
return environment.getProperty(key, Integer.class);
}
public static Integer getConfigInteger(String key, Integer defaultValue) {
Integer value = getConfigInteger(key);
return value != null ? value : defaultValue;
}
@Override
public void setEnvironment(Environment environment) {
Global.environment = environment;
}
/**
* 当前对象实例
*/
private static Global global = new Global();
/**
* 获取当前对象实例
*/
public static Global getInstance() {
return global;
}
}
■ UI修改
① message_zh_CN.properties
jobinfo_field_weimail=企业微信告警
jobinfo_field_weimail_placeholder=例:user1|user2&&party1|party2,不发给部门省略&&以及后面,多个对象|分隔
② jobinfo.index.1.js
$("#updateModal .form input[name='alarmWei']").val( row.alarmWei );
③ jobinfo.index.ftl
<div class="form-group">
<label for="lastname" class="col-sm-2 control-label">${
I18n.jobinfo_field_weimail}<font color="black">*</font></label>
<div class="col-sm-8"><input type="text" class="form-control" name="alarmWei" placeholder="${I18n.jobinfo_field_weimail_placeholder}" maxlength="100" ></div>
</div>
■ curd代码
① xml
XxlJobInfoMapper.xml
<result column="alarm_wei" property="alarmWei" />
t.alarm_wei,
alarm_wei,
#{alarmWei},
alarm_wei = #{alarmWei},
② bean
com.xxl.job.admin.core.model.XxlJobInfo
private String alarmWei; // 企业微信告警
③ servicempl
com.xxl.job.admin.service.impl.XxlJobServiceImpl
exists_jobInfo.setAlarmWei(jobInfo.getAlarmWei());
④ 告警thread
com.xxl.job.admin.core.thread.JobFailMonitorHelper
if (info != null && ((info.getAlarmEmail() != null && info.getAlarmEmail().trim().length() > 0)
|| (info.getAlarmWei() != null && info.getAlarmWei().trim().length() > 0))) {
⑤ WeiJobAlarm
com.xxl.job.admin.core.alarm.impl.WeiJobAlarm
终于来到了具体的告警实现,也就是网上所谓的复制粘贴改改就好了。。。
package com.xxl.job.admin.core.alarm.impl;
import com.xxl.job.admin.core.alarm.JobAlarm;
import com.xxl.job.admin.core.alarm.WeiUtils.IacsUrlDataVo;
import com.xxl.job.admin.core.alarm.WeiUtils.SendWeChatUtils;
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.model.ReturnT;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.text.MessageFormat;
/**
* job alarm by wei
*
* @author bbq 2021-05-12
*/
@Component
public class WeiJobAlarm implements JobAlarm {
private static Logger logger = LoggerFactory.getLogger(WeiJobAlarm.class);
private static String token = "";
/**
* fail alarm
*
* @param jobLog
*/
@Override
public boolean doAlarm(XxlJobInfo info, XxlJobLog jobLog){
boolean alarmResult = true;
// send monitor email
if (info!=null && info.getAlarmWei()!=null && info.getAlarmWei().trim().length()>0) {
// alarmContent
String alarmContent = "Alarm Job LogId=" + jobLog.getId();
if (jobLog.getTriggerCode() != ReturnT.SUCCESS_CODE) {
alarmContent += "\nTriggerMsg=\n" + jobLog.getTriggerMsg().replaceAll("<br>","|| ");
}
if (jobLog.getHandleCode()>0 && jobLog.getHandleCode() != ReturnT.SUCCESS_CODE) {
alarmContent += "\nHandleCode=" + jobLog.getHandleMsg();
}
// email info
XxlJobGroup group = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().load(Integer.valueOf(info.getJobGroup()));
String content = MessageFormat.format(loadEmailJobAlarmTemplate(),
group!=null?group.getTitle():"null",
info.getId(),
info.getJobDesc(),
alarmContent);
SendWeChatUtils msgUtils = new SendWeChatUtils();
try {
String touser = info.getAlarmWei().trim();
String toparty = "";
if(touser.contains("&&")) {
String str[] = touser.split("&&");
touser = str[0];
if(str.length > 1) {
toparty = str[1];
}
}
String postdata = msgUtils.createpostdata(touser, toparty,"markdown", 应用id, "content", content);
String resp = msgUtils.post("utf-8", SendWeChatUtils.CONTENT_TYPE, (new IacsUrlDataVo()).getSendMessage_Url(), postdata, SendWeChatUtils.token);
// System.out.println("请求数据======>" + postdata);
// System.out.println("发送微信的响应数据======>" + resp);
} catch (IOException e) {
e.printStackTrace();
alarmResult = false;
}
}
return alarmResult;
}
/**
* load email job alarm template
*
* @return
*/
private static final String loadEmailJobAlarmTemplate(){
String mailBodyTemplate = "# {2}\n"
+ "> `**事项详情**` \n"
+ "> " + I18nUtil.getString("jobinfo_field_jobgroup") + ":<font color=\"comment\">{0}</font> \n"
+ "> " + I18nUtil.getString("jobinfo_field_id")+":<font color=\"warning\">{1}</font>\n"
+ "> " + I18nUtil.getString("jobconf_monitor_alarm_title") + ":<font color=\"comment\">"
+ I18nUtil.getString("jobconf_monitor_alarm_type") + "</font>\n"
+ "> "+ I18nUtil.getString("jobconf_monitor_alarm_content") + ":\n<font color=\"info\">{3}</font> \n";
return mailBodyTemplate;
}
}
⑥ 数据库
最后别忘了往你的数据库添加字段即完成。
最后
然后就可以类似配置邮箱一样配置企微了,可配置部门和用户。