java钉钉通讯录同步
钉钉做了好好几个项目了,和阿里云还有阿里钉钉合作也挺不错。因为之前就做过微信公众号,接触钉钉感觉还是比较顺手的,虽然也有一些不一样的地方。
因为之前写了一个微信公众号的开发文档,一直想写一个钉钉的开发文档,一直没有时间,先写个钉钉通讯录同步的吧~~
废话不多说,先上菜吧~~
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钉钉通讯录同步的更多相关文章
- Java钉钉开发_03_通讯录管理之 人员管理 和 部门管理
一.本节要点 1.通讯录权限 ISV(应用服务商)默认无管理通讯录的权限,企业应用默认有所有通讯录权限. 2.数据传输格式—JSON 请参见: Java_数据交换_fastJSON_01_用法入门 二 ...
- 用java实现“钉钉微应用,免登进入某H5系统首页“功能”
一.前言 哈哈,这是我的第一篇博客. 先说一下这个小功能的具体场景: 用户登录钉钉app,点击微应用,获取当前用户的信息,与H5系统的数据库的用户信息对比,如果存在该用户,则点击后直接进入H5系统的首 ...
- Java企业微信开发_03_通讯录同步
一.本节要点 1.获取通讯录密钥 获取方式: 登录企业微信—>管理工具—>通讯录同步助手—>开启“API接口同步” ; 开启后,即可看到通讯录密钥,也可设置通讯录API的权限:读取 ...
- Java钉钉开发_01_开发前的准备
源码已上传GitHub:传送门 一.准备事项 1.1 一个能在公网上访问的项目: 参见:Java微信开发_02_本地服务器映射外网 1.2 一个钉钉账号 去注册 1.3 创建一个应用 登录钉钉后台 ...
- Java钉钉开发_02_免登授权(身份验证)(附源码)
源码已上传GitHub: https://github.com/shirayner/DingTalk_Demo 一.本节要点 1.免登授权的流程 (1)签名校验 (2)获取code,并传到后台 (3) ...
- shell+钉钉机器人完成java程序中断后自启动和实时监控
java实时程序在运行过程中偶尔出现异常信息中断的情况,通过shell脚本即可完成自启动. 以下为监控一个实时的java程序的shell脚本. 通过每10秒检查一次java程序的进程,来判断程序是否处 ...
- Java钉钉开发_02_免登授权(身份验证)
源码已上传GitHub: https://github.com/shirayner/DingTalk_Demo 一.本节要点 1.免登授权的流程 (1)签名校验 (2)获取code,并传到后台 (3) ...
- Java企业微信开发_02_通讯录同步
一.本节要点 1.获取通讯录密钥 获取方式: 登录企业微信—>管理工具—>通讯录同步助手—>开启“API接口同步” ; 开启后,即可看到通讯录密钥,也可设置通讯录API的权限:读取 ...
- 开发笔记—钉钉服务商应用isv开发,从应用配置,到获取客户企业通讯录
以第三方企业微应用为例 在第三方企业微应用应用时,比较底层的需求,就是应用需要获取客户企业的通讯录,即部门/员工的数据.本人整理以下几个关键数据,供大家开发参考. 新建第三方微应用时,能拿到这些初始数 ...
随机推荐
- Java Swing 界面中文乱码问题解决(Idea环境)
编译(build)的时候和运行的时候使用同样的字符集就可以了.这里,我都设置为“UTF-8”.具体做当如下: File->Settings->Build...->Compiler,在 ...
- python3-可变长度参数函数(*args 和 **kwargs)
简述: *args: 可变长度元组参数 **kwargs: 可变长度字典参数 ------------------------------------------------------- ...
- prepareRefresh()方法源码探究
该方法目的是做刷新上下文前的准备工作: 首先清空bean扫描器map中的内容,然后调用父类的prepareRefresh方法: 父类的准备刷新方法,主要做了3个工作: 1.简单的标志赋值----> ...
- mybatis-plus的代码生成器
简介:构建自定义mybatis-plus模板,自动生成mybatis,entity,mapper,service,controller 项目源码:https://github.com/y369q369 ...
- Best Practices and Recommendations for RAC databases with SGA size over 100GB (文档 ID 1619155.1)
Best Practices and Recommendations for RAC databases with SGA size over 100GB (文档 ID 1619155.1) APPL ...
- 记一次解决cmd中执行java提示"找不到或无法加载主类"的问题
今天遇到一个问题:在cmd命令行中,用javac编译java文件可以成功,但是用java执行却提示“找不到或无法加载主类”.现将该问题的原因以及解决办法记录一下. 先理解一下系统变量path和clas ...
- python 常用技巧
一.字符串与数值的转换 Python中字符串转换为数值: str_num = '99' num = int(str_num) 整型数转换为字符串: num = 99 str_num = str(num ...
- android 手机权限管理——PermissionsDispatcher
Android6.0 之后某些权限需要动态申请,相比于之前版本复杂了许多.不过已经有大神给我们写好了框架(PermissionsDispatcher),我们用起来还是很方便. 1.添加引用 根据 gr ...
- python count函数
描述 Python count() 方法用于统计字符串里某个字符出现的次数.可选参数为在字符串搜索的开始与结束位置. 语法 count()方法语法: str.count(sub, start= 0,e ...
- 【HDFS API编程】图解客户端从HDFS读数据的流程