企业内部应用对接钉钉 -- 钉钉回调

记录自己踩得坑,希望可以帮助更多人。

首先,想说钉钉官方文档写的,,也挺全,也不全,内容也挺丰富,有写东西吧,也真不好找,内容比较散。。。

言归正传。。。
整个流程,依旧先阅读官网:https://ding-doc.dingtalk.com/doc#/serverapi2/pwz3r5

  1. 第一步:注册业务事件回调接口,主要目的就是,给钉钉一个接口,这个接口作用是:如有回调发生,钉钉就会给这个接口返回变更的数据,这个接口就是这一步骤中传入的参数url 【具体url如何写,后面文中会说】
  2. 第二步:解读下这个流程:
    在这里插入图片描述
    这个流程意思:第一步中填入的url参数,也就是那个接收回调的接口,需要验证它的可用性,需要给钉钉返回一个加密的“success”json数据。【加密/解密 后面讲】
    第三步:第一,第二步骤都没问题了,就可以开始开发了,可以成功接收回调了。然后依旧多次阅读官方文档,每次读可能都有新的发现。

下面就是疑难杂症。。。给出代码:
回调事件通用类:

package com.wekj.peanut.utils;

import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.DingTalkClient;
import com.dingtalk.api.request.*;
import com.dingtalk.api.response.*;
import com.taobao.api.ApiException;
import lombok.extern.slf4j.Slf4j;
import java.util.List;

/**
 * 回调事件通用类
 * 竹蜻蜓 2020/2/22 21:21
 */
@Slf4j
public class EventChangeUtils {

	/**
	 * 注册事件回调接口
	 * 竹蜻蜓 2020/2/22 20:56
	 * @param accessToken
	 * @param callBackTag
	 * @param token
	 * @param aesKey
	 * @param url
	 * @return
	 */
	public static OapiCallBackRegisterCallBackResponse registerEventChange(String accessToken, List<String> callBackTag, String token, String aesKey, String url) throws ApiException {
		DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/call_back/register_call_back");
		OapiCallBackRegisterCallBackRequest request = new OapiCallBackRegisterCallBackRequest();
		request.setUrl(url);
		request.setAesKey(aesKey);
		request.setToken(token);
		request.setCallBackTag(callBackTag);
		log.info("EventChangeUtils.registerEventChange()方法中传入参数回调url 值为:" + request.getUrl());
		OapiCallBackRegisterCallBackResponse response = client.execute(request,accessToken);
		return response;
	}

	/**
	 * 查询事件回调接口
	 * 竹蜻蜓 2020/2/22 20:55
	 * @param accessToken
	 * @return
	 */
	public static String getEventChange(String accessToken) throws ApiException {
		DingTalkClient  client = new DefaultDingTalkClient("https://oapi.dingtalk.com/call_back/get_call_back");
		OapiCallBackGetCallBackRequest request = new OapiCallBackGetCallBackRequest();
		request.setHttpMethod("GET");
		OapiCallBackGetCallBackResponse response = client.execute(request,accessToken);
		return response.toString();
	}

	/**
	 * 更新事件回调接口
	 * 竹蜻蜓 2020/2/22 20:55
	 * @param accessToken
	 * @param callBackTag
	 * @param token
	 * @param aesKey
	 * @param url
	 * @return
	 */
	public static String updateEventChange(String accessToken, List<String> callBackTag, String token, String aesKey, String url) throws ApiException {
		DingTalkClient  client = new DefaultDingTalkClient("https://oapi.dingtalk.com/call_back/update_call_back");
		OapiCallBackUpdateCallBackRequest request = new OapiCallBackUpdateCallBackRequest();
		request.setUrl(url);
		request.setAesKey(aesKey);
		request.setToken(token);
		request.setCallBackTag(callBackTag);
		OapiCallBackUpdateCallBackResponse response = client.execute(request,accessToken);
		return response.toString();
	}

	/**
	 * 删除事件回调接口
	 * 竹蜻蜓 2020/2/22 20:55
	 * @param accessToken
	 * @return
	 */
	public static String deleteEventChange(String accessToken) throws  ApiException {
		DingTalkClient  client = new DefaultDingTalkClient("https://oapi.dingtalk.com/call_back/delete_call_back");
		OapiCallBackDeleteCallBackRequest request = new OapiCallBackDeleteCallBackRequest();
		request.setHttpMethod("GET");
		OapiCallBackDeleteCallBackResponse response = client.execute(request,accessToken);
		return response.toString();
	}

	/**
	 * 获取回调失败的结果
	 * 钉钉服务器给回调接口推送时,有可能因为各种原因推送失败(比如网络异常),此时钉钉将保留此次变更事件。用户可以通过此回调接口获取推送失败的变更事件。
	 * 竹蜻蜓 2020/2/22 20:57
	 * @param accessToken
	 * @return
	 */
	public static String getFailedResult(String accessToken) throws  ApiException {
		DingTalkClient  client = new DefaultDingTalkClient("https://oapi.dingtalk.com/call_back/get_call_back_failed_result");
		OapiCallBackGetCallBackFailedResultRequest request = new OapiCallBackGetCallBackFailedResultRequest();
		request.setHttpMethod("GET");
		OapiCallBackGetCallBackFailedResultResponse response = client.execute(request,accessToken);
		return response.toString();
	}
}
package com.wekj.peanut.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.DingTalkClient;
import com.dingtalk.api.request.OapiCallBackDeleteCallBackRequest;
import com.dingtalk.api.response.OapiCallBackDeleteCallBackResponse;
import com.dingtalk.api.response.OapiCallBackRegisterCallBackResponse;
import com.dingtalk.oapi.lib.aes.DingTalkEncryptException;
import com.dingtalk.oapi.lib.aes.DingTalkEncryptor;
import com.dingtalk.oapi.lib.aes.Utils;
import com.taobao.api.ApiException;
import com.wekj.peanut.service.*;
import com.wekj.peanut.utils.Constant;
import com.wekj.peanut.utils.DingTalkUtils;
import com.wekj.peanut.utils.EventChangeUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
 * 钉钉回调类
 *
 * @author 竹蜻蜓
 * @date 2020/2/22 16:19
 */
@Slf4j
@RequestMapping(value = "/callBackController")
@RestController
public class CallbackController {

    @Resource
    private DingDepartmentService dingDepartmentService;

    @Resource
    private DingUserService dingUserService;

    /**
     * 1.注册钉钉回调接口
     * 竹蜻蜓 2020/2/25 13:03
     *
     * @param
     * @return
     */
    public static void registerCallback() throws Exception {
        log.info("1.开始注册");
        String accessToken = DingTalkUtils.getAccessToken(Constant.APPKEY, Constant.APPSECRET);
        try {
            // 先删除企业已有的回调
            DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/call_back/delete_call_back");
            OapiCallBackDeleteCallBackRequest request = new OapiCallBackDeleteCallBackRequest();
            request.setHttpMethod("GET");
            OapiCallBackDeleteCallBackResponse execute = client.execute(request, accessToken);
            log.info("2.删除已有回调结果:" + execute.isSuccess());
            // 重新为企业注册回调
            List<String> depList = Arrays.asList("org_dept_create", "org_dept_modify", "org_dept_remove",
                    "user_add_org", "user_modify_org", "user_leave_org", "user_active_org",
                    "org_remove", "org_change",
                    "bpms_task_change", "bpms_instance_change");
            String url = "http://****外网可以访问的地址**/callBackController/callback";
            OapiCallBackRegisterCallBackResponse res = EventChangeUtils.registerEventChange(accessToken, depList, Constant.TOKEN, Constant.ENCODING_AES_KEY, url);
            if (res.isSuccess()) {
                log.info("6.回调注册成功了!!!");
            } else {
                log.error("6.回调注册失败了!!!");
            }
        } catch (ApiException e) {
            log.error("回调注册失败了!!!EventChangeServlet.registerCallback()方法异常");
        }
    }


    /**
     * 2.回调地址里的url方法  --- 获取变更数据
     * 竹蜻蜓 2020/2/25 13:03
     *
     * @param signature
     * @param timestamp
     * @param nonce
     * @param json
     * @return
     */
    @RequestMapping(value = "/callback", method = RequestMethod.POST)
    @ResponseBody
    public Map<String, String> callback(@RequestParam(value = "signature", required = false) String signature,
                                        @RequestParam(value = "timestamp", required = false) String timestamp,
                                        @RequestParam(value = "nonce", required = false) String nonce,
                                        @RequestBody(required = false) JSONObject json) throws DingTalkEncryptException {

        log.info("3.进入方法callback()");
        log.info("3.signature值为:" + signature);
        log.info("3.timestamp值为:" + timestamp);
        log.info("3.nonce值为:" + nonce);
        log.info("3.json值为:" + json.toString());
        DingTalkEncryptor dingTalkEncryptor = new DingTalkEncryptor(Constant.TOKEN, Constant.ENCODING_AES_KEY, Constant.CORPID);
        // 从post请求的body中获取回调信息的加密数据
        String encrypt = json.getString("encrypt");
        log.info("3.加密数据encrypt值为:" + encrypt);
        // 解密
        String plainText = dingTalkEncryptor.getDecryptMsg(signature, timestamp, nonce, encrypt);
        log.info("3.解密出的明文plainText值为:" + plainText);
        JSONObject obj = JSON.parseObject(plainText);
        log.info("4.开始处理业务。。。Obj=" + obj);
        // 根据回调数据类型做不同的业务处理
        String eventType = obj.getString("EventType");
        switch (eventType) {
            //通讯录用户增加
            case "user_add_org":
                break;
            //通讯录用户更改
            case "user_modify_org":
                break;
            //通讯录用户离职
            case "user_leave_org":                
                break;
            // 通讯录企业部门创建
            case "org_dept_create":               
                break;
            // 通讯录企业部门修改
            case "org_dept_modify":                
                break;
            //通讯录企业部门删除
            case "org_dept_remove":
            	break;
            //企业被解散
            case "org_remove":
                break;
            case "check_url":
                break;
            case "bpms_task_change":
                break;
            case "bpms_instance_change":
                break;
            default: //do something
                break;
        }
        // 返回success的加密信息表示回调处理成功
        log.info("5.开始加密‘success’字符串,并返回。。");
        return dingTalkEncryptor.getEncryptedMap("success", System.currentTimeMillis(), Utils.getRandomStr(8));
    }
}

这些代码需要导入的jar包:想办法去官网找,实在找不到私信我吧。
在这里插入图片描述

  • 踩坑1:
    如果没有做幂等,是不是发现钉钉回调同时触发好几次。。。导入入库多条相同数据。。。钉钉官方给出的消息
    在这里插入图片描述
    所以我们需要做幂等操作,,原因给出来了,根据你的业务,自行处理吧。

  • 踩坑2:
    注册回调的 url 地址怎么写?
    首先你先写出上面的 callback() 方法,,然后确保用外网能访问进来这个方法,关于外网,看下面踩坑3.
    外网没问题了,能访问通这个方法了,就把这个方法路径,填写到注册回调的 Url 参数位置去,,就可以了。

  • 踩坑3:
    开发时候,一般要访问外网,我们都要借助内网穿透工具,正巧,钉钉官网提供的有。。。结果用了半天发现这个工具不好用。。。换工具吧:内网穿透工具

如上:基本都能梳理通整个逻辑了吧????

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章