钉钉做了好好几个项目了,和阿里云还有阿里钉钉合作也挺不错。因为之前就做过微信公众号,接触钉钉感觉还是比较顺手的,虽然也有一些不一样的地方。
因为之前写了一个微信公众号的开发文档,一直想写一个钉钉的开发文档,一直没有时间,先写个钉钉通讯录同步的吧~~
废话不多说,先上菜吧~~
1.ORACLE官方网站下载JCE无限制权限策略文件:因为钉钉的通讯录同步是通过回调来实现的,而回调信息是加密过的需要解密,先要替换jdk/jre里security文件夹内的两个jar包:local_policy.jar和US_export_policy.jar
我用的是jdk8,其他版本请对应下载
替换方式:
(此文件夹的local_policy.jar和US_export_policy.jar是JDK8的,若是其他版本的请按照下放地址下载)
异常java.security.InvalidKeyException:illegal Key Size和『计算解密文字错误』的解决方案:
在官方网站下载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的下载地址:http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html
下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt。
如果安装的是JRE,将两个jar文件放到%JRE_HOME% \lib\security目录下覆盖原来的文件,
如果安装的是JDK,将两个jar文件放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件。
2.通讯录回调:
import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.servlet.ServletInputStream; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.taobao.api.ApiException; import com.alibaba.fastjson.JSONObject; /** * <p>通讯录事件回调<p> * @version 1.0 * @author li_hao * @date 2017年12月21日 */ @WebServlet("/callbackreceive") public class CallBackServlet extends HttpServlet { private static final long serialVersionUID = -1785796919047156450L; public CallBackServlet() { super(); } protected void doPost(HttpServletRequest request, HttpServletResponse response) { doGet(request, response); } /* * 接收钉钉服务器的回调数据 * */ protected void doGet(HttpServletRequest request, HttpServletResponse response){ try { /** url中的签名 **/ String msgSignature = request.getParameter("signature"); /** url中的时间戳 **/ String timeStamp = request.getParameter("timestamp"); /** url中的随机字符串 **/ String nonce = request.getParameter("nonce"); /** 取得JSON对象中的encrypt字段 **/ String encrypt = ""; /** 获取post数据包数据中的加密数据 **/ ServletInputStream sis = request.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(sis)); String line = null; StringBuilder sb = new StringBuilder(); while ((line = br.readLine()) != null) { sb.append(line); } JSONObject jsonEncrypt = JSONObject.parseObject(sb.toString()); encrypt = jsonEncrypt.getString("encrypt"); String decodeEncrypt = decodeEncrypt(msgSignature, timeStamp, nonce, encrypt); //密文解密 JSONObject decodeEncryptJson = JSONObject.parseObject(decodeEncrypt); String eventType = decodeEncryptJson.getString("EventType"); //回调类型 String UserIds = decodeEncryptJson.getString("UserId"); //用户发生变更的userid列表 String DeptIds = decodeEncryptJson.getString("DeptId"); //部门发生变更的deptId列表 String res = "success"; //res是需要返回给钉钉服务器的字符串,一般为success;"check_create_suite_url"和"check_update_suite_url"事件为random字段;(具体请查看文档或者对应eventType的处理步骤) JSONObject jsonObjectData = new JSONObject(); //根据不同的回调类型,进行相应的操作 switch (eventType) { case AddressListRegister.USER_ADD_ORG : //通讯录用户增加 break; case AddressListRegister.USER_MODIFY_ORG : //通讯录用户更改 break; case AddressListRegister.USER_LEAVE_ORG : //通讯录用户离职 break; case AddressListRegister.ORG_ADMIN_ADD : //通讯录用户被设为管理员 break; case AddressListRegister.ORG_ADMIN_REMOVE : //通讯录用户被取消设置管理员 break; case AddressListRegister.ORG_DEPT_CREATE : //通讯录企业部门创建 break; case AddressListRegister.ORG_DEPT_MODIFY : //通讯录企业部门修改 break; case AddressListRegister.ORG_DEPT_REMOVE : //通讯录企业部门删除 break; case AddressListRegister.ORG_REMOVE : //企业被解散 break; case AddressListRegister.ORG_CHANGE : //企业信息发生变更 break; case AddressListRegister.LABEL_USER_CHANGE : //员工角色信息发生变更 break; case AddressListRegister.LABEL_CONF_ADD : //增加角色或者角色组 break; case AddressListRegister.LABEL_CONF_DEL : //删除角色或者角色组 break; case AddressListRegister.LABEL_CONF_MODIFY : //修改角色或者角色组 break; case AddressListRegister.CHECK_URL : //测试回调接口事件类型 System.out.println("测试回调接口!"); break; default: // do something break; } response.getWriter().append(codeEncrypt(res, timeStamp, nonce).toString()); //返回加密后的数据 } catch (Exception e) { e.printStackTrace(); } } /** * 创建加密/解密 类 * @return */ public DingTalkEncryptor createDingTalkEncryptor(){ DingTalkEncryptor dingTalkEncryptor = null; //加密方法类 try { dingTalkEncryptor = new DingTalkEncryptor(AddressListRegister.TOKEN, AddressListRegister.AES_KEY,AddressListRegister.CORPID); //创建加解密类 } catch (DingTalkEncryptException e) { e.printStackTrace(); } return dingTalkEncryptor; } /** * encrypt解密 * @param msgSignature * @param timeStamp * @param nonce * @param encrypt 密文 * @return decodeEncrypt 解密后的明文 */ public String decodeEncrypt(String msgSignature,String timeStamp,String nonce,String encrypt){ String decodeEncrypt = null; try { decodeEncrypt = createDingTalkEncryptor().getDecryptMsg(msgSignature, timeStamp, nonce, encrypt); //encrypt解密 } catch (DingTalkEncryptException e) { e.printStackTrace(); } return decodeEncrypt; } /** * 对返回信息进行加密 * @param res * @param timeStamp * @param nonce * @return */ public JSONObject codeEncrypt(String res,String timeStamp,String nonce){ long timeStampLong = Long.parseLong(timeStamp); Map<String, String> jsonMap = null; try { jsonMap = createDingTalkEncryptor().getEncryptedMap(res, timeStampLong, nonce); //jsonMap是需要返回给钉钉服务器的加密数据包 } catch (DingTalkEncryptException e) { System.out.println(e.getMessage()); e.printStackTrace(); } JSONObject json = new JSONObject(); json.putAll(jsonMap); return json; } //测试方法 public static void main(String[] args) throws ApiException { String accesstoken = "xxxxxxxxxxxxxxxxxxxxxxxxx"; String token = AddressListRegister.TOKEN; String aesKey = AddressListRegister.AES_KEY; String callBackUrl = "http://xxxx/callbackreceive"; List<String> listStr = new ArrayList<String>(); listStr.add("user_add_org"); listStr.add("user_modify_org"); listStr.add("user_leave_org"); listStr.add("org_dept_create"); listStr.add("org_dept_modify"); listStr.add("org_dept_remove"); JSONObject registerCallBack = DingTalkUtil.updateCallBack(accesstoken, listStr, token, aesKey, callBackUrl); System.out.println("注册事件返回:"+registerCallBack); JSONObject callBack = DingTalkUtil.getCallBack(accesstoken); System.out.println("查询注册事件:"+callBack); } }
几个参数和通讯录注册 需要监听的事件类型:
/** * <p>几个参数和通讯录注册 需要监听的事件类型<p> * @version 1.0 * @author li_hao * @date 2017年12月15日 */ public class AddressListRegister{ /**企业的corpid */ public static final String CORPID = "xxxxxxxxxx"; /**钉钉开放平台上,开发者设置的token */ public static final String TOKEN = "token"; /**数据加密密钥。用于回调数据的加密,长度固定为43个字符,从a-z, A-Z, 0-9共62个字符中选取,您可以随机生成,ISV(服务提供商)推荐使用注册套件时填写的EncodingAESKey */ public static final String AES_KEY = "xxxxx7p5qnb6zs3xxxxxlkfmxqfkv23d40yd0xxxxxx"; /**通讯录用户增加 */ public static final String USER_ADD_ORG = "user_add_org"; /**通讯录用户更改*/ public static final String USER_MODIFY_ORG = "user_modify_org"; /** 通讯录用户离职 */ public static final String USER_LEAVE_ORG = "user_leave_org"; /** 通讯录用户被设为管理员 */ public static final String ORG_ADMIN_ADD = "org_admin_add"; /** 通讯录用户被取消设置管理员 */ public static final String ORG_ADMIN_REMOVE = "org_admin_remove"; /**通讯录企业部门创建*/ public static final String ORG_DEPT_CREATE = "org_dept_create"; /** 通讯录企业部门修改 */ public static final String ORG_DEPT_MODIFY = "org_dept_modify"; /**通讯录企业部门删除*/ public static final String ORG_DEPT_REMOVE = "org_dept_remove"; /**企业被解散*/ public static final String ORG_REMOVE = "org_remove"; /**企业信息发生变更*/ public static final String ORG_CHANGE = "org_change"; /**员工角色信息发生变更*/ public static final String LABEL_USER_CHANGE = "label_user_change"; /**增加角色或者角色组*/ public static final String LABEL_CONF_ADD = "label_conf_add"; /**删除角色或者角色组*/ public static final String LABEL_CONF_DEL = "label_conf_del"; /**修改角色或者角色组*/ public static final String LABEL_CONF_MODIFY = "label_conf_modify"; /**测试回调接口事件类型*/ public static final String CHECK_URL = "check_url"; }
加解密方法:
import java.io.ByteArrayOutputStream; import java.nio.charset.Charset; import java.security.MessageDigest; import java.util.*; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; 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 */ public class DingTalkEncryptor { 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 ISV进行配置的时候应该传对应套件的SUITE_KEY(第一次创建时传的是默认的CREATE_SUITE_KEY),普通企业是Corpid * @throws DingTalkEncryptException 执行失败,请查看该异常的错误码和具体的错误信息 */ public DingTalkEncryptor(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 + "="); } /** * 将和钉钉开放平台同步的消息体加密,返回加密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); StringBuffer sb = new StringBuffer(); for (int i = 0; i < 4; i++) { sb.append(array[i]); } String str = sb.toString(); 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); } } }
加解密异常类:
import java.util.HashMap; import java.util.Map; /** * 加解密异常类 */ public class DingTalkEncryptException extends Exception { /**成功**/ public static final int SUCCESS = 0; /**加密明文文本非法**/ public final static int ENCRYPTION_PLAINTEXT_ILLEGAL = 900001; /**加密时间戳参数非法**/ public final static int ENCRYPTION_TIMESTAMP_ILLEGAL = 900002; /**加密随机字符串参数非法**/ public final static int ENCRYPTION_NONCE_ILLEGAL = 900003; /**不合法的aeskey**/ public final static int AES_KEY_ILLEGAL = 900004; /**签名不匹配**/ public final static int SIGNATURE_NOT_MATCH = 900005; /**计算签名错误**/ public final static int COMPUTE_SIGNATURE_ERROR = 900006; /**计算加密文字错误**/ public final static int COMPUTE_ENCRYPT_TEXT_ERROR = 900007; /**计算解密文字错误**/ public final static int COMPUTE_DECRYPT_TEXT_ERROR = 900008; /**计算解密文字长度不匹配**/ public final static int COMPUTE_DECRYPT_TEXT_LENGTH_ERROR = 900009; /**计算解密文字suiteKey(ISV)或者corpid(普通企业)不匹配**/ public final static int COMPUTE_DECRYPT_TEXT_CORPID_ERROR = 900010; private static Map<Integer,String> msgMap = new HashMap<Integer,String>(); static{ msgMap.put(SUCCESS,"成功"); msgMap.put(ENCRYPTION_PLAINTEXT_ILLEGAL,"加密明文文本非法"); msgMap.put(ENCRYPTION_TIMESTAMP_ILLEGAL,"加密时间戳参数非法"); msgMap.put(ENCRYPTION_NONCE_ILLEGAL,"加密随机字符串参数非法"); msgMap.put(SIGNATURE_NOT_MATCH,"签名不匹配"); msgMap.put(COMPUTE_SIGNATURE_ERROR,"签名计算失败"); msgMap.put(AES_KEY_ILLEGAL,"不合法的aes key"); msgMap.put(COMPUTE_ENCRYPT_TEXT_ERROR,"计算加密文字错误"); msgMap.put(COMPUTE_DECRYPT_TEXT_ERROR,"计算解密文字错误"); msgMap.put(COMPUTE_DECRYPT_TEXT_LENGTH_ERROR,"计算解密文字长度不匹配"); msgMap.put(COMPUTE_DECRYPT_TEXT_CORPID_ERROR,"计算解密文字suiteKey(ISV)或者corpid(普通企业)不匹配"); } public Integer code; public DingTalkEncryptException(Integer exceptionCode){ super(msgMap.get(exceptionCode)); this.code = exceptionCode; } }
PKCS7算法的加密填充:
import java.nio.charset.Charset; import java.util.Arrays; /* * PKCS7算法的加密填充 */ public class PKCS7Padding { private final static Charset CHARSET = Charset.forName("utf-8"); private final static int BLOCK_SIZE = 32; /** * 填充mode字节 * @param count * @return */ public static byte[] getPaddingBytes(int count) { int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE); if (amountToPad == 0) { amountToPad = BLOCK_SIZE; } char padChr = chr(amountToPad); String tmp = new String(); for (int index = 0; index < amountToPad; index++) { tmp += padChr; } return tmp.getBytes(CHARSET); } /** * 移除mode填充字节 * @param decrypted * @return */ public static byte[] removePaddingBytes(byte[] decrypted) { int pad = (int) decrypted[decrypted.length - 1]; if (pad < 1 || pad > BLOCK_SIZE) { pad = 0; } return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad); } private static char chr(int a) { byte target = (byte) (a & 0xFF); return (char) target; } }
加解密工具类:
import java.util.Random; /** * 加解密工具类 */ public class Utils { /** * * @return */ 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(); } /* * int转byte数组,高位在前 */ public static byte[] int2Bytes(int count) { byte[] byteArr = new byte[4]; byteArr[3] = (byte) (count & 0xFF); byteArr[2] = (byte) (count >> 8 & 0xFF); byteArr[1] = (byte) (count >> 16 & 0xFF); byteArr[0] = (byte) (count >> 24 & 0xFF); return byteArr; } /** * 高位在前bytes数组转int * @param byteArr * @return */ public static int bytes2int(byte[] byteArr) { int count = 0; for (int i = 0; i < 4; i++) { count <<= 8; count |= byteArr[i] & 0xff; } return count; } }
接口方法:
/** * 通讯录:注册事件回调接口 * @param accesstoken 企业的accesstoken * @param callBackTag 需要监听的事件类型,共有20种(Array[String]) * @param token 加解密需要用到的token,ISV(服务提供商)推荐使用注册套件时填写的token,普通企业可以随机填写 * @param aesKey 数据加密密钥。用于回调数据的加密,长度固定为43个字符,从a-z, A-Z, 0-9共62个字符中选取,您可以随机生成,ISV(服务提供商)推荐使用注册套件时填写的EncodingAESKey * @param callBackUrl 接收事件回调的url * @return * @throws ApiException */ public static JSONObject registerCallBack(String accesstoken,List<String> callBackTag,String token,String aesKey,String callBackUrl) throws ApiException{ String url = CommomUrl.REGISTER_CALL_BACK.replace("ACCESS_TOKEN", accesstoken); JSONObject jsonReq = new JSONObject(); jsonReq.put("call_back_tag", callBackTag); jsonReq.put("token", token); jsonReq.put("aes_key", aesKey); jsonReq.put("url", callBackUrl); System.out.println(jsonReq.toString()); JSONObject jsonObject = doPostStr(url, jsonReq.toString()); return jsonObject; } /** * 通讯录:查询事件回调接口 * @param accesstoken 企业的accesstoken * @return */ public static JSONObject getCallBack(String accesstoken){ String url = CommomUrl.GET_CALL_BACK.replace("ACCESS_TOKEN", accesstoken); JSONObject jsonObject = doGetStr(url); return jsonObject; } /** * 通讯录:更新事件回调接口 * @param accesstoken 企业的accesstoken * @param callBackTag 需要监听的事件类型,共有20种(Array[String]) * @param token 加解密需要用到的token,ISV(服务提供商)推荐使用注册套件时填写的token,普通企业可以随机填写 * @param aesKey 数据加密密钥。用于回调数据的加密,长度固定为43个字符,从a-z, A-Z, 0-9共62个字符中选取,您可以随机生成,ISV(服务提供商)推荐使用注册套件时填写的EncodingAESKey * @param callBackUrl 接收事件回调的url * @return * @throws ApiException */ public static JSONObject updateCallBack(String accesstoken,List<String> callBackTag,String token,String aesKey,String callBackUrl) throws ApiException{ String url = CommomUrl.UPDATE_CALL_BACK.replace("ACCESS_TOKEN", accesstoken); JSONObject jsonReq = new JSONObject(); jsonReq.put("call_back_tag", callBackTag); jsonReq.put("token", token); jsonReq.put("aes_key", aesKey); jsonReq.put("url", callBackUrl); JSONObject jsonObject = doPostStr(url, jsonReq.toString()); return jsonObject; } /** * 通讯录:删除事件回调接口 * @param accesstoken 企业的accesstoken * @return */ public static JSONObject deleteCallBack(String accesstoken){ String url = CommomUrl.DELETE_CALL_BACK.replace("ACCESS_TOKEN", accesstoken); JSONObject jsonObject = doGetStr(url); return jsonObject; } /** * 通讯录:获取回调失败的结果 * @param accesstoken 企业的accesstoken * @return */ public static JSONObject getCallBackFailedResult(String accesstoken){ String url = CommomUrl.GET_CALL_BACK_FAILED_RESULT.replace("ACCESS_TOKEN", accesstoken); JSONObject jsonObject = doGetStr(url); return jsonObject; }
CommonUrl:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @version : 1.0 * @Author : li_hao * @Description : 钉钉接口地址类 * @Date : 2017-06-10 17:47 **/ public class CommomUrl { private static Logger log = LoggerFactory.getLogger(CommomUrl.class); /** 注册事件回调接口(请求方式:post) */ public static final String REGISTER_CALL_BACK = "https://oapi.dingtalk.com/call_back/register_call_back?access_token=ACCESS_TOKEN"; /** 查询事件回调接口(请求方式:get) */ public static final String GET_CALL_BACK = "https://oapi.dingtalk.com/call_back/get_call_back?access_token=ACCESS_TOKEN"; /** 更新事件回调接口(请求方式:post) */ public static final String UPDATE_CALL_BACK = "https://oapi.dingtalk.com/call_back/update_call_back?access_token=ACCESS_TOKEN"; /** 删除事件回调接口(请求方式:get) */ public static final String DELETE_CALL_BACK = "https://oapi.dingtalk.com/call_back/delete_call_back?access_token=ACCESS_TOKEN"; /** 获取回调失败的结果 (请求方式:get)*/ public static final String GET_CALL_BACK_FAILED_RESULT = "https://oapi.dingtalk.com/call_back/get_call_back_failed_result?access_token=ACCESS_TOKEN"; }
get请求、post请求:
/** * get请求 * @param url 为接口地址参数 * @return */ public static JSONObject doGetStr(String url){ CloseableHttpClient httpClient = HttpClients.createDefault(); HttpGet httpGet = new HttpGet(url); CloseableHttpResponse response = null; JSONObject jsonObject = null;//接收结果 try { response = httpClient.execute(httpGet); HttpEntity entity = response.getEntity();//从消息体里获取结果 if(entity!=null){ String result = EntityUtils.toString(entity,"UTF-8"); jsonObject = JSONObject.parseObject(result); } EntityUtils.consume(entity); } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { if(response != null){ response.close(); } } catch (IOException e) { e.printStackTrace(); } } return jsonObject; } /** * post请求 * @param url 为接口地址参数 * @param outStr * @return */ public static JSONObject doPostStr(String url,String outStr){ CloseableHttpClient httpClient = HttpClients.createDefault(); //DefaultHttpClient httpClient = new DefaultHttpClient(); HttpPost httpPost = new HttpPost(url); JSONObject jsonObject = null; try { httpPost.setEntity(new StringEntity(outStr, "UTF-8")); HttpResponse response = httpClient.execute(httpPost); String result = EntityUtils.toString(response.getEntity(), "UTF-8"); jsonObject = JSONObject.parseObject(result); } catch (Exception e) { e.printStackTrace(); } return jsonObject; }
以上就是通讯录同步的所有代码了,注:钉钉ISV回调加密解密的方法和上述加密解密方法相同。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!