钉钉平台接入文档
来源:互联网 发布:app打印软件下载 编辑:程序博客网 时间:2024/04/28 20:56
钉钉平台接入文档
Auth-date: [add by Easzz 2017-07-18]
1.接入说明
由于业务发展,需要接入钉钉平台,特整理了一份简明的接入文档,此文档旨在帮助用户快速熟悉钉钉平台,调用钉钉相关接口,以实现具体的业务逻辑。
详细的官方文档地址:https://open-doc.dingtalk.com/
2.入门
2.1相关术语解释
- 企业内部应用:企业自建应用,只能用于本企业内部
- 第三方应用(ISV):第三方企业建立的应用,本企业使用,需要授权等过程。
- 免登:即单点登录,在企业H5微应用中,通过身份认证接口获取当前钉钉用户的个人信息,然后和数据库中的相关信息比对,正确即可自动登录,无需输入用户名密码。
- AccessToken:访问钉钉接口的唯一票据,调用接口需要附带AccessToken才能成功。通过CropID和Corp_secret换取。有效期7200秒,需全局缓存。
- CorpID:企业的ID (在开发者后台中得到)
- corp_secret:企业的秘钥(在开发者后台中得到)
- suite_key:
- suite_secret:
- suite_ticket:
- suite_access_token:
- tmp_auth_code:
- permanent_auth_code:
- 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环境中,可以通过钉钉的相关接口调用原生控件,例如拍照,录音,地图定位等。接口调用步骤
- 页面需要引入js文件 http://g.alicdn.com/dingding/open-develop/1.6.9/dingtalk.js 得到一个全局变量dd
- 需要进行JSAPI权限验证,demo地址https://github.com/injekt/openapi-demo-java/blob/master/WebContent/javascripts/demo.js。
- 调用钉钉接口。
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 + "×tamp=" + 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建立连接
两种连接方式
- 主动调用,即后台主动调用钉钉接口,操作通讯录,获取钉钉数据,或者通过微应用来向用户推送消息等。
- 调用时需要使用https协议、Json数据格式、UTF8编码,访问域名为 https://oapi.dingtalk.com。
- 每次调用需要带上AccessToken,AccessToken参数由CorpID和CorpSecret换取。
- POST请求请在HTTP Header中设置 Content-Type:application/json,否则接口调用失败。
- 主动调用有一定的频率限制。
- 回调模式:当用户触发某个事件,钉钉会向我们的后台地址(此地址在注册回调事件时指定)推送此事件的相关信息,例如通讯录人员增加了,会触发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接入流程
下面一个完整的接入流程图
钉钉会定时向我们的服务器回调地址推送ticket,此ticket的作用是结合 suite_key , suite_secret 换取套件访问TOKEN (suit_access_token)。
企业管理员(用户)在安装此应用时,会有授权的界面,点击之后,钉钉会向我们后后推送临时授权码(temp_auth_code)。
.通过suit_access_token和临时授权码(temp_auth_code)换取永久授权码(permanent_code)。
临时授权码通过企业管理员(用户)点击安装授权的时候推送到我们的后台来获取。
通过永久授权码 和 授权方的企业id (auth_copid),来获取企业授权的access_token,通过这个token,可以来操作企业的相关接口。
企业用户授权开通套件时,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小时,期限内重新获取,返回相同结果,并自动续期
- 钉钉平台接入文档
- 钉钉服务端开发文档
- 钉钉开放平台“常见问题常见问题常见问题“
- 钉钉的API
- 钉钉打卡神器
- 钉钉机器人
- 钉钉自定义机器人
- 钉钉消息发送
- 钉钉报警
- 钉钉定时打卡
- 钉钉子
- 钉钉的功能介绍
- 钉钉发送信息脚本
- 钉钉机器人自动提醒
- 钉钉消息推送教程
- C# 钉钉系列目录
- jenkins 集成钉钉机器人
- 钉钉企业消息开发
- 单例模式写法
- Keras 示例代码 02 antirectifier.py 结果演示及代码解析
- 问题 : 只包含因子2 3 5的数
- JAVA企业面试题精选 Servlet和JSP 31-40
- JavaScript中获取样式值的方法总结
- 钉钉平台接入文档
- MySQL系列—建索引的几大原则和使用索引优化查询
- Hibernate 的 HelloWorld
- 问题 : 1 10 100 1000
- jsp简介
- Java instanceof
- 问题 : 最小周长
- rocketMQ消息堆积监控的java实现
- 【从0到1学Web前端】CSS伪类和伪元素