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

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

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

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.通讯录回调:

  1. import java.io.BufferedReader;
  2. import java.io.InputStreamReader;
  3. import java.util.ArrayList;
  4. import java.util.List;
  5. import java.util.Map;
  6. import javax.servlet.ServletInputStream;
  7. import javax.servlet.annotation.WebServlet;
  8. import javax.servlet.http.HttpServlet;
  9. import javax.servlet.http.HttpServletRequest;
  10. import javax.servlet.http.HttpServletResponse;
  11. import com.taobao.api.ApiException;
  12. import com.alibaba.fastjson.JSONObject;
  13.  
  14. /**
  15. * <p>通讯录事件回调<p>
  16. * @version 1.0
  17. * @author li_hao
  18. * @date 2017年12月21日
  19. */
  20. @WebServlet("/callbackreceive")
  21. public class CallBackServlet extends HttpServlet {
  22.  
  23. private static final long serialVersionUID = -1785796919047156450L;
  24.  
  25. public CallBackServlet() {
  26. super();
  27. }
  28.  
  29. protected void doPost(HttpServletRequest request, HttpServletResponse response) {
  30. doGet(request, response);
  31. }
  32.  
  33. /*
  34. * 接收钉钉服务器的回调数据
  35. *
  36. */
  37. protected void doGet(HttpServletRequest request, HttpServletResponse response){
  38. try {
  39. /** url中的签名 **/
  40. String msgSignature = request.getParameter("signature");
  41. /** url中的时间戳 **/
  42. String timeStamp = request.getParameter("timestamp");
  43. /** url中的随机字符串 **/
  44. String nonce = request.getParameter("nonce");
  45. /** 取得JSON对象中的encrypt字段 **/
  46. String encrypt = "";
  47.  
  48. /** 获取post数据包数据中的加密数据 **/
  49. ServletInputStream sis = request.getInputStream();
  50. BufferedReader br = new BufferedReader(new InputStreamReader(sis));
  51. String line = null;
  52. StringBuilder sb = new StringBuilder();
  53. while ((line = br.readLine()) != null) {
  54. sb.append(line);
  55. }
  56. JSONObject jsonEncrypt = JSONObject.parseObject(sb.toString());
  57. encrypt = jsonEncrypt.getString("encrypt");
  58.  
  59. String decodeEncrypt = decodeEncrypt(msgSignature, timeStamp, nonce, encrypt); //密文解密
  60. JSONObject decodeEncryptJson = JSONObject.parseObject(decodeEncrypt);
  61.  
  62. String eventType = decodeEncryptJson.getString("EventType"); //回调类型
  63. String UserIds = decodeEncryptJson.getString("UserId"); //用户发生变更的userid列表
  64. String DeptIds = decodeEncryptJson.getString("DeptId"); //部门发生变更的deptId列表
  65. String res = "success"; //res是需要返回给钉钉服务器的字符串,一般为success;"check_create_suite_url"和"check_update_suite_url"事件为random字段;(具体请查看文档或者对应eventType的处理步骤)
  66.  
  67. JSONObject jsonObjectData = new JSONObject();
  68. //根据不同的回调类型,进行相应的操作
  69. switch (eventType) {
  70. case AddressListRegister.USER_ADD_ORG :
  71. //通讯录用户增加
  72.  
  73. break;
  74. case AddressListRegister.USER_MODIFY_ORG :
  75. //通讯录用户更改
  76.  
  77. break;
  78. case AddressListRegister.USER_LEAVE_ORG :
  79. //通讯录用户离职
  80.  
  81. break;
  82. case AddressListRegister.ORG_ADMIN_ADD :
  83. //通讯录用户被设为管理员
  84.  
  85. break;
  86. case AddressListRegister.ORG_ADMIN_REMOVE :
  87. //通讯录用户被取消设置管理员
  88.  
  89. break;
  90. case AddressListRegister.ORG_DEPT_CREATE :
  91. //通讯录企业部门创建
  92.  
  93. break;
  94. case AddressListRegister.ORG_DEPT_MODIFY :
  95. //通讯录企业部门修改
  96.  
  97. break;
  98. case AddressListRegister.ORG_DEPT_REMOVE :
  99. //通讯录企业部门删除
  100.  
  101. break;
  102. case AddressListRegister.ORG_REMOVE :
  103. //企业被解散
  104.  
  105. break;
  106. case AddressListRegister.ORG_CHANGE :
  107. //企业信息发生变更
  108.  
  109. break;
  110. case AddressListRegister.LABEL_USER_CHANGE :
  111. //员工角色信息发生变更
  112.  
  113. break;
  114. case AddressListRegister.LABEL_CONF_ADD :
  115. //增加角色或者角色组
  116.  
  117. break;
  118. case AddressListRegister.LABEL_CONF_DEL :
  119. //删除角色或者角色组
  120.  
  121. break;
  122. case AddressListRegister.LABEL_CONF_MODIFY :
  123. //修改角色或者角色组
  124.  
  125. break;
  126. case AddressListRegister.CHECK_URL :
  127. //测试回调接口事件类型
  128.  
  129. System.out.println("测试回调接口!");
  130. break;
  131. default: // do something
  132. break;
  133. }
  134. response.getWriter().append(codeEncrypt(res, timeStamp, nonce).toString()); //返回加密后的数据
  135. } catch (Exception e) {
  136. e.printStackTrace();
  137. }
  138.  
  139. }
  140.  
  141. /**
  142. * 创建加密/解密 类
  143. * @return
  144. */
  145. public DingTalkEncryptor createDingTalkEncryptor(){
  146. DingTalkEncryptor dingTalkEncryptor = null; //加密方法类
  147. try {
  148. dingTalkEncryptor = new DingTalkEncryptor(AddressListRegister.TOKEN, AddressListRegister.AES_KEY,AddressListRegister.CORPID); //创建加解密类
  149. } catch (DingTalkEncryptException e) {
  150. e.printStackTrace();
  151. }
  152. return dingTalkEncryptor;
  153. }
  154.  
  155. /**
  156. * encrypt解密
  157. * @param msgSignature
  158. * @param timeStamp
  159. * @param nonce
  160. * @param encrypt 密文
  161. * @return decodeEncrypt 解密后的明文
  162. */
  163. public String decodeEncrypt(String msgSignature,String timeStamp,String nonce,String encrypt){
  164. String decodeEncrypt = null;
  165. try {
  166. decodeEncrypt = createDingTalkEncryptor().getDecryptMsg(msgSignature, timeStamp, nonce, encrypt); //encrypt解密
  167. } catch (DingTalkEncryptException e) {
  168. e.printStackTrace();
  169. }
  170. return decodeEncrypt;
  171. }
  172.  
  173. /**
  174. * 对返回信息进行加密
  175. * @param res
  176. * @param timeStamp
  177. * @param nonce
  178. * @return
  179. */
  180. public JSONObject codeEncrypt(String res,String timeStamp,String nonce){
  181. long timeStampLong = Long.parseLong(timeStamp);
  182. Map<String, String> jsonMap = null;
  183. try {
  184. jsonMap = createDingTalkEncryptor().getEncryptedMap(res, timeStampLong, nonce); //jsonMap是需要返回给钉钉服务器的加密数据包
  185. } catch (DingTalkEncryptException e) {
  186. System.out.println(e.getMessage());
  187. e.printStackTrace();
  188. }
  189. JSONObject json = new JSONObject();
  190. json.putAll(jsonMap);
  191. return json;
  192. }
  193.  
  194. //测试方法
  195. public static void main(String[] args) throws ApiException {
  196. String accesstoken = "xxxxxxxxxxxxxxxxxxxxxxxxx";
  197. String token = AddressListRegister.TOKEN;
  198. String aesKey = AddressListRegister.AES_KEY;
  199. String callBackUrl = "http://xxxx/callbackreceive";
  200.  
  201. List<String> listStr = new ArrayList<String>();
  202. listStr.add("user_add_org");
  203. listStr.add("user_modify_org");
  204. listStr.add("user_leave_org");
  205.  
  206. listStr.add("org_dept_create");
  207. listStr.add("org_dept_modify");
  208. listStr.add("org_dept_remove");
  209.  
  210. JSONObject registerCallBack = DingTalkUtil.updateCallBack(accesstoken, listStr, token, aesKey, callBackUrl);
  211. System.out.println("注册事件返回:"+registerCallBack);
  212.  
  213. JSONObject callBack = DingTalkUtil.getCallBack(accesstoken);
  214. System.out.println("查询注册事件:"+callBack);
  215.  
  216. }
  217.  
  218. }

几个参数和通讯录注册 需要监听的事件类型:

  1. /**
  2. * <p>几个参数和通讯录注册 需要监听的事件类型<p>
  3. * @version 1.0
  4. * @author li_hao
  5. * @date 2017年12月15日
  6. */
  7. public class AddressListRegister{
  8.  
  9. /**企业的corpid */
  10. public static final String CORPID = "xxxxxxxxxx";
  11. /**钉钉开放平台上,开发者设置的token */
  12. public static final String TOKEN = "token";
  13. /**数据加密密钥。用于回调数据的加密,长度固定为43个字符,从a-z, A-Z, 0-9共62个字符中选取,您可以随机生成,ISV(服务提供商)推荐使用注册套件时填写的EncodingAESKey */
  14. public static final String AES_KEY = "xxxxx7p5qnb6zs3xxxxxlkfmxqfkv23d40yd0xxxxxx";
  15.  
  16. /**通讯录用户增加 */
  17. public static final String USER_ADD_ORG = "user_add_org";
  18. /**通讯录用户更改*/
  19. public static final String USER_MODIFY_ORG = "user_modify_org";
  20. /** 通讯录用户离职 */
  21. public static final String USER_LEAVE_ORG = "user_leave_org";
  22. /** 通讯录用户被设为管理员 */
  23. public static final String ORG_ADMIN_ADD = "org_admin_add";
  24. /** 通讯录用户被取消设置管理员 */
  25. public static final String ORG_ADMIN_REMOVE = "org_admin_remove";
  26. /**通讯录企业部门创建*/
  27. public static final String ORG_DEPT_CREATE = "org_dept_create";
  28. /** 通讯录企业部门修改 */
  29. public static final String ORG_DEPT_MODIFY = "org_dept_modify";
  30. /**通讯录企业部门删除*/
  31. public static final String ORG_DEPT_REMOVE = "org_dept_remove";
  32. /**企业被解散*/
  33. public static final String ORG_REMOVE = "org_remove";
  34. /**企业信息发生变更*/
  35. public static final String ORG_CHANGE = "org_change";
  36. /**员工角色信息发生变更*/
  37. public static final String LABEL_USER_CHANGE = "label_user_change";
  38. /**增加角色或者角色组*/
  39. public static final String LABEL_CONF_ADD = "label_conf_add";
  40. /**删除角色或者角色组*/
  41. public static final String LABEL_CONF_DEL = "label_conf_del";
  42. /**修改角色或者角色组*/
  43. public static final String LABEL_CONF_MODIFY = "label_conf_modify";
  44. /**测试回调接口事件类型*/
  45. public static final String CHECK_URL = "check_url";
  46. }

加解密方法:

  1. import java.io.ByteArrayOutputStream;
  2. import java.nio.charset.Charset;
  3. import java.security.MessageDigest;
  4. import java.util.*;
  5.  
  6. import javax.crypto.Cipher;
  7. import javax.crypto.spec.IvParameterSpec;
  8. import javax.crypto.spec.SecretKeySpec;
  9.  
  10. import org.apache.commons.codec.binary.Base64;
  11.  
  12. /**
  13. * 加解密方法
  14. * 在ORACLE官方网站下载JCE无限制权限策略文件
  15. * JDK6的下载地址:http://www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html
  16. * JDK7的下载地址: http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html
  17. */
  18. public class DingTalkEncryptor {
  19.  
  20. private static final Charset CHARSET = Charset.forName("utf-8");
  21. private static final Base64 base64 = new Base64();
  22. private byte[] aesKey;
  23. private String token;
  24. private String corpId;
  25. /**ask getPaddingBytes key固定长度**/
  26. private static final Integer AES_ENCODE_KEY_LENGTH = 43;
  27. /**加密随机字符串字节长度**/
  28. private static final Integer RANDOM_LENGTH = 16;
  29.  
  30. /**
  31. * 构造函数
  32. * @param token 钉钉开放平台上,开发者设置的token
  33. * @param encodingAesKey 钉钉开放台上,开发者设置的EncodingAESKey
  34. * @param corpId ISV进行配置的时候应该传对应套件的SUITE_KEY(第一次创建时传的是默认的CREATE_SUITE_KEY),普通企业是Corpid
  35. * @throws DingTalkEncryptException 执行失败,请查看该异常的错误码和具体的错误信息
  36. */
  37. public DingTalkEncryptor(String token, String encodingAesKey, String corpId) throws DingTalkEncryptException{
  38. if (null==encodingAesKey || encodingAesKey.length() != AES_ENCODE_KEY_LENGTH) {
  39. throw new DingTalkEncryptException(DingTalkEncryptException.AES_KEY_ILLEGAL);
  40. }
  41. this.token = token;
  42. this.corpId = corpId;
  43. aesKey = Base64.decodeBase64(encodingAesKey + "=");
  44. }
  45.  
  46. /**
  47. * 将和钉钉开放平台同步的消息体加密,返回加密Map
  48. * @param plaintext 传递的消息体明文
  49. * @param timeStamp 时间戳
  50. * @param nonce 随机字符串
  51. * @return
  52. * @throws DingTalkEncryptException
  53. */
  54. public Map<String,String> getEncryptedMap(String plaintext, Long timeStamp, String nonce) throws DingTalkEncryptException {
  55. if(null==plaintext){
  56. throw new DingTalkEncryptException(DingTalkEncryptException.ENCRYPTION_PLAINTEXT_ILLEGAL);
  57. }
  58. if(null==timeStamp){
  59. throw new DingTalkEncryptException(DingTalkEncryptException.ENCRYPTION_TIMESTAMP_ILLEGAL);
  60. }
  61. if(null==nonce){
  62. throw new DingTalkEncryptException(DingTalkEncryptException.ENCRYPTION_NONCE_ILLEGAL);
  63. }
  64. // 加密
  65. String encrypt = encrypt(Utils.getRandomStr(RANDOM_LENGTH), plaintext);
  66. String signature = getSignature(token, String.valueOf(timeStamp), nonce, encrypt);
  67. Map<String,String> resultMap = new HashMap<String, String>();
  68. resultMap.put("msg_signature", signature);
  69. resultMap.put("encrypt", encrypt);
  70. resultMap.put("timeStamp", String.valueOf(timeStamp));
  71. resultMap.put("nonce", nonce);
  72. return resultMap;
  73. }
  74.  
  75. /**
  76. * 密文解密
  77. * @param msgSignature 签名串
  78. * @param timeStamp 时间戳
  79. * @param nonce 随机串
  80. * @param encryptMsg 密文
  81. * @return 解密后的原文
  82. * @throws DingTalkEncryptException
  83. */
  84. public String getDecryptMsg(String msgSignature, String timeStamp, String nonce, String encryptMsg)throws DingTalkEncryptException {
  85. //校验签名
  86. String signature = getSignature(token, timeStamp, nonce, encryptMsg);
  87. if (!signature.equals(msgSignature)) {
  88. throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_SIGNATURE_ERROR);
  89. }
  90. // 解密
  91. String result = decrypt(encryptMsg);
  92. return result;
  93. }
  94.  
  95. /*
  96. * 对明文加密.
  97. * @param text 需要加密的明文
  98. * @return 加密后base64编码的字符串
  99. */
  100. private String encrypt(String random, String plaintext) throws DingTalkEncryptException {
  101. try {
  102. byte[] randomBytes = random.getBytes(CHARSET);
  103. byte[] plainTextBytes = plaintext.getBytes(CHARSET);
  104. byte[] lengthByte = Utils.int2Bytes(plainTextBytes.length);
  105. byte[] corpidBytes = corpId.getBytes(CHARSET);
  106. ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
  107. byteStream.write(randomBytes);
  108. byteStream.write(lengthByte);
  109. byteStream.write(plainTextBytes);
  110. byteStream.write(corpidBytes);
  111. byte[] padBytes = PKCS7Padding.getPaddingBytes(byteStream.size());
  112. byteStream.write(padBytes);
  113. byte[] unencrypted = byteStream.toByteArray();
  114. byteStream.close();
  115. Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
  116. SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
  117. IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16);
  118. cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
  119. byte[] encrypted = cipher.doFinal(unencrypted);
  120. String result = base64.encodeToString(encrypted);
  121. return result;
  122. } catch (Exception e) {
  123. throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_ENCRYPT_TEXT_ERROR);
  124. }
  125. }
  126.  
  127. /*
  128. * 对密文进行解密.
  129. * @param text 需要解密的密文
  130. * @return 解密得到的明文
  131. */
  132. private String decrypt(String text) throws DingTalkEncryptException {
  133. byte[] originalArr;
  134. try {
  135. // 设置解密模式为AES的CBC模式
  136. Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
  137. SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
  138. IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));
  139. cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);
  140. // 使用BASE64对密文进行解码
  141. byte[] encrypted = Base64.decodeBase64(text);
  142. // 解密
  143. originalArr = cipher.doFinal(encrypted);
  144. } catch (Exception e) {
  145. throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_DECRYPT_TEXT_ERROR);
  146. }
  147.  
  148. String plainText;
  149. String fromCorpid;
  150. try {
  151. // 去除补位字符
  152. byte[] bytes = PKCS7Padding.removePaddingBytes(originalArr);
  153. // 分离16位随机字符串,网络字节序和corpId
  154. byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20);
  155. int plainTextLegth = Utils.bytes2int(networkOrder);
  156. plainText = new String(Arrays.copyOfRange(bytes, 20, 20 + plainTextLegth), CHARSET);
  157. fromCorpid = new String(Arrays.copyOfRange(bytes, 20 + plainTextLegth, bytes.length), CHARSET);
  158. } catch (Exception e) {
  159. throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_DECRYPT_TEXT_LENGTH_ERROR);
  160. }
  161.  
  162. // corpid不相同的情况
  163. if (!fromCorpid.equals(corpId)) {
  164. throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_DECRYPT_TEXT_CORPID_ERROR);
  165. }
  166. return plainText;
  167. }
  168.  
  169. /**
  170. * 数字签名
  171. * @param token isv token
  172. * @param timestamp 时间戳
  173. * @param nonce 随机串
  174. * @param encrypt 加密文本
  175. * @return
  176. * @throws DingTalkEncryptException
  177. */
  178. public String getSignature(String token, String timestamp, String nonce, String encrypt) throws DingTalkEncryptException {
  179. try {
  180. String[] array = new String[] { token, timestamp, nonce, encrypt };
  181. Arrays.sort(array);
  182. StringBuffer sb = new StringBuffer();
  183. for (int i = 0; i < 4; i++) {
  184. sb.append(array[i]);
  185. }
  186. String str = sb.toString();
  187. MessageDigest md = MessageDigest.getInstance("SHA-1");
  188. md.update(str.getBytes());
  189. byte[] digest = md.digest();
  190.  
  191. StringBuffer hexstr = new StringBuffer();
  192. String shaHex = "";
  193. for (int i = 0; i < digest.length; i++) {
  194. shaHex = Integer.toHexString(digest[i] & 0xFF);
  195. if (shaHex.length() < 2) {
  196. hexstr.append(0);
  197. }
  198. hexstr.append(shaHex);
  199. }
  200. return hexstr.toString();
  201. } catch (Exception e) {
  202. throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_SIGNATURE_ERROR);
  203. }
  204. }
  205.  
  206. }

加解密异常类:

  1. import java.util.HashMap;
  2. import java.util.Map;
  3.  
  4. /**
  5. * 加解密异常类
  6. */
  7. public class DingTalkEncryptException extends Exception {
  8. /**成功**/
  9. public static final int SUCCESS = 0;
  10. /**加密明文文本非法**/
  11. public final static int ENCRYPTION_PLAINTEXT_ILLEGAL = 900001;
  12. /**加密时间戳参数非法**/
  13. public final static int ENCRYPTION_TIMESTAMP_ILLEGAL = 900002;
  14. /**加密随机字符串参数非法**/
  15. public final static int ENCRYPTION_NONCE_ILLEGAL = 900003;
  16. /**不合法的aeskey**/
  17. public final static int AES_KEY_ILLEGAL = 900004;
  18. /**签名不匹配**/
  19. public final static int SIGNATURE_NOT_MATCH = 900005;
  20. /**计算签名错误**/
  21. public final static int COMPUTE_SIGNATURE_ERROR = 900006;
  22. /**计算加密文字错误**/
  23. public final static int COMPUTE_ENCRYPT_TEXT_ERROR = 900007;
  24. /**计算解密文字错误**/
  25. public final static int COMPUTE_DECRYPT_TEXT_ERROR = 900008;
  26. /**计算解密文字长度不匹配**/
  27. public final static int COMPUTE_DECRYPT_TEXT_LENGTH_ERROR = 900009;
  28. /**计算解密文字suiteKey(ISV)或者corpid(普通企业)不匹配**/
  29. public final static int COMPUTE_DECRYPT_TEXT_CORPID_ERROR = 900010;
  30.  
  31. private static Map<Integer,String> msgMap = new HashMap<Integer,String>();
  32. static{
  33. msgMap.put(SUCCESS,"成功");
  34. msgMap.put(ENCRYPTION_PLAINTEXT_ILLEGAL,"加密明文文本非法");
  35. msgMap.put(ENCRYPTION_TIMESTAMP_ILLEGAL,"加密时间戳参数非法");
  36. msgMap.put(ENCRYPTION_NONCE_ILLEGAL,"加密随机字符串参数非法");
  37. msgMap.put(SIGNATURE_NOT_MATCH,"签名不匹配");
  38. msgMap.put(COMPUTE_SIGNATURE_ERROR,"签名计算失败");
  39. msgMap.put(AES_KEY_ILLEGAL,"不合法的aes key");
  40. msgMap.put(COMPUTE_ENCRYPT_TEXT_ERROR,"计算加密文字错误");
  41. msgMap.put(COMPUTE_DECRYPT_TEXT_ERROR,"计算解密文字错误");
  42. msgMap.put(COMPUTE_DECRYPT_TEXT_LENGTH_ERROR,"计算解密文字长度不匹配");
  43. msgMap.put(COMPUTE_DECRYPT_TEXT_CORPID_ERROR,"计算解密文字suiteKey(ISV)或者corpid(普通企业)不匹配");
  44. }
  45.  
  46. public Integer code;
  47. public DingTalkEncryptException(Integer exceptionCode){
  48. super(msgMap.get(exceptionCode));
  49. this.code = exceptionCode;
  50. }
  51. }

PKCS7算法的加密填充:

  1. import java.nio.charset.Charset;
  2. import java.util.Arrays;
  3.  
  4. /*
  5. * PKCS7算法的加密填充
  6. */
  7.  
  8. public class PKCS7Padding {
  9. private final static Charset CHARSET = Charset.forName("utf-8");
  10. private final static int BLOCK_SIZE = 32;
  11.  
  12. /**
  13. * 填充mode字节
  14. * @param count
  15. * @return
  16. */
  17. public static byte[] getPaddingBytes(int count) {
  18. int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE);
  19. if (amountToPad == 0) {
  20. amountToPad = BLOCK_SIZE;
  21. }
  22. char padChr = chr(amountToPad);
  23. String tmp = new String();
  24. for (int index = 0; index < amountToPad; index++) {
  25. tmp += padChr;
  26. }
  27. return tmp.getBytes(CHARSET);
  28. }
  29.  
  30. /**
  31. * 移除mode填充字节
  32. * @param decrypted
  33. * @return
  34. */
  35. public static byte[] removePaddingBytes(byte[] decrypted) {
  36. int pad = (int) decrypted[decrypted.length - 1];
  37. if (pad < 1 || pad > BLOCK_SIZE) {
  38. pad = 0;
  39. }
  40. return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad);
  41. }
  42.  
  43. private static char chr(int a) {
  44. byte target = (byte) (a & 0xFF);
  45. return (char) target;
  46. }
  47.  
  48. }

加解密工具类:

  1. import java.util.Random;
  2.  
  3. /**
  4. * 加解密工具类
  5. */
  6. public class Utils {
  7. /**
  8. *
  9. * @return
  10. */
  11. public static String getRandomStr(int count) {
  12. String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  13. Random random = new Random();
  14. StringBuffer sb = new StringBuffer();
  15. for (int i = 0; i < count; i++) {
  16. int number = random.nextInt(base.length());
  17. sb.append(base.charAt(number));
  18. }
  19. return sb.toString();
  20. }
  21.  
  22. /*
  23. * int转byte数组,高位在前
  24. */
  25. public static byte[] int2Bytes(int count) {
  26. byte[] byteArr = new byte[4];
  27. byteArr[3] = (byte) (count & 0xFF);
  28. byteArr[2] = (byte) (count >> 8 & 0xFF);
  29. byteArr[1] = (byte) (count >> 16 & 0xFF);
  30. byteArr[0] = (byte) (count >> 24 & 0xFF);
  31. return byteArr;
  32. }
  33.  
  34. /**
  35. * 高位在前bytes数组转int
  36. * @param byteArr
  37. * @return
  38. */
  39. public static int bytes2int(byte[] byteArr) {
  40. int count = 0;
  41. for (int i = 0; i < 4; i++) {
  42. count <<= 8;
  43. count |= byteArr[i] & 0xff;
  44. }
  45. return count;
  46. }
  47. }

接口方法:

  1. /**
  2. * 通讯录:注册事件回调接口
  3. * @param accesstoken 企业的accesstoken
  4. * @param callBackTag 需要监听的事件类型,共有20种(Array[String])
  5. * @param token 加解密需要用到的token,ISV(服务提供商)推荐使用注册套件时填写的token,普通企业可以随机填写
  6. * @param aesKey 数据加密密钥。用于回调数据的加密,长度固定为43个字符,从a-z, A-Z, 0-9共62个字符中选取,您可以随机生成,ISV(服务提供商)推荐使用注册套件时填写的EncodingAESKey
  7. * @param callBackUrl 接收事件回调的url
  8. * @return
  9. * @throws ApiException
  10. */
  11. public static JSONObject registerCallBack(String accesstoken,List<String> callBackTag,String token,String aesKey,String callBackUrl) throws ApiException{
  12. String url = CommomUrl.REGISTER_CALL_BACK.replace("ACCESS_TOKEN", accesstoken);
  13. JSONObject jsonReq = new JSONObject();
  14. jsonReq.put("call_back_tag", callBackTag);
  15. jsonReq.put("token", token);
  16. jsonReq.put("aes_key", aesKey);
  17. jsonReq.put("url", callBackUrl);
  18.  
  19. System.out.println(jsonReq.toString());
  20. JSONObject jsonObject = doPostStr(url, jsonReq.toString());
  21. return jsonObject;
  22. }
  23.  
  24. /**
  25. * 通讯录:查询事件回调接口
  26. * @param accesstoken 企业的accesstoken
  27. * @return
  28. */
  29. public static JSONObject getCallBack(String accesstoken){
  30. String url = CommomUrl.GET_CALL_BACK.replace("ACCESS_TOKEN", accesstoken);
  31. JSONObject jsonObject = doGetStr(url);
  32. return jsonObject;
  33. }
  34.  
  35. /**
  36. * 通讯录:更新事件回调接口
  37. * @param accesstoken 企业的accesstoken
  38. * @param callBackTag 需要监听的事件类型,共有20种(Array[String])
  39. * @param token 加解密需要用到的token,ISV(服务提供商)推荐使用注册套件时填写的token,普通企业可以随机填写
  40. * @param aesKey 数据加密密钥。用于回调数据的加密,长度固定为43个字符,从a-z, A-Z, 0-9共62个字符中选取,您可以随机生成,ISV(服务提供商)推荐使用注册套件时填写的EncodingAESKey
  41. * @param callBackUrl 接收事件回调的url
  42. * @return
  43. * @throws ApiException
  44. */
  45. public static JSONObject updateCallBack(String accesstoken,List<String> callBackTag,String token,String aesKey,String callBackUrl) throws ApiException{
  46. String url = CommomUrl.UPDATE_CALL_BACK.replace("ACCESS_TOKEN", accesstoken);
  47. JSONObject jsonReq = new JSONObject();
  48. jsonReq.put("call_back_tag", callBackTag);
  49. jsonReq.put("token", token);
  50. jsonReq.put("aes_key", aesKey);
  51. jsonReq.put("url", callBackUrl);
  52.  
  53. JSONObject jsonObject = doPostStr(url, jsonReq.toString());
  54. return jsonObject;
  55. }
  56.  
  57. /**
  58. * 通讯录:删除事件回调接口
  59. * @param accesstoken 企业的accesstoken
  60. * @return
  61. */
  62. public static JSONObject deleteCallBack(String accesstoken){
  63. String url = CommomUrl.DELETE_CALL_BACK.replace("ACCESS_TOKEN", accesstoken);
  64. JSONObject jsonObject = doGetStr(url);
  65. return jsonObject;
  66. }
  67.  
  68. /**
  69. * 通讯录:获取回调失败的结果
  70. * @param accesstoken 企业的accesstoken
  71. * @return
  72. */
  73. public static JSONObject getCallBackFailedResult(String accesstoken){
  74. String url = CommomUrl.GET_CALL_BACK_FAILED_RESULT.replace("ACCESS_TOKEN", accesstoken);
  75. JSONObject jsonObject = doGetStr(url);
  76. return jsonObject;
  77. }

CommonUrl:

  1. import org.slf4j.Logger;
  2. import org.slf4j.LoggerFactory;
  3.  
  4. /**
  5. * @version : 1.0
  6. * @Author : li_hao
  7. * @Description : 钉钉接口地址类
  8. * @Date : 2017-06-10 17:47
  9. **/
  10. public class CommomUrl {
  11. private static Logger log = LoggerFactory.getLogger(CommomUrl.class);
  12.  
  13. /** 注册事件回调接口(请求方式:post) */
  14. public static final String REGISTER_CALL_BACK = "https://oapi.dingtalk.com/call_back/register_call_back?access_token=ACCESS_TOKEN";
  15.  
  16. /** 查询事件回调接口(请求方式:get) */
  17. public static final String GET_CALL_BACK = "https://oapi.dingtalk.com/call_back/get_call_back?access_token=ACCESS_TOKEN";
  18.  
  19. /** 更新事件回调接口(请求方式:post) */
  20. public static final String UPDATE_CALL_BACK = "https://oapi.dingtalk.com/call_back/update_call_back?access_token=ACCESS_TOKEN";
  21.  
  22. /** 删除事件回调接口(请求方式:get) */
  23. public static final String DELETE_CALL_BACK = "https://oapi.dingtalk.com/call_back/delete_call_back?access_token=ACCESS_TOKEN";
  24.  
  25. /** 获取回调失败的结果 (请求方式:get)*/
  26. public static final String GET_CALL_BACK_FAILED_RESULT = "https://oapi.dingtalk.com/call_back/get_call_back_failed_result?access_token=ACCESS_TOKEN";
  27.  
  28. }

get请求、post请求:

  1. /**
  2. * get请求
  3. * @param url 为接口地址参数
  4. * @return
  5. */
  6. public static JSONObject doGetStr(String url){
  7. CloseableHttpClient httpClient = HttpClients.createDefault();
  8. HttpGet httpGet = new HttpGet(url);
  9. CloseableHttpResponse response = null;
  10. JSONObject jsonObject = null;//接收结果
  11. try {
  12. response = httpClient.execute(httpGet);
  13. HttpEntity entity = response.getEntity();//从消息体里获取结果
  14. if(entity!=null){
  15. String result = EntityUtils.toString(entity,"UTF-8");
  16. jsonObject = JSONObject.parseObject(result);
  17. }
  18. EntityUtils.consume(entity);
  19. } catch (ClientProtocolException e) {
  20. e.printStackTrace();
  21. } catch (IOException e) {
  22. e.printStackTrace();
  23. } finally {
  24. try {
  25. if(response != null){
  26. response.close();
  27. }
  28. } catch (IOException e) {
  29. e.printStackTrace();
  30. }
  31. }
  32. return jsonObject;
  33. }
  34.  
  35. /**
  36. * post请求
  37. * @param url 为接口地址参数
  38. * @param outStr
  39. * @return
  40. */
  41. public static JSONObject doPostStr(String url,String outStr){
  42. CloseableHttpClient httpClient = HttpClients.createDefault();
  43. //DefaultHttpClient httpClient = new DefaultHttpClient();
  44. HttpPost httpPost = new HttpPost(url);
  45. JSONObject jsonObject = null;
  46. try {
  47. httpPost.setEntity(new StringEntity(outStr, "UTF-8"));
  48. HttpResponse response = httpClient.execute(httpPost);
  49. String result = EntityUtils.toString(response.getEntity(), "UTF-8");
  50. jsonObject = JSONObject.parseObject(result);
  51. } catch (Exception e) {
  52. e.printStackTrace();
  53. }
  54. return jsonObject;
  55. }

以上就是通讯录同步的所有代码了,注:钉钉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. 利用grep参数查看某关键词前后几行内容

    查看文件中含有“哈哈哈”关键字所在行后5行内容 cat xxxxxx | grep -A 5 哈哈哈 查看文件中含有“哈哈哈”关键字所在行前5行内容 cat xxxxxx | grep -B 5 哈哈 ...

  2. Laya for H5 之Bug追踪

    Laya For H5之Bug追踪 H5游戏一旦上线后,如何跟踪用户的崩溃日志呢?现在有很多第三方的工具,比如fundebug,其sdk接入简单,只需寥寥几行代码就可以追踪h5游戏的崩溃日志,bug日 ...

  3. JSONObject 转List 强制类型转换错误

    JSONArray arr=(JSONArray)map.getOrDefault("data","");List<DHD> data=JSONOb ...

  4. hex转mif文件 verilog

    用FPGA来跑ARM 核的时候,刚开始将Keil编译产生的hex文件拿来仿真和下到板子上的时候,发现程序运行不正确.细细观察仿真波形发现,在Altera的ROM IP中直接调用Keil产生的hex文件 ...

  5. To be taught if i am fortunate

    此博客算是我自娱自乐的海洋球池吧. 由于我十分的菜并且文笔拙劣,所以您可能并不能在这找到什么有用的信息或者好玩的东西(或者exciting的内容). 如果您能指出我的一些错误,我将十分感激.

  6. Eclipse下支持编写HTML/JS/CSS/JSP页面的自动提示

    地址:https://blog.csdn.net/AinUser/article/details/64904339 使用eclipse自带的插件,无需另外安装插件,具体步骤如下 1.打开eclipse ...

  7. 【人工智能】从零开始学好人工智能,AI知识体系和框架

    写在前面: 最近公司的业务方向开始向AI方向改变(人工智能+文娱),但是现阶段AI方面的知识还没有储备,所以作为测试,也开始学习这方面的知识,不掉队. 知识储备: 1.阶段一-高等数学       高 ...

  8. bootstrap的使用集锦

    在使用div样式的时候可以根据页面布局来调整大小 <div class="col-md-8 col-md-offset-3"># col-md-8 div所占的空间大小 ...

  9. kettle中文乱码问题

    db连接->选项 配置参数 characterEncoding,设置值为gbk/utf8.

  10. 移动端常用UI框架

    作为一个前端人员来说,总结几款相对来说不错的用于移动端开发的UI框架是非常必要的,以下几种移动端UI框架就能基本满足工作中开发需要,根据项目需求,选用合适的框架搭建项目,更能容易提高开发效率. 一.M ...