package cn.iocoder.yudao.module.wxcp.service.notice;
|
|
import cn.hutool.core.util.StrUtil;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
import cn.iocoder.yudao.module.wxcp.controller.admin.notice.vo.WxCpNoticePageReqVO;
|
import cn.iocoder.yudao.module.wxcp.controller.admin.notice.vo.WxCpNoticeSaveReqVO;
|
import cn.iocoder.yudao.module.wxcp.controller.admin.vo.WxCpPushReqVO;
|
import cn.iocoder.yudao.module.wxcp.controller.admin.vo.WxCpPushResultVO;
|
import cn.iocoder.yudao.module.wxcp.dal.dataobject.notice.WxCpNoticeDO;
|
import cn.iocoder.yudao.module.wxcp.dal.mysql.notice.WxCpNoticeMapper;
|
import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpPushScopeEnum;
|
import cn.iocoder.yudao.module.wxcp.enums.notice.WxCpNoticeStatusEnum;
|
import cn.iocoder.yudao.module.wxcp.enums.notice.WxCpNoticeTypeEnum;
|
import cn.iocoder.yudao.module.wxcp.service.push.WxCpMessagePushService;
|
import cn.iocoder.yudao.module.system.dal.dataobject.storage.SystemStorageBlobDO;
|
import cn.iocoder.yudao.module.system.service.storage.SystemStorageBlobService;
|
import jakarta.annotation.Resource;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.stereotype.Service;
|
import org.springframework.validation.annotation.Validated;
|
|
import java.io.File;
|
import java.time.LocalDateTime;
|
import java.util.Objects;
|
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static cn.iocoder.yudao.module.wxcp.enums.ErrorCodeConstants.*;
|
|
/**
|
* 企业微信通知 Service 实现类
|
*
|
* @author 超级管理员
|
*/
|
@Service
|
@Validated
|
@Slf4j
|
public class WxCpNoticeServiceImpl implements WxCpNoticeService {
|
|
/**
|
* 企业微信文本消息上限约 2048 字节,中文按 3 字节算。
|
* 这里按字符数留出足够余量,超出部分截断并在末尾提示,避免整条推送失败。
|
*/
|
private static final int MAX_PUSH_CONTENT_LENGTH = 600;
|
|
@Resource
|
private WxCpNoticeMapper wxcpNoticeMapper;
|
@Resource
|
private WxCpMessagePushService messagePushService;
|
@Resource
|
private SystemStorageBlobService storageBlobService;
|
|
/** 企业微信文件消息上限 20MB */
|
private static final long MAX_FILE_SIZE = 20L * 1024L * 1024L;
|
|
@Override
|
public Long createNotice(WxCpNoticeSaveReqVO createReqVO) {
|
WxCpNoticeDO notice = BeanUtils.toBean(createReqVO, WxCpNoticeDO.class);
|
if (notice.getNoticeType() == null) {
|
notice.setNoticeType(WxCpNoticeTypeEnum.TEXT.getType());
|
}
|
notice.setStatus(WxCpNoticeStatusEnum.DRAFT.getStatus());
|
notice.setPushStatus(0);
|
// 推送范围选填,不传默认全员;推送时还能在弹窗里改
|
if (notice.getPushScope() == null) {
|
notice.setPushScope(WxCpPushScopeEnum.ALL.getScope());
|
}
|
wxcpNoticeMapper.insert(notice);
|
return notice.getId();
|
}
|
|
@Override
|
public void updateNotice(WxCpNoticeSaveReqVO updateReqVO) {
|
WxCpNoticeDO notice = validateNoticeExists(updateReqVO.getId());
|
if (Objects.equals(notice.getStatus(), WxCpNoticeStatusEnum.REVOKED.getStatus())) {
|
throw exception(NOTICE_CANNOT_UPDATE);
|
}
|
WxCpNoticeDO updateObj = BeanUtils.toBean(updateReqVO, WxCpNoticeDO.class);
|
// 状态与推送信息由系统维护,不允许前端覆盖
|
updateObj.setStatus(null);
|
updateObj.setPushStatus(null);
|
updateObj.setPushTime(null);
|
wxcpNoticeMapper.updateById(updateObj);
|
}
|
|
@Override
|
public void deleteNotice(Long id) {
|
validateNoticeExists(id);
|
wxcpNoticeMapper.deleteById(id);
|
}
|
|
@Override
|
public WxCpNoticeDO getNotice(Long id) {
|
return wxcpNoticeMapper.selectById(id);
|
}
|
|
@Override
|
public PageResult<WxCpNoticeDO> getNoticePage(WxCpNoticePageReqVO pageReqVO) {
|
return wxcpNoticeMapper.selectPage(pageReqVO);
|
}
|
|
@Override
|
public void publishNotice(Long id) {
|
WxCpNoticeDO notice = validateNoticeExists(id);
|
if (Objects.equals(notice.getStatus(), WxCpNoticeStatusEnum.PUBLISHED.getStatus())) {
|
return; // 已发布,幂等返回
|
}
|
if (Objects.equals(notice.getStatus(), WxCpNoticeStatusEnum.REVOKED.getStatus())) {
|
throw exception(NOTICE_STATUS_REVOKED);
|
}
|
wxcpNoticeMapper.updateById(WxCpNoticeDO.builder()
|
.id(id).status(WxCpNoticeStatusEnum.PUBLISHED.getStatus()).build());
|
}
|
|
@Override
|
public void revokeNotice(Long id) {
|
validateNoticeExists(id);
|
wxcpNoticeMapper.updateById(WxCpNoticeDO.builder()
|
.id(id).status(WxCpNoticeStatusEnum.REVOKED.getStatus()).build());
|
}
|
|
@Override
|
public WxCpPushResultVO pushNotice(WxCpPushReqVO pushReqVO) {
|
WxCpNoticeDO notice = validateNoticeExists(pushReqVO.getId());
|
if (!Objects.equals(notice.getStatus(), WxCpNoticeStatusEnum.PUBLISHED.getStatus())) {
|
throw exception(NOTICE_STATUS_NOT_PUBLISHED);
|
}
|
|
Integer scope = pushReqVO.getPushScope() != null ? pushReqVO.getPushScope() : notice.getPushScope();
|
String target = StrUtil.isNotBlank(pushReqVO.getPushTarget())
|
? pushReqVO.getPushTarget() : notice.getPushTarget();
|
if (scope == null) {
|
scope = WxCpPushScopeEnum.ALL.getScope();
|
}
|
|
// 推送时允许重新选择模板类型;未选择则沿用通知上保存的模板
|
Integer noticeType = pushReqVO.getNoticeType() != null
|
? pushReqVO.getNoticeType()
|
: (notice.getNoticeType() != null ? notice.getNoticeType() : WxCpNoticeTypeEnum.TEXT.getType());
|
String summary = StrUtil.blankToDefault(pushReqVO.getSummary(), notice.getSummary());
|
String linkUrl = StrUtil.blankToDefault(pushReqVO.getLinkUrl(), notice.getLinkUrl());
|
String linkBtnText = StrUtil.blankToDefault(pushReqVO.getLinkBtnText(), notice.getLinkBtnText());
|
String coverUrl = StrUtil.blankToDefault(pushReqVO.getCoverUrl(), notice.getCoverUrl());
|
Long fileBlobId = pushReqVO.getFileBlobId() != null ? pushReqVO.getFileBlobId() : notice.getFileBlobId();
|
String fileName = StrUtil.blankToDefault(pushReqVO.getFileName(), notice.getFileName());
|
|
// 按本次选择的模板类型校验必填项
|
if (Objects.equals(noticeType, WxCpNoticeTypeEnum.TEXT.getType())
|
&& StrUtil.isBlank(notice.getContent())) {
|
throw exception(NOTICE_CONTENT_REQUIRED);
|
}
|
if ((Objects.equals(noticeType, WxCpNoticeTypeEnum.CARD.getType())
|
|| Objects.equals(noticeType, WxCpNoticeTypeEnum.NEWS.getType()))
|
&& StrUtil.isBlank(linkUrl)) {
|
throw exception(NOTICE_LINK_URL_REQUIRED);
|
}
|
if (Objects.equals(noticeType, WxCpNoticeTypeEnum.FILE.getType()) && fileBlobId == null) {
|
throw exception(NOTICE_FILE_REQUIRED);
|
}
|
|
WxCpPushResultVO result;
|
if (Objects.equals(noticeType, WxCpNoticeTypeEnum.CARD.getType())) {
|
result = messagePushService.pushCard(notice.getTitle(),
|
StrUtil.blankToDefault(summary, notice.getContent()),
|
linkUrl, StrUtil.blankToDefault(linkBtnText, "去填报"), scope, target);
|
} else if (Objects.equals(noticeType, WxCpNoticeTypeEnum.FILE.getType())) {
|
result = pushFileNotice(fileBlobId, fileName, notice, scope, target);
|
} else if (Objects.equals(noticeType, WxCpNoticeTypeEnum.NEWS.getType())) {
|
result = messagePushService.pushNews(notice.getTitle(),
|
StrUtil.blankToDefault(summary, notice.getContent()),
|
linkUrl, coverUrl, scope, target);
|
} else {
|
result = messagePushService.pushText(buildPushContent(notice), scope, target);
|
}
|
|
if (Boolean.TRUE.equals(result.getSuccess())) {
|
wxcpNoticeMapper.updateById(WxCpNoticeDO.builder()
|
.id(notice.getId())
|
.pushScope(scope)
|
.pushTarget(target)
|
.pushStatus(1)
|
.pushTime(LocalDateTime.now())
|
.build());
|
}
|
return result;
|
}
|
|
/**
|
* 文件通知:从系统附件中心取文件,交给推送服务上传企业微信并发送
|
*/
|
private WxCpPushResultVO pushFileNotice(Long fileBlobId, String fileName, WxCpNoticeDO notice,
|
Integer scope, String target) {
|
if (fileBlobId == null) {
|
throw exception(NOTICE_FILE_REQUIRED);
|
}
|
SystemStorageBlobDO blob = storageBlobService.getStorageBlob(fileBlobId);
|
if (blob == null) {
|
throw exception(NOTICE_FILE_NOT_EXISTS);
|
}
|
if (blob.getByteSize() != null && blob.getByteSize() > MAX_FILE_SIZE) {
|
throw exception(NOTICE_FILE_TOO_LARGE);
|
}
|
File file = storageBlobService.getPublicFile(blob.getUidFilename(), blob.getResourceKey());
|
if (file == null || !file.exists()) {
|
throw exception(NOTICE_FILE_NOT_EXISTS);
|
}
|
String actualFileName = StrUtil.blankToDefault(fileName, blob.getOriginalFilename());
|
return messagePushService.pushFile(file, actualFileName, buildPushContent(notice), scope, target);
|
}
|
|
/**
|
* 按模板类型校验必填项
|
*/
|
private void validateNoticeByType(WxCpNoticeSaveReqVO reqVO) {
|
Integer type = reqVO.getNoticeType() != null
|
? reqVO.getNoticeType() : WxCpNoticeTypeEnum.TEXT.getType();
|
if (Objects.equals(type, WxCpNoticeTypeEnum.TEXT.getType())
|
&& StrUtil.isBlank(reqVO.getContent())) {
|
throw exception(NOTICE_CONTENT_REQUIRED);
|
}
|
if ((Objects.equals(type, WxCpNoticeTypeEnum.CARD.getType())
|
|| Objects.equals(type, WxCpNoticeTypeEnum.NEWS.getType()))
|
&& StrUtil.isBlank(reqVO.getLinkUrl())) {
|
throw exception(NOTICE_LINK_URL_REQUIRED);
|
}
|
if (Objects.equals(type, WxCpNoticeTypeEnum.FILE.getType())
|
&& reqVO.getFileBlobId() == null) {
|
throw exception(NOTICE_FILE_REQUIRED);
|
}
|
}
|
|
/**
|
* 拼装推送正文:标题加粗样式用不了,改用【】包裹;过长则截断并提示
|
*/
|
private String buildPushContent(WxCpNoticeDO notice) {
|
String body = StrUtil.blankToDefault(notice.getContent(), "");
|
String content = "【" + notice.getTitle() + "】\n" + body;
|
if (content.length() > MAX_PUSH_CONTENT_LENGTH) {
|
content = content.substring(0, MAX_PUSH_CONTENT_LENGTH)
|
+ "\n……(内容过长已截断,请到系统内查看完整通知)";
|
}
|
return content;
|
}
|
|
private WxCpNoticeDO validateNoticeExists(Long id) {
|
if (id == null) {
|
throw exception(NOTICE_NOT_EXISTS);
|
}
|
WxCpNoticeDO notice = wxcpNoticeMapper.selectById(id);
|
if (notice == null) {
|
throw exception(NOTICE_NOT_EXISTS);
|
}
|
return notice;
|
}
|
|
}
|