package cn.iocoder.yudao.module.wxcp.service.push;
|
|
import cn.hutool.core.util.StrUtil;
|
import cn.iocoder.yudao.module.wxcp.controller.admin.vo.WxCpPushResultVO;
|
import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpPushScopeEnum;
|
import cn.iocoder.yudao.module.wxcp.framework.config.WxCpProperties;
|
import jakarta.annotation.Resource;
|
import lombok.extern.slf4j.Slf4j;
|
import me.chanjar.weixin.cp.api.WxCpService;
|
import me.chanjar.weixin.cp.bean.message.WxCpMessage;
|
import me.chanjar.weixin.cp.bean.message.WxCpMessageSendResult;
|
import org.springframework.stereotype.Service;
|
import org.springframework.validation.annotation.Validated;
|
|
import java.util.Objects;
|
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static cn.iocoder.yudao.module.wxcp.enums.ErrorCodeConstants.ACTIVITY_PUSH_TARGET_EMPTY;
|
|
/**
|
* 企业微信消息推送 Service 实现类
|
*
|
* @author 超级管理员
|
*/
|
@Service
|
@Validated
|
@Slf4j
|
public class WxCpMessagePushServiceImpl implements WxCpMessagePushService {
|
|
@Resource
|
private WxCpService wxCpService;
|
@Resource
|
private WxCpProperties wxCpProperties;
|
|
@Override
|
public WxCpPushResultVO pushCard(String title, String description, String url,
|
String btnTxt, Integer scope, String target) {
|
// textcard 类型的 url 必填,留空企业微信会返回 41010
|
if (StrUtil.isBlank(url)) {
|
throw new IllegalArgumentException("推送卡片消息时 url 不能为空");
|
}
|
WxCpMessage message = WxCpMessage.TEXTCARD()
|
.agentId(agentId())
|
.title(title)
|
.description(description)
|
.url(url)
|
.btnTxt(StrUtil.blankToDefault(btnTxt, "查看详情"))
|
.build();
|
return doSend(message, scope, target, "card");
|
}
|
|
@Override
|
public WxCpPushResultVO pushText(String content, Integer scope, String target) {
|
WxCpMessage message = WxCpMessage.TEXT()
|
.agentId(agentId())
|
.content(content)
|
.build();
|
return doSend(message, scope, target, "text");
|
}
|
|
/**
|
* 统一处理接收者解析、发送与错误包装
|
*/
|
private WxCpPushResultVO doSend(WxCpMessage message, Integer scope, String target, String type) {
|
Recipients recipients = resolveRecipients(scope, target);
|
message.setToUser(recipients.toUser);
|
message.setToParty(recipients.toParty);
|
|
WxCpMessageSendResult result;
|
try {
|
result = wxCpService.getMessageService().send(message);
|
} catch (Exception e) {
|
// 网络/配置异常不向上抛,转成可读结果,避免前端拿到 500 不知道原因
|
log.error("[doSend][企业微信消息推送异常 type={} toUser={} toParty={}]",
|
type, recipients.toUser, recipients.toParty, e);
|
return new WxCpPushResultVO(false, null, "推送异常:" + e.getMessage(),
|
recipients.toUser, recipients.toParty);
|
}
|
|
if (result.getErrCode() != null && result.getErrCode() == 0) {
|
log.info("[doSend][企业微信消息推送成功 type={} toUser={} toParty={}]",
|
type, recipients.toUser, recipients.toParty);
|
return new WxCpPushResultVO(true, 0, "推送成功", recipients.toUser, recipients.toParty);
|
}
|
log.warn("[doSend][企业微信消息推送失败 type={} errCode={} errMsg={}]",
|
type, result.getErrCode(), result.getErrMsg());
|
return new WxCpPushResultVO(false, result.getErrCode(),
|
"企业微信返回:" + result.getErrMsg(), recipients.toUser, recipients.toParty);
|
}
|
|
/**
|
* 解析推送范围与目标,转成企业微信要求的 | 分隔格式
|
*/
|
private Recipients resolveRecipients(Integer scope, String target) {
|
int actualScope = scope != null ? scope : WxCpPushScopeEnum.ALL.getScope();
|
if (Objects.equals(actualScope, WxCpPushScopeEnum.ALL.getScope())) {
|
return new Recipients("@all", null);
|
}
|
if (StrUtil.isBlank(target)) {
|
throw exception(ACTIVITY_PUSH_TARGET_EMPTY);
|
}
|
// 同时容忍中英文逗号与竖线,避免配置时手误
|
String normalized = target.replace(",", ",").replace(",", "|").replace(" ", "");
|
if (Objects.equals(actualScope, WxCpPushScopeEnum.PARTY.getScope())) {
|
return new Recipients(null, normalized);
|
}
|
return new Recipients(normalized, null);
|
}
|
|
private Integer agentId() {
|
return StrUtil.isBlank(wxCpProperties.getAgentId())
|
? null : Integer.valueOf(wxCpProperties.getAgentId());
|
}
|
|
private record Recipients(String toUser, String toParty) {
|
}
|
|
}
|