钉钉
- 钉钉的接入和微信公众号基本时一致的,唯一不同的是微信很难拿到手机号切公众号接口的权限和账号本身特性有关,而钉钉拿到手机号还是很容易的,并且钉钉接口的权限只需要申请就可以了。
- 首先我们注册一个后台账号并登陆进去创建一个企业内部应用。我这里就是创建的test应用。
- 到这里我们就拥有了
CorpId,AgentId,AppKey,AppSecrect
四个参数了。这四个参数在不同的地方分别被使用。都说了和微信是一个套路,那么剩下的自然是需要我们配置权限管理。
- 下面是我针对目前功能所开通的权限。如果你嫌麻烦那就所有权限全部开通。
接口 | 权限点code | 全部状态筛选 | 操作 |
---|
个人手机号信息 | 获取用户个人信息 | Contact.User.mobile | 已开通 |
通讯录个人信息读权限 | 获取用户通讯录个人信息 | Contact.User.Read | 已开通 |
调用SNS API时需要具备的基本权限 | 查询个人授权记录 | snsapi_base | 已开通 |
企业员工手机号信息 | | fieldMobile | 已开通 |
通讯录部门信息读权限 | 获取部门详情;获取指定用户的所有父部门列表;获取部门列表;获取指定部门的所有父部门列表;查看更多 | qyapi_get_department_list | 已开通 |
成员信息读权限 | 获取用户高管模式设置;查询用户详情;获取部门用户userid列表;获取管理员列表;查看更多 | qyapi_get_member | 已开通 |
根据手机号姓名获取成员信息的接口访问权限 | 根据手机号获取userid | qyapi_get_member_by_mobile | 已开通 |
通讯录部门成员读权限 | 查询部门用户完整信息;获取部门用户基础信息;获取角色详情;获取指定角色的员工列表 | qyapi_get_department_member | 已开通 |
调用企业API基础权限 | 生成jsapi ticket;生成微应用管理后台accessToken;查询连接器主数据详情;分页拉取连接器主数据;查看更多 | qyapi_base | 已开通 |
待办应用中待办写权限 | 更新待办执行者状态;新增钉钉待办任务;更新钉钉待办任务;删除钉钉待办任务 | Todo.Todo.Write | 已开通 |
待办应用中待办读权限 | 查询企业下用户待办列表;根据sourceId获取钉钉待办任务详情;获取钉钉待办任务详情 | Todo.Todo.Read | 已开通 |
测试用例
获取部门列表
- 钉钉和公众号不同的是,钉钉细分很多,树形结构一直递归下去。和微信公众号一样我们也需要获取企业下(公众号)列表。
@Test
public void getDeptListTest() {
final List<OapiV2DepartmentListsubResponse.DeptBaseResponse> deptBaseResponses = deptService.selectDeptList(null);
for (OapiV2DepartmentListsubResponse.DeptBaseResponse deptBaseRespons : deptBaseResponses) {
System.out.println(JSON.toJSONString(deptBaseRespons));
}
}
- 如果行获取跟部门下的用户列表我们deptId=1或者不传。
{"autoAddUser":true,"createDeptGroup":true,"deptId":581377085,"name":"运营部","parentId":1}
{"autoAddUser":true,"createDeptGroup":true,"deptId":581377086,"name":"设计部","parentId":1}
{"autoAddUser":true,"createDeptGroup":true,"deptId":581377087,"name":"产品部","parentId":1}
{"autoAddUser":true,"createDeptGroup":true,"deptId":581377088,"name":"人事部","parentId":1}
{"autoAddUser":true,"createDeptGroup":true,"deptId":581377089,"name":"行政部","parentId":1}
获取部门用户列表
@Test
public void getUserInfo() {
Long deptId = 1L
final List<AbstrctUser> userList = userInfoService.selectUserListBaseOnDeptId(deptId)
for (AbstrctUser abstrctUser : userList) {
System.out.println(JSON.toJSONString(abstrctUser))
}
}
{"userId":"XXX","userName":"丁1"}
{"userId":"XXXX","userName":"张1"}
通过手机号获取用户信息
@Test
public void getUsrDetailOnPhoneTest() {
String phone = "phone"
final List<AbstrctUser> userList = userInfoService.selectUserBaseOnPhone(phone)
for (AbstrctUser abstrctUser : userList) {
System.out.println(JSON.toJSONString(abstrctUser))
}
}
发送信息
@Test
public void sendMsg() {
for (int i = 0; i < 1; i++) {
List<String> userIds = Arrays.asList(new String[]{"tet,ttttt"});
List<Long> deptIds = Arrays.asList(new Long[]{1l});
messageService.sendToDeptInUser(userIds,deptIds,false,new TextMessage("你们好,元旦放假了!!!,我正在测试消息发送多人"+UUID.randomUUID()));
}
}
免登录
前端
- 同样的钉钉中我们也需要获取当前登陆用户信息,所以还需要我们钉钉登陆授权。
- 点我了解登陆认证钉钉
npm install dingtalk-jsapi --save
进行安装。
dd.ready(function() {
// dd.ready参数为回调函数,在环境准备就绪时触发,jsapi的调用需要保证在该回调函数触发后调用,否则无效。
dd.runtime.permission.requestAuthCode({
corpId: "corpid",
onSuccess: function(result) {
},
onFail : function(err) {}
});
});
- 安装完成后就可以通过上面代码获取授权码code,其中需要我们的参数
corpId
也是我们在企业配置账号中的信息之一。
后端
- 前端获取到code之后,后端就可以根据code获取到当前登陆用户了。
https://oapi.dingtalk.com/user/getuserinfo?access_token=%s&code=%s
文件上传
- 同样我们钉钉发送消息也不仅仅局限于文本,
https://oapi.dingtalk.com/media/upload
。
- 经过和微信的对比我们知道和微信支持的类型有很大的交集。所以在微信章节中我提到消息进行抽象化 , 现在我们可以共用一套实体类,出现两者不共同的我们在单独的创建特有的实体满足不同需求。
public MeterialResponse uploadPic(Meterial meterial, String fileName, InputStream inputStream) {
DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/media/upload")
OapiMediaUploadRequest req = new OapiMediaUploadRequest()
req.setType(meterial.getType())
// 要上传的媒体文件
FileItem item = new FileItem(fileName,inputStream)
req.setMedia(item)
OapiMediaUploadResponse rsp=null
try {
rsp = client.execute(req, tokenService.accessAndGetDingDingToken())
} catch (ApiException e) {
e.printStackTrace()
}
MeterialResponse response = new MeterialResponse()
final Field[] declaredFields = rsp.getClass().getDeclaredFields()
JSONObject jsonObject = new JSONObject()
for (Field declaredField : declaredFields) {
final ApiField annotation = declaredField.getAnnotation(ApiField.class)
if (null == annotation) {
continue
}
final String value = annotation.value()
declaredField.setAccessible(true)
try {
jsonObject.put(value, declaredField.get(rsp))
} catch (IllegalAccessException e) {
e.printStackTrace()
}
}
BeanUtils.transBeanWithTranAnnotation(jsonObject, response)
return response
}
发送消息
- 在上面我们已经实现了上传文件了。返回的
media_id
就是我们交互的载体。我们发送信息只需要将media_id
发送过去就行了。
@Test
public void sendMultiTypeMessage() {
String fileName = "/macpower.png"
InputStream resourceAsStream = this.getClass().getResourceAsStream(fileName)
final MeterialResponse response = uploadService.uploadPic(new ImageMeterial(), fileName, resourceAsStream)
System.out.println(response)
Message message = new ImageMessage(response.getMediaId())
List<String> userIds = Arrays.asList(new String[]{"manager2239"})
List<Long> deptIds = Arrays.asList(new Long[]{1l})
messageService.sendToDeptInUser(userIds,deptIds,false,message)
}
代码说明
- 首先我将包管理放在根目录管理。
message-api
用于管理微信,钉钉通用部分,比如定义通用方法,微信,钉钉都需要实现发送信息功能,那么关于接口的定义就是放在message-api
中的。还有就是关于消息的抽象的定义也是在message-api
中完成的。包括一些工具类放在message-api
完成。
message-demo
是针对钉钉,微信的测试用例。在demo中搭建好测试环境。编写测试用例。
- 剩下的就是
dingding-message-root
和wechat-message-root
两个模块了。顾名思义就是钉钉微信的处理业务。两个模块结构是一样的,内部会支持message-api
定义的骨架的基础上进行定制开发。比如wechat-message-root
中会开发微信特有的上传图文的功能。如果我们想使用该功能的话就不能仅仅用公共的MessageService
。 而是需要使用更加明确的WechatMessageService
- 再者,我们使用spring框架基本上是覆盖很广。所以每个
message-root
还需要在细分。即wechat-message-root
下细分为spring-wechat-starter
和wechat-core
。 前者是对spring的开箱即用的支持,后者才是真正对接微信公众号的服务功能。
- 可以看到service.impl中主要都是实现
message-api
模块中定义好的接口。在实现主接口的基础上需要扩展封住的功能会在wechat-core
中进行扩展开发。
- 因为钉钉,微信都实现
message-api
模块的接口,且内部都有spring的开箱即用功能,而spring容器中beanName是唯一的。而且为了我们自己能确定使用具体的模块的be an ,我们需要在注册的时候制定我们beanName,这样我们在使用的时候在通过名称查找。
- 比如我这样定义了一个bean
@Bean("wechatMessageService")
public MessageService wechatMessageService(TokenService tokenService) {
WechatMessageServiceImpl wechatMessageService = new WechatMessageServiceImpl();
wechatMessageService.setTokenService(tokenService);
return wechatMessageService;
}
@Autowired
@Qualifier("wechatMessageService")
MessageService messageService;
- 这样我们在使用的时候在两者公共的功能时,只需要通过对应的service完成就可以了。比如群发功能。这个时候如果我们微信钉钉都需要实现,那么我们可以通过这样引入service
@Autowired
List<MessageService> messageServiceList ;
总结
- 系统对接其实并没啥技术含量,只要我们根据官网文档一步一步操作就可以了。我们就是调用api 。 真正的高度时在平台的实现。比如我们的接口调用的权限基于o a u t h2完成的登陆鉴权。这些在maltcloud中我们都有去实现。后面有机会会结合实战去解读下oauth2和如何设计完成接口权限调度
- 除了微信的对接外,常见的还有支付宝的支付功能。这也是在掉接口核心的永远都是平台的功能。如果有需要我们后面在看看支付宝的接入