xxl-job企业微信告警实现

本文详细介绍了如何在XXL-Job中集成微信企业号告警功能,包括UI调整、配置参数、工具类准备、Curd操作及告警发送核心代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

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 &copy; 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;
    }

}

⑥ 数据库
最后别忘了往你的数据库添加字段即完成。

最后

然后就可以类似配置邮箱一样配置企微了,可配置部门和用户。

### 如何使用XXL-JOB集成钉钉告警 为了实现XXL-JOB与钉钉告警集成,可以通过自定义报警逻辑来完成。以下是具体的实现方法: #### 1. 自定义AlarmHandler 在XXL-JOB中,可以扩展`com.xxl.job.core.handler.impl.AlarmCallbackHandler`接口来自定义告警处理逻辑。具体来说,需要创建一个新的类继承该接口并重写其中的方法。 ```java @Component public class DingDingAlarmHandler extends AlarmCallbackHandler { @Override public void callback(JobLog jobLog) { try { String content = buildMessage(jobLog); sendToDingDing(content); } catch (Exception e) { log.error("Failed to send alarm message", e); } } private String buildMessage(JobLog jobLog) { StringBuilder sb = new StringBuilder(); sb.append("任务名称: ").append(jobLog.getJobName()).append("\n"); sb.append("执行时间: ").append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(jobLog.getTriggerTime()))).append("\n"); sb.append("失败原因: ").append(jobLog.getHandleMsg()); return sb.toString(); } private void sendToDingDing(String content) throws Exception { String webhookUrl = "https://oapi.dingtalk.com/robot/send?access_token=your_access_token"; // 替换为实际Webhook地址 JSONObject jsonBody = new JSONObject(); jsonBody.put("msgtype", "text"); JSONObject textObj = new JSONObject(); textObj.put("content", content); jsonBody.put("text", textObj); OkHttpClient client = new OkHttpClient(); RequestBody body = RequestBody.create(JSON, jsonBody.toJSONString()); Request request = new Request.Builder() .url(webhookUrl) .post(body) .build(); Response response = client.newCall(request).execute(); if (!response.isSuccessful()) { throw new IOException("Unexpected code " + response); } } } ``` 上述代码实现了以下功能: - 构建了一个名为 `DingDingAlarmHandler` 的类[^4]。 - 定义了构建消息内容的方法 `buildMessage()` 和发送到钉钉的消息推送方法 `sendToDingDing()`。 - 使用OkHttp库向钉钉机器人API发起HTTP请求以发送告警信息。 #### 2. 修改配置文件 为了让XXL-JOB知道应该调用哪个告警处理器,在`application.properties`或者`application.yml`中设置如下参数: ```properties xxl.job.alarm.callback.url=com.example.DingDingAlarmHandler # 设置全限定名 ``` 如果采用的是YAML格式,则应这样书写: ```yaml xxl: job: alarm: callback: url: com.example.DingDingAlarmHandler ``` 此操作指定了当任务运行失败时所使用的回调类路径[^5]。 #### 3. 测试验证 启动应用后,尝试让某个定时任务故意抛出异常或超时未返回成功状态,观察是否能够接收到钉钉上的通知消息。如果一切正常工作的话,那么就说明已经成功集成了钉钉告警机制。 --- ### 问题
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值