钉钉平台接入文档

来源:互联网 发布:app打印软件下载 编辑:程序博客网 时间:2024/04/28 20:56

钉钉平台接入文档

Auth-date: [add by Easzz 2017-07-18]

1.接入说明

由于业务发展,需要接入钉钉平台,特整理了一份简明的接入文档,此文档旨在帮助用户快速熟悉钉钉平台,调用钉钉相关接口,以实现具体的业务逻辑。

详细的官方文档地址:https://open-doc.dingtalk.com/

2.入门

2.1相关术语解释

  1. 企业内部应用:企业自建应用,只能用于本企业内部
  2. 第三方应用(ISV):第三方企业建立的应用,本企业使用,需要授权等过程。
  3. 免登:即单点登录,在企业H5微应用中,通过身份认证接口获取当前钉钉用户的个人信息,然后和数据库中的相关信息比对,正确即可自动登录,无需输入用户名密码。
  4. AccessToken:访问钉钉接口的唯一票据,调用接口需要附带AccessToken才能成功。通过CropID和Corp_secret换取。有效期7200秒,需全局缓存。
  5. CorpID:企业的ID (在开发者后台中得到)
  6. corp_secret:企业的秘钥(在开发者后台中得到)
  7. suite_key:
  8. suite_secret:
  9. suite_ticket:
  10. suite_access_token:
  11. tmp_auth_code:
  12. permanent_auth_code:
  13. jsapi_ticket:是开发者调用钉钉JS接口的临时授权码,其作用主要用于生成签名,有效期7200秒,建议全局缓存。

2.2官方Demo,SDK与调试工具

Java Demo

  • demo说明https://bbs.aliyun.com/read/291640.html?spm=a219a.7629140.0.0.Jv8hW9

  • 企业内部应用demo地址https://github.com/ddtalk/HarleyCorp?spm=a219a.7629140.0.0.Jv8hW9

  • isv应用地址

    • https://github.com/hetaoZhong/ding-isv-common 公共方法类库
    • https://github.com/hetaoZhong/ding-isv-access 对接工程

调试工具

  • 移动端JSAPI调试工具:http://wsdebug.dingtalk.com 打开后,在页面上配置jsapi的参数,即传入的参数,点击手机会有相应的反馈
  • 服务端企业API调试工具https://debug.dingtalk.com/?spm=a219a.7629140.0.0.l9Pmzq
  • 服务端ISVAPI接入调试工具https://debug.dingtalk.com/isv.html

SDK

java SDK 分为TOP(淘宝开放平台)和OAPI两种,前者是通过restfull方式请求,后者直接使用http请求。

TOP SDK地址https://open-doc.dingtalk.com/doc/sdk.htm

OAPI SDK 地址https://github.com/ddtalk/client_sdk

大部分接口可以直接使用OAPI调用,例如通讯录(人员,部门),获取token,jsapi_ticket等。

少部分接口,例如审批,角色相关等 ,使用TOP方式调用。

具体的使用方式官方接口文档均有详细说明,后续会选取几个作为例子。

3.客户端开发

3.1开始开发

微应用

企业创建微应用后,在钉钉客户端工作台中会出现相应的应用,点进去,即进入到H5开发环境,可以完成一些业务需求。在H5环境中,可以通过钉钉的相关接口调用原生控件,例如拍照,录音,地图定位等。接口调用步骤

  1. 页面需要引入js文件 http://g.alicdn.com/dingding/open-develop/1.6.9/dingtalk.js 得到一个全局变量dd
  2. 需要进行JSAPI权限验证,demo地址https://github.com/injekt/openapi-demo-java/blob/master/WebContent/javascripts/demo.js。
  3. 调用钉钉接口。

JSAPI列表https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.mNdKWw&treeId=171&articleId=106834&docType=1

有些API**不需要ddconfig,可以直接在dd.ready里面调用。有些需要**ddconfig,要先鉴权,才能调用成功。

3.2JSAPI鉴权

调用jsapi之前需要进行dd.config(),所需参数如下,参数一般是从后台计算然后返回到前端页面。注意,timeStamp,nonceStr一定和后端计算签名所用随机数和时间戳是一致的。

dd.config({    agentId: '', // 必填,微应用ID    corpId: '',//必填,企业ID    timeStamp: , // 必填,生成签名的时间戳    nonceStr: '', // 必填,生成签名的随机串    signature: '', // 必填,签名    type:0/1,   //选填。0表示微应用的jsapi,1表示服务窗的jsapi。不填默认为0。该参数从dingtalk.js的0.8.3版本开始支持    jsApiList : [ 'runtime.info', 'biz.contact.choose',        'device.notification.confirm', 'device.notification.alert',        'device.notification.prompt', 'biz.ding.post',        'biz.util.openLink' ] // 必填,需要使用的jsapi列表,注意:不要带dd。});

其中的signature签名是通过jsapi_ticket(通过jsapi_ticket接口获得),noncestr(随机字符串,自己随便填写即可),timestamp(当前时间戳,具体值为当前时间到1970年1月1号的秒数),url(当前网页的URL,不包含#及其后面部分,需要对url中query部分做一次urldecode)这几个参数计算得到的。具体代码如下

//向前端返回的config数据public static String getConfig(HttpServletRequest request) {        String urlString = request.getRequestURL().toString();        String queryString = request.getQueryString();        String queryStringEncode;        String url;        if (queryString != null) {            queryStringEncode = URLDecoder.decode(queryString);            url = urlString + "?" + queryStringEncode;        } else {            url = urlString;        }        String nonceStr = getRandomStr();        long timeStamp = System.currentTimeMillis() / 1000;        String signedUrl = url;        String accessToken = null;        String ticket = null;        String signature = null;        String agentid = null;        try {            accessToken = AuthHelper.getAccessToken();            ticket = AuthHelper.getJsapiTicket(accessToken);            signature = AuthHelper.sign(ticket, nonceStr, timeStamp, signedUrl);            agentid = "111853665";//微应用ID        } catch (OApiException e) {            e.printStackTrace();        }        String configValue = "{jsticket:'" + ticket + "',signature:'" + signature + "',nonceStr:'" + nonceStr + "',timeStamp:'"+ timeStamp + "',corpId:'" + Env.CORP_ID + "',agentid:'" + agentid + "'}";        System.out.println(configValue);        return configValue;    } /**     * 签名算法     * @param ticket      * @param nonceStr 随机数     * @param timeStamp 时间戳     * @param url  前端页面url     * @return     * @throws OApiException     */    public static String sign(String ticket, String nonceStr, long timeStamp, String url) throws OApiException {        String plain = "jsapi_ticket=" + ticket + "&noncestr=" + nonceStr + "&timestamp=" +      String.valueOf(timeStamp) + "&url=" + url;        try {            MessageDigest sha1 = MessageDigest.getInstance("SHA-1");            sha1.reset();            sha1.update(plain.getBytes("UTF-8"));            return bytesToHex(sha1.digest());        } catch (NoSuchAlgorithmException e) {            throw new OApiResultException(e.getMessage());        } catch (UnsupportedEncodingException e) {            throw new OApiResultException(e.getMessage());        }    }

在dd.config成功后触发dd.ready(function(){}); jsapi需要在此回调函数触发后使用

dd.ready(function(){   //拍照接口    dd.biz.util.uploadImageFromCamera({            onSuccess: function (info) {                alert(JSON.stringify(info));            },            onFail: function (err) {                alert("camera fail:"+JSON.stringify(err));            }        })});

dd.config失败后触发dd.error(function(error){}); 错误信息可以在返回的error中查看

dd.error(function(error){       /**        {           message:"错误信息",//message信息会展示出钉钉服务端生成签名使用的参数,请和您生成签名的参数作对比,找出错误的参数           errorCode:"错误码"        }       **/       alert('dd error: ' + JSON.stringify(err));});

3.3免登

免登流程图

在dd.config之后,可以调用免登接口获取code,再通过code获取用户身份,具体代码如下

dd.ready(function(){  dd.runtime.permission.requestAuthCode({    corpId: "corpid", //传入企业ID    onSuccess: function(result) {    /*{        code: 'hYLK98jkf0m' //string authCode    }*/      alert(result.code);    },    onFail : function(err) {}  })});

拿到code后,通过/user/getuserinfo接口 获取到用户的userid,再通过/user/get 接口获取到用户的详细信息。和数据库中的用户信息比对,一致则允许登录。

4.服务端开发(企业内部)

4.1建立连接

两种连接方式

  1. 主动调用,即后台主动调用钉钉接口,操作通讯录,获取钉钉数据,或者通过微应用来向用户推送消息等。
    • 调用时需要使用https协议、Json数据格式、UTF8编码,访问域名为 https://oapi.dingtalk.com。
    • 每次调用需要带上AccessToken,AccessToken参数由CorpID和CorpSecret换取。
    • POST请求请在HTTP Header中设置 Content-Type:application/json,否则接口调用失败。
    • 主动调用有一定的频率限制。
  2. 回调模式:当用户触发某个事件,钉钉会向我们的后台地址(此地址在注册回调事件时指定)推送此事件的相关信息,例如通讯录人员增加了,会触发user_add_org事件,我们后台可以监听接受到此事件的信息,可以获得增加人员的userid,来操作一些业务,同步数据库等。回调事件需要提前注册,后续会详细说明。

4.2SDK调用说明

官方提供的SDK分为两种,TOP和OAPI

其中TOP需要导入taobao-sdk-java.jar这个jar包。

OAPI需要导入

  • client-sdk.core-{version}.jar:SDK核心类库,负责网络通信及API扫描
  • client-sdk.common-{version}.jar:SDK内共用代码如注解的定义
  • client-sdk.api-{version}.jar:SDK会扫描此包内的API,如需自定义新的API可按照规范在此包内增加
  • client-sdk.spring-{version}.jar:如果需要在Spring环境中使用SDK才需要依赖此jar

这四个jar包。

TOP方式调用实例(审批)

接口文档https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.ppBVWX&treeId=355&articleId=106846&docType=1

首先需要在后台OA模板中新增审批模板,只支持三种表单控件,单行文本框,图片和明细,其中明细中可以包含多个文本框和图片

另外还需要获取processCode,在模板的url参数中获取processCode

DingTalkClient client = new DefaultDingTalkClient('https://eco.taobao.com/router/rest');SmartworkBpmsProcessinstanceCreateRequest req = new SmartworkBpmsProcessinstanceCreateRequest();req.setAgentId(41605932L);//微应用IDreq.setProcessCode("PROC-BH3K80XU-LC9M6901U1R5IER41D1S1-V8V6VN4J-X");req.setOriginatorUserId("1124321606898017");//审批发起人req.setDeptId(13688215L);//部门IDreq.setApprovers("1124321606898017");;//审批人req.setCcList("zhangsan,lisi");//抄送人List<FormComponentValueVo> list2 = new ArrayList<FormComponentValueVo>();FormComponentValueVo obj3 = new FormComponentValueVo();list2.add(obj3);obj3.setName("文本框"); //name 需要和设计表单时的标题名一致obj3.setValue("测试文本数据审批");req.setFormComponentValues(list2);SmartworkBpmsProcessinstanceCreateResponse rsp = client.execute(req, access_token);System.out.println(rsp.getBody());

OAPI调用实例

平台大部分接口可以直接通过OAPI接口来调用,其底层也是封装的http请求,更方便我们的调用。

在client-sdk.api.jar中根据分类来封装了不同的接口请求,以及对应的Model,我们可以直接使用。

    @Test    public void getUserInfo(String userId) {        try {          //通过userId获取用户的详细信息            CorpUserService corpUserService = ServiceFactory.getInstance().getOpenService(CorpUserService.class);            CorpUserDetail corpUser = corpUserService.getCorpUser(access_token, userId);            System.out.println(corpUser.getName());        } catch (ServiceNotExistException | SdkInitException | ServiceException e) {            e.printStackTrace();        }    }    @Test    public String getAccessToken(String corpID,String,corpSecret) {        try {          //获取AccessToken         ServiceFactory serviceFactory = ServiceFactory.getInstance();         CorpConnectionService corpConnectionService =serviceFactory.getOpenService(CorpConnectionService.class);         return corpConnectionService.getCorpToken(corpID, corpSecret);        } catch (ServiceNotExistException | SdkInitException | ServiceException e) {            e.printStackTrace();        }    }

4.3回调事件注册

文档地址https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.vLNbs0&treeId=385&articleId=104975&docType=1#s1

用户的一些操作,会触发回调事件,(例如通讯录成员变更,群回话变更,以及审批单据的变化)。触发后,钉钉会向我们的回调地址推送消息,通过消息来执行特定的业务逻辑。

要使用回调,必须先要注册回调事件

钉钉服务器向回调url推送事件的时候,会携带signature,timestamp,nonce,以及加密的encrypt字段,我们需要进行解密,来获取我们想要的数据。在接收到推送之后,需要返回加密后的“success”字符串,代表已经接收到了推送。如果不返回,钉钉会持续推送,达到一定阈值将不再推送。所以在回调事件中,如果有比较复杂的业务逻辑,建议使用异步来执行。让”success”字符串成功返回,避免重复推送。

下面是具体的代码

 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {        /**url中的签名**/        String msgSignature = request.getParameter("signature");        /**url中的时间戳**/        String timeStamp = request.getParameter("timestamp");        /**url中的随机字符串**/        String nonce = request.getParameter("nonce");        /**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());        String encrypt = jsonEncrypt.getString("encrypt");        /**对encrypt进行解密**/        DingTalkEncryptor dingTalkEncryptor = null;        String plainText = null;        try {            dingTalkEncryptor = new DingTalkEncryptor(Env.TOKEN, Env.ENCODING_AES_KEY, Env.CORP_ID);            plainText = dingTalkEncryptor.getDecryptMsg(msgSignature, timeStamp, nonce, encrypt);        } catch (DingTalkEncryptException e) {            System.out.println(e.getMessage());            e.printStackTrace();        }        /**对从encrypt解密出来的明文进行处理**/        JSONObject plainTextJson = JSONObject.parseObject(plainText);        //异步业务操作,防止响应时间过长,钉钉重复推送回调。        CallBackThread callBackThread=new CallBackThread(plainTextJson);        callBackThread.start();       /* String eventType = plainTextJson.getString("EventType");        switch (eventType) {            case "user_add_org"://通讯录用户增加 do something                JSONArray userId = plainTextJson.getJSONArray("UserId");                for(int i=0;i<userId.size();i++){                    UserTest.getUserInfo(userId.get(i).toString());                }                break;            case "user_modify_org"://通讯录用户更改 do something                break;            case "user_leave_org"://通讯录用户离职  do something                JSONArray leave_id = plainTextJson.getJSONArray("UserId");                break;            case "org_admin_add"://通讯录用户被设为管理员 do something                break;            case "org_admin_remove"://通讯录用户被取消设置管理员 do something                break;            case "org_dept_create"://通讯录企业部门创建 do something                break;            case "org_dept_modify"://通讯录企业部门修改 do something                break;            case "org_dept_remove"://通讯录企业部门删除 do something                break;            case "org_remove"://企业被解散 do something                break;            case "check_url"://do something                break;            case "bpms_task_change":                System.out.println(plainTextJson.toString());                CallBackThread cth=new CallBackThread(plainTextJson);                cth.start();                break;            case "bpms_instance_change":                System.out.println(plainTextJson.toString());                break;            default: //do something                break;        }*/        /**对返回信息进行加密**/        long timeStampLong = Long.parseLong(timeStamp);        Map<String, String> jsonMap = null;        try {            jsonMap = dingTalkEncryptor.getEncryptedMap("success", timeStampLong, nonce);        } catch (DingTalkEncryptException e) {            System.out.println(e.getMessage());            e.printStackTrace();        }        JSONObject json = new JSONObject();        json.putAll(jsonMap);        response.getWriter().append(json.toString());    }

注册回调事件

调用/call_back/register_call_back/ 接口来注册回调事件

可以注册的回调事件一共有20种 https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.vLNbs0&treeId=385&articleId=104975&docType=1#s2

 public static void main(String[] args) {        String accessToken = "";        try {            accessToken = AuthHelper.getAccessToken();        } catch (OApiException e) {            e.printStackTrace();        }   //注册四个回调事件,审批变化,通讯录用户增加,修改,删除        String t1 = "bpms_task_change";        String t2="user_add_org";        String t3="user_modify_org";        String t4="user_leave_org";        List<String> callBackTag = new ArrayList<>();        callBackTag.add(t1);        callBackTag.add(t2);        callBackTag.add(t3);        callBackTag.add(t4);        String callBackUrl = "http://easzz.tunnel.echomod.cn/selfApp/eventreceive";//后台接受的回调地址,即上方代码        try {            String s = EventChangeHelper.registerEventChange(accessToken, callBackTag, Env.TOKEN, Env.ENCODING_AES_KEY, callBackUrl);            System.out.println(s);        } catch (OApiException e) {            e.printStackTrace();        }    }//注册事件回调接口    public static String registerEventChange(String accessToken, List<String> callBackTag, String token, String aesKey, String url) throws OApiException{        String signUpUrl = Env.OAPI_HOST + "/call_back/register_call_back?" +                "access_token=" + accessToken;        JSONObject args = new JSONObject();        args.put("call_back_tag", callBackTag);        args.put("token", token);        args.put("aes_key", aesKey);        args.put("url", url);        JSONObject response = HttpHelper.httpPost(signUpUrl, args);        if (response.containsKey("errcode")) {            return response.getString("errcode");        }        else {            return null;        }    }

若需要添加其他的回调事件,需要调用更新回调事件接口。

4.4群机器人

可以做为监控报警等。

建立一个自定义机器人,添加成员,获取机器人对应的Webhook地址

下面的例子为云销售无法访问时,向群推送一条消息。

 private String url = "https://oapi.dingtalk.com/robot/send?access_token=2dfdb25ef9874a322c7476f5afa38f7d497e4bc79565915fae54605e47b3e0e0";    @Test    public void connTest() {        if (!isLive()) {            TextMessage textMessage = new TextMessage();            textMessage.setText("http://www.cloudsales.com.cn 无法连接...");            List<String> list = new ArrayList<>();            list.add("13687240031");            textMessage.setAtMobile(list);            try {                HttpHelper.httpPost(url, JSONObject.parse(textMessage.toString()));            } catch (OApiException e) {                e.printStackTrace();            }        }    }    private Boolean isLive() {        String ur = "http://www.cloudsales.com.cn";        HttpGet httpGet = new HttpGet(ur);        CloseableHttpResponse closeableHttpResponse = null;        CloseableHttpClient httpClient = HttpClients.createDefault();        RequestConfig config = RequestConfig.custom().setConnectionRequestTimeout(5000).setSocketTimeout(5000).build();        httpGet.setConfig(config);        try {            closeableHttpResponse = httpClient.execute(httpGet, new BasicHttpContext());            return closeableHttpResponse.getStatusLine().getStatusCode() == 200;        } catch (IOException e) {            e.printStackTrace();        }        return false;    }

5.服务端开发(ISV应用)

5.1第三方应用说明(ISV)

ISV为专门开发软件的提供商,经过审核后可以上架到钉钉应用市场,企业的管理员可以在市场搜索来添加到本企业的工作面板中,添加的时候,会出现一个授权的界面,上面显示此应用需要的一些权限信息,确定之后,普通员工就可以在钉钉客户端看到此应用。ISV应用相对于企业内部应用开发来说,开发要复杂一些,主要是处理回调授权的过程。

5.2接入前准备

开发ISV应用,需要企业先进行认证,认证通过之后才可以开发。

认证后,注册后台开发者,注册完之后,就可以新建一个套件(suite)

其中Token和数据加密秘钥需要保存在后台,后续处理回调请求的时候,需要用此秘钥来进行解密。

注册时,需验证回调URL有效性,钉钉会发出一个回调事件,,返回加密后的“Random”,验证通过才算注册完成。

此地址作用于三个场景,1.创建套件 2.授权,激活。3.用户购买套件。

一共有11个回调类型。

钉钉服务器会定时向此地址推送Ticket ,我们后台需要保存起来,后续会结合suite_key,suite_secret获取套件访问token(suite_access_token)

注册完成之后,会生成套件Key(suite_key),套件secret(suite_secret)

随后可以创建微应用,微应用的主页地址需要使用$CORPID$模板参数表示corpid,

例如http://www.cloudsales.com.cn/isv/index.html?corpid=$CORPID$

其中的$CORPID$,会自动替换为当前企业的corpid,用于获取企业的access_token

5.3接入流程

下面一个完整的接入流程图

  1. 钉钉会定时向我们的服务器回调地址推送ticket,此ticket的作用是结合 suite_key , suite_secret 换取套件访问TOKEN (suit_access_token)

  2. 企业管理员(用户)在安装此应用时,会有授权的界面,点击之后,钉钉会向我们后后推送临时授权码(temp_auth_code)

  3. .通过suit_access_token和临时授权码(temp_auth_code)换取永久授权码(permanent_code)

  4. 临时授权码通过企业管理员(用户)点击安装授权的时候推送到我们的后台来获取。

  5. 通过永久授权码 和 授权方的企业id (auth_copid),来获取企业授权的access_token,通过这个token,可以来操作企业的相关接口。

  6. 企业用户授权开通套件时,ISV需要激活此套件(调用激活接口),否则企业无法使用。

5.4部署安装

地址https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.lXYJrx&treeId=366&articleId=105374&docType=1

6.常见问题

官方FAQ地址https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.YetWfq&treeId=173&articleId=105077&docType=1

企业应用中,access_token过期时间为2小时,期限内获取返回相同的数据,并自动续期。

​ Jsticket过期时间为2小时,期限内获取,是全新的jsticket,过期时间为2h

ISV应用中,jsticket过期时间为2小时,期限内重新获取,返回相同结果,并自动续期

原创粉丝点击