1. 背景
在笔者开发的大数据平台XSailboat中有 消息中心 模块,用来全平台的消息收集,整理分拆、订阅发送等功能。消息推送方式支持钉钉群聊、钉钉单聊、短信通知。现记录一下企业机器人消息单聊推送的实现过程。
2. 钉钉开发文档
这是官方的开发文档地址:《机器人发送、查询和撤回单聊消息》
将其中的主要过程抄录一下:
步骤一:登录开发者后台,点击应用开发-企业内部开发,创建机器人。
步骤二:点击机器人应用-基础信息,获取AppKey和AppSecret。
步骤三:添加接口调用权限,点击“机器人”,申请企业内机器人发送消息权限。申请权限无需审批,默认开通。
步骤四:上线机器人。进入版本管理与发布页面,点击上线,机器人的状态变更为已发布。
步骤五:获取应用访问凭证获取企业内部应用的accessToken。调用接口时,通过accessToken鉴权调用者身份。
步骤六:调用机器人单聊相关的API:
- 调用服务端API-批量发送单聊消息,获取消息processQueryKey。
- 根据消息processQueryKey,调用服务端API-批量查询单聊机器人是否已读,查询机器人发送的单聊消息对方是否已读。
- 根据消息processQueryKey,调用服务端API-批量撤回单聊消息,撤回机器人发送的单聊消息。
3. 记录自己的操作过程
- 上企业的 开发者后台 创建应用(应用名:大数据平台-消息中心)。
- 创建了应用之后,再点进应用,创建机器人,创建好了之后发布。
- 申请“机器人”里面的 “企业内机器人发送消息权限” 和“通讯录管理”中的 “根据手机号姓名获取成员信息的接口访问权限”。
- 根绝Appkey和AppSecret获取token。
- 《获取企业内部应用的access_token》
- 定向向某人发送钉钉消息时需要指定userId,而消息中心配置的消息订阅人设置的是手机号。所以需要将手机号翻译成userId。
- 《根据手机号查询企业账号用户》
- 发送消息的API。
- 《批量发送人与机器人会话中机器人消息》
4. 示例代码
package com.cimstech.sailboat.ms.msg;import com.cimstech.sailboat.ms.msg.sender.Letter;
import com.cimstech.xfront.common.http.HttpClient;
import com.cimstech.xfront.common.http.Request;
import com.cimstech.xfront.common.json.JSONArray;
import com.cimstech.xfront.common.json.JSONObject;
import com.cimstech.xfront.common.text.XString;
import com.cimstech.xfront.common.time.XTime;public class Test
{/*** 获取token,查询userId的服务地址*/static final String sDingService = "https://oapi.dingtalk.com" ;/*** 发送钉钉消息的服务地址*/static final String sDingSendService = "https://api.dingtalk.com" ;/*** 凭借appKey和appSecret获取token*/static final String sPath_GetToken = "/gettoken" ;/*** 通过手机号获取企业账号用户*/static final String sPath_GetUserByMobile = "/topapi/v2/user/getbymobile" ;static final String sPath_SendMsgOfSingleChat = "/v1.0/robot/oToMessages/batchSend" ;static final String sDetailPtn_n = "[以及另外{}条详情..]({})" ;static final String sDetailPtn_1 = "[详情..]({})" ;static final String sNoLink = "以及另外{}条" ;static String[] sColors = new String[] {"#FF0000", "#ffa500", "#0fdbdb", "#12e512"} ;static String sDetailLink = "http://XXXXX/sailmsg/msgrow?sendBatch={}&totalAmount={}" ;static JSONObject toJSON(Letter aLetter){int level = aLetter.getLevel() ;String color = level < sColors.length? sColors[level]:null ;StringBuilder strBld = new StringBuilder("【消息中心-").append(aLetter.getSubscriberItemName()).append("】").append("-[") ;if(color != null)strBld.append("<font color=").append(color).append(">") ;strBld.append(level).append("级") ;if(color != null)strBld.append("</font>") ;
// String partition = aLetter.getPartition() ;strBld.append("]") ; // .append(sSiteMap.getOrDefault(partition , partition)) ;String title = strBld.toString() ;strBld.setLength(0);strBld.append("**").append(title).append("** \n > ").append("<font color=#000000>").append(aLetter.getContent()).append("</font>").append(" \n \n ") ;strBld.append("<font color=#666666>").append(aLetter.getEventTime()).append("</font>").append(" ") ;if(XString.isEmpty(sDetailLink)){if(aLetter.getReduceAmount() > 0)strBld.append(XString.msgFmt(sNoLink , aLetter.getReduceAmount())) ;}else{if(aLetter.getReduceAmount() > 1){strBld.append(XString.msgFmt(sDetailPtn_n, aLetter.getReduceAmount(), XString.msgFmt(sDetailLink , aLetter.getSendBatch() , aLetter.getReduceAmount()))) ;}else{strBld.append(XString.msgFmt(sDetailPtn_1, XString.msgFmt(sDetailLink , aLetter.getSendBatch() , aLetter.getReduceAmount()))) ;}}String content = strBld.toString() ;return new JSONObject().put("title" , title).put("text", content);}public static void main(String[] args) throws Exception{String appKey = "ddddddddddddddd" ;String appSecret = "ssssssssssssssssssssssssssssssss" ;String mobile = "130XXXXXXXXX" ;String robotCode = "dingXXXXXXXXX" ;// 1.获取AccessTokenHttpClient client = HttpClient.ofUrl(sDingService) ;long expireTime = System.currentTimeMillis() ;/*** {* "errcode": 0,* "access_token": "aaaaaaaaaaaaaaaaa",* "errmsg": "ok",* "expires_in": 7200* }*/JSONObject jo = client.askJo(Request.GET().path(sPath_GetToken).queryParam("appkey" , appKey).queryParam("appsecret", appSecret)) ;System.out.println("获取到的Token:"+jo) ;String accessToken = jo.optString("access_token") ;expireTime += jo.optLong("expires_in") * 1000 ;/*** {* "errcode": 0,* "errmsg": "ok",* "result":* {* "exclusive_account_userid_list": [],* "userid": "bbbbbbbbbbbbb"* },* "request_id": "ccccccc"* }*/// 2. 查询userIdjo = client.askJo(Request.POST().path(sPath_GetUserByMobile).queryParam("access_token" , accessToken).setJsonEntity(new JSONObject().put("mobile" , mobile).put("support_exclusive_account_search" , true))) ;System.out.println("获取到的User信息:"+jo);String userId = jo.pathString(null , "result" , "userid") ;Letter letter = new Letter(null) ;letter.setLevel(5);letter.setContent("单聊测试") ;letter.setEventTime(XTime.current$yyyyMMddHHmmss());letter.setReduceAmount(1) ;letter.setSendBatch("xxxxxxxxxxxxxxx") ;letter.setSubscriberItemName("单聊测试订阅项");String msgParam = toJSON(letter).toJSONString() ;/*** {* "flowControlledStaffIdList": [],* "invalidStaffIdList": [],* "processQueryKey": "xxxxxxxxxxxxxx"* }*/// 3.发送消息jo = HttpClient.ofUrl(sDingSendService).askJo(Request.POST().path(sPath_SendMsgOfSingleChat).header("x-acs-dingtalk-access-token" , accessToken).setJsonEntity(new JSONObject().put("robotCode" , robotCode).put("userIds", new JSONArray().put(userId)).put("msgKey", "sampleMarkdown").put("msgParam" , msgParam))) ;System.out.println("发送消息收到的回复:"+jo);}}
效果: