Merge branch 'feature/REQ-1465' of axzsource.com:universal/infrastructure/backend/msg-center-plat into dev

This commit is contained in:
luofu 2023-10-21 11:38:50 +08:00
commit 83fb151634
31 changed files with 988 additions and 36 deletions

View File

@ -110,6 +110,10 @@
<version>1.0.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>cn.axzo.im.center</groupId>
<artifactId>im-center-api</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,27 @@
package cn.axzo.msg.center.message.controller;
import cn.axzo.msg.center.message.service.GeneralMessageService;
import cn.axzo.msg.center.service.general.client.GeneralMessageClient;
import cn.axzo.msg.center.service.general.request.GeneralMessageSendRequest;
import cn.azxo.framework.common.model.CommonResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RestController;
/**
* @author cold_blade
* @date 2023/10/19
* @version 1.0
*/
@Slf4j
@RestController
@RequiredArgsConstructor
public class GeneralMessageController implements GeneralMessageClient {
private final GeneralMessageService generalMessageService;
@Override
public CommonResponse<String> sendMessage(GeneralMessageSendRequest request) {
return CommonResponse.success(generalMessageService.sendMessage(request));
}
}

View File

@ -1,16 +1,23 @@
package cn.axzo.msg.center.message.domain.dto;
import cn.axzo.msg.center.domain.entity.MessageBaseTemplate;
import cn.axzo.msg.center.service.dto.MessageCardContentItemDTO;
import cn.axzo.msg.center.service.enums.MessageCategoryEnum;
import cn.axzo.msg.center.service.enums.PushTerminalEnum;
import cn.axzo.msg.center.utils.JSONObjectUtil;
import com.alibaba.fastjson.JSON;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.commons.collections.CollectionUtils;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @description
@ -45,9 +52,9 @@ public class MessageTemplateDTO implements Serializable {
*/
private String content;
/**
* 卡片信息,json字串
* 消息卡片信息标签列表
*/
private String cardContent;
private List<MessageCardContentItemDTO> msgCardContentItems;
/**
* 所属消息类型
*/
@ -60,6 +67,14 @@ public class MessageTemplateDTO implements Serializable {
* 模板路由信息
*/
private List<RawMessageRouterDTO> routers;
/**
* 推送终端
*/
private List<PushTerminalEnum> pushTerminals;
/**
* APP最小版本支持可不配
*/
private String minAppVersion;
public static MessageTemplateDTO from(MessageBaseTemplate baseTemplate, List<RawMessageRouterDTO> routers) {
return MessageTemplateDTO.builder()
@ -67,13 +82,24 @@ public class MessageTemplateDTO implements Serializable {
.code(baseTemplate.getCode())
.title(baseTemplate.getTitle())
.content(baseTemplate.getContent())
.cardContent(baseTemplate.getCardContent())
.msgCardContentItems(JSONObjectUtil.parseArray(baseTemplate.getCardContent(), MessageCardContentItemDTO.class))
.msgCategory(baseTemplate.getMsgCategory())
.icon(baseTemplate.getIcon())
.routers(routers)
.pushTerminals(JSON.parseArray(baseTemplate.getPushTerminal(), PushTerminalEnum.class))
.minAppVersion(baseTemplate.getMinAppVersion())
.build();
}
public Map<String, String> toCardContentMap() {
if (CollectionUtils.isEmpty(msgCardContentItems)) {
return Collections.emptyMap();
}
Map<String, String> map = new HashMap<>();
msgCardContentItems.forEach(e -> map.put(e.getLabel(), e.getValue()));
return map;
}
@Override
public String toString() {
return JSON.toJSONString(this);

View File

@ -9,6 +9,7 @@ import cn.axzo.msg.center.service.enums.RouterCategoryEnum;
import cn.axzo.msg.center.service.enums.TerminalTypeEnum;
import cn.axzo.msg.center.utils.MessageRouterUtil;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
@ -126,6 +127,16 @@ public class RawMessageRouterDTO implements Serializable {
.build();
}
public RawMessageRouterDTO deepClone() {
return RawMessageRouterDTO.builder()
.desc(this.desc)
.category(this.category)
.terminals(this.terminals.stream().map(MessageRouterTerminalDTO::deepClone).collect(Collectors.toList()))
.templateCode(this.templateCode)
.style(Lists.newArrayList(this.getStyle()))
.build();
}
@Override
public String toString() {
return JSON.toJSONString(this);

View File

@ -1,11 +1,11 @@
package cn.axzo.msg.center.message.domain.param;
import cn.axzo.msg.center.service.dto.MessageCardContentItemDTO;
import cn.axzo.msg.center.service.dto.MessageRouterButtonDTO;
import cn.axzo.msg.center.service.enums.MessageCategoryEnum;
import cn.axzo.msg.center.service.enums.PushTerminalEnum;
import cn.axzo.msg.center.service.template.request.MessageTemplateCreateRequest;
import cn.axzo.msg.center.service.template.request.MessageTemplateUpdateRequest;
import cn.axzo.msg.center.utils.JSONObjectUtil;
import com.alibaba.fastjson.JSON;
import lombok.AllArgsConstructor;
import lombok.Builder;
@ -59,9 +59,9 @@ public class MessageTemplateSaveOrUpdateParam implements Serializable {
*/
private String content;
/**
* 卡片信息,json字串
* 消息卡片信息标签列表
*/
private String cardContent;
private List<MessageCardContentItemDTO> msgCardContentItems;
/**
* 模板icon
*/
@ -88,7 +88,7 @@ public class MessageTemplateSaveOrUpdateParam implements Serializable {
.leafGroupNodes(request.getLeafGroupNodes())
.title(request.getMsgTitle())
.content(request.getMsgContent())
.cardContent(JSONObjectUtil.checkAndReturn(request.getMsgCardInfo()))
.msgCardContentItems(request.getMsgCardContentItems())
.icon(request.getMsgIcon())
.operatorId(request.getOperatorId())
.routers(request.getRouters())
@ -103,7 +103,7 @@ public class MessageTemplateSaveOrUpdateParam implements Serializable {
.leafGroupNodes(request.getLeafGroupNodes())
.title(request.getMsgTitle())
.content(request.getMsgContent())
.cardContent(JSONObjectUtil.checkAndReturn(request.getMsgCardInfo()))
.msgCardContentItems(request.getMsgCardContentItems())
.icon(request.getMsgIcon())
.operatorId(request.getOperatorId())
.routers(request.getRouters())

View File

@ -0,0 +1,214 @@
package cn.axzo.msg.center.message.domain.vo;
import cn.axzo.msg.center.domain.entity.GeneralMessageRecord;
import cn.axzo.msg.center.service.dto.MessageCardContentItemDTO;
import cn.axzo.msg.center.service.dto.MessageRouterButtonDTO;
import cn.axzo.msg.center.service.enums.RouterCategoryEnum;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.commons.collections.CollectionUtils;
import java.io.Serializable;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author cold_blade
* @date 2023/10/19
* @version 1.0
*/
@Setter
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class GeneralMessagePushVO implements Serializable {
private static final long serialVersionUID = -9017550674630922381L;
/**
* 消息的唯一标识
*/
private String identityCode;
/**
* 模板编码
*/
private String templateCode;
/**
* 顶部图片 - 模板icon地址
*/
private String cardBannerUrl;
/**
* 卡片标题标题 - 消息标题
*/
private String cardTitle;
/**
* 详情按钮
*/
private CardButton cardDetailButton;
/**
* 副标题 - 消息所属组织信息
*/
private List<Subtitle> subtitles;
/**
* 消息内容
*/
private String cardContent;
/**
* 卡片信息
*/
private List<CardExtensionItem> cardExtension;
/**
* 按钮操作区域
*/
private List<CardButton> cardButtons;
/**
* 业务编码
*/
private String bizCode;
/**
* 消息发送时间戳
*/
private Long sendTimestamp;
public static GeneralMessagePushVO from(GeneralMessageRecord record, String templateIcon, String orgIcon,
List<MessageRouterButtonDTO> routerButtons,
List<MessageCardContentItemDTO> cardContentItems) {
CardButton cardDetailButton = CollectionUtils.isEmpty(routerButtons) ? null : routerButtons.stream()
.filter(e -> RouterCategoryEnum.DETAIL.equals(e.getCategory()))
.findFirst()
.map(CardButton::from)
.orElse(null);
List<CardButton> cardButtons = CollectionUtils.isEmpty(routerButtons) ? Collections.emptyList() :
routerButtons.stream()
.filter(e -> !RouterCategoryEnum.DETAIL.equals(e.getCategory()))
.map(CardButton::from)
.collect(Collectors.toList());
List<CardExtensionItem> cardExtension = CollectionUtils.isEmpty(cardContentItems) ? Collections.emptyList() :
cardContentItems.stream()
.map(CardExtensionItem::from)
.collect(Collectors.toList());
return GeneralMessagePushVO.builder()
.identityCode(record.getIdentityCode())
.templateCode(record.getTemplateCode())
.cardBannerUrl(templateIcon)
.cardTitle(record.getTitle())
.cardDetailButton(cardDetailButton)
.subtitles(Lists.newArrayList(Subtitle.from(record, orgIcon)))
.cardContent(record.getContent())
.cardExtension(cardExtension)
.cardButtons(cardButtons)
.bizCode(record.getBizCode())
.sendTimestamp(record.getCreateAt().getTime())
.build();
}
@Override
public String toString() {
return JSON.toJSONString(this);
}
@Setter
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
static class Subtitle {
/**
* 图标 - 对应消息所属组织的图标
*/
private String iconUrl;
/**
* 标题 - 对应消息所属组织的名称
*/
private String title;
static Subtitle from(GeneralMessageRecord record, String orgIcon) {
return Subtitle.builder()
.title(record.getOrgName())
.iconUrl(orgIcon)
.build();
}
@Override
public String toString() {
return JSON.toJSONString(this);
}
}
@Setter
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
static class CardButton {
/**
* 按钮标题
*/
private String title;
/**
* 按钮操作类型: JUMP - 页面跳转, ACTION - 接口调用
*/
private String action;
/**
* 按钮点击后的跳转地址
*/
private List<ButtonAction> actionPaths;
static CardButton from(MessageRouterButtonDTO routerButton) {
return CardButton.builder()
.title(routerButton.getDesc())
.action(routerButton.getCategory().name())
.actionPaths(routerButton.getTerminals().stream()
.map(e -> new ButtonAction(e.getTerminalType().name(), e.getUrl()))
.collect(Collectors.toList())
).build();
}
@Getter
@AllArgsConstructor
static class ButtonAction {
/**
* 平台
*/
private String platform;
/**
* 跳转地址
*/
private String url;
}
}
@Setter
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
static class CardExtensionItem {
private String title;
private String detail;
static CardExtensionItem from(MessageCardContentItemDTO cardContentItem) {
return CardExtensionItem.builder()
.title(cardContentItem.getLabel())
.detail(cardContentItem.getValue())
.build();
}
@Override
public String toString() {
return JSON.toJSONString(this);
}
}
}

View File

@ -0,0 +1,19 @@
package cn.axzo.msg.center.message.service;
import cn.axzo.msg.center.service.general.request.GeneralMessageSendRequest;
/**
* @author cold_blade
* @date 2023/10/19
* @version 1.0
*/
public interface GeneralMessageService {
/**
* 发送消息
*
* @param request 消息所需参数
* @return 消息的唯一标识
*/
String sendMessage(GeneralMessageSendRequest request);
}

View File

@ -0,0 +1,145 @@
package cn.axzo.msg.center.message.service.impl;
import cn.axzo.im.center.api.feign.MessageApi;
import cn.axzo.im.center.api.vo.req.MessageInfo;
import cn.axzo.msg.center.common.exception.ServiceException;
import cn.axzo.msg.center.common.utils.PlaceholderResolver;
import cn.axzo.msg.center.dal.GeneralMessageRecordDao;
import cn.axzo.msg.center.domain.entity.GeneralMessageRecord;
import cn.axzo.msg.center.message.domain.dto.MessageTemplateDTO;
import cn.axzo.msg.center.message.domain.dto.RawMessageRouterDTO;
import cn.axzo.msg.center.message.domain.vo.GeneralMessagePushVO;
import cn.axzo.msg.center.message.service.GeneralMessageService;
import cn.axzo.msg.center.message.service.MessageTemplateNewService;
import cn.axzo.msg.center.service.dto.MessageCardContentItemDTO;
import cn.axzo.msg.center.service.dto.MessageRouterButtonDTO;
import cn.axzo.msg.center.service.enums.GeneralMessageStateEnum;
import cn.axzo.msg.center.service.enums.PushTerminalEnum;
import cn.axzo.msg.center.service.general.request.GeneralMessageSendRequest;
import cn.axzo.msg.center.utils.MessageRouterUtil;
import cn.axzo.msg.center.utils.UUIDUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* @author cold_blade
* @date 2023/10/19
* @version 1.0
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class GeneralMessageServiceImpl implements GeneralMessageService {
private final String orgIcon = "www.baidu.com";
private final MessageApi messageApi;
private final MessageTemplateNewService messageTemplateNewService;
private final GeneralMessageRecordDao generalMessageRecordDao;
@Override
public String sendMessage(GeneralMessageSendRequest request) {
// 查询模板基础信息
MessageTemplateDTO template = messageTemplateNewService.queryByTemplateCode(request.getTemplateCode())
.orElseThrow(() -> new ServiceException("未查询到对应的模板"));
// 构建消息记录并存储
GeneralMessageRecord messageRecord = buildMessageRecord(request, template);
generalMessageRecordDao.save(messageRecord);
// 异步推送
pushMessage(messageRecord, template);
return messageRecord.getIdentityCode();
}
private GeneralMessageRecord buildMessageRecord(GeneralMessageSendRequest request, MessageTemplateDTO template) {
return GeneralMessageRecord.builder()
.identityCode(UUIDUtil.uuidString())
.senderPersonId(request.getSenderPersonId())
.senderId(request.getSenderIdentity().getId())
.senderType(request.getSenderIdentity().getType())
.receiverPersonId(request.getReceiverPersonId())
.receiverId(request.getReceiverIdentity().getId())
.receiverType(request.getReceiverIdentity().getType())
.templateCode(template.getCode())
.title(parseString(template.getTitle(), request.getBizExtParams()))
.content(parseString(template.getContent(), request.getBizExtParams()))
.orgType(request.getOrgType())
.orgId(request.getOrgId())
.orgName(request.getOrgName())
.state(GeneralMessageStateEnum.HAS_BEEN_SENT)
.bizCode(request.getBizCode())
.routerParams(request.getRouterParams())
.bizExtParams(request.getBizExtParams())
.build();
}
private void pushMessage(GeneralMessageRecord record, MessageTemplateDTO template) {
if (CollectionUtils.isEmpty(template.getPushTerminals())) {
// 模板未配置任何推送终端
return;
}
List<String> appTypes = template.getPushTerminals().stream()
.map(PushTerminalEnum::getImTerminalFlag).collect(Collectors.toList());
GeneralMessagePushVO message = convert(record, template);
MessageInfo msgInfo = new MessageInfo();
msgInfo.setAppTypeList(appTypes);
// TODO: [cold_blade] [P2] 第一期只支持发送机器人相关的消息
msgInfo.setToPersonIdList(Lists.newArrayList(String.valueOf(record.getReceiverPersonId())));
msgInfo.setMsgHeader(record.getTitle());
msgInfo.setMsgContent(record.getContent());
msgInfo.setMsgTemplateId(record.getTemplateCode());
msgInfo.setMsgTemplateContent(JSON.toJSONString(message));
// 扩展信息
Map<String, String> ext = new HashMap<>();
ext.put("minAppVersion", template.getMinAppVersion());
msgInfo.setExtendsInfo(ext);
messageApi.sendMessage(msgInfo);
}
private GeneralMessagePushVO convert(GeneralMessageRecord record, MessageTemplateDTO template) {
// 对应模板的路由列表
List<RawMessageRouterDTO> rawRouters = template.getRouters();
// 解析路由地址
List<RawMessageRouterDTO> routers = rawRouters.stream()
.map(e -> MessageRouterUtil.parseAndConcatRouteUrl(e, record.getRouterParams()))
.collect(Collectors.toList());
// 转化成客户端展示的数据模型
List<MessageRouterButtonDTO> routerButtons = routers.stream()
.map(RawMessageRouterDTO::toMessageRouterButton)
.collect(Collectors.toList());
// 获取模板卡片信息
List<MessageCardContentItemDTO> rawCardContentItems = template.getMsgCardContentItems();
List<MessageCardContentItemDTO> cardContentItems = rawCardContentItems;
if (CollectionUtils.isNotEmpty(rawCardContentItems) && Objects.nonNull(record.getBizExtParams())
&& !record.getBizExtParams().isEmpty()) {
// 克隆避免修改入参
cardContentItems = cardContentItems.stream()
.map(MessageCardContentItemDTO::deepClone)
.collect(Collectors.toList());
cardContentItems.forEach(e -> {
String value = PlaceholderResolver.getDefaultResolver()
.resolveByMap(e.getValue(), record.getBizExtParams());
e.setValue(value);
});
}
return GeneralMessagePushVO.from(record, template.getIcon(), orgIcon, routerButtons, cardContentItems);
}
private String parseString(String string, JSONObject params) {
if (Objects.isNull(params)) {
return string;
}
return PlaceholderResolver.getDefaultResolver().resolveByMap(string, params);
}
}

View File

@ -16,6 +16,7 @@ import cn.axzo.msg.center.message.service.MessageTemplateGroupService;
import cn.axzo.msg.center.message.service.MessageTemplateNewService;
import cn.axzo.msg.center.message.service.MessageTemplateRouterService;
import cn.axzo.msg.center.service.dto.MessageBaseTemplateDTO;
import cn.axzo.msg.center.service.dto.MessageCardContentItemDTO;
import cn.axzo.msg.center.service.dto.MessageRouterButtonDTO;
import cn.axzo.msg.center.service.enums.PushTerminalEnum;
import cn.axzo.msg.center.service.enums.StatusEnum;
@ -311,7 +312,7 @@ public class MessageTemplateNewServiceImpl implements MessageTemplateNewService
template.setMsgCategory(param.getMsgCategory());
template.setTitle(param.getTitle());
template.setContent(param.getContent());
template.setCardContent(JSONObjectUtil.checkAndReturn(param.getCardContent()));
template.setCardContent(JSONObjectUtil.toJSONString(param.getMsgCardContentItems()));
template.setIcon(param.getIcon());
template.setPushTerminal(JSONObjectUtil.toJSONString(param.getPushTerminals()));
template.setCreatorId(param.getOperatorId());
@ -339,8 +340,8 @@ public class MessageTemplateNewServiceImpl implements MessageTemplateNewService
.set(CollectionUtils.isNotEmpty(param.getPushTerminals()), MessageBaseTemplate::getPushTerminal,
JSON.toJSONString(param.getPushTerminals()))
.set(StringUtils.isNotBlank(param.getTitle()), MessageBaseTemplate::getTitle, param.getTitle())
.set(StringUtils.isNotBlank(param.getCardContent()), MessageBaseTemplate::getCardContent,
param.getCardContent())
.set(CollectionUtils.isNotEmpty(param.getMsgCardContentItems()), MessageBaseTemplate::getCardContent,
JSONObjectUtil.toJSONString(param.getMsgCardContentItems()))
.set(StringUtils.isNotBlank(param.getContent()), MessageBaseTemplate::getContent, param.getContent())
.set(StringUtils.isNotBlank(param.getIcon()), MessageBaseTemplate::getIcon, param.getIcon())
.update();
@ -367,7 +368,7 @@ public class MessageTemplateNewServiceImpl implements MessageTemplateNewService
.msgCategory(record.getMsgCategory())
.title(record.getTitle())
.content(record.getContent())
.cardContent(record.getCardContent())
.cardContentItems(JSONObjectUtil.parseArray(record.getCardContent(), MessageCardContentItemDTO.class))
.icon(record.getIcon())
.pushTerminals(JSON.parseArray(record.getPushTerminal(), PushTerminalEnum.class))
.createTimestamp(record.getCreateAt().getTime())
@ -408,7 +409,7 @@ public class MessageTemplateNewServiceImpl implements MessageTemplateNewService
.pushTerminals(JSON.parseArray(record.getPushTerminal(), PushTerminalEnum.class))
.msgTitle(record.getTitle())
.msgContent(record.getContent())
.msgCardInfo(record.getCardContent())
.cardContentItems(JSONObjectUtil.parseArray(record.getCardContent(), MessageCardContentItemDTO.class))
.msgIcon(record.getIcon())
.routers(routerButtons)
.createTimestamp(record.getCreateAt().getTime())

View File

@ -185,7 +185,7 @@ public class PendingMessageNewServiceImpl implements PendingMessageNewService {
String cardContent = messageTemplates.stream()
.filter(e -> Objects.equals(e.getCode(), pendingMessageRecord.getTemplateCode()))
.findFirst()
.map(MessageTemplateDTO::getCardContent)
.map(e -> JSON.toJSONString(e.toCardContentMap()))
.orElse(null);
if (StringUtils.isNotBlank(cardContent) && StringUtils.isNotBlank(pendingMessageRecord.getRouterParams())) {
cardContent = PlaceholderResolver.getDefaultResolver()

View File

@ -8,6 +8,8 @@ import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
/**
@ -34,6 +36,19 @@ public final class JSONObjectUtil {
return JSON.parseObject(str);
}
/**
* 解析JSON字符串若字符串格式不正确抛异常
*
* @param str 待解析的字符串
* @return List<T>
*/
public static <T> List<T> parseArray(String str, Class<T> clazz) {
if (StringUtils.isBlank(str)) {
return Collections.emptyList();
}
return JSON.parseArray(str, clazz);
}
public static String toJSONString(Object obj) {
if (Objects.isNull(obj)) {
return EMPTY_JSON_OBJ_STR;

View File

@ -8,13 +8,16 @@ import cn.axzo.msg.center.service.enums.ButtonStyleEnum;
import cn.axzo.msg.center.service.enums.RouterCategoryEnum;
import cn.axzo.msg.center.service.enums.TerminalTypeEnum;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* @description
@ -47,6 +50,25 @@ public final class MessageRouterUtil {
.findFirst().orElseGet(() -> rawMessageRouter.getTerminals().get(0));
}
/**
* 根据指定的终端类型选取合适的路由
*
* @param rawMessageRouter 原始的模板路由策略
* @param excludeTerminalType 待排除的终端类型
* @return 合适的路由数据
*/
public static List<MessageRouterTerminalDTO> selectWithout(RawMessageRouterDTO rawMessageRouter,
TerminalTypeEnum excludeTerminalType) {
if (RouterCategoryEnum.ACTION.equals(rawMessageRouter.getCategory())) {
// 如果配置路由是API调用这与终端无关
return rawMessageRouter.getTerminals();
}
return rawMessageRouter.getTerminals().stream()
.filter(e -> !Objects.equals(excludeTerminalType, e.getTerminalType()))
// 若没有匹配的默认选择第一个然后由前端去进行最终判断
.collect(Collectors.toList());
}
/**
* 判断路由参数的合法性
*
@ -74,6 +96,41 @@ public final class MessageRouterUtil {
}
}
/**
* 解析模板上配置的路由地址,将发送消息时的参数替换上去并将路由参数追加到模板的路由地址后面兼容APP端新老版本
*
* @param router 路由信息
* @param routerParam 路由参数
* @return RawMessageRouterDTO
*/
public static RawMessageRouterDTO parseAndConcatRouteUrl(RawMessageRouterDTO router, JSONObject routerParam) {
// 路由参数有效
if (Objects.nonNull(routerParam) && !routerParam.isEmpty()) {
// 拷贝一份避免修改入参
router = router.deepClone();
router.getTerminals().forEach(e -> {
// 替换原始URL中的参数变量
String routerUrl = PlaceholderResolver.getDefaultResolver().resolveByMap(e.getUrl(), routerParam);
// 将routerParam追加到原始的URL后面
routerUrl = concatRouterParam(routerUrl, routerParam);
e.setUrl(routerUrl);
});
}
return router;
}
private static String concatRouterParam(String originalUrl, JSONObject routerParam) {
StringBuilder concatUrlBuilder = new StringBuilder(originalUrl);
if (!originalUrl.contains("?")) {
concatUrlBuilder.append("?");
}
for (Map.Entry<String, Object> entry : routerParam.entrySet()) {
concatUrlBuilder.append("&").append(entry.getKey()).append("=").append(entry.getValue());
}
return concatUrlBuilder.toString();
}
/**
* 解析按钮style
*

View File

@ -47,9 +47,9 @@ public class MessageBaseTemplateDTO implements Serializable {
*/
private String content;
/**
* 卡片信息,json字串
* 卡片信息标签列表
*/
private String cardContent;
private List<MessageCardContentItemDTO> cardContentItems;
/**
* 模板icon
*/

View File

@ -0,0 +1,46 @@
package cn.axzo.msg.center.service.dto;
import com.alibaba.fastjson.JSON;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.io.Serializable;
/**
* @author cold_blade
* @date 2023/10/20
* @version 1.0
*/
@Setter
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MessageCardContentItemDTO implements Serializable {
private static final long serialVersionUID = -8696875791099408327L;
/**
* 卡片内容标签
*/
private String label;
/**
* 卡片内容标签值
*/
private String value;
public MessageCardContentItemDTO deepClone() {
return MessageCardContentItemDTO.builder()
.label(this.label)
.value(this.value)
.build();
}
@Override
public String toString() {
return JSON.toJSONString(this);
}
}

View File

@ -36,6 +36,7 @@ public class MessageRouterButtonDTO implements Serializable {
* 路由分类
* JUMP: 直接跳转
* ACTION: 接口调用
* DETAIL: 页面详情
*/
private RouterCategoryEnum category;
/**

View File

@ -55,6 +55,8 @@ public class MessageRouterDTO implements Serializable {
private TerminalTypeEnum terminalType;
/**
* 按钮样式配置
* HIGH_LIGHT: 按钮高亮展示
* OVER_CARD: 按钮显示在卡片
*/
private List<ButtonStyleEnum> style;

View File

@ -39,6 +39,13 @@ public class MessageRouterTerminalDTO implements Serializable {
*/
private TerminalTypeEnum terminalType;
public MessageRouterTerminalDTO deepClone() {
return MessageRouterTerminalDTO.builder()
.terminalType(this.terminalType)
.url(this.url)
.build();
}
@Override
public String toString() {
return JSON.toJSONString(this);

View File

@ -18,12 +18,13 @@ public enum PushTerminalEnum {
/**
* B-安心筑企业版
*/
B_ENTERPRISE_APP("B-安心筑企业版"),
B_ENTERPRISE_APP("B-安心筑企业版", "CMP"),
/**
* C-安心筑工人版
*/
C_WORKER_APP("C-安心筑工人版"),
C_WORKER_APP("C-安心筑工人版", "CM"),
;
private final String desc;
private final String imTerminalFlag;
}

View File

@ -16,7 +16,9 @@ import lombok.Getter;
public enum RouterCategoryEnum {
JUMP("直接跳转"),
ACTION("接口调用");
ACTION("接口调用"),
DETAIL("页面详情"),
;
private final String desc;
}

View File

@ -0,0 +1,34 @@
package cn.axzo.msg.center.service.general.client;
import cn.axzo.msg.center.service.general.request.GeneralMessageSendRequest;
import cn.axzo.msg.center.service.pending.client.fallback.PendingMessageClientFallback;
import cn.azxo.framework.common.model.CommonResponse;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import javax.validation.Valid;
/**
* 普通消息模块
*
* @author cold_blade
* @date 2023/10/18
* @version 1.0
*/
@Component
@FeignClient(value = "msg-center", url = "${server.serviceUrl:http://msg-center:8080}",
fallback = PendingMessageClientFallback.class)
public interface GeneralMessageClient {
/**
* 发送消息
*
* @param request 消息所需参数
* @return 消息的唯一标识
*/
@PostMapping(value = "/general-message/send", produces = {MediaType.APPLICATION_JSON_VALUE})
CommonResponse<String> sendMessage(@RequestBody @Valid GeneralMessageSendRequest request);
}

View File

@ -0,0 +1,23 @@
package cn.axzo.msg.center.service.general.client.fallback;
import cn.axzo.msg.center.service.general.client.GeneralMessageClient;
import cn.axzo.msg.center.service.general.request.GeneralMessageSendRequest;
import cn.azxo.framework.common.model.CommonResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* @author cold_blade
* @date 2023/10/18
* @version 1.0
*/
@Slf4j
@Component
public class GeneralMessageClientFallback implements GeneralMessageClient {
@Override
public CommonResponse<String> sendMessage(GeneralMessageSendRequest request) {
log.error("fall back while sending message. req:{}", request);
return CommonResponse.error("fall back while sending message");
}
}

View File

@ -0,0 +1,89 @@
package cn.axzo.msg.center.service.general.request;
import cn.axzo.msg.center.service.dto.IdentityDTO;
import cn.axzo.msg.center.service.enums.OrganizationTypeEnum;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
/**
* @description
* 发送消息
* @author cold_blade
* @date 2023/10/18
* @version 1.0
*/
@Setter
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class GeneralMessageSendRequest implements Serializable {
private static final long serialVersionUID = -3030926259836918967L;
/**
* 模板编码
*/
@NotBlank(message = "templateCode is required")
private String templateCode;
/**
* 消息发送者自然人id
*/
@NotNull(message = "senderPersonId is required")
private Long senderPersonId;
/**
* 消息发送者身份
*/
@NotNull(message = "senderIdentity is required")
private IdentityDTO senderIdentity;
/**
* 消息接收者自然人id
*/
@NotNull(message = "receiverPersonId is required")
private Long receiverPersonId;
/**
* 消息接收者身份
*/
@NotNull(message = "receiverIdentity is required")
private IdentityDTO receiverIdentity;
/**
* 消息所属组织类型
*/
private OrganizationTypeEnum orgType;
/**
* 消息所属组织Id
*/
private Long orgId;
/**
* 消息所属组织名称
*/
private String orgName;
/**
* 发送消息的业务编码
*/
private String bizCode;
/**
* 路由参数
* 该字段仅存放路由相关的变量及对应的值
*/
private JSONObject routerParams;
/**
* 业务扩展参数
* 标题/内容/以及卡片等的变量及对应的值放到该字段
*/
private JSONObject bizExtParams;
@Override
public String toString() {
return JSON.toJSONString(this);
}
}

View File

@ -0,0 +1,117 @@
package cn.axzo.msg.center.service.general.response;
import cn.axzo.msg.center.service.dto.IdentityDTO;
import cn.axzo.msg.center.service.dto.MessageRouterDTO;
import cn.axzo.msg.center.service.enums.OrganizationTypeEnum;
import cn.axzo.msg.center.service.enums.PushTerminalEnum;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.io.Serializable;
import java.util.List;
/**
* @description
* 普通消息记录数模型
* @author cold_blade
* @date 2023/10/18
* @version 1.0
*/
@Setter
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class GeneralMessageResponse implements Serializable {
private static final long serialVersionUID = 5740922087866033787L;
/**
* 消息的唯一标识
*/
private String identityCode;
/**
* 模板编码
*/
private String templateCode;
/**
* 模板icon地址
*/
private String templateIcon;
/**
* 消息标题
*/
private String title;
/**
* 消息内容
*/
private String content;
/**
* 卡片信息
*/
private String cardContent;
/**
* 消息发送者自然人id
*/
private Long senderPersonId;
/**
* 消息发送者身份
*/
private IdentityDTO senderIdentity;
/**
* 消息接收者自然人id
*/
private Long receiverPersonId;
/**
* 消息接收者身份
*/
private IdentityDTO receiverIdentity;
/**
* 消息所属组织类型
*/
private OrganizationTypeEnum orgType;
/**
* 消息所属组织Id
*/
private Long orgId;
/**
* 消息所属组织名称
*/
private String orgName;
/**
* 业务编码
*/
private String bizCode;
/**
* 业务状态描述
*/
private String bizDesc;
/**
* 消息发送时间戳
*/
private Long sendTimestamp;
/**
* 路由信息,可为空
*/
private List<MessageRouterDTO> routers;
/**
* 参数及其对应的值的JSON串
*/
private JSONObject routerParams;
/**
* 发送终端eg
* B_ENTERPRISE_APPB-安心筑企业版
* C_WORKER_APPC-安心筑工人版
*/
private List<PushTerminalEnum> pushTerminals;
@Override
public String toString() {
return JSON.toJSONString(this);
}
}

View File

@ -1,5 +1,6 @@
package cn.axzo.msg.center.service.template.request;
import cn.axzo.msg.center.service.dto.MessageCardContentItemDTO;
import cn.axzo.msg.center.service.dto.MessageRouterButtonDTO;
import cn.axzo.msg.center.service.enums.MessageCategoryEnum;
import cn.axzo.msg.center.service.enums.PushTerminalEnum;
@ -60,9 +61,9 @@ public class MessageTemplateCreateRequest implements Serializable {
@NotBlank(message = "msgTitle is required")
private String msgTitle;
/**
* 消息卡片信息,JSON字串
* 消息卡片信息标签列表可为空
*/
private String msgCardInfo;
private List<MessageCardContentItemDTO> msgCardContentItems;
/**
* 消息内容
*/

View File

@ -1,5 +1,6 @@
package cn.axzo.msg.center.service.template.request;
import cn.axzo.msg.center.service.dto.MessageCardContentItemDTO;
import cn.axzo.msg.center.service.dto.MessageRouterButtonDTO;
import cn.axzo.msg.center.service.enums.PushTerminalEnum;
import com.alibaba.fastjson.JSON;
@ -53,9 +54,9 @@ public class MessageTemplateUpdateRequest implements Serializable {
*/
private String msgTitle;
/**
* 消息卡片信息,JSON字串
* 消息卡片信息标签列表
*/
private String msgCardInfo;
private List<MessageCardContentItemDTO> msgCardContentItems;
/**
* 消息内容
*/

View File

@ -1,5 +1,6 @@
package cn.axzo.msg.center.service.template.response;
import cn.axzo.msg.center.service.dto.MessageCardContentItemDTO;
import cn.axzo.msg.center.service.dto.MessageRouterButtonDTO;
import cn.axzo.msg.center.service.enums.MessageCategoryEnum;
import cn.axzo.msg.center.service.enums.PushTerminalEnum;
@ -53,9 +54,9 @@ public class MessageTemplateDetailResponse implements Serializable {
*/
private String msgTitle;
/**
* 消息卡片信息,JSON字串
* 卡片信息标签列表
*/
private String msgCardInfo;
private List<MessageCardContentItemDTO> cardContentItems;
/**
* 消息内容
*/

View File

@ -0,0 +1,18 @@
package cn.axzo.msg.center.dal;
import cn.axzo.msg.center.dal.mapper.GeneralMessageRecordMapper;
import cn.axzo.msg.center.domain.entity.GeneralMessageRecord;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* @description
* @author cold_blade
* @date 2023/10/5
* @version 1.0
*/
@Slf4j
@Component
public class GeneralMessageRecordDao extends ServiceImpl<GeneralMessageRecordMapper, GeneralMessageRecord> {
}

View File

@ -5,8 +5,14 @@ import cn.axzo.msg.center.service.enums.GeneralMessageStateEnum;
import cn.axzo.msg.center.service.enums.IdentityTypeEnum;
import cn.axzo.msg.center.service.enums.OrganizationTypeEnum;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.io.Serializable;
@ -20,27 +26,42 @@ import java.io.Serializable;
*/
@Setter
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("general_message_record")
public class GeneralMessageRecord extends BaseEntity<GeneralMessageRecord> implements Serializable {
private static final long serialVersionUID = -1271402884501451185L;
/**
* 发送者ID
* 消息的唯一标识
*/
private String identityCode;
/**
* 发送者自然人 ID
*/
private Long senderPersonId;
/**
* 发送者身份ID
*/
private Long senderId;
/**
* 接收者ID
* 发送者身份类型
*/
private IdentityTypeEnum senderType;
/**
* 接收者自然人 ID
*/
private Long receiverPersonId;
/**
* 接收者身份ID
*/
private Long receiverId;
/**
* 接收者类型
* 接收者身份类型
*/
private IdentityTypeEnum receiverType;
/**
* 自然人 ID
*/
private Long personId;
/**
* 模板编码
*/
@ -76,13 +97,19 @@ public class GeneralMessageRecord extends BaseEntity<GeneralMessageRecord> imple
/**
* 路由参数留存
*/
private String routerParams;
@TableField(typeHandler = FastjsonTypeHandler.class)
private JSONObject routerParams;
/**
* 重试次数
* 业务扩展参数
*/
@TableField(typeHandler = FastjsonTypeHandler.class)
private JSONObject bizExtParams;
/**
* 推送重试次数
*/
private Integer retryCount;
/**
* 最终失败原因
* 推送最终失败原因
*/
private String failCause;

View File

@ -62,6 +62,10 @@ public class MessageBaseTemplate extends BaseEntity<MessageBaseTemplate> impleme
* 推送终端配置 JSON字串
*/
private String pushTerminal;
/**
* APP最小版本支持可不配
*/
private String minAppVersion;
/**
* 创建者自然人id
*/

View File

@ -0,0 +1,55 @@
package cn.axzo.msg.center;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import feign.Target;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.Objects;
/**
* @author cold_blade
* @date 2023/10/19
* @version 1.0
*/
@Slf4j
@Component
@Profile({"dev", "test", "local"})
public class FeignConfig implements RequestInterceptor, EnvironmentAware {
private Environment environment;
private static String POD_NAMESPACE;
static {
Map<String, String> env = System.getenv();
if (env != null) {
POD_NAMESPACE = env.get("MY_POD_NAMESPACE");
}
log.info("init FeignConfig, POD_NAMESPACE value is {}", POD_NAMESPACE);
}
@SneakyThrows
@Override
public void apply(RequestTemplate requestTemplate) {
if (POD_NAMESPACE == null) {
Target<?> target = requestTemplate.feignTarget();
String profile = environment.getProperty("spring.profiles.active");
if (Objects.equals(profile, "dev")) {
requestTemplate.target("http://dev-app.axzo.cn/" + target.name());
} else if (Objects.equals(profile, "test")) {
requestTemplate.target("http://test-api.axzo.cn/" + target.name());
}
}
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
}

View File

@ -3,6 +3,8 @@ package cn.axzo.msg.center;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Configuration;
import static cn.axzo.msg.center.MsgCenterConfig.IM_CENTER;
/**
* @author cn
* @version 1.0
@ -10,6 +12,8 @@ import org.springframework.context.annotation.Configuration;
* @date 2023/5/30 11:33
*/
@Configuration
@EnableFeignClients
@EnableFeignClients(basePackages = {IM_CENTER})
public class MsgCenterConfig {
public static final String IM_CENTER = "cn.axzo.im.center.api.feign";
}