package cn.iocoder.yudao.module.system.service.notify; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.module.infra.api.websocket.WebSocketSenderApi; import cn.iocoder.yudao.module.system.dal.dataobject.notify.NotifyTemplateDO; import cn.iocoder.yudao.module.system.service.notify.dto.NotifyMessagePushDTO; import com.google.common.annotations.VisibleForTesting; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import jakarta.annotation.Resource; import java.util.Map; import java.util.Objects; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; /** * 站内信发送 Service 实现类 * * @author xrcoder */ @Service @Validated @Slf4j public class NotifySendServiceImpl implements NotifySendService { @Resource private NotifyTemplateService notifyTemplateService; @Resource private NotifyMessageService notifyMessageService; @Resource private WebSocketSenderApi webSocketSenderApi; @Override public Long sendSingleNotifyToAdmin(Long userId, String templateCode, Map templateParams) { return sendSingleNotify(userId, UserTypeEnum.ADMIN.getValue(), templateCode, templateParams); } @Override public Long sendSingleNotifyToMember(Long userId, String templateCode, Map templateParams) { return sendSingleNotify(userId, UserTypeEnum.MEMBER.getValue(), templateCode, templateParams); } @Override public Long sendSingleNotify(Long userId, Integer userType, String templateCode, Map templateParams) { // 校验模版 NotifyTemplateDO template = validateNotifyTemplate(templateCode); if (Objects.equals(template.getStatus(), CommonStatusEnum.DISABLE.getStatus())) { log.info("[sendSingleNotify][模版({})已经关闭,无法给用户({}/{})发送]", templateCode, userId, userType); return null; } // 校验参数 validateTemplateParams(template, templateParams); // 发送站内信 String content = notifyTemplateService.formatNotifyTemplateContent(template.getContent(), templateParams); Long messageId = notifyMessageService.createNotifyMessage(userId, userType, template, content, templateParams); // 实时推送:前端铃铛收到后立即刷新未读数,避免只能等待轮询 pushToWebSocket(userId, userType, messageId, template, content); return messageId; } /** * 通过 WebSocket 把站内信实时推送给用户(该用户所有在线页面都会收到) * * 说明:推送只是“提前通知前端刷新”,推送失败不影响站内信本身,因此这里吞掉异常仅记录日志。 */ private void pushToWebSocket(Long userId, Integer userType, Long messageId, NotifyTemplateDO template, String content) { if (messageId == null) { return; } NotifyMessagePushDTO message = new NotifyMessagePushDTO(); message.setMessageId(messageId); message.setTemplateCode(template.getCode()); message.setTemplateNickname(template.getNickname()); message.setTemplateContent(content); try { webSocketSenderApi.sendObject(userType, userId, NotifyMessagePushDTO.MESSAGE_TYPE, message); } catch (Exception ex) { log.warn("[pushToWebSocket][向用户({}/{})推送站内信({})失败]", userId, userType, messageId, ex); } } @VisibleForTesting public NotifyTemplateDO validateNotifyTemplate(String templateCode) { // 获得站内信模板。考虑到效率,从缓存中获取 NotifyTemplateDO template = notifyTemplateService.getNotifyTemplateByCodeFromCache(templateCode); // 站内信模板不存在 if (template == null) { throw exception(NOTICE_NOT_FOUND); } return template; } /** * 校验站内信模版参数是否确实 * * @param template 邮箱模板 * @param templateParams 参数列表 */ @VisibleForTesting public void validateTemplateParams(NotifyTemplateDO template, Map templateParams) { template.getParams().forEach(key -> { Object value = templateParams.get(key); if (value == null) { throw exception(NOTIFY_SEND_TEMPLATE_PARAM_MISS, key); } }); } }