企业微信、飞书、钉钉机器人消息发送工具类

1、实例化WebClient对象

其实你也可以使用RestTemplate,我这里主要是用到了webflux框架,所以需要实例化客户端请求对象

java 复制代码
@Bean
public WebClient webClient(){
    HttpClient httpClient = getHttpClient();
    return WebClient.builder()
            .clientConnector(new ReactorClientHttpConnector(httpClient)).build();
}

private HttpClient getHttpClient() {
    ConnectionProvider provider = ConnectionProvider.builder("你爱咋咋的,一般用你项目名即可")
            .maxConnections(500)
            .maxIdleTime(Duration.ofSeconds(10))
            .maxLifeTime(Duration.ofSeconds(20))
            .pendingAcquireTimeout(Duration.ofSeconds(30))
            .pendingAcquireTimer((r, d) -> {
                Timeout t = wheel.newTimeout(timeout -> r.run(), d.toMillis(), TimeUnit.MILLISECONDS);
                return () -> t.cancel();
            })
            .fifo()
            .build();

    HttpClient httpClient = HttpClient.create(provider);
    return httpClient;
}

2、发送及有效性测试工具类

java 复制代码
import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;

import static com.paratera.console.notice.utils.Constants.*;

/**
 * 机器人发送工具类(微信,飞书,钉钉)
 *
 * @author huxiang
 */
@Component
@Slf4j
public class RobotUtil {

    @Autowired
    private WebClient webClient;

    /**
     * 机器人发送消息(markdown格式)
     *
     * @param robotUrl   机器人地址
     * @param type       类型:1微信,2飞书,3钉钉
     * @param context    markdown文本内容
     * @param signSecret 签名校验(飞书,钉钉使用,可为空,具体要根据用户是否启用签名)
     */
    public void sendMsg(String robotUrl, Integer type, String context, String signSecret) {
        JSONObject msgObj = JSONUtil.createObj();
        JSONObject mdObj = JSONUtil.createObj();
        switch (type) {
            case ROBOT_TYPE_WX:
                msgObj.set("msgtype", "markdown");
                mdObj.set("content", context);
                msgObj.set("markdown", mdObj);
                break;
            case ROBOT_TYPE_FS:
                if (StringUtils.isNotEmpty(signSecret)) {
                    msgObj = getFsSignObj(signSecret);
                }
                msgObj.set("msg_type", "interactive");
                mdObj.set("tag", "lark_md");
                mdObj.set("content", context);
                JSONArray elements = JSONUtil.createArray();
                JSONObject wrapObj = JSONUtil.createObj();
                wrapObj.set("tag", "div");
                wrapObj.set("text", mdObj);
                elements.put(wrapObj);
                JSONObject cardObj = JSONUtil.createObj();
                cardObj.set("elements", elements);
                msgObj.set("card", cardObj);
                break;
            case ROBOT_TYPE_DD:
                if (StringUtils.isNotEmpty(signSecret)) {
                    robotUrl = getDdRobotURL(robotUrl, signSecret);
                }
                msgObj.set("msgtype", "markdown");
                mdObj.set("title", "通知");
                mdObj.set("text", context);
                msgObj.set("markdown", mdObj);
                break;
        }
        webClient.post()
                .uri(robotUrl)
                .contentType(MediaType.APPLICATION_JSON)
                .body(BodyInserters.fromValue(msgObj))
                .retrieve().bodyToMono(String.class).subscribe(
                        result -> {
                            log.info("机器人通知发送结果:" + result);
                        }
                );
    }

    /**
     * 机器人有效性测试 0表示成功,其他表示失败
     *
     * @param robotUrl   机器人地址
     * @param type       类型:1微信,2飞书,3钉钉
     * @param signSecret 签名校验(飞书,钉钉使用,可为空,具体要根据用户是否启用签名)
     * @return
     */
    public Integer sendTestMsg(String robotUrl, Integer type, String signSecret) {
        JSONObject msgObj = JSONUtil.createObj();
        JSONObject mdObj = JSONUtil.createObj();
        ObjectMapper mapper = new ObjectMapper();
        switch (type) {
            case ROBOT_TYPE_WX:
                msgObj.set("msgtype", "markdown");
                mdObj.set("content", "机器人有效性测试!");
                msgObj.set("markdown", mdObj);
                break;
            case ROBOT_TYPE_FS:
                if (StringUtils.isNotEmpty(signSecret)) {
                    msgObj = getFsSignObj(signSecret);
                }
                msgObj.set("msg_type", "interactive");
                mdObj.set("tag", "lark_md");
                mdObj.set("content", "机器人有效性测试!");
                JSONArray elements = JSONUtil.createArray();
                JSONObject wrapObj = JSONUtil.createObj();
                wrapObj.set("tag", "div");
                wrapObj.set("text", mdObj);
                elements.put(wrapObj);
                JSONObject cardObj = JSONUtil.createObj();
                cardObj.set("elements", elements);
                msgObj.set("card", cardObj);
                break;
            case ROBOT_TYPE_DD:
                if (StringUtils.isNotEmpty(signSecret)) {
                    robotUrl = getDdRobotURL(robotUrl, signSecret);
                }
                msgObj.set("msgtype", "markdown");
                mdObj.set("title", "通知");
                mdObj.set("text", "机器人有效性测试!");
                msgObj.set("markdown", mdObj);
                break;

        }
        Mono<String> mono = webClient.post()
                .uri(robotUrl)
                .contentType(MediaType.APPLICATION_JSON)
                .body(BodyInserters.fromValue(msgObj))
                .retrieve().bodyToMono(String.class);
        String result = mono.block();
        try {
            Map res = mapper.readValue(result, Map.class);
            return (Integer) (res.containsKey("errcode") ? res.get("errcode") : res.get("code"));
        } catch (JsonProcessingException e) {
            log.error("类型转换异常-RobotUtil.sendTestMsg:" + e);
        }
        return -1;
    }

    /**
     * 飞书获取带签名的消息体
     *
     * @return
     */
    public JSONObject getFsSignObj(String signSecret) {
        JSONObject signObj = JSONUtil.createObj();
        Long timestamp = DateUtil.currentSeconds();
        String sign = null;
        try {
            //把timestamp+"\n"+密钥当做签名字符串
            String stringToSign = timestamp + "\n" + signSecret;
            //使用HmacSHA256算法计算签名
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(stringToSign.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
            byte[] signData = mac.doFinal(new byte[]{});
            sign = Base64.encodeBase64String(signData);
        } catch (Exception e) {
            log.error("飞书获取签名失败:" + e);
        }
        if (StringUtils.isEmpty(sign)) {
            return null;
        }
        signObj.set("timestamp", timestamp);
        signObj.set("sign", sign);
        return signObj;
    }


    /**
     * 钉钉获取带签名的机器人推送URL
     *
     * @return
     */

    public String getDdRobotURL(String robotUrl, String signSecret) {
        Long timestamp = System.currentTimeMillis();
        String stringToSign = timestamp + "\n" + signSecret;
        String sign = null;
        try {
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(signSecret.getBytes("UTF-8"), "HmacSHA256"));
            byte[] signData = mac.doFinal(stringToSign.getBytes("UTF-8"));
            sign = URLEncoder.encode(Base64.encodeBase64String(signData), "UTF-8");
        } catch (Exception e) {
            log.error("钉钉获取签名失败:" + e);
        }
        if (StringUtils.isEmpty(sign)) {
            return null;
        }
        return robotUrl + "&timestamp=" + timestamp + "&sign=" + sign;
    }
}
相关推荐
2501_9061505614 分钟前
私有部署问卷系统操作实战记录-DWSurvey
java·运维·服务器·spring·开源
better_liang25 分钟前
每日Java面试场景题知识点之-TCP/IP协议栈与Socket编程
java·tcp/ip·计算机网络·网络编程·socket·面试题
niucloud-admin37 分钟前
java服务端——controller控制器
java·开发语言
To Be Clean Coder38 分钟前
【Spring源码】通过 Bean 工厂获取 Bean 的过程
java·后端·spring
Fortunate Chen1 小时前
类与对象(下)
java·javascript·jvm
程序员水自流1 小时前
【AI大模型第9集】Function Calling,让AI大模型连接外部世界
java·人工智能·llm
‿hhh1 小时前
综合交通运行协调与应急指挥平台项目说明
java·ajax·npm·json·需求分析·个人开发·规格说明书
小徐Chao努力1 小时前
【Langchain4j-Java AI开发】06-工具与函数调用
java·人工智能·python
无心水1 小时前
【神经风格迁移:全链路压测】33、全链路监控与性能优化最佳实践:Java+Python+AI系统稳定性保障的终极武器
java·python·性能优化
萧曵 丶1 小时前
Synchronized 详解及 JDK 版本优化
java·多线程·synchronized