钉钉做了好好几个项目了,和阿里云还有阿里钉钉合作也挺不错。因为之前就做过微信公众号,接触钉钉感觉还是比较顺手的,虽然也有一些不一样的地方。

因为之前写了一个微信公众号的开发文档,一直想写一个钉钉的开发文档,一直没有时间,先写个钉钉通讯录同步的吧~~

废话不多说,先上菜吧~~

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回调加密解密的方法和上述加密解密方法相同。

java钉钉通讯录同步的更多相关文章

  1. Java钉钉开发_03_通讯录管理之 人员管理 和 部门管理

    一.本节要点 1.通讯录权限 ISV(应用服务商)默认无管理通讯录的权限,企业应用默认有所有通讯录权限. 2.数据传输格式—JSON 请参见: Java_数据交换_fastJSON_01_用法入门 二 ...

  2. 用java实现“钉钉微应用,免登进入某H5系统首页“功能”

    一.前言 哈哈,这是我的第一篇博客. 先说一下这个小功能的具体场景: 用户登录钉钉app,点击微应用,获取当前用户的信息,与H5系统的数据库的用户信息对比,如果存在该用户,则点击后直接进入H5系统的首 ...

  3. Java企业微信开发_03_通讯录同步

    一.本节要点 1.获取通讯录密钥 获取方式: 登录企业微信—>管理工具—>通讯录同步助手—>开启“API接口同步”  ; 开启后,即可看到通讯录密钥,也可设置通讯录API的权限:读取 ...

  4. Java钉钉开发_01_开发前的准备

    源码已上传GitHub:传送门 一.准备事项 1.1  一个能在公网上访问的项目: 参见:Java微信开发_02_本地服务器映射外网 1.2  一个钉钉账号 去注册 1.3 创建一个应用 登录钉钉后台 ...

  5. Java钉钉开发_02_免登授权(身份验证)(附源码)

    源码已上传GitHub: https://github.com/shirayner/DingTalk_Demo 一.本节要点 1.免登授权的流程 (1)签名校验 (2)获取code,并传到后台 (3) ...

  6. shell+钉钉机器人完成java程序中断后自启动和实时监控

    java实时程序在运行过程中偶尔出现异常信息中断的情况,通过shell脚本即可完成自启动. 以下为监控一个实时的java程序的shell脚本. 通过每10秒检查一次java程序的进程,来判断程序是否处 ...

  7. Java钉钉开发_02_免登授权(身份验证)

    源码已上传GitHub: https://github.com/shirayner/DingTalk_Demo 一.本节要点 1.免登授权的流程 (1)签名校验 (2)获取code,并传到后台 (3) ...

  8. Java企业微信开发_02_通讯录同步

    一.本节要点 1.获取通讯录密钥 获取方式: 登录企业微信—>管理工具—>通讯录同步助手—>开启“API接口同步”  ; 开启后,即可看到通讯录密钥,也可设置通讯录API的权限:读取 ...

  9. 开发笔记—钉钉服务商应用isv开发,从应用配置,到获取客户企业通讯录

    以第三方企业微应用为例 在第三方企业微应用应用时,比较底层的需求,就是应用需要获取客户企业的通讯录,即部门/员工的数据.本人整理以下几个关键数据,供大家开发参考. 新建第三方微应用时,能拿到这些初始数 ...

随机推荐

  1. three.js中的文字

    1.三维文字 三维字体文字,使用的是FontLoader,字体文件通过来facetype.js生成 addCityText: function () { var self = this; var ci ...

  2. 2、Redis 底层原理:Cluster 集群部署与详解

    Redis 简介 Redis 提供数据缓存服务,内部数据都存在内存中,所以访问速度非常快. 早期,Redis 单应用服务亦能满足企业的需求.之后,业务量的上升,单机的读写能力满足不了业务的需求,技术上 ...

  3. 初次使用Windbg检查C#程序内存

    1. 下载windbg并安装. 我下载的是 Windbg 6.12.注意,windbg分32位和64位,由分析环境的位数决定.我这里安装的是32位的.安装过程很简单,一路next就可以. 2. 准备被 ...

  4. 序列化模块_pickle

    序列化: 把不能够直接存储的数据变成字节流(bytes)保存在文件, 进行持久化存储 反序列化: 任何数据都可以转成字节流(bytes)进行存储: 1. dumps 把任意对象序列化 li = [1, ...

  5. Linux 版 SecureCRT 界面变为 Windows 2000 风格的解决办法

    SecureCRT 是一款非常好用的远程终端连接软件,支持 Windows.Linux.macOS 全平台.由于现在工作平台主要在 Linux 系统上,SecureCRT 也是必备软件.一开始安装的是 ...

  6. Jmeter发送邮件功能SMTP Sampler

    介绍Jmeter的发送邮件功能,使用的Sampler是SMTP Sampler,详细说明每个配置项的功能 从上往下介绍需要用到的配置项: Server settings Server: 服务器地址 P ...

  7. 乘法DAC一点知识

    在应用电路中发现乘法DAC,以前没有用过所谓的乘法DAC.查过资料发现,其实所有的DAC都可以看作是个“乘法器”-------将输入数字量与基准电压相乘. 一般DAC的输出是VOUT=VREF*D/M ...

  8. extentreports

    关于extentreports使用的一些个人见解 首先导入jar包, 使用maven导入,我再次首先导入的是 <version>4.0.5</version>版本的jar包,但 ...

  9. Winform 弹出窗体等待

    显示效果: FrmWaiting.cs: public FrmWaitingBox(EventHandler<EventArgs> Method,string msg) { Initial ...

  10. scala 随笔

    创建map,并向map添加元素 val idMap = Map( "group_id" -> "GID", "sim_id" -> ...