liu
2026-09-30 91491239c5d4611b5b1404a7abeb2bb23d0edad5
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
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) {
    }
 
}