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) { } }