钉钉通讯录同步二开案例

栏目:云苍穹知识作者:金蝶来源:金蝶云社区发布:2024-09-23浏览:1

钉钉通讯录同步二开案例

一、背景

目前钉钉支持手机号或者邮箱同步钉钉通讯录,有些客户需要使用工号同步,或者客户使用了企业专属账号,苍穹当前版本产品接口不支持同步(v6.0.6版本以上支持),则需要现场二开解决。

二、方案

新增一个调度任务,在调度中调用钉钉的接口进行通讯录同步。

具体步骤

1. 获取token

根据配置好的应用信息获取token,方法如下:

请求方式:GET

请求地址:https://oapi.dingtalk.com/gettoken ?appkey=xxx&appsecret=xxx

请求参数:

appkey

String

dingeqqpkv3xxxx

应用的唯一标识key。

appsecret

String

GT-lsu-taDAsTsxxxx

应用的密钥。AppKey和AppSecret可在钉钉开发者后台 的应用详情页面获取。


2. 获取授权部门

在钉钉管理后台【权限管理】中需要配置授权部门,如果调用接口获取非授权部门的人员信息则会提示无权限,所以需要主动获取授权部门。

请求方式:GET

请求地址:https://oapi.dingtalk.com/auth/scopes ? access_token =xxxxx

请求参数:

access_token

String

6ed1bxxx

调用该接口的应用凭证。


3. 获取授权部门及其子部门

请求方式:GET

请求地址:

https://oapi.dingtalk.com/department/list? access_token =xxx& fetch_child =true&id=xxx

请求参数:

access_token

String

6ed1bxxx

调用服务端API的应用凭证。

lang

String

zh_CN

通讯录语言,默认zh_CN。

fetch_child

Boolean

true

是否递归部门的全部子部门。

id

String

1

父部门ID。

如果不传,默认部门为根部门,根部门ID为1。


4. 获取部门下的人员信息

请求方式:POST

请求地址:https://oapi.dingtalk.com/topapi/v2/user/list ? access_token =xxxx

请求参数:

access_token

String

be3Fxxxx

调用该接口的应用凭证。

Body参数:

dept_id

Number

10

部门ID,可调用获取部门列表 获取,如果是根部门,该参数传1。

cursor

Number

0

分页查询的游标,最开始传0,后续传返回参数中的next_cursor值。

size

Number

10

分页大小。

order_field

String

modify_desc

部门成员的排序规则,默认不传是按自定义排序(custom):

entry_asc:代表按照进入部门的时间升序

entry_desc:代表按照进入部门的时间降序

modify_asc:代表按照部门信息修改时间升序

modify_desc:代表按照部门信息修改时间降序

custom:代表用户定义(未定义时按照拼音)排序

contain_access_limit

Boolean

false

是否返回访问受限的员工:

true:返回

false:不返回

language

String

zh_CN

通讯录语言,取值。


5. 根据相关字段(如手机号或者邮箱)与苍穹(星瀚)人员信息进行匹配

6. 将钉钉人员信息保存在映射表中

参考代码:

package hnzb.sys.base.plugin.task;


import com.alibaba.fastjson.JSON;

import com.alibaba.fastjson.JSONArray;

import com.alibaba.fastjson.JSONObject;

import kd.bos.context.RequestContext;

import kd.bos.dataentity.utils.ObjectUtils;

import kd.bos.dd.service.DingDingServiceHelper;

import kd.bos.exception.KDException;

import kd.bos.lang.Lang;

import kd.bos.logging.Log;

import kd.bos.logging.LogFactory;

import kd.bos.org.utils.Consts;

import kd.bos.schedule.executor.AbstractTask;

import kd.bos.sec.user.task.SynUserTypeEnum;

import kd.bos.sec.user.utils.UserOperationUtils;

import kd.bos.util.HttpClientUtils;

import kd.bos.util.StringUtils;

import kd.sdk.plugin.Plugin;


import java.io.IOException;

import java.util.*;



/**

* @BelongsProject: hnzb-cosmic

* @BelongsPackage: hnzb.sys.base.plugin.task

* @Author:

* @CreateTime: 2023-12-21  17:37

* @Description: TODO 查询系统中指定日期修改的组织单元、人员、岗位,然后同步到主数据系统

* @Version: 1.0

* @PageNumber:

*/

public class SynDingUserToUserMappingTask extends AbstractTask {

   private static Log logger = LogFactory.getLog(SynDingUserToUserMappingTask.class);


   @Override

   public void execute(RequestContext requestContext, Map<String, Object> map) throws KDException {

       synDing();

   }


   /**

    * 钉钉openid同步

    */

   public static void synDing() {

       String accessToken = DingDingServiceHelper.getAccess_token();

       logger.info("authScope" + accessToken);

       if (kd.bos.util.StringUtils.isEmpty(accessToken)) {

           return;

       }

       Map<String, List<String>> authScope = getAuthScope(accessToken);

       logger.info("authScope" + authScope);


       //如果设置了权限范围 则只查询有权的组织及人员

       if (isAuthScope(authScope)) {

           List<String> authedUser = authScope.get("authed_user");

           logger.info("authedUser" + authedUser);

           int userSize = 0;

           int deptSize = 0;

           //授权人员

           if (null != authedUser && !authedUser.isEmpty()) {

               List<Map<String, String>> userList = getAuthScopeUsers(authedUser, accessToken);

               logger.info("userList" + userList);

               UserOperationUtils.saveIMMapping(userList, "mobile", Consts.PHONE, SynUserTypeEnum.DINGDING.getValue());

               userSize = authedUser.size();

           }

           List<String> authedDept = authScope.get("authed_dept");

           logger.info("authed_dept" + authedDept);


           int deptCount = 0;

           //授权部门

           if (null != authedDept && !authedDept.isEmpty()) {

               //获取子部门

               List<String> allDept = getChildrenList(accessToken,authedDept);

               authedDept.addAll(allDept);

               deptSize = authedDept.size();

               deptCount = saveUserMappingByDept(accessToken, authedDept, imTypeFieldName, sysFieldName);

               logger.info("deptCount" + deptCount);


           }

           return;

       }

       List<String> deptList = DingDingServiceHelper.getDeptList(accessToken);

       if (deptList == null || deptList.isEmpty()) {

           return;

       }

       saveUserMappingByDept(accessToken, deptList);


   }


    private static List<String> getChildrenList(String accessToken, List<String> authedDept) {

       List<String> allDept = new ArrayList<>(authedDept.size());

       for (String deptId : authedDept) {

           List<String> deptList = getDeptList(accessToken,deptId);

           allDept.addAll(deptList);

       }

       return allDept;

   }


   private static int saveUserMappingByDept(String accessToken, List<String> deptList) {

       int completedCount = 1;

       for (String deptId : deptList) {

           long offset = 0;

           long size = 100;

           while (true) {

               List<Map<String, String>> userList = getDeptUserList(accessToken,

                       Long.parseLong(deptId), offset, size, null);

               logger.error("saveUserMappingByDept**userList" + userList);


               if (userList == null || userList.isEmpty()) {

                   break;

               }

               boolean mobile = UserOperationUtils.saveIMMapping(userList, "mobile", Consts.PHONE, SynUserTypeEnum.DINGDING.getValue());

               logger.error("saveUserMappingByDept**mobile" + mobile);


               if (userList.size() < 100) {

                   break;

               }

               offset += 100;

           }

           completedCount++;

       }

       return completedCount;

   }


   private static boolean isAuthScope(Map<String, List<String>> authScope) {

       //授权范围为空

       if (authScope.isEmpty()) {

           logger.error("authScope is empty");

           return false;

       }

       //授权范围为全部员工

       List<String> authedDept = authScope.get("authed_dept");

       if (null != authedDept && !authedDept.isEmpty()) {

           String dept = authedDept.get(0);

           if ("1".equals(dept)) {

               logger.info("authed_dept : 1");

               return false;

           }

       }

       return true;

   }



   /**

    * 获取钉钉应用的授权范围

    *

    * @param token

    * @return

    */

   public static Map<String, List<String>> getAuthScope(String token) {


       String ddHost = "https://oapi.dingtalk.com";

       StringBuilder url = new StringBuilder(ddHost).append("/auth/scopes?access_token=");

       url.append(token);

       try {

           String data = HttpClientUtils.get(url.toString());

           HashMap<String, Object> map = JSON.parseObject(data, HashMap.class);

           //权限范围

           Map<String, Object> authOrgScopes = (Map<String, Object>) map.get("auth_org_scopes");

           //授权人员

           JSONArray authed_user = (JSONArray) authOrgScopes.get("authed_user");

           //授权部门

           JSONArray authed_dept = (JSONArray) authOrgScopes.get("authed_dept");

           HashMap<String, List<String>> dataMap = new HashMap<>(2);

           if (authed_user != null) {

               List<String> userList = authed_user.toJavaList(String.class);

               dataMap.put("authed_user", userList);

           }

           if (authed_dept != null) {

               List<String> deptList = authed_dept.toJavaList(String.class);

               dataMap.put("authed_dept", deptList);

           }

           return dataMap;


       } catch (Exception e) {

           logger.error(e);

       }

       return Collections.emptyMap();

   }


   /**

    * 根据授权范围获取人员信息

    *

    * @param authedUser  授权范围:授权人员

    * @param accessToken

    * @return

    */

   public static List<Map<String, String>> getAuthScopeUsers(List<String> authedUser, String accessToken) {

       List<Map<String, String>> userList = new ArrayList<>(16);

       //1.获取授权人员信息

       String ddHost = "https://oapi.dingtalk.com";

       StringBuilder url = new StringBuilder(ddHost).append("/topapi/v2/user/get?access_token=");

       url.append(accessToken);

       HashMap<String, Object> params = new HashMap<>(2);

       params.put("language", Lang.get().name());

       try {

           for (String userId : authedUser) {

               params.put("userid", userId);

               String data = HttpClientUtils.post(url.toString(), null, params);

               Map<String, Object> map = JSON.parseObject(data, Map.class);

               if ("0".equals(map.get("errcode").toString())) {

                   Map<String, String> result = (Map<String, String>) map.get("result");

                   //区号+手机号

                   String mobile = getMobileAndState(result);

                   result.put("mobile", mobile);

                   userList.add(result);

               } else {

                   logger.error(map.get("errmsg").toString());

               }


           }


           logger.info("222userList" + userList);


       } catch (IOException e) {

           logger.error(e);

       }

       return userList;

   }


   /**

    * 带有区号的话根据区号拼接手机号

    *

    * @param result

    * @return

    */

   public static String getMobileAndState(Map<String, String> result) {

       String state = getStateCode(result);

       String mobile = result.get("mobile");

       //86 默认不加

       if (org.apache.commons.lang3.StringUtils.isBlank(state) || "86".equals(state)) {

           return mobile;

       }

       return state + "-" + mobile;

   }


   private static String getStateCode(Map<String, String> result) {

       if (StringUtils.isNotEmpty(result.get("state_code"))) {

           return result.get("state_code");

       }

       if (StringUtils.isNotEmpty(result.get("stateCode"))) {

           return result.get("stateCode");

       }

       if (StringUtils.isNotEmpty(result.get("statecode"))) {

           return result.get("statecode");

       }

       return null;

   }



   public static List<String> getDeptList(String accesstoken, String deptId) {

       if (null == accesstoken || accesstoken.trim().isEmpty()) {

           logger.info("error ::: access_token is null !");

           return null;

       }


       StringBuilder getDeptList_url = new StringBuilder("https://oapi.dingtalk.com").append("/department/list?access_token=");

       getDeptList_url.append(accesstoken).append("&fetch_child=true");

       if (org.apache.commons.lang3.StringUtils.isNotBlank(deptId)) {

           getDeptList_url.append("&id=").append(deptId);

       }


       HashMap<String, Object> map = null;

       try {

           String data = HttpClientUtils.get(getDeptList_url.toString());

           map = JSON.parseObject(data, HashMap.class);

           logger.info("getUserId_url response: " + data);

       } catch (Exception e) {

           logger.error(e);

       }


       if (map == null)

           return null;

       else if ("0".equals(map.get("errcode").toString())) {

           String department = map.get("department").toString();

           List<Map<String, Object>> deptList = JSON.parseObject(department, List.class);

           List<String> deptIds = new ArrayList<>(deptList.size());

           for (Map<String, Object> dept : deptList) {

               deptIds.add(dept.get("id").toString());

           }

           return deptIds;

       } else {

           logger.error(map.get("errmsg").toString());

           return null;

       }

   }


   public static List<Map<String, String>> getDeptUserList(String access_token, long department_id, long offset,

                                                           long size, String order) {

       if (null == access_token || access_token.trim().isEmpty()) {

           logger.info("error ::: access_token is null !");

           return Collections.emptyList();

       }


       StringBuilder getDeptUserList_url = new StringBuilder("https://oapi.dingtalk.com").append("/topapi/v2/user/list?access_token=");

       getDeptUserList_url.append(access_token);


       Map<String, Object> body = new HashMap<>(4);

       body.put("dept_id", department_id);

       body.put("cursor", offset);

       body.put("size", size);


       HashMap<String, Object> map = null;

       try {

           String data = HttpClientUtils.post(getDeptUserList_url.toString(), null, body);

           map = JSON.parseObject(data, HashMap.class);

       } catch (Exception e) {

           logger.error(e);

       }


       if (map == null) {

           return Collections.emptyList();

       }

       if ("0".equals(map.get("errcode").toString())) {

           List<Map<String, String>> userList = new ArrayList<>();

           JSONObject result = (JSONObject) map.get("result");

           if (ObjectUtils.isEmpty(result)) {

               return Collections.emptyList();

           }

           JSONArray userListStr = (JSONArray) result.get("list");

           userList = changeStateMobile(userListStr);

           return userList;

       }

       logger.error(map.get("errmsg").toString());

       return Collections.emptyList();


   }



   private static List<Map<String, String>> changeStateMobile(JSONArray userList) {

       List<Map<String, String>> hashMaps = new ArrayList<>(userList.size());

       for (Object o : userList) {

           if (!ObjectUtils.isEmpty(o)) {

               JSONObject jsonObject = (JSONObject) o;

               Map hashMap = jsonObject.toJavaObject(Map.class);

               String mobileAndState = getMobileAndState(hashMap);

               hashMap.put("mobile", mobileAndState);

               hashMaps.add(hashMap);

           }

       }

       return hashMaps;

   }


}



三、常见问题:

1. 海外手机号携带区号怎么同步?

钉钉将手机号字段与区号字段分开传递,需要代码手动将区号与手机号拼接在一起。如图:

2. 6.0版本之前不支持部分员工授权同步,如何二开?

主动获取授权部门进行同步,详情见二开步骤2,步骤3。

3. 企业(专属)账号怎么同步?

企业专属账号需要使用钉钉最新的接口获取人员信息。

4. 不使用手机号或者邮箱同步,使用其他字段,如工号进行同步,如何设置?

重写UserOperationUtils.saveIMMapping方法,将此方法中的手机号字段映射改成自己需要的字段进行映射

钉钉通讯录同步二开案例

一、背景目前钉钉支持手机号或者邮箱同步钉钉通讯录,有些客户需要使用工号同步,或者客户使用了企业专属账号,苍穹当前版本产品接口不支持...
点击下载文档
确认删除?
回到顶部
客服QQ
  • 客服QQ点击这里给我发消息