Son aktif 1718772450

文本消息推送、获取部门列表、获取部门下用户列表

WeChatUtil.java Ham
1public class WeChatUtil {
2 private final static Logger log = LoggerFactory.getLogger(WeChatUtil.class);
3
4 private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
5 private static final OkHttpClient OKHTTP_CLIENT = new OkHttpClient.Builder().build();
6
7 //缓存token-key
8 private static final String CACHE_KEY = "WECHAT_TOKEN_CACHE_KEY";
9 private static final String TOKEN_KEY = "ACCESS_TOKEN";
10
11// private final Config config = ConfigKit.me().get("WeChatUtil.Config",Config.class);
12
13 /**
14 * 发送文本消息
15 *
16 * @param content 消息内容,最长不超过2048个字节,超过将截断
17 * @param userIds 指定接收消息的成员,成员ID列表 多个接收者用‘|’分隔,最多支持1000个 指定为"@all",则向该企业应用的全部成员发送
18 * @return 是否发送成功
19 */
20 public static synchronized boolean sendMsg(Config config, String content, String... userIds) {
21 WorkAccessToken token = getOrFlushToken(config);
22 if (token == null) {
23 log.info("发送企业微信消息-获取 TOEKN 为空!");
24 return false;
25 }
26 //构建发送消息
27 JSONObject postParam = new JSONObject().fluentPut("touser", StringUtils.join(Arrays.asList(userIds), "|"))
28 .fluentPut("msgtype", "text")
29 .fluentPut("agentid", config.getAgentId())
30 .fluentPut("text", new JSONObject() {{
31 put("content", content);
32 }});
33
34 String msgUrl = String.format(config.getMessageSendUrl(), token.getAccessToken());
35 try (
36 Response response = OKHTTP_CLIENT.newCall(new Request.Builder()
37 .post(RequestBody.create(postParam.toJSONString(), MediaType.parse("application/json;charset=utf-8")))
38 .url(msgUrl)
39 .build()).execute()
40 ) {
41 ResponseBody body = response.body();
42 if (Objects.isNull(body)) {
43 log.info("发送企业微信消息-返回结果为空!");
44 return false;
45 }
46 String resp = body.string();
47 log.info("messageSendUrl:{}", config.getMessageSendUrl());
48 log.info("msgUrl:{}", msgUrl);
49 log.info("postParam:{}", postParam.toJSONString());
50 log.info("res:{}", resp);
51 if (!StringUtils.isNotBlank(resp)) {
52 log.info("发送企业微信消息-返回结果为空!");
53 return false;
54 }
55 try {
56 WorkMsgResp workResp = OBJECT_MAPPER.readValue(resp, WorkMsgResp.class);
57 return workResp != null && workResp.isSuccess();
58 } catch (JsonProcessingException e) {
59 log.error("发送企业微信消息时异常!", e);
60 return false;
61 }
62 } catch (Exception e) {
63 log.error("发送企业微信消息时异常!", e);
64 return false;
65 }
66
67 }
68
69 /**
70 * 获取部门列表
71 *
72 * @param departmentId 部门id。获取指定部门及其下的子部门(以及子部门的子部门等等,递归)。 如果不填,默认获取全量组织架构
73 * @return 部门列表
74 */
75 public static synchronized List<Department> listDepartments(Config config, Integer departmentId) {
76 WorkAccessToken token = getOrFlushToken(config);
77 if (token == null) {
78 log.info("获取企业微信部门列表-获取 TOEKN 为空!");
79 return null;
80 }
81 String url = String.format(config.getListDepartmentUrl(), token.getAccessToken(), departmentId);
82 try (
83 Response response = OKHTTP_CLIENT.newCall(new Request.Builder().url(url).build()).execute()
84 ) {
85 ResponseBody body = response.body();
86 if (Objects.isNull(body)) {
87 log.info("获取企业微信部门列表-返回结果为空!");
88 return null;
89 }
90 String resp = body.string();
91 log.info("listDepartmentUrl:{}", url);
92 log.info("listDepartmentRes:{}", resp);
93 if (!StringUtils.isNotBlank(resp)) {
94 log.info("获取企业微信部门列表-返回结果为空!");
95 return null;
96 }
97 try {
98 JSONObject jsonObject = JSONObject.parseObject(resp);
99 if (jsonObject == null || !jsonObject.containsKey("department") || jsonObject.getInteger("errcode") != 0) {
100 log.info("获取企业微信部门列表-返回结果为空!");
101 return null;
102 }
103 return jsonObject.getJSONArray("department").toJavaList(Department.class);
104 } catch (Exception e) {
105 log.error("获取企业微信部门列表时异常!", e);
106 return null;
107 }
108 } catch (Exception e) {
109 log.error("获取企业微信部门列表时异常!", e);
110 return null;
111 }
112 }
113
114 /**
115 * 获取部门成员列表
116 *
117 * @param departmentId 部门编号
118 * @return 用户列表
119 */
120 public static synchronized List<User> listUsersOfDepartment(Config config, Integer departmentId) {
121 WorkAccessToken token = getOrFlushToken(config);
122 if (token == null) {
123 log.info("获取企业微信部门成员列表-获取 TOEKN 为空!");
124 return null;
125 }
126 String url = String.format(config.getListUserOfDepartmentUrl(), token.getAccessToken(), departmentId);
127 try (
128 Response response = OKHTTP_CLIENT.newCall(new Request.Builder().url(url).build()).execute()
129 ) {
130 ResponseBody body = response.body();
131 if (Objects.isNull(body)) {
132 log.info("获取企业微信部门成员列表-返回结果为空!");
133 return null;
134 }
135 String resp = body.string();
136 log.info("listUserOfDepartmentUrl:{}", url);
137 log.info("listUserOfDepartmentRes:{}", resp);
138 if (!StringUtils.isNotBlank(resp)) {
139 log.info("获取企业微信部门成员列表-返回结果为空!");
140 return null;
141 }
142 try {
143 JSONObject jsonObject = JSONObject.parseObject(resp);
144 if (jsonObject == null || !jsonObject.containsKey("userlist") || jsonObject.getInteger("errcode") != 0) {
145 log.info("获取企业微信部门成员列表-返回结果为空!");
146 return null;
147 }
148 return jsonObject.getJSONArray("userlist").toJavaList(User.class);
149 } catch (Exception e) {
150 log.error("获取企业微信部门成员列表时异常!", e);
151 return null;
152 }
153 } catch (Exception e) {
154 log.error("获取企业微信部门成员列表时异常!", e);
155 return null;
156 }
157 }
158
159
160 /**
161 * 获取或刷新token <br>
162 * 1. 从缓存中获取token <br>
163 * 2. 如果 token 存在且未过期, 直接返回token <br>
164 * 3. 如果 token 不存在或已过期, 请求企业微信获取新的token <br>
165 * 3. 将获取到的token存入缓存 <br>
166 * 4. 返回token
167 * @return token
168 */
169 private static WorkAccessToken getOrFlushToken(Config config) {
170 MapApi tokenCacheMap = FerryCache.proManager().map(CACHE_KEY);
171 WorkAccessToken token = (WorkAccessToken) tokenCacheMap.hashGet(TOKEN_KEY);
172 if (token != null && token.isUnExpired()) return token;
173 synchronized (WeChatUtil.class) {
174 try {
175 token = (WorkAccessToken) tokenCacheMap.hashGet(TOKEN_KEY);
176 if (token != null && token.isUnExpired()) return token;
177 token = requestToken(config);
178 if (token != null) {
179 tokenCacheMap.hashSet(TOKEN_KEY, token);
180 }
181 } catch (Exception e) {
182 log.error("获取或刷新token时异常!", e);
183 }
184 }
185 return token;
186 }
187
188 /**
189 * 请求企业微信获取token
190 * @return access token
191 */
192 private static WorkAccessToken requestToken(Config config) {
193 String url = String.format(config.getAccessTokenUrl(), config.getCorpId(), config.getSecret());
194 String resp;
195 try (
196 Response response = OKHTTP_CLIENT.newCall(new Request.Builder().url(url).build()).execute()
197 ) {
198 ResponseBody body = response.body();
199 if (Objects.isNull(body)) {
200 log.error("请求企业微信[url:" + url + "]获取ACCESS_TOKEN异常!");
201 return null;
202 }
203 resp = body.string();
204 log.info("accessTokenUrl:{}", url);
205 log.info("accessTokenRes:{}", resp);
206 } catch (Exception e) {
207 log.error("请求企业微信[url:{}]获取ACCESS_TOKEN异常!", url, e);
208 return null;
209 }
210 if (!StringUtils.isNotBlank(resp)) {
211 log.info("请求企业微信获取ACCESS_TOKEN为空!");
212 return null;
213 }
214
215 try {
216 WorkAccessToken token = OBJECT_MAPPER.readValue(resp, WorkAccessToken.class);
217 if (token == null || !token.isSuccess()) {
218 log.info("请求企业微信获取ACCESS_TOKEN失败!-TOKEN:{}", resp);
219 return null;
220 }
221 return token;
222 } catch (JsonProcessingException e) {
223 log.error("请求企业微信[url:{}]获取ACCESS_TOKEN异常!", url, e);
224 return null;
225 }
226 }
227
228
229 /**
230 * 企业微信token
231 */
232 private static class WorkAccessToken implements Serializable {
233 private static final long serialVersionUID = -1704884833558543917L;
234 /**
235 * 数据示例
236 * {
237 * "errcode": 0,
238 * "errmsg": "ok",
239 * "access_token": "hyVmxRCyYUAH0...",
240 * "expires_in": 7200
241 * }
242 */
243 //成功状态码
244 private static final Integer SUCCESS_CODE = 0;
245 private static final Integer PERIOD = 60;//token 过期缓冲时间-s
246
247 /**
248 * 状态码
249 * 出错返回码,为0表示成功,非0表示调用失败
250 *
251 * @see "https://elinkuat.spic.com.cn/api/doc#10649"
252 */
253 @JsonProperty("errcode")
254 private Integer errCode;
255 //消息 返回码提示语
256 @JsonProperty("errmsg")
257 private String errMsg;
258 //token 获取到的凭证,最长为512字节
259 @JsonProperty("access_token")
260 private String accessToken;
261 //过期时间 凭证的有效时间(秒)
262 @JsonProperty("expires_in")
263 private Long expiresIn;
264 //创建时间 秒
265 private Long createTime = TimeUnit.SECONDS.toSeconds(System.currentTimeMillis());
266
267 //是否成功
268 public boolean isSuccess() {
269 return this.errCode != null && this.errCode.equals(SUCCESS_CODE);
270 }
271
272 //是否过期-> true 未过期 false 过期
273 public boolean isUnExpired() {
274 return TimeUnit.SECONDS.toSeconds(System.currentTimeMillis()) + PERIOD
275 <= (expiresIn + createTime);
276 }
277
278 /**********************************get & set******************************/
279
280 public Integer getErrCode() {
281 return errCode;
282 }
283
284 public void setErrCode(Integer errCode) {
285 this.errCode = errCode;
286 }
287
288 public String getErrMsg() {
289 return errMsg;
290 }
291
292 public void setErrMsg(String errMsg) {
293 this.errMsg = errMsg;
294 }
295
296 public String getAccessToken() {
297 return accessToken;
298 }
299
300 public void setAccessToken(String accessToken) {
301 this.accessToken = accessToken;
302 }
303
304 public Long getExpiresIn() {
305 return expiresIn;
306 }
307
308 public void setExpiresIn(Long expiresIn) {
309 this.expiresIn = expiresIn;
310 }
311
312 public Long getCreateTime() {
313 return createTime;
314 }
315
316 public void setCreateTime(Long createTime) {
317 this.createTime = createTime;
318 }
319
320 @Override
321 public boolean equals(Object o) {
322 if (this == o) return true;
323 if (o == null || getClass() != o.getClass()) return false;
324 WorkAccessToken that = (WorkAccessToken) o;
325 return Objects.equals(errCode, that.errCode) && Objects.equals(errMsg, that.errMsg) && Objects.equals(accessToken, that.accessToken) && Objects.equals(expiresIn, that.expiresIn) && Objects.equals(createTime, that.createTime);
326 }
327
328 @Override
329 public int hashCode() {
330 return Objects.hash(errCode, errMsg, accessToken, expiresIn, createTime);
331 }
332
333 @Override
334 public String toString() {
335 return "WorkAccessToken{" +
336 "errCode=" + errCode +
337 ", errMsg='" + errMsg + '\'' +
338 ", accessToken='" + accessToken + '\'' +
339 ", expiresIn=" + expiresIn +
340 ", createTime=" + createTime +
341 '}';
342 }
343 }
344
345 /**
346 * 企业微信消息返回
347 */
348 private static class WorkMsgResp implements Serializable {
349 /**
350 * {
351 * "errcode" : 0,
352 * "errmsg" : "ok",
353 * "invaliduser" : "UserID1", // 不区分大小写,返回的列表都统一转为小写
354 * "invalidparty" : "PartyID1",
355 * "msgid" : "xxxxxxxxxx"
356 * }
357 */
358
359 //成功状态码
360 private static final Integer SUCCESS_CODE = 0;
361
362 /**
363 * 状态码
364 * 出错返回码,为0表示成功,非0表示调用失败
365 *
366 * @see "https://elinkuat.spic.com.cn/api/doc#10649"
367 */
368 @JsonProperty("errcode")
369 private Integer errCode;
370 //消息 返回码提示语
371 @JsonProperty("errmsg")
372 private String errMsg;
373
374 @JsonProperty("invaliduser")
375 private String invalidUser;
376
377 @JsonProperty("invalidparty")
378 private String invalidParty;
379
380 @JsonProperty("msgid")
381 private String msgId;
382
383 //是否成功
384 public boolean isSuccess() {
385 return this.errCode != null && this.errCode.equals(SUCCESS_CODE);
386 }
387
388 /**********************************get & set******************************/
389 public String getErrMsg() {
390 return errMsg;
391 }
392
393 public void setErrMsg(String errMsg) {
394 this.errMsg = errMsg;
395 }
396
397 public String getInvalidUser() {
398 return invalidUser;
399 }
400
401 public void setInvalidUser(String invalidUser) {
402 this.invalidUser = invalidUser;
403 }
404
405 public String getInvalidParty() {
406 return invalidParty;
407 }
408
409 public void setInvalidParty(String invalidParty) {
410 this.invalidParty = invalidParty;
411 }
412
413 public String getMsgId() {
414 return msgId;
415 }
416
417 public void setMsgId(String msgId) {
418 this.msgId = msgId;
419 }
420
421 @Override
422 public String toString() {
423 return "WorkResp{" +
424 "errCode=" + errCode +
425 ", errMsg='" + errMsg + '\'' +
426 ", invalidUser='" + invalidUser + '\'' +
427 ", invalidParty='" + invalidParty + '\'' +
428 ", msgId='" + msgId + '\'' +
429 '}';
430 }
431 }
432
433 /**
434 * 企业微信部门
435 */
436 public static class Department implements Serializable {
437 // {
438// "id": 2,
439// "name": "广州研发中心",
440// "name_en": "RDGZ",
441// "department_leader":["zhangsan","lisi"],
442// "parentid": 1,
443// "order": 10
444// }
445
446 /**
447 * 部门编号
448 */
449 @JsonProperty("id")
450 private Integer id;
451 /**
452 * 部门名称
453 */
454 @JsonProperty("name")
455 private String name;
456 /**
457 * 部门名称(英文)
458 */
459 @JsonProperty("name_en")
460 private String nameEn;
461 /**
462 * 部门名称(英文)
463 */
464 @JsonProperty("department_leader")
465 private List<String> departmentLeader;
466 /**
467 * 父元素编号
468 */
469 @JsonProperty("parentid")
470 private Integer parentId;
471 /**
472 * 排序编号
473 */
474 @JsonProperty("order")
475 private Integer order;
476
477 public Integer getId() {
478 return id;
479 }
480
481 public void setId(Integer id) {
482 this.id = id;
483 }
484
485 public String getName() {
486 return name;
487 }
488
489 public void setName(String name) {
490 this.name = name;
491 }
492
493 public String getNameEn() {
494 return nameEn;
495 }
496
497 public void setNameEn(String nameEn) {
498 this.nameEn = nameEn;
499 }
500
501 public List<String> getDepartmentLeader() {
502 return departmentLeader;
503 }
504
505 public void setDepartmentLeader(List<String> departmentLeader) {
506 this.departmentLeader = departmentLeader;
507 }
508
509 public Integer getParentId() {
510 return parentId;
511 }
512
513 public void setParentId(Integer parentId) {
514 this.parentId = parentId;
515 }
516
517 public Integer getOrder() {
518 return order;
519 }
520
521 public void setOrder(Integer order) {
522 this.order = order;
523 }
524
525 @Override
526 public boolean equals(Object o) {
527 if (this == o) return true;
528 if (o == null || getClass() != o.getClass()) return false;
529 Department that = (Department) o;
530 return Objects.equals(id, that.id) && Objects.equals(name, that.name) && Objects.equals(nameEn, that.nameEn) && Objects.equals(departmentLeader, that.departmentLeader) && Objects.equals(parentId, that.parentId) && Objects.equals(order, that.order);
531 }
532
533 @Override
534 public int hashCode() {
535 return Objects.hash(id, name, nameEn, departmentLeader, parentId, order);
536 }
537
538 @Override
539 public String toString() {
540 return "Department{" +
541 "id=" + id +
542 ", name='" + name + '\'' +
543 ", nameEn='" + nameEn + '\'' +
544 ", departmentLeader=" + departmentLeader +
545 ", parentId=" + parentId +
546 ", order=" + order +
547 '}';
548 }
549 }
550
551 /**
552 * 企业微信用户
553 */
554 public static class User implements Serializable {
555// {
556// "userid": "zhangsan",
557// "name": "张三",
558// "department": [1, 2],
559// "open_userid": "xxxxxx"
560// }
561
562 /**
563 * 成员UserID。对应管理端的帐号
564 */
565 @JsonProperty("userid")
566 private String userId;
567 /**
568 * 成员名称
569 */
570 @JsonProperty("name")
571 private String name;
572 /**
573 * 成员所属部门id列表
574 */
575 @JsonProperty("department")
576 private List<Integer> department;
577 /**
578 * 全局唯一。对于同一个服务商,不同应用获取到企业内同一个成员的open_userid是相同的,最多64个字节。仅第三方应用可获取
579 */
580 @JsonProperty("open_userid")
581 private String openUserId;
582
583 public String getUserId() {
584 return userId;
585 }
586
587 public void setUserId(String userId) {
588 this.userId = userId;
589 }
590
591 public String getName() {
592 return name;
593 }
594
595 public void setName(String name) {
596 this.name = name;
597 }
598
599 public List<Integer> getDepartment() {
600 return department;
601 }
602
603 public void setDepartment(List<Integer> department) {
604 this.department = department;
605 }
606
607 public String getOpenUserId() {
608 return openUserId;
609 }
610
611 public void setOpenUserId(String openUserId) {
612 this.openUserId = openUserId;
613 }
614
615 @Override
616 public boolean equals(Object o) {
617 if (this == o) return true;
618 if (o == null || getClass() != o.getClass()) return false;
619 User user = (User) o;
620 return Objects.equals(userId, user.userId) && Objects.equals(name, user.name) && Objects.equals(department, user.department) && Objects.equals(openUserId, user.openUserId);
621 }
622
623 @Override
624 public int hashCode() {
625 return Objects.hash(userId, name, department, openUserId);
626 }
627
628 @Override
629 public String toString() {
630 return "User{" +
631 "userId='" + userId + '\'' +
632 ", name='" + name + '\'' +
633 ", department=" + department +
634 ", openUserId='" + openUserId + '\'' +
635 '}';
636 }
637 }
638
639 /**
640 * 企业微信配置
641 */
642 public static class Config extends BaseConfigBean implements Serializable {
643 private String secret;
644 private String corpId;
645 private String agentId;
646 // https://10.252.1.8/cgi-bin/gettoken?corpid=%s&corpsecret=%s
647 private String accessTokenUrl; //获取access_token
648 // https://10.252.1.8/cgi-bin/message/send?access_token=%s
649 private String messageSendUrl;//给企业微信成员发送消息
650 // https://qyapi.weixin.qq.com/cgi-bin/department/list?access_token=%s&id=%s
651 private String listDepartmentUrl; // 列举部门列表
652 // https://qyapi.weixin.qq.com/cgi-bin/user/simplelist?access_token=ACCESS_TOKEN&department_id=DEPARTMENT_ID
653 private String listUserOfDepartmentUrl; // 获取部门成员
654
655 public String getSecret() {
656 return secret;
657 }
658
659 public void setSecret(String secret) {
660 this.secret = secret;
661 }
662
663 public String getCorpId() {
664 return corpId;
665 }
666
667 public void setCorpId(String corpId) {
668 this.corpId = corpId;
669 }
670
671 public String getAccessTokenUrl() {
672 return accessTokenUrl;
673 }
674
675 public void setAccessTokenUrl(String accessTokenUrl) {
676 this.accessTokenUrl = accessTokenUrl;
677 }
678
679 public String getMessageSendUrl() {
680 return messageSendUrl;
681 }
682
683 public void setMessageSendUrl(String messageSendUrl) {
684 this.messageSendUrl = messageSendUrl;
685 }
686
687 public String getListDepartmentUrl() {
688 return listDepartmentUrl;
689 }
690
691 public void setListDepartmentUrl(String listDepartmentUrl) {
692 this.listDepartmentUrl = listDepartmentUrl;
693 }
694
695 public String getListUserOfDepartmentUrl() {
696 return listUserOfDepartmentUrl;
697 }
698
699 public void setListUserOfDepartmentUrl(String listUserOfDepartmentUrl) {
700 this.listUserOfDepartmentUrl = listUserOfDepartmentUrl;
701 }
702
703 public String getAgentId() {
704 return agentId;
705 }
706
707 public void setAgentId(String agentId) {
708 this.agentId = agentId;
709 }
710
711 @Override
712 public boolean equals(Object o) {
713 if (this == o) return true;
714 if (o == null || getClass() != o.getClass()) return false;
715 Config config = (Config) o;
716 return Objects.equals(secret, config.secret) && Objects.equals(corpId, config.corpId) && Objects.equals(agentId, config.agentId) && Objects.equals(accessTokenUrl, config.accessTokenUrl) && Objects.equals(messageSendUrl, config.messageSendUrl) && Objects.equals(listDepartmentUrl, config.listDepartmentUrl) && Objects.equals(listUserOfDepartmentUrl, config.listUserOfDepartmentUrl);
717 }
718
719 @Override
720 public int hashCode() {
721 return Objects.hash(secret, corpId, agentId, accessTokenUrl, messageSendUrl, listDepartmentUrl, listUserOfDepartmentUrl);
722 }
723
724 @Override
725 public String toString() {
726 return "Config{" +
727 "secret='" + secret + '\'' +
728 ", corpId='" + corpId + '\'' +
729 ", agentId='" + agentId + '\'' +
730 ", accessTokenUrl='" + accessTokenUrl + '\'' +
731 ", messageSendUrl='" + messageSendUrl + '\'' +
732 ", listDepartmentUrl='" + listDepartmentUrl + '\'' +
733 ", listUserOfDepartmentUrl='" + listUserOfDepartmentUrl + '\'' +
734 '}';
735 }
736 }
737
738// public static void main(String[] args) {
739// WeChatUtil weChatUtil = new WeChatUtil();
740// Config config = new Config();
741// config.secret = "6eqa8hRx9DEUR6ecbnkGtuLk8cjrw4TgGM1Oh6ygBh4";
742// config.corpId = "ww7660541f094cb144";
743// config.accessTokenUrl = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s";
744// config.messageSendUrl = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=%s";
745// config.listDepartmentUrl = "https://qyapi.weixin.qq.com/cgi-bin/department/list?access_token=%s&id=%s";
746
747// weChatUtil.config = config;
748// List<Department> departments = weChatUtil.listDepartments(null);
749// System.out.println(departments);
750// List<User> users = weChatUtil.listUsersOfDepartment(1);
751// System.out.println(users);
752// }
753}
754