1.注册业务接口
监听事件在官方的说明中可以看见地址:https://ding-doc.dingtalk.com/doc#/serverapi2/skn8ld
监听事件参数位置,下面以审批事件做个说明:
注意:
缺失[‘’]错误
{
"errcode":71010,
"errmsg":"POST的JSON数据不包含所需要的参数字段或包含的参数格式非法"
}
调用成功的回调
{
"errcode":71012,
"errmsg":"url地址访问异常,错误原因为:org.apache.http.conn.ConnectTimeoutException: Connect to 223.95.85.243:8066 [223.95.85.243/223.95.85.243] failed: connect timed out HttpRequest: curl 'http://223.95.85.243:8066/callback?signature=90e6cd4484ac83997322b4eaf68477450d1093ca×tamp=1591148525407&nonce=sIUPJMaj' -d '{\"encrypt\":\"I5rXe2M+nvAK5YdkOq6RYzXWeV3+LeLOB1xOQvYZCsCPuxonmg/K1Vr+uGu/Ze67fiBvwDNsC/B+c8wcC5MQaGuxZiw6bNm6ii7z70r78b4Wbo+A3icraqygsSMAWRNd\"}' -H 'Content-Type:application/json' HttpCode:0 HttpReponse:null"
}
2.编写url校验业务代码
下面采用钉钉官方写的方法:
官方代码地址:https://github.com/opendingtalk/eapp-isv-quick-start-java/blob/master/src/main/java/com/controller/CallbackController.java
官方架包:
3个都给予给出
链接:https://pan.baidu.com/s/140donn7UW5vzlHeR1Ct20Q
提取码:le56
将架包加入lib中,引用
<dependency>
<groupId>com.taobao.top</groupId>
<artifactId>top-api-sdk-dev</artifactId>
<version>dingtalk-SNAPSHOT</version>
<scope>system</scope>
<systemPath>${pom.basedir}/src/main/resources/lib/taobao-sdk-java-auto_1479188381469-20180525.jar</systemPath>
</dependency>
<dependency>
<groupId>com.taobao.top</groupId>
<artifactId>lippi-oapi-encrpt</artifactId>
<version>dingtalk-SNAPSHOT</version>
<scope>system</scope>
<systemPath>${pom.basedir}/src/main/resources/lib/lippi-oapi-encrpt.jar</systemPath>
</dependency>
官方代码
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.config.Constant;
import com.dingtalk.oapi.lib.aes.DingTalkEncryptException;
import com.dingtalk.oapi.lib.aes.DingTalkEncryptor;
import com.dingtalk.oapi.lib.aes.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* ISV 小程序回调信息处理
*/
@RestController
public class CallbackController {
private static final Logger bizLogger = LoggerFactory.getLogger("BIZ_CALLBACKCONTROLLER");
private static final Logger mainLogger = LoggerFactory.getLogger(CallbackController.class);
/**
* 创建应用,验证回调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";
/**
* 企业授权开通应用事件
*/
private static final String EVENT_TMP_AUTH_CODE = "tmp_auth_code";
/**
* 相应钉钉回调时的值
*/
private static final String CALLBACK_RESPONSE_SUCCESS = "success";
@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) {
String params = " signature:"+signature + " timestamp:"+timestamp +" nonce:"+nonce+" json:"+json;
try {
bizLogger.info("begin /callback"+params);
DingTalkEncryptor dingTalkEncryptor = new DingTalkEncryptor(Constant.TOKEN, Constant.ENCODING_AES_KEY,
Constant.SUITE_KEY);
//从post请求的body中获取回调信息的加密数据进行解密处理
String encryptMsg = json.getString("encrypt");
String plainText = dingTalkEncryptor.getDecryptMsg(signature, timestamp, nonce, encryptMsg);
JSONObject obj = JSON.parseObject(plainText);
//根据回调数据类型做不同的业务处理
String eventType = obj.getString("EventType");
if (EVENT_CHECK_CREATE_SUITE_URL.equals(eventType)) {
bizLogger.info("验证新创建的回调URL有效性: " + plainText);
} else if (EVENT_CHECK_UPADTE_SUITE_URL.equals(eventType)) {
bizLogger.info("验证更新回调URL有效性: " + plainText);
} else if (EVENT_SUITE_TICKET.equals(eventType)) {
//suite_ticket用于用签名形式生成accessToken(访问钉钉服务端的凭证),需要保存到应用的db。
//钉钉会定期向本callback url推送suite_ticket新值用以提升安全性。
//应用在获取到新的时值时,保存db成功后,返回给钉钉success加密串(如本demo的return)
bizLogger.info("应用suite_ticket数据推送: " + plainText);
} else if (EVENT_TMP_AUTH_CODE.equals(eventType)) {
//本事件应用应该异步进行授权开通企业的初始化,目的是尽最大努力快速返回给钉钉服务端。用以提升企业管理员开通应用体验
//即使本接口没有收到数据或者收到事件后处理初始化失败都可以后续再用户试用应用时从前端获取到corpId并拉取授权企业信息,
// 进而初始化开通及企业。
bizLogger.info("企业授权开通应用事件: " + plainText);
} else {
// 其他类型事件处理
}
// 返回success的加密信息表示回调处理成功
return dingTalkEncryptor.getEncryptedMap(CALLBACK_RESPONSE_SUCCESS, System.currentTimeMillis(), Utils.getRandomStr(8));
} catch (Exception e) {
//失败的情况,应用的开发者应该通过告警感知,并干预修复
mainLogger.error("process callback failed!"+params,e);
return null;
}
}
}
配置文件
/**
* 项目中的常量定义类
*/
public class Constant {
public static final String SUITE_KEY="";
public static final String SUITE_SECRET="";
//ENCODING_AES_KEY为随便填写的那43位
public static final String ENCODING_AES_KEY = "";
//Tonke为下方图片中的开始填写的
public static final String TOKEN = "";
}
Token,ENCODING_AES_KEY为下方的
SUITE_KEY为这个CorpId
地址验证成功后回调
{
"errcode":0,
"errmsg":"ok"
}
注:如果断点调试也会发生url验证异常错误,可以输出官方代码中的encryptMsg和plainText查看回调事件
结果为
{
"processInstanceId": "d920377-1c-ec5ccce0b226",
"finishTime": 15909950,
"corpId": "dingc85569c",
"EventType": "bpms_instance_change",
"businessId": "200115140",
"title": "测试模板",
"type": "finish",
"url": "https://aflow.dingtalk.com/dingtalk/mobile/homepage.htm?corpid=dingc85569cd86a80cdb&dd_share=false&showmenu=false&dd_progress=false&back=native&procInstId=d9920377-cf29-4f87-b81c-ec5ccce0b226&taskId=&swfrom=isv&dinghash=approval&dd_from=corp#approval",
"result": "refuse",
"createTime": 1590995679000,
"processCode": "369B-47D4-83AA-F2BB36D",
"bizCategoryId": "",
"staffId": "21044214"
}