背景介绍
企业和客户(B端)进行对接时,往往需要同步客户的组织和成员信息,现在企业大多使用的办公软件有三样:企业微信、钉钉、飞书。现需要对接企业微信,在客户授权企业三方应用时,同步组织和用户信息,方便企业使用
需求分析
企业微信官方提供了开发者平台,当然你首先得需要进入一个企业,才可以登录查看开发文档

开发模式
简单区分方式:企业和三方比较多,企业自己对自己,三方是别人对自己
基于这种背景需求,选择三方开发

进入通讯录开发,进入之前建议阅读下开发必读
了解基本概念及参数
开发前提
该需求不是从0开发
已经有的
- 创建应用
- 注册一个测试账号安装了服务商应用(我们的应用)
- 通过授权拿到了基本信息(如公司id,秘钥等)
- 有这些表示可以直接去请求接口链接
所需接口
其实只需要两个接口
- 获得部门列表
- 获得部门成员
详细步骤官方已经写的很清楚,也不照本宣科了,只说一些关键点和坑点
获得部门列表
- id参数可以不传,或者传0来获取所有部门信息
access_token可以通过官方提供的方式获取,如果根据步骤发现获取不到那就是被加密了,要联系当时搭这个应用的人要
- 无法获得部门名称,用id代替名称了
获得部门成员
- id是必传的
- access_token同上
- 无法获得用户名称,同样用id代替名称
问题分析
现在问题出现了,无法获取到部门名称和用户名称。这个如果不解决,那同步了个寂寞。所以这个问题是一定是要解决的
方法选择
官方提供了两个方法,第一个是前端实现,第二个是后端实现

但我推荐使用第二个,原因有下
- 打通这个需求是后端实现的,不要加别人的工作量
- 就算前端实现也是显示解决了问题,存数据库的还是加密的,别的地方用不了,而且前端官方给的SDK还需要进行企业微信授权登录才可以使用
- 同步也不只是去企微显示用,可能有多个地方进行了依赖或者显示
当然第一个比第二个简单,为啥?因为不用你做啊
所以实现重点就是如图的通讯录ID转译部分

通讯录转译

先看看官方说的,我当时是理解不了,需要用户那边点击下载把文件给你?
在看看转译的一个流程

最终会拿到url来下载这个解密的文件,前提是用户登录?我当时就觉得这个很麻烦,为此还请教了专业人员

我这边想实现的一个效果是当我们点击“同步”的时候完成一系列操作,如果还需要多余的操作就应该考虑是否可以实现全自动化,如果不能,那难点在哪,能不能去解决
实现过程
ps:利用postman走一遍流程,在用代码实现
代码实现要逐层递进,保证各个环节单元测试通过
上传需要转译的文件
provider_access_token参数与之前的access_token不是同一个token,官方也提供了获取的方法
会返回media_id供下一步使用
异步通讯录id转译
output_file_name参数可以不要
output_file_format参数可以不要
会返回job_id供下一步使用
获取异步任务结果
url获取大多没什么问题,但下载需要条件,否则如图

下载条件:用户通过oauth2授权登录或者单点登录才能进行下载。如果不懂官方说的什么屁话,且听我一言
需要客户登录后台(管理员权限)进入应用管理的三方小程序里,在进入服务商后台进行授权
第二种,第三方小程序使用企微登录

测试小技巧:可以到服务商可信域名所在的网站进行测试(打开F12,自定义href标签插入下载)
需要注意的是,无法通过referer指定可信域名的方式投机取巧,登录态才是最重要的

这表明无法通过一站式的方式解决,需要用户下载文件给到这边进行解密,中转一下才可实现
代码实现
编程语言Java
代码参考
gitHub(WxJava)
微信开发Java SDK包括微信的所有功能,非常强大,从0开发推荐,我不是所以没用
github(qywx-third-java)
企微第三方开发demo,一些方法实现可以参考
转译文件excel格式如下

企业id:企业在这边的标识
id:这边数据库的主键
name:部门/用户名称
ps:因为最后这个文件还要拿回来用,每个字段都有其对应的作用
关键代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
@Async public void transferInfo(String tenant) { log.info("执行任务"); String mediaId = getMediaId(tenant); String jobId = getJobId(mediaId); String url = getUrl(jobId); try { readExcel(url); } catch (ExcelCommonException e) { log.error("转译失败,未经过oauth2授权登录或者单点登录或者不为可信域名,无法下载该文件", e); } catch (Exception e) { log.error("未知异常", e); } }
|
各方法实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| private String getMediaId(String tenant) { List<WxExcel> excelList = getExcelList(tenant); String rootPath = System.getProperty("user.dir"); String filePath= rootPath + "/wechat" + System.currentTimeMillis() + ".xlsx"; EasyExcel.write(filePath, WxExcel.class).sheet("企业微信转译").doWrite(excelList); String providerAccessToken = workWechatHelper.getProviderAccessToken(); String url = String.format(workWechatConfig.getUploadUrl(), providerAccessToken); MultiValueMap<String, Object> params = new LinkedMultiValueMap<>(); FileSystemResource resource = new FileSystemResource(new File(filePath)); params.add("media",resource); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.MULTIPART_FORM_DATA); HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(params, headers); Map<String, String> post = RestTemplateHelper.post(url, entity); FileUtil.del(filePath); if (post != null) { String code = post.get("errcode"); if (Objects.equals(code, workWechatConfig.getSuccessCode())) { return post.get("media_id"); } else { log.error("调用失败,失败码为:{}, 失败原因为:{}", code, post.get("errmsg")); } } return null; }
private String getJobId(String mediaId, String tenant) { String providerToken = workWechatHelper.getProviderAccessToken(); String url = String.format(workWechatConfig.getJobUrl(), providerToken);
Map<String, Object> map = new HashMap<>(1 << 3);
String corpId = workWechatHelper.getConfig("appId", tenant);
if (corpId == null) { log.error("调用失败,获取corpId失败"); return null; } map.put("auth_corpid", corpId);
JSONArray mediaList = new JSONArray(); mediaList.add(mediaId); map.put("media_id_list",mediaList);
Map<String, String> post = RestTemplateHelper.post(url, map); if (post != null) { String code = post.get("errcode"); if (Objects.equals(code, workWechatConfig.getSuccessCode())) { return post.get("jobid"); } else { log.error("调用失败,失败码为:{}, 失败原因为:{}", code, post.get("errmsg")); } } return null; }
|