|
|
@@ -18,8 +18,12 @@ import lombok.extern.slf4j.Slf4j;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
import java.time.Duration;
|
|
|
+import java.util.ArrayList;
|
|
|
import java.util.Collections;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.LinkedHashMap;
|
|
|
import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
/**
|
|
|
@@ -44,39 +48,93 @@ public class EnterpriseWechatMsgService {
|
|
|
/** 给指定企微用户ID列表发送文本消息;返回是否被企微接口成功接收 */
|
|
|
public boolean sendTextToUsers(List<String> wecomUserIds, String content) {
|
|
|
if (wecomUserIds == null || wecomUserIds.isEmpty()) return false;
|
|
|
- String accessToken = this.getCorpAccessToken();
|
|
|
- if (accessToken == null) {
|
|
|
- log.warn("[企微消息] 无法获取 access_token");
|
|
|
- return false;
|
|
|
- }
|
|
|
- try {
|
|
|
- EnterpriseWechatSendTextMsgReq req = EnterpriseWechatSendTextMsgReq.toUsers(
|
|
|
- wecomUserIds, wechatProperties.getAgentId(), content);
|
|
|
- EnterpriseWechatSendTextMsgRsp rsp = enterpriseWechatApi.sendTextMsg(accessToken, req);
|
|
|
- return logSendResult(rsp, "文本消息", wecomUserIds.size());
|
|
|
- } catch (Exception e) {
|
|
|
- log.error("[企微消息] 发送失败", e);
|
|
|
- return false;
|
|
|
+ boolean allOk = true;
|
|
|
+ // 多企业部署:接收人按所属企业分组,各自用本企业的 token + agentid 发送
|
|
|
+ for (Map.Entry<String, List<String>> entry : groupUserIdsByCorp(wecomUserIds).entrySet()) {
|
|
|
+ EnterpriseWechatCorp corp = this.getCorpById(entry.getKey());
|
|
|
+ List<String> corpUserIds = entry.getValue();
|
|
|
+ String accessToken = this.getCorpAccessToken(corp);
|
|
|
+ if (accessToken == null) {
|
|
|
+ log.warn("[企微消息] 无法获取企业 {} 的 access_token,跳过 {} 人", entry.getKey(), corpUserIds.size());
|
|
|
+ allOk = false;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ EnterpriseWechatSendTextMsgReq req = EnterpriseWechatSendTextMsgReq.toUsers(
|
|
|
+ corpUserIds, this.resolveAgentId(corp), content);
|
|
|
+ EnterpriseWechatSendTextMsgRsp rsp = enterpriseWechatApi.sendTextMsg(accessToken, req);
|
|
|
+ allOk &= logSendResult(rsp, "文本消息", corpUserIds.size());
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("[企微消息] 发送失败 corpId={}", entry.getKey(), e);
|
|
|
+ allOk = false;
|
|
|
+ }
|
|
|
}
|
|
|
+ return allOk;
|
|
|
}
|
|
|
|
|
|
/** 给指定企微用户ID列表发送文本卡片消息(可点击跳转,用于审批待办/催办);返回是否发送成功 */
|
|
|
public boolean sendTextCardToUsers(List<String> wecomUserIds, String title, String description, String url, String btnText) {
|
|
|
if (wecomUserIds == null || wecomUserIds.isEmpty()) return false;
|
|
|
- String accessToken = this.getCorpAccessToken();
|
|
|
- if (accessToken == null) {
|
|
|
- log.warn("[企微消息] 无法获取 access_token");
|
|
|
- return false;
|
|
|
+ boolean allOk = true;
|
|
|
+ // 多企业部署:接收人按所属企业分组发送,卡片链接携带 corpId 供 H5 免登按企业路由
|
|
|
+ for (Map.Entry<String, List<String>> entry : groupUserIdsByCorp(wecomUserIds).entrySet()) {
|
|
|
+ EnterpriseWechatCorp corp = this.getCorpById(entry.getKey());
|
|
|
+ List<String> corpUserIds = entry.getValue();
|
|
|
+ String accessToken = this.getCorpAccessToken(corp);
|
|
|
+ if (accessToken == null) {
|
|
|
+ log.warn("[企微消息] 无法获取企业 {} 的 access_token,跳过 {} 人", entry.getKey(), corpUserIds.size());
|
|
|
+ allOk = false;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ EnterpriseWechatSendTextCardMsgReq req = EnterpriseWechatSendTextCardMsgReq.toUsers(
|
|
|
+ corpUserIds, this.resolveAgentId(corp), title, description,
|
|
|
+ appendCorpIdToUrl(url, entry.getKey()), btnText);
|
|
|
+ EnterpriseWechatSendTextMsgRsp rsp = enterpriseWechatApi.sendTextCardMsg(accessToken, req);
|
|
|
+ allOk &= logSendResult(rsp, "文本卡片", corpUserIds.size());
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("[企微消息] 文本卡片发送失败 corpId={}", entry.getKey(), e);
|
|
|
+ allOk = false;
|
|
|
+ }
|
|
|
}
|
|
|
- try {
|
|
|
- EnterpriseWechatSendTextCardMsgReq req = EnterpriseWechatSendTextCardMsgReq.toUsers(
|
|
|
- wecomUserIds, wechatProperties.getAgentId(), title, description, url, btnText);
|
|
|
- EnterpriseWechatSendTextMsgRsp rsp = enterpriseWechatApi.sendTextCardMsg(accessToken, req);
|
|
|
- return logSendResult(rsp, "文本卡片", wecomUserIds.size());
|
|
|
- } catch (Exception e) {
|
|
|
- log.error("[企微消息] 文本卡片发送失败", e);
|
|
|
- return false;
|
|
|
+ return allOk;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 接收人按所属企业分组:wecom_user_id → sys_user.corp_id;无记录/为空 → 默认授权企业 */
|
|
|
+ private Map<String, List<String>> groupUserIdsByCorp(List<String> wecomUserIds) {
|
|
|
+ List<String> distinct = wecomUserIds.stream().distinct().collect(Collectors.toList());
|
|
|
+ Map<String, String> userCorpMap = new HashMap<>();
|
|
|
+ sysUserMapper.selectBatchByWecomUserIds(distinct).forEach(u ->
|
|
|
+ userCorpMap.putIfAbsent(u.getWecomUserId(), u.getCorpId()));
|
|
|
+ EnterpriseWechatCorp defaultCorp = this.getAuthorizedCorp();
|
|
|
+ String defaultCorpId = defaultCorp == null ? null : defaultCorp.getCorpId();
|
|
|
+ Map<String, List<String>> grouped = new LinkedHashMap<>();
|
|
|
+ for (String userid : wecomUserIds) {
|
|
|
+ String corpId = userCorpMap.getOrDefault(userid, defaultCorpId);
|
|
|
+ grouped.computeIfAbsent(corpId, k -> new ArrayList<>()).add(userid);
|
|
|
}
|
|
|
+ return grouped;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 按 corp_id 查企业;为空回退默认授权企业 */
|
|
|
+ public EnterpriseWechatCorp getCorpById(String corpId) {
|
|
|
+ if (corpId == null || corpId.isBlank()) {
|
|
|
+ return this.getAuthorizedCorp();
|
|
|
+ }
|
|
|
+ return wechatCorpMapper.selectByCorpId(corpId);
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 发送用的应用 agentid:优先企业自身(代开发各企业 agentid 不同),为空回退全局配置 */
|
|
|
+ public Integer resolveAgentId(EnterpriseWechatCorp corp) {
|
|
|
+ return corp != null && corp.getAgentId() != null ? corp.getAgentId() : wechatProperties.getAgentId();
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 卡片链接追加 corpId 参数(H5 免登按企业取 appid/token 用) */
|
|
|
+ private String appendCorpIdToUrl(String url, String corpId) {
|
|
|
+ if (url == null || corpId == null || corpId.isBlank()) {
|
|
|
+ return url;
|
|
|
+ }
|
|
|
+ return url + (url.contains("?") ? "&" : "?") + "corpId=" + corpId;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -133,9 +191,13 @@ public class EnterpriseWechatMsgService {
|
|
|
return corps.isEmpty() ? null : corps.get(0);
|
|
|
}
|
|
|
|
|
|
- /** 获取企业 access_token(Redis 缓存,TTL 7000s) */
|
|
|
+ /** 获取默认授权企业的 access_token(未绑定企业的用户/内部调用使用) */
|
|
|
public String getCorpAccessToken() {
|
|
|
- EnterpriseWechatCorp corp = this.getAuthorizedCorp();
|
|
|
+ return this.getCorpAccessToken(this.getAuthorizedCorp());
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 获取指定企业的 access_token(Redis 按 corp_id 缓存,TTL 7000s) */
|
|
|
+ public String getCorpAccessToken(EnterpriseWechatCorp corp) {
|
|
|
if (corp == null) return null;
|
|
|
|
|
|
String cacheKey = ACCESS_TOKEN_CACHE_KEY + corp.getCorpId();
|