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.common.bean.result.WxMediaUploadResult;
|
import me.chanjar.weixin.cp.bean.article.NewArticle;
|
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");
|
}
|
|
@Override
|
public WxCpPushResultVO pushNews(String title, String description, String url, String picUrl,
|
Integer scope, String target) {
|
if (StrUtil.isBlank(url)) {
|
throw new IllegalArgumentException("推送图文消息时 url 不能为空");
|
}
|
NewArticle article = NewArticle.builder()
|
.title(title)
|
.description(StrUtil.blankToDefault(description, ""))
|
.url(url)
|
.picUrl(picUrl)
|
.build();
|
WxCpMessage message = WxCpMessage.NEWS()
|
.agentId(agentId())
|
.addArticle(article)
|
.build();
|
return doSend(message, scope, target, "news");
|
}
|
|
@Override
|
public WxCpPushResultVO pushFile(java.io.File file, String fileName, String textContent,
|
Integer scope, String target) {
|
if (file == null || !file.exists()) {
|
return new WxCpPushResultVO(false, null, "文件不存在,无法推送", null, null);
|
}
|
try {
|
// 1. 上传文件到企业微信,换取 mediaId
|
WxMediaUploadResult upload = wxCpService.getMediaService().upload("file", file);
|
if (upload == null || StrUtil.isBlank(upload.getMediaId())) {
|
return new WxCpPushResultVO(false, null, "文件上传企业微信失败", null, null);
|
}
|
// 2. 先发送说明文本,再发送文件;文本失败不阻塞文件发送
|
if (StrUtil.isNotBlank(textContent)) {
|
pushText(textContent, scope, target);
|
}
|
// 3. 发送文件消息
|
WxCpMessage message = WxCpMessage.FILE()
|
.agentId(agentId())
|
.mediaId(upload.getMediaId())
|
.build();
|
WxCpPushResultVO result = doSend(message, scope, target, "file");
|
log.info("[pushFile][文件推送完成 fileName={} success={}]", fileName, result.getSuccess());
|
return result;
|
} catch (Exception e) {
|
log.error("[pushFile][文件上传或推送异常 fileName={}]", fileName, e);
|
return new WxCpPushResultVO(false, null, "文件推送异常:" + e.getMessage(), null, null);
|
}
|
}
|
|
@Override
|
public WxCpPushResultVO pushImage(java.io.File file, Integer scope, String target) {
|
if (file == null || !file.exists()) {
|
return new WxCpPushResultVO(false, null, "图片文件不存在,无法推送", null, null);
|
}
|
try {
|
// 企业微信图片消息要求先用 image 类型上传换取 mediaId
|
WxMediaUploadResult upload = wxCpService.getMediaService().upload("image", file);
|
if (upload == null || StrUtil.isBlank(upload.getMediaId())) {
|
return new WxCpPushResultVO(false, null, "图片上传企业微信失败", null, null);
|
}
|
WxCpMessage message = WxCpMessage.IMAGE()
|
.agentId(agentId())
|
.mediaId(upload.getMediaId())
|
.build();
|
return doSend(message, scope, target, "image");
|
} catch (Exception e) {
|
log.error("[pushImage][图片上传或推送异常 file={}]", file.getName(), e);
|
return new WxCpPushResultVO(false, null, "图片推送异常:" + e.getMessage(), null, null);
|
}
|
}
|
|
/**
|
* 统一处理接收者解析、发送与错误包装
|
*/
|
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) {
|
}
|
|
}
|