钉钉小程序三方企业应用事件与回调

程序,三方,企业应用,事件,回调 · 浏览次数 : 71

小编点评

代码验证结果。归纳总结以上内容,生成内容时需要带简单的排版 以下代码验证结果: ```java public DingTalkEncryptException(Integer exceptionCode) { super((String)msgMap.get(exceptionCode)); this.code = exceptionCode; } ``` 代码中定义了 `msgMap`,它包含各种异常信息的描述信息。 例如,以下代码描述了 `ENCRYPTION_NONCE_ILLEGAL` 错误: ```java msgMap.put(900003, "加密随机字符串参数非法"); ``` 根据这个描述信息,可以生成内容时带简单的排版,例如: ```java "加密随机字符串参数非法" ``` 以下代码展示如何生成内容时带简单的排版: ```java public String generateContent(int exceptionCode) throws Exception { String msg = msgMap.get(exceptionCode); if (msg == null) { throw new Exception("无法生成内容"); } return msg; } ``` 代码中定义了 `generateContent`方法,它接收异常代码,并从 `msgMap`中获取相关描述信息。如果描述信息为空,则抛出异常。 最后,代码的 `generateContent`方法返回的内容,包含了异常信息的描述信息。

正文

事件回调

订阅事件后,开放平台将会在事件发生时(如:机器人入群)向请求网址推送消息。

钉钉三方企业应用事件与回调配置界面如下:

推送方式有三种:

  • Http 推送(官方不推荐)
  • SyncHttp 推送 (官方推荐)
  • RDS 推送(官方推荐)

Http和SyncHttp没有太大的区别,按钉钉的说法是:与HTTP推送方式比,SyncHTTP推送的是业务数据的最终状态,开发者可以直接使用推送的数据。

RDS的话必须依赖阿里云的RDS,也就是必须去阿里云买数据库,这个就比较鸡肋了,关键官方还强推,毕竟云钉一体嘛。

综上,我们可以选择 SyncHttp 推送的方式实现钉钉三方企业应用事件与回调,帮助文档链接:https://open.dingtalk.com/document/isvapp/configure-synchttp-push。

代码实现

1. 注意事项

由于钉钉需要设置一个回调请求地址,接收事件回调的 URL,必须是公网可以访问的url地址。

解决方案1:申请一台有公网固定ip的机器部署应用,我就是这样做的;
解决方案2:内网穿透,比如frp穿透,钉钉也给了帮助文档:https://open.dingtalk.com/document/resourcedownload/alibaba-cloud-frp-intranet-penetration-tool

2. 应用开发

项目结构

配置文件

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.10</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>DingTalkCallback</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>DingTalkCallback</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>2.0.21</version>
        </dependency>

        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

代码

DingTalkCallbackApplication.java

package com.example.dingtalkcallback;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DingTalkCallbackApplication {

    public static void main(String[] args) {
        SpringApplication.run(DingTalkCallbackApplication.class, args);
    }

}

CallbackController.java

package com.example.dingtalkcallback.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

import com.example.dingtalkcallback.config.Constant;
import com.example.dingtalkcallback.util.DingCallbackCrypto;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * ISV 小程序回调信息处理
 */
@RestController
@RequestMapping("/")
public class CallbackController {

    private final Logger log = LoggerFactory.getLogger(getClass());

    /**
     * 创建应用,验证回调URL创建有效事件(第一次保存回调URL之前)
     */
    private static final String EVENT_CHECK_CREATE_SUITE_URL = "check_create_suite_url";

    /**
     * 创建应用,验证回调URL变更有效事件(第一次保存回调URL之后)
     */
    private static final String EVENT_CHECK_UPADTE_SUITE_URL = "check_update_suite_url";

    /**
     * suite_ticket推送事件
     */
    private static final String EVENT_SUITE_TICKET = "suite_ticket";

    /**
     * 高优先级数据,激活应用等,推送的数据包括suite_ticket推送事件
     */
    private static final String SYNC_HTTP_PUSH_HIGH = "SYNC_HTTP_PUSH_HIGH";

    /**
     * 企业授权开通应用事件
     */
    private static final String EVENT_TMP_AUTH_CODE = "tmp_auth_code";

    @PostMapping(value = "dingCallback")
    public Object dingCallback(
        @RequestParam(value = "signature") String signature,
        @RequestParam(value = "timestamp") Long timestamp,
        @RequestParam(value = "nonce") String nonce,
        @RequestBody(required = false) JSONObject body
    ) {
        String params = "signature:" + signature + " timestamp:" + timestamp + " nonce:" + nonce + " body:" + body;
        try {
            log.info("begin callback:" + params);
            DingCallbackCrypto dingCallbackCrypto = new DingCallbackCrypto(Constant.TOKEN, Constant.ENCODING_AES_KEY,
                Constant.SUITE_KEY);

            // 从post请求的body中获取回调信息的加密数据进行解密处理
            String encrypt = body.getString("encrypt");
            String plainText = dingCallbackCrypto.getDecryptMsg(signature, timestamp.toString(), nonce, encrypt);
            JSONObject callBackContent = JSON.parseObject(plainText);
            log.info("解析后的值为:{}", callBackContent.toJSONString());
            // 根据回调事件类型做不同的业务处理
            String eventType = callBackContent.getString("EventType");
            if (EVENT_CHECK_CREATE_SUITE_URL.equals(eventType)) {
                log.info("验证新创建的回调URL有效性: " + plainText);
            } else if (EVENT_CHECK_UPADTE_SUITE_URL.equals(eventType)) {
                log.info("验证更新回调URL有效性: " + plainText);
            } else if (EVENT_SUITE_TICKET.equals(eventType)) {
                // suite_ticket用于用签名形式生成accessToken(访问钉钉服务端的凭证),需要保存到应用的db。
                // 钉钉会定期向本callback url推送suite_ticket新值用以提升安全性。
                // 应用在获取到新的时值时,保存db成功后,返回给钉钉success加密串(如本demo的return)
                log.info("应用suite_ticket数据推送: " + plainText);
            } else if (EVENT_TMP_AUTH_CODE.equals(eventType)) {
                // 本事件应用应该异步进行授权开通企业的初始化,目的是尽最大努力快速返回给钉钉服务端。用以提升企业管理员开通应用体验
                // 即使本接口没有收到数据或者收到事件后处理初始化失败都可以后续再用户试用应用时从前端获取到corpId并拉取授权企业信息,进而初始化开通及企业。
                log.info("企业授权开通应用事件: " + plainText);
            } else if (SYNC_HTTP_PUSH_HIGH.equals(eventType)) {
                JSONObject data = (JSONObject)callBackContent.getJSONArray("bizData").get(0);
                log.info(data.toJSONString());
                log.info("解析后的suite_ticket为" + JSONObject.parseObject(data.getString("biz_data")).getString("suiteTicket"));
            } else {
                // 其他类型事件处理
            }

            // 返回success的加密信息表示回调处理成功
            return dingCallbackCrypto.getEncryptedMap("success", timestamp, nonce);
        } catch (Exception e) {
            //失败的情况,应用的开发者应该通过告警感知,并干预修复
            log.error("process callback fail." + params, e);
            return "fail";
        }
    }
}

ApiUrlConstant.java

package com.example.dingtalkcallback.config;

public class ApiUrlConstant {
    /**
     * 获取可访问企业相关信息的accessToken的URL
     */
    public static final String URL_GET_CORP_TOKEN = "https://oapi.dingtalk.com/service/get_corp_token";

    /**
     *获取用户在企业内userId的接口URL
     */
    public static final String URL_GET_USER_INFO = "https://oapi.dingtalk.com/user/getuserinfo";
}

Constant.java (就是小程序相关的配置,也可以放到application.properties中)

package com.example.dingtalkcallback.config;

/**
 * 项目中的常量定义类
 */
public class Constant {
    /**
     * 应用的SuiteKey,登录开发者后台,点击应用管理,进入应用详情可见
     */
    public static final String SUITE_KEY="";

    /**
     * 应用的SuiteSecret,登录开发者后台,点击应用管理,进入应用详情可见
     */
    public static final String SUITE_SECRET="";

    /**
     * 回调URL签名用。应用的签名Token, 登录开发者后台,点击应用管理,进入应用详情可见
     */
    public static final String TOKEN = "";

    /**
     * 回调URL加解密用。应用的"数据加密密钥",登录开发者后台,点击应用管理,进入应用详情可见
     */
    public static final String ENCODING_AES_KEY = "";

}

DingCallbackCrypto.java (关键解码类)

package com.example.dingtalkcallback.util;

import java.io.ByteArrayOutputStream;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.Permission;
import java.security.PermissionCollection;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.security.Security;
import java.lang.reflect.Field;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import com.alibaba.fastjson.JSON;

import org.apache.commons.codec.binary.Base64;

/**
 * 钉钉开放平台加解密方法
 * 在ORACLE官方网站下载JCE无限制权限策略文件
 * JDK6的下载地址:http://www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html
 * JDK7的下载地址: http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html
 * JDK8的下载地址 https://www.oracle.com/java/technologies/javase-jce8-downloads.html
 */
public class DingCallbackCrypto {

    private static final Charset CHARSET = Charset.forName("utf-8");
    private static final Base64 base64 = new Base64();
    private byte[] aesKey;
    private String token;
    private String corpId;
    /**
     * ask getPaddingBytes key固定长度
     **/
    private static final Integer AES_ENCODE_KEY_LENGTH = 43;
    /**
     * 加密随机字符串字节长度
     **/
    private static final Integer RANDOM_LENGTH = 16;

    /**
     * 构造函数
     *
     * @param token          钉钉开放平台上,开发者设置的token
     * @param encodingAesKey 钉钉开放台上,开发者设置的EncodingAESKey
     * @param corpId         企业自建应用-事件订阅, 使用appKey
     *                       企业自建应用-注册回调地址, 使用corpId
     *                       第三方企业应用, 使用suiteKey
     *
     * @throws DingTalkEncryptException 执行失败,请查看该异常的错误码和具体的错误信息
     */
    public DingCallbackCrypto(String token, String encodingAesKey, String corpId) throws DingTalkEncryptException {
        if (null == encodingAesKey || encodingAesKey.length() != AES_ENCODE_KEY_LENGTH) {
            throw new DingTalkEncryptException(DingTalkEncryptException.AES_KEY_ILLEGAL);
        }
        this.token = token;
        this.corpId = corpId;
        aesKey = Base64.decodeBase64(encodingAesKey + "=");
    }

    public Map<String, String> getEncryptedMap(String plaintext) throws DingTalkEncryptException {
        return getEncryptedMap(plaintext, System.currentTimeMillis(), Utils.getRandomStr(16));
    }

    /**
     * 将和钉钉开放平台同步的消息体加密,返回加密Map
     *
     * @param plaintext 传递的消息体明文
     * @param timeStamp 时间戳
     * @param nonce     随机字符串
     * @return
     * @throws DingTalkEncryptException
     */
    public Map<String, String> getEncryptedMap(String plaintext, Long timeStamp, String nonce)
        throws DingTalkEncryptException {
        if (null == plaintext) {
            throw new DingTalkEncryptException(DingTalkEncryptException.ENCRYPTION_PLAINTEXT_ILLEGAL);
        }
        if (null == timeStamp) {
            throw new DingTalkEncryptException(DingTalkEncryptException.ENCRYPTION_TIMESTAMP_ILLEGAL);
        }
        if (null == nonce) {
            throw new DingTalkEncryptException(DingTalkEncryptException.ENCRYPTION_NONCE_ILLEGAL);
        }
        // 加密
        String encrypt = encrypt(Utils.getRandomStr(RANDOM_LENGTH), plaintext);
        String signature = getSignature(token, String.valueOf(timeStamp), nonce, encrypt);
        Map<String, String> resultMap = new HashMap<String, String>();
        resultMap.put("msg_signature", signature);
        resultMap.put("encrypt", encrypt);
        resultMap.put("timeStamp", String.valueOf(timeStamp));
        resultMap.put("nonce", nonce);
        return resultMap;
    }

    /**
     * 密文解密
     *
     * @param msgSignature 签名串
     * @param timeStamp    时间戳
     * @param nonce        随机串
     * @param encryptMsg   密文
     * @return 解密后的原文
     * @throws DingTalkEncryptException
     */
    public String getDecryptMsg(String msgSignature, String timeStamp, String nonce, String encryptMsg)
        throws DingTalkEncryptException {
        //校验签名
        String signature = getSignature(token, timeStamp, nonce, encryptMsg);
        if (!signature.equals(msgSignature)) {
            throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_SIGNATURE_ERROR);
        }
        // 解密
        String result = decrypt(encryptMsg);
        return result;
    }

    /*
     * 对明文加密.
     * @param text 需要加密的明文
     * @return 加密后base64编码的字符串
     */
    private String encrypt(String random, String plaintext) throws DingTalkEncryptException {
        try {
            byte[] randomBytes = random.getBytes(CHARSET);
            byte[] plainTextBytes = plaintext.getBytes(CHARSET);
            byte[] lengthByte = Utils.int2Bytes(plainTextBytes.length);
            byte[] corpidBytes = corpId.getBytes(CHARSET);
            ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
            byteStream.write(randomBytes);
            byteStream.write(lengthByte);
            byteStream.write(plainTextBytes);
            byteStream.write(corpidBytes);
            byte[] padBytes = PKCS7Padding.getPaddingBytes(byteStream.size());
            byteStream.write(padBytes);
            byte[] unencrypted = byteStream.toByteArray();
            byteStream.close();
            Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
            SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
            IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16);
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
            byte[] encrypted = cipher.doFinal(unencrypted);
            String result = base64.encodeToString(encrypted);
            return result;
        } catch (Exception e) {
            throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_ENCRYPT_TEXT_ERROR);
        }
    }

    /*
     * 对密文进行解密.
     * @param text 需要解密的密文
     * @return 解密得到的明文
     */
    private String decrypt(String text) throws DingTalkEncryptException {
        byte[] originalArr;
        try {
            // 设置解密模式为AES的CBC模式
            Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
            SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
            IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));
            cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);
            // 使用BASE64对密文进行解码
            byte[] encrypted = Base64.decodeBase64(text);
            // 解密
            originalArr = cipher.doFinal(encrypted);
        } catch (Exception e) {
            throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_DECRYPT_TEXT_ERROR);
        }

        String plainText;
        String fromCorpid;
        try {
            // 去除补位字符
            byte[] bytes = PKCS7Padding.removePaddingBytes(originalArr);
            // 分离16位随机字符串,网络字节序和corpId
            byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20);
            int plainTextLegth = Utils.bytes2int(networkOrder);
            plainText = new String(Arrays.copyOfRange(bytes, 20, 20 + plainTextLegth), CHARSET);
            fromCorpid = new String(Arrays.copyOfRange(bytes, 20 + plainTextLegth, bytes.length), CHARSET);
        } catch (Exception e) {
            throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_DECRYPT_TEXT_LENGTH_ERROR);
        }

        // corpid不相同的情况
        if (!fromCorpid.equals(corpId)) {
            throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_DECRYPT_TEXT_CORPID_ERROR);
        }
        return plainText;
    }

    /**
     * 数字签名
     *
     * @param token     isv token
     * @param timestamp 时间戳
     * @param nonce     随机串
     * @param encrypt   加密文本
     * @return
     * @throws DingTalkEncryptException
     */
    public String getSignature(String token, String timestamp, String nonce, String encrypt)
        throws DingTalkEncryptException {
        try {
            String[] array = new String[] {token, timestamp, nonce, encrypt};
            Arrays.sort(array);
            System.out.println(JSON.toJSONString(array));
            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < 4; i++) {
                sb.append(array[i]);
            }
            String str = sb.toString();
            System.out.println(str);
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            md.update(str.getBytes());
            byte[] digest = md.digest();

            StringBuffer hexstr = new StringBuffer();
            String shaHex = "";
            for (int i = 0; i < digest.length; i++) {
                shaHex = Integer.toHexString(digest[i] & 0xFF);
                if (shaHex.length() < 2) {
                    hexstr.append(0);
                }
                hexstr.append(shaHex);
            }
            return hexstr.toString();
        } catch (Exception e) {
            throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_SIGNATURE_ERROR);
        }
    }

    public static class Utils {
        public Utils() {
        }

        public static String getRandomStr(int count) {
            String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
            Random random = new Random();
            StringBuffer sb = new StringBuffer();

            for (int i = 0; i < count; ++i) {
                int number = random.nextInt(base.length());
                sb.append(base.charAt(number));
            }

            return sb.toString();
        }

        public static byte[] int2Bytes(int count) {
            byte[] byteArr = new byte[] {(byte)(count >> 24 & 255), (byte)(count >> 16 & 255), (byte)(count >> 8 & 255),
                (byte)(count & 255)};
            return byteArr;
        }

        public static int bytes2int(byte[] byteArr) {
            int count = 0;

            for (int i = 0; i < 4; ++i) {
                count <<= 8;
                count |= byteArr[i] & 255;
            }

            return count;
        }
    }

    public static class PKCS7Padding {
        private static final Charset CHARSET = Charset.forName("utf-8");
        private static final int BLOCK_SIZE = 32;

        public PKCS7Padding() {
        }

        public static byte[] getPaddingBytes(int count) {
            int amountToPad = 32 - count % 32;
            if (amountToPad == 0) {
                amountToPad = 32;
            }

            char padChr = chr(amountToPad);
            String tmp = new String();

            for (int index = 0; index < amountToPad; ++index) {
                tmp = tmp + padChr;
            }

            return tmp.getBytes(CHARSET);
        }

        public static byte[] removePaddingBytes(byte[] decrypted) {
            int pad = decrypted[decrypted.length - 1];
            if (pad < 1 || pad > 32) {
                pad = 0;
            }

            return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad);
        }

        private static char chr(int a) {
            byte target = (byte)(a & 255);
            return (char)target;
        }
    }

    public static class DingTalkEncryptException extends Exception {
        public static final int SUCCESS = 0;
        public static final int ENCRYPTION_PLAINTEXT_ILLEGAL = 900001;
        public static final int ENCRYPTION_TIMESTAMP_ILLEGAL = 900002;
        public static final int ENCRYPTION_NONCE_ILLEGAL = 900003;
        public static final int AES_KEY_ILLEGAL = 900004;
        public static final int SIGNATURE_NOT_MATCH = 900005;
        public static final int COMPUTE_SIGNATURE_ERROR = 900006;
        public static final int COMPUTE_ENCRYPT_TEXT_ERROR = 900007;
        public static final int COMPUTE_DECRYPT_TEXT_ERROR = 900008;
        public static final int COMPUTE_DECRYPT_TEXT_LENGTH_ERROR = 900009;
        public static final int COMPUTE_DECRYPT_TEXT_CORPID_ERROR = 900010;
        private static Map<Integer, String> msgMap = new HashMap();
        private Integer code;

        static {
            msgMap.put(0, "成功");
            msgMap.put(900001, "加密明文文本非法");
            msgMap.put(900002, "加密时间戳参数非法");
            msgMap.put(900003, "加密随机字符串参数非法");
            msgMap.put(900005, "签名不匹配");
            msgMap.put(900006, "签名计算失败");
            msgMap.put(900004, "不合法的aes key");
            msgMap.put(900007, "计算加密文字错误");
            msgMap.put(900008, "计算解密文字错误");
            msgMap.put(900009, "计算解密文字长度不匹配");
            msgMap.put(900010, "计算解密文字corpid不匹配");
        }

        public Integer getCode() {
            return this.code;
        }

        public DingTalkEncryptException(Integer exceptionCode) {
            super((String)msgMap.get(exceptionCode));
            this.code = exceptionCode;
        }
    }
    static {
        try {
            Security.setProperty("crypto.policy", "limited");
            RemoveCryptographyRestrictions();
        } catch (Exception var1) {
        }

    }
    private static void RemoveCryptographyRestrictions() throws Exception {
        Class<?> jceSecurity = getClazz("javax.crypto.JceSecurity");
        Class<?> cryptoPermissions = getClazz("javax.crypto.CryptoPermissions");
        Class<?> cryptoAllPermission = getClazz("javax.crypto.CryptoAllPermission");
        if (jceSecurity != null) {
            setFinalStaticValue(jceSecurity, "isRestricted", false);
            PermissionCollection defaultPolicy = (PermissionCollection)getFieldValue(jceSecurity, "defaultPolicy", (Object)null, PermissionCollection.class);
            if (cryptoPermissions != null) {
                Map<?, ?> map = (Map)getFieldValue(cryptoPermissions, "perms", defaultPolicy, Map.class);
                map.clear();
            }

            if (cryptoAllPermission != null) {
                Permission permission = (Permission)getFieldValue(cryptoAllPermission, "INSTANCE", (Object)null, Permission.class);
                defaultPolicy.add(permission);
            }
        }

    }
    private static Class<?> getClazz(String className) {
        Class clazz = null;

        try {
            clazz = Class.forName(className);
        } catch (Exception var3) {
        }

        return clazz;
    }
    private static void setFinalStaticValue(Class<?> srcClazz, String fieldName, Object newValue) throws Exception {
        Field field = srcClazz.getDeclaredField(fieldName);
        field.setAccessible(true);
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(field, field.getModifiers() & -17);
        field.set((Object)null, newValue);
    }
    private static <T> T getFieldValue(Class<?> srcClazz, String fieldName, Object owner, Class<T> dstClazz) throws Exception {
        Field field = srcClazz.getDeclaredField(fieldName);
        field.setAccessible(true);
        return dstClazz.cast(field.get(owner));
    }

}

校验结果

与钉钉小程序三方企业应用事件与回调相似的内容:

钉钉小程序三方企业应用事件与回调

事件回调 订阅事件后,开放平台将会在事件发生时(如:机器人入群)向请求网址推送消息。 钉钉三方企业应用事件与回调配置界面如下: 推送方式有三种: Http 推送(官方不推荐) SyncHttp 推送 (官方推荐) RDS 推送(官方推荐) Http和SyncHttp没有太大的区别,按钉钉的说法是:与

小程序入门4—钉钉群机器人消息通知和钉钉工作通知

前言 在消息通知这块,钉钉可谓是玩出了花,比如工作通知、群机器人通知,还有那万恶的Ding一下。钉钉的通知不仅花样多,而且大部分渠道都支持自定义,也即可以自定义设置发送时间、发送内容,并且还支持多种样式的消息如文本、卡片、Markdown等。 这篇文章我主要介绍一下常用的两类:钉钉群机器人通知和钉钉

[转帖] GC耗时高,原因竟是服务流量小?

原创:扣钉日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处。 简介# 最近,我们系统配置了GC耗时的监控,但配置上之后,系统会偶尔出现GC耗时大于1s的报警,排查花了一些力气,故在这里分享下。 发现问题# 我们系统分多个环境部署,出现GC长耗时的是俄罗斯环境,其它环境没有这个问题,

[转帖] 记一次使用gdb诊断gc问题全过程

记一次使用gdb诊断gc问题全过程 原创:扣钉日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处。 简介# 上次解决了GC长耗时问题后,系统果然平稳了许多,这是之前的文章《GC耗时高,原因竟是服务流量小?》然而,过了一段时间,我检查GC日志时,又发现了一个GC问题,如下:从这个图中可

把盏言欢,款款而谈,ChatGPT结合钉钉机器人(outgoing回调)打造人工智能群聊/单聊场景,基于Python3.10

就像黑火药时代里突然诞生的核弹一样,OpenAI的ChatGPT语言模型的横空出世,是人工智能技术发展史上的一个重要里程碑。这是一款无与伦比、超凡绝伦的模型,能够进行自然语言推理和对话,并且具有出色的语言生成能力。

使用Jiralert实现AlertManager告警对接Jira

简介 Alertmanager 处理由客户端应用程序(如 Prometheus server)发送的警报。它负责去重(deduplicating),分组(grouping),并将它们路由(routing)到正确的接收器(receiver)集成,如电子邮件,微信,或钉钉。它还负责处理警报的静默/屏蔽(

Prometheus Alertmanager生产配置趟过的坑总结

简介 Alertmanager 处理由客户端应用程序(如 Prometheus server)发送的警报。它负责去重(deduplicating),分组(grouping),并将它们路由(routing)到正确的接收器(receiver)集成,如电子邮件,微信,或钉钉。它还负责处理警报的静默/屏蔽(

[转帖]tar、gzip、zip、jar是什么,怎么查看?

https://www.cnblogs.com/codelogs/p/16702759.html 原创:扣钉日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处。 简介# 如果你是后端程序员,我想你一定见过*.tar.gz、*.zip、*.jar后缀的文件吧,这些都是压缩文件,那这些文

[转帖] JVM诊断命令jcmd介绍

https://www.cnblogs.com/codelogs/p/16535451.html 原创:扣钉日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处。 简介# 从JDK7开始,jdk提供了一个方便扩展的诊断命令jcmd,用来取代之前比较分散的jdk基础命令,如jps、jst

[转帖] 容器内的Linux诊断工具0x.tools

https://www.cnblogs.com/codelogs/p/16242999.html 原创:扣钉日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处。 简介# Linux上有大量的问题诊断工具,如perf、bcc等,但这些诊断工具,虽然功能强大,但却需要很高的权限才可以使用