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

This commit is contained in:
luofu 2023-10-16 11:13:18 +08:00
commit c7d351d784
24 changed files with 791 additions and 298 deletions

View File

@ -110,6 +110,11 @@
<version>1.0.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>cn.axzo.pokonyan</groupId>
<artifactId>pokonyan</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -29,9 +29,8 @@ public class MessageTemplateController implements MessageTemplateClient {
@Override
public CommonResponse<String> save(MessageTemplateCreateRequest request) {
MessageTemplateCreateParam param = MessageTemplateCreateParam.from(request);
messageTemplateNewService.createTemplate(param);
return CommonResponse.success(param.getTemplateCode());
return CommonResponse.success(
messageTemplateNewService.createTemplate(MessageTemplateCreateParam.from(request)));
}
@Override

View File

@ -1,6 +1,6 @@
package cn.axzo.msg.center.message.controller;
import cn.axzo.msg.center.message.domain.dto.MessageGroupNodeDTO;
import cn.axzo.msg.center.message.domain.dto.MessageGroupNodeStatisticDTO;
import cn.axzo.msg.center.message.domain.dto.PendingMessageDTO;
import cn.axzo.msg.center.message.domain.param.MessageGroupNodeStatisticParam;
import cn.axzo.msg.center.message.domain.param.PendingMessagePushParam;
@ -36,10 +36,10 @@ public class PendingMessageNewController implements PendingMessageClient {
@Override
public CommonResponse<List<MessageGroupNodeResponse>> groupStatistic(MessageGroupNodeStatisticRequest request) {
List<MessageGroupNodeDTO> groupNodes = pendingMessageNewService
List<MessageGroupNodeStatisticDTO> groupNodes = pendingMessageNewService
.groupStatistic(MessageGroupNodeStatisticParam.from(request));
return CommonResponse.success(groupNodes.stream()
.map(MessageGroupNodeDTO::toResponse)
.map(MessageGroupNodeStatisticDTO::toResponse)
.collect(Collectors.toList())
);
}

View File

@ -0,0 +1,58 @@
package cn.axzo.msg.center.message.domain.dto;
import cn.axzo.msg.center.service.dto.GroupTreeNodeDTO;
import com.alibaba.fastjson.JSON;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.Collection;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* @author cold_blade
* @date 2023/10/14
* @version 1.0
*/
@Setter
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class GroupTreeNodePathDTO implements Serializable {
private static final long serialVersionUID = -6325338437781927201L;
private static final String NODE_NAME_PATH_SPLITER = "/";
private static final String NODE_CODE_PATH_SPLITER = ":";
/**
* 结点编码
*/
private String nodeCode;
/**
* 结点名称格式的路径
*/
private String nodeNamePath;
/**
* 结点编码格式的路径
*/
private String nodeCodePath;
public static GroupTreeNodePathDTO of(String nodeCode, Collection<GroupTreeNodeDTO> nodes) {
String nodeNamePath = formatPath(GroupTreeNodeDTO::getNodeName, nodes, NODE_NAME_PATH_SPLITER);
String nodeCodePath = formatPath(GroupTreeNodeDTO::getNodeCode, nodes, NODE_CODE_PATH_SPLITER);
return new GroupTreeNodePathDTO(nodeCode, nodeNamePath, nodeCodePath);
}
private static String formatPath(Function<GroupTreeNodeDTO, String> pathFunc, Collection<GroupTreeNodeDTO> nodes,
String spliter) {
return nodes.stream().map(pathFunc).collect(Collectors.joining(spliter));
}
@Override
public String toString() {
return JSON.toJSONString(this);
}
}

View File

@ -1,87 +0,0 @@
package cn.axzo.msg.center.message.domain.dto;
import cn.axzo.core.utils.converter.BeanConverter;
import cn.axzo.msg.center.domain.entity.MessageGroupNode;
import cn.axzo.msg.center.service.enums.MessageGroupNodeCategoryEnum;
import cn.axzo.msg.center.service.pending.response.MessageGroupNodeResponse;
import com.alibaba.fastjson.JSON;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.io.Serializable;
import java.util.List;
import java.util.stream.Collectors;
/**
* @description
* 消息分类结点DTO
*
* @author cold_blade
* @date 2023/9/26
* @version 1.0
*/
@Setter
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MessageGroupNodeDTO implements Serializable {
private static final long serialVersionUID = 5171436359992401120L;
/**
* 结点类型
* GENERAL_MESSAGE_CENTER: 消息中心
* GENERAL_MESSAGE_MODULE: 消息模块
* GENERAL_MESSAGE_CATEGORY: 消息分类
* PENDING_MESSAGE_CENTER: 待办中心
* PENDING_MESSAGE_MODULE: 待办模块
* PENDING_MESSAGE_CATEGORY: 待办分类
*/
private MessageGroupNodeCategoryEnum category;
/**
* 结点名称
*/
private String nodeName;
/**
* 结点编码
*/
private String nodeCode;
/**
* 父节点编码
*/
private String parentNodeCode;
/**
* 结点对应的代办数量
*/
private Integer pendingCount;
/**
* 子节点列表
*/
private List<MessageGroupNodeDTO> children;
public static MessageGroupNodeDTO from(MessageGroupNode groupNode) {
return MessageGroupNodeDTO.builder()
.category(groupNode.getCategory())
.nodeName(groupNode.getName())
.nodeCode(groupNode.getCode())
.parentNodeCode(groupNode.getParentCode())
.build();
}
public MessageGroupNodeResponse toResponse() {
MessageGroupNodeResponse response = BeanConverter.convert(this, MessageGroupNodeResponse.class);
List<MessageGroupNodeResponse> children = this.children.stream()
.map(MessageGroupNodeDTO::toResponse).collect(Collectors.toList());
response.setNodeChildren(children);
return response;
}
@Override
public String toString() {
return JSON.toJSONString(this);
}
}

View File

@ -0,0 +1,76 @@
package cn.axzo.msg.center.message.domain.dto;
import cn.axzo.basics.common.model.IBaseTree;
import cn.axzo.core.utils.converter.BeanConverter;
import cn.axzo.msg.center.service.dto.GroupTreeNodeDTO;
import cn.axzo.msg.center.service.pending.response.MessageGroupNodeResponse;
import com.alibaba.fastjson.JSON;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.io.Serializable;
import java.util.List;
import java.util.stream.Collectors;
/**
* @description
* 消息分类结点DTO
*
* @author cold_blade
* @date 2023/9/26
* @version 1.0
*/
@Setter
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MessageGroupNodeStatisticDTO implements IBaseTree<MessageGroupNodeStatisticDTO, String>, Serializable {
private static final long serialVersionUID = 5171436359992401120L;
/**
* 树结点
*/
private GroupTreeNodeDTO treeNode;
/**
* 子节点列表
*/
private List<MessageGroupNodeStatisticDTO> nodeChildren;
/**
* 结点对应的代办数量
*/
private Integer pendingCount;
public static MessageGroupNodeStatisticDTO of(GroupTreeNodeDTO groupNode) {
return MessageGroupNodeStatisticDTO.builder()
.treeNode(groupNode)
.build();
}
public MessageGroupNodeResponse toResponse() {
MessageGroupNodeResponse response = BeanConverter.convert(this, MessageGroupNodeResponse.class);
List<MessageGroupNodeResponse> children = this.nodeChildren.stream()
.map(MessageGroupNodeStatisticDTO::toResponse).collect(Collectors.toList());
response.setNodeChildren(children);
return response;
}
@Override
public String toString() {
return JSON.toJSONString(this);
}
@Override
public String getNodeCode() {
return treeNode.getNodeCode();
}
@Override
public String getParentNodeCode() {
return treeNode.getParentNodeCode();
}
}

View File

@ -1,10 +1,10 @@
package cn.axzo.msg.center.message.domain.param;
import cn.axzo.msg.center.service.dto.MessageRouterButtonDTO;
import cn.axzo.msg.center.service.dto.MessageRouterDTO;
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.utils.UUIDUtil;
import cn.axzo.msg.center.utils.JSONObjectUtil;
import com.alibaba.fastjson.JSON;
import lombok.AllArgsConstructor;
import lombok.Builder;
@ -36,9 +36,9 @@ public class MessageTemplateCreateParam implements Serializable {
*/
private String templateName;
/**
* 系统自动生成的模板code
* 消息分类树的叶结点的结点编码列表
*/
private String templateCode;
private List<String> leafGroupNodes;
/**
* 所属消息类型
* GENERAL_MESSAGE: 普通消息
@ -69,18 +69,25 @@ public class MessageTemplateCreateParam implements Serializable {
* 路由列表
*/
private List<MessageRouterButtonDTO> routers;
/**
* 推送终端配置
* B_ENTERPRISE_APP: B-安心筑企业版
* C_WORKER_APP: C-安心筑工人版
*/
private List<PushTerminalEnum> pushTerminals;
public static MessageTemplateCreateParam from(MessageTemplateCreateRequest request) {
return MessageTemplateCreateParam.builder()
.templateName(request.getTemplateName())
.templateCode(UUIDUtil.uuidString())
.msgCategory(request.getCategory())
.leafGroupNodes(request.getLeafGroupNodes())
.title(request.getMsgTitle())
.content(request.getMsgContent())
.cardContent(request.getMsgCardInfo())
.cardContent(JSONObjectUtil.checkAndReturn(request.getMsgCardInfo()))
.icon(request.getMsgIcon())
.operatorId(request.getOperatorId())
.routers(request.getRouters())
.pushTerminals(request.getPushTerminals())
.build();
}

View File

@ -0,0 +1,41 @@
package cn.axzo.msg.center.message.service;
import cn.axzo.msg.center.service.dto.GroupTreeNodeDTO;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
/**
* 消息分类结点管理
*
* @author cold_blade
* @date 2023/10/14
* @version 1.0
*/
public interface MessageGroupNodeService {
/**
* 获取分类结点叶结点名称的路径
*
* @param leafGroupNodeCodes 分类结点叶结点的编码列表
* @return 分类结点叶结点名称的路径
*/
Map<String, String> leafGroupNodeNamePaths(Collection<String> leafGroupNodeCodes);
/**
* 获取分类结点叶结点编码的路径
*
* @param leafGroupNodeCodes 分类结点叶结点的编码列表
* @return 分类结点叶结点编码的路径
*/
Map<String, String> leafGroupNodeCodePaths(Collection<String> leafGroupNodeCodes);
/**
* 根据结点编码查询结点信息
*
* @param rootNodeCode 结点编码
* @return 结点信息
*/
Optional<GroupTreeNodeDTO> queryRootNode(String rootNodeCode);
}

View File

@ -1,41 +0,0 @@
package cn.axzo.msg.center.message.service;
import cn.axzo.msg.center.message.domain.dto.MessageGroupNodeDTO;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
/**
* 消息分类管理
*
* @author cold_blade
* @date 2023/9/20
* @version 1.0
*/
public interface MessageGroupService {
/**
* 通过结点编码查询结点信息
*
* @param nodeCode 结点编码
* @return 结点信息
*/
Optional<MessageGroupNodeDTO> queryByNodeCode(String nodeCode);
/**
* 查询指定结点的字节的信息
*
* @param nodeCode 指定结点编码
* @return 子节点列表信息
*/
List<MessageGroupNodeDTO> listChildren(String nodeCode);
/**
* 模板关联分类
*
* @param templateNode 模板编码
* @param pathList 分类path列表
*/
void templateGroup(String templateNode, Collection<String> pathList);
}

View File

@ -0,0 +1,53 @@
package cn.axzo.msg.center.message.service;
import cn.axzo.msg.center.message.domain.dto.GroupTreeNodePathDTO;
import cn.axzo.msg.center.service.dto.GroupTreeNodeDTO;
import java.util.List;
import java.util.Optional;
/**
* 消息分类树节点缓存管理
*
* @author cold_blade
* @date 2023/10/14
* @version 1.0
*/
public interface MessageGroupTreeNodeCacheService {
/**
* 获取所有有效的分类结点树的根节点
*
* @return 树根节点列表
*/
List<GroupTreeNodeDTO> listAllRootNodes();
/**
* 获取指定结点所在的树的根节点
*
* @param rootNodeCode 结点编码
* @return 根节点
*/
Optional<GroupTreeNodeDTO> queryRootNode(String rootNodeCode);
/**
* 根据结点编码查询结点信息
*
* @param groupNodeCode 结点编码
* @return 结点信息
*/
Optional<GroupTreeNodeDTO> queryNode(String groupNodeCode);
/**
* 获取叶结点对应的树的路径
*
* @param leafNodeCode 叶结点编码
* @return 路径
*/
Optional<GroupTreeNodePathDTO> queryLeafNodePath(String leafNodeCode);
/**
* 刷新缓存
*/
void refreshCache();
}

View File

@ -1,5 +1,6 @@
package cn.axzo.msg.center.message.service;
import java.util.Collection;
import java.util.List;
/**
@ -18,4 +19,12 @@ public interface MessageTemplateGroupService {
* @return 模板编码列表
*/
List<String> listMessageTemplateCodes(String groupNodeCode);
/**
* 模板关联分类
*
* @param templateNode 模板编码
* @param groupNodeCodes 分类结点编码列表
*/
void templateGroup(String templateNode, Collection<String> groupNodeCodes);
}

View File

@ -20,7 +20,7 @@ public interface MessageTemplateNewService {
*
* @param param 模板内容参数
*/
void createTemplate(MessageTemplateCreateParam param);
String createTemplate(MessageTemplateCreateParam param);
/**
* 通过模板编码查询模板信息

View File

@ -1,12 +1,11 @@
package cn.axzo.msg.center.message.service;
import cn.axzo.msg.center.message.domain.dto.MessageGroupNodeDTO;
import cn.axzo.msg.center.message.domain.dto.MessageGroupNodeStatisticDTO;
import cn.axzo.msg.center.message.domain.dto.PendingMessageDTO;
import cn.axzo.msg.center.message.domain.param.MessageGroupNodeStatisticParam;
import cn.axzo.msg.center.message.domain.param.PendingMessagePushParam;
import cn.axzo.msg.center.service.enums.TerminalTypeEnum;
import cn.axzo.msg.center.service.pending.request.PendingMessagePageRequest;
import cn.axzo.msg.center.service.pending.request.PendingMessagePushRequest;
import cn.axzo.msg.center.service.pending.response.PendingMessageResponse;
import cn.azxo.framework.common.model.Page;
@ -22,7 +21,7 @@ import java.util.Optional;
*/
public interface PendingMessageNewService {
List<MessageGroupNodeDTO> groupStatistic(MessageGroupNodeStatisticParam param);
List<MessageGroupNodeStatisticDTO> groupStatistic(MessageGroupNodeStatisticParam param);
/**
* 代办列表分页查询

View File

@ -0,0 +1,62 @@
package cn.axzo.msg.center.message.service.impl;
import cn.axzo.msg.center.message.domain.dto.GroupTreeNodePathDTO;
import cn.axzo.msg.center.message.service.MessageGroupNodeService;
import cn.axzo.msg.center.message.service.MessageGroupTreeNodeCacheService;
import cn.axzo.msg.center.service.dto.GroupTreeNodeDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* @author cold_blade
* @date 2023/10/14
* @version 1.0
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class MessageGroupNodeServiceImpl implements MessageGroupNodeService {
private final MessageGroupTreeNodeCacheService messageGroupTreeNodeCacheService;
@Override
public Map<String, String> leafGroupNodeNamePaths(Collection<String> leafGroupNodeCodes) {
if (CollectionUtils.isEmpty(leafGroupNodeCodes)) {
log.info("leafGroupNodeCodes is empty.");
return Collections.emptyMap();
}
return leafGroupNodeCodes.stream()
.map(messageGroupTreeNodeCacheService::queryLeafNodePath)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toMap(GroupTreeNodePathDTO::getNodeCode,
GroupTreeNodePathDTO::getNodeNamePath, (cur, pre) -> cur));
}
@Override
public Map<String, String> leafGroupNodeCodePaths(Collection<String> leafGroupNodeCodes) {
if (CollectionUtils.isEmpty(leafGroupNodeCodes)) {
log.info("leafGroupNodeCodes is empty.");
return Collections.emptyMap();
}
return leafGroupNodeCodes.stream()
.map(messageGroupTreeNodeCacheService::queryLeafNodePath)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toMap(GroupTreeNodePathDTO::getNodeCode,
GroupTreeNodePathDTO::getNodeCodePath, (cur, pre) -> cur));
}
@Override
public Optional<GroupTreeNodeDTO> queryRootNode(String rootNodeCode) {
return messageGroupTreeNodeCacheService.queryRootNode(rootNodeCode);
}
}

View File

@ -1,70 +0,0 @@
package cn.axzo.msg.center.message.service.impl;
import cn.axzo.msg.center.dal.MessageGroupNodeDao;
import cn.axzo.msg.center.dal.MessageTemplateGroupDao;
import cn.axzo.msg.center.domain.entity.MessageGroupNode;
import cn.axzo.msg.center.domain.entity.MessageTemplateGroup;
import cn.axzo.msg.center.message.domain.dto.MessageGroupNodeDTO;
import cn.axzo.msg.center.message.service.MessageGroupService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* @author cold_blade
* @date 2023/10/6
* @version 1.0
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class MessageGroupServiceImpl implements MessageGroupService {
private final MessageGroupNodeDao messageGroupNodeDao;
private final MessageTemplateGroupDao messageTemplateGroupDao;
@Override
public Optional<MessageGroupNodeDTO> queryByNodeCode(String nodeCode) {
if (StringUtils.isBlank(nodeCode)) {
return Optional.empty();
}
MessageGroupNode groupNode = messageGroupNodeDao.lambdaQuery()
.eq(MessageGroupNode::getCode, nodeCode)
.eq(MessageGroupNode::getIsDelete, 0)
.one();
return Optional.ofNullable(groupNode).map(MessageGroupNodeDTO::from);
}
@Override
public List<MessageGroupNodeDTO> listChildren(String nodeCode) {
return messageGroupNodeDao.lambdaQuery()
.eq(MessageGroupNode::getParentCode, nodeCode)
.eq(MessageGroupNode::getIsDelete, 0)
.list().stream()
.map(MessageGroupNodeDTO::from)
.collect(Collectors.toList());
}
@Override
public void templateGroup(String templateNode, Collection<String> pathList) {
if (StringUtils.isBlank(templateNode)
|| CollectionUtils.isEmpty(pathList)) {
return;
}
List<MessageTemplateGroup> rows = pathList.stream()
.map(e -> {
MessageTemplateGroup group = new MessageTemplateGroup();
group.setTemplateCode(templateNode);
group.setPath(e);
return group;
}).collect(Collectors.toList());
messageTemplateGroupDao.saveBatch(rows);
}
}

View File

@ -0,0 +1,185 @@
package cn.axzo.msg.center.message.service.impl;
import cn.axzo.basics.common.util.TreeUtil;
import cn.axzo.msg.center.common.enums.TableIsDeleteEnum;
import cn.axzo.msg.center.dal.MessageGroupNodeDao;
import cn.axzo.msg.center.domain.entity.MessageGroupNode;
import cn.axzo.msg.center.message.domain.dto.GroupTreeNodePathDTO;
import cn.axzo.msg.center.message.service.MessageGroupTreeNodeCacheService;
import cn.axzo.msg.center.service.dto.GroupTreeNodeDTO;
import cn.axzo.pokonyan.config.redis.RedisUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* @author cold_blade
* @date 2023/10/14
* @version 1.0
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class MessageGroupTreeNodeCacheServiceImpl implements MessageGroupTreeNodeCacheService {
private static final String CACHE_KEY = "msg-center:message:group:cache-key";
private static final String CACHE_VALUE = "msg-center:message:group:cache-val";
private static final long CACHE_KEY_TIMEOUT_DAYS = 1;
private final MessageGroupNodeDao messageGroupNodeDao;
private List<GroupTreeNodeDTO> allGroupTreeRootNodesCache = Collections.emptyList();
private List<GroupTreeNodePathDTO> leafTreeNodePathsCache = Collections.emptyList();
@Override
public List<GroupTreeNodeDTO> listAllRootNodes() {
return getAllGroupTreeRootNodesCache();
}
@Override
public Optional<GroupTreeNodeDTO> queryRootNode(String rootNodeCode) {
if (StringUtils.isBlank(rootNodeCode)) {
log.info("rootNodeCode is blank.");
return Optional.empty();
}
return getAllGroupTreeRootNodesCache().stream()
.filter(e -> Objects.equals(e.getNodeCode(), rootNodeCode))
.findFirst();
}
@Override
public Optional<GroupTreeNodeDTO> queryNode(String groupNodeCode) {
if (StringUtils.isBlank(groupNodeCode)) {
log.info("groupNodeCode is blank.");
return Optional.empty();
}
return getAllGroupTreeRootNodesCache().stream()
.map(e -> findTreeNode(e, groupNodeCode))
.filter(Objects::nonNull)
.findFirst();
}
@Override
public Optional<GroupTreeNodePathDTO> queryLeafNodePath(String leafNodeCode) {
if (StringUtils.isBlank(leafNodeCode)) {
log.info("leafNodeCode is blank.");
return Optional.empty();
}
return getLeafTreeNodePathsCache().stream()
.filter(e -> Objects.equals(e.getNodeCode(), leafNodeCode))
.findFirst();
}
@Override
public void refreshCache() {
// 清除redis中的缓存标识
RedisUtil.KeyOps.delete(CACHE_KEY);
// 清除本地缓存
allGroupTreeRootNodesCache = Collections.emptyList();
leafTreeNodePathsCache = Collections.emptyList();
}
private GroupTreeNodeDTO findTreeNode(GroupTreeNodeDTO root, String treeNodeCode) {
if (Objects.equals(root.getNodeCode(), treeNodeCode)) {
return root;
}
LinkedList<GroupTreeNodeDTO> queue = new LinkedList<>(root.getNodeChildren());
while (!queue.isEmpty()) {
GroupTreeNodeDTO node = queue.pop();
Optional<GroupTreeNodeDTO> childOp = node.getChild(treeNodeCode);
if (childOp.isPresent()) {
return childOp.get();
}
queue.addAll(node.getNodeChildren());
}
return null;
}
private List<GroupTreeNodeDTO> getAllGroupTreeRootNodesCache() {
if (RedisUtil.KeyOps.hasKey(CACHE_KEY)) {
// 其它结点进行了本地缓存但是当前服务进程还未进行缓存
if (CollectionUtils.isEmpty(allGroupTreeRootNodesCache)) {
// 本地缓存初始化不更新redis中的缓存标识
initialize(false);
}
} else {
// 本地缓存初始化并更新redis中的缓存标识
initialize(true);
}
return this.allGroupTreeRootNodesCache;
}
private List<GroupTreeNodePathDTO> getLeafTreeNodePathsCache() {
if (RedisUtil.KeyOps.hasKey(CACHE_KEY)) {
// 其它结点进行了本地缓存但是当前服务进程还未进行缓存
if (leafTreeNodePathsCache.isEmpty()) {
// 本地缓存初始化不更新redis中的缓存标识
initialize(false);
}
} else {
// 本地缓存初始化并更新redis中的缓存标识
initialize(true);
}
return leafTreeNodePathsCache;
}
private synchronized void initialize(boolean refreshCache) {
List<GroupTreeNodeDTO> groupNodes = listAllValidNodesFromDB();
allGroupTreeRootNodesCache = TreeUtil.buildTree(groupNodes);
leafTreeNodePathsCache = allGroupTreeRootNodesCache.stream()
.flatMap(e -> parseRootNode(e).stream())
.collect(Collectors.toList());
if (refreshCache) {
RedisUtil.StringOps.setIfAbsent(CACHE_KEY, CACHE_VALUE, CACHE_KEY_TIMEOUT_DAYS, TimeUnit.DAYS);
}
}
private List<GroupTreeNodeDTO> listAllValidNodesFromDB() {
return messageGroupNodeDao.lambdaQuery()
.eq(MessageGroupNode::getIsDelete, TableIsDeleteEnum.NORMAL.value)
.list().stream()
.map(this::convert)
.collect(Collectors.toList());
}
private GroupTreeNodeDTO convert(MessageGroupNode groupNode) {
return GroupTreeNodeDTO.builder()
.nodeName(groupNode.getName())
.nodeCode(groupNode.getCode())
.category(groupNode.getCategory())
.parentNodeCode(groupNode.getParentCode())
.isLeaf(groupNode.getIsLeaf())
.status(groupNode.getStatus())
.build();
}
private List<GroupTreeNodePathDTO> parseRootNode(GroupTreeNodeDTO root) {
List<GroupTreeNodePathDTO> paths = Lists.newArrayList();
LinkedList<GroupTreeNodeDTO> pathNodeStack = new LinkedList<>();
LinkedList<GroupTreeNodeDTO> stack = new LinkedList<>();
stack.push(root);
while (!stack.isEmpty()) {
GroupTreeNodeDTO node = stack.pop();
pathNodeStack.push(node);
if (CollectionUtils.isEmpty(node.getNodeChildren())) {
// 当前结点为树的叶结点逻辑上的有可能不是业务维度的叶结点
paths.add(GroupTreeNodePathDTO.of(node.getNodeCode(), pathNodeStack));
pathNodeStack.pop();
} else {
stack.addAll(node.getNodeChildren());
}
}
return paths;
}
}

View File

@ -2,14 +2,18 @@ package cn.axzo.msg.center.message.service.impl;
import cn.axzo.msg.center.dal.MessageTemplateGroupDao;
import cn.axzo.msg.center.domain.entity.MessageTemplateGroup;
import cn.axzo.msg.center.message.service.MessageGroupNodeService;
import cn.axzo.msg.center.message.service.MessageTemplateGroupService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
@ -22,6 +26,7 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor
public class MessageTemplateGroupServiceImpl implements MessageTemplateGroupService {
private final MessageGroupNodeService messageGroupNodeService;
private final MessageTemplateGroupDao messageTemplateGroupDao;
@Override
@ -37,4 +42,22 @@ public class MessageTemplateGroupServiceImpl implements MessageTemplateGroupServ
.map(MessageTemplateGroup::getTemplateCode)
.collect(Collectors.toList());
}
@Override
public void templateGroup(String templateNode, Collection<String> groupNodeCodes) {
if (StringUtils.isBlank(templateNode)
|| CollectionUtils.isEmpty(groupNodeCodes)) {
return;
}
Map<String, String> pathMap = messageGroupNodeService.leafGroupNodeCodePaths(groupNodeCodes);
List<MessageTemplateGroup> rows = groupNodeCodes.stream()
.filter(pathMap::containsKey)
.map(e -> {
MessageTemplateGroup group = new MessageTemplateGroup();
group.setTemplateCode(templateNode);
group.setPath(pathMap.get(e));
return group;
}).collect(Collectors.toList());
messageTemplateGroupDao.saveBatch(rows);
}
}

View File

@ -1,18 +1,24 @@
package cn.axzo.msg.center.message.service.impl;
import cn.axzo.core.utils.converter.BeanConverter;
import cn.axzo.basics.common.util.AssertUtil;
import cn.axzo.msg.center.common.enums.ServiceErrorCodeEnum;
import cn.axzo.msg.center.dal.MessageBaseTemplateDao;
import cn.axzo.msg.center.domain.entity.MessageBaseTemplate;
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.param.MessageTemplateCreateParam;
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.utils.JSONObjectUtil;
import cn.axzo.msg.center.utils.UUIDUtil;
import cn.axzo.pokonyan.config.redis.RedisUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
import java.util.List;
@ -33,19 +39,24 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor
public class MessageTemplateNewServiceImpl implements MessageTemplateNewService {
private static final String SAVE_TEMPLATE_MUTEX_KEY = "msg-center:template:save:%s";
private static final long TRY_LOCK_TIMEOUT_MILLS = 1000;
private static final int RETRY_CNT_MAX = 3;
private final MessageBaseTemplateDao messageBaseTemplateDao;
private final MessageTemplateGroupService messageTemplateGroupService;
private final MessageTemplateRouterService messageTemplateRouterService;
@Override
public void createTemplate(MessageTemplateCreateParam param) {
// TODO: [cold_blade] [P2] 模板与分类的关系
messageBaseTemplateDao.save(convert(param));
if (CollectionUtils.isNotEmpty(param.getRouters())) {
List<RawMessageRouterDTO> routers = param.getRouters().stream()
.map(e -> RawMessageRouterDTO.from(e, param.getTemplateCode()))
.collect(Collectors.toList());
messageTemplateRouterService.batchInsert(routers);
}
@Transactional(rollbackFor = Exception.class)
public String createTemplate(MessageTemplateCreateParam param) {
// 创建模板基础数据
String templateCode = saveTemplate(param);
// 创建模板的路由数据
saveTemplateRouters(param, templateCode);
// 创建模板与分类的关联关系数据
messageTemplateGroupService.templateGroup(templateCode, param.getLeafGroupNodes());
return templateCode;
}
@Override
@ -87,12 +98,65 @@ public class MessageTemplateNewServiceImpl implements MessageTemplateNewService
.collect(Collectors.toList());
}
private String saveTemplate(MessageTemplateCreateParam param) {
String templateCode = UUIDUtil.uuidString();
MessageBaseTemplate template = convert(param);
boolean result = doSaveTemplate(template, templateCode);
int retryCnt = 0;
while (!result && retryCnt++ < RETRY_CNT_MAX) {
// 默认重试{@cod RETRY_CNT_MAX}{@code RETRY_CNT_MAX}次后依然失败就报错
templateCode = UUIDUtil.uuidString();
result = doSaveTemplate(template, templateCode);
}
AssertUtil.isTrue(result, ServiceErrorCodeEnum.SYSTEM_BUSY.getDesc());
return templateCode;
}
private boolean doSaveTemplate(MessageBaseTemplate template, String templateCode) {
String requestId = UUIDUtil.uuidRawString();
try {
String opKey = String.format(SAVE_TEMPLATE_MUTEX_KEY, templateCode);
boolean lockResult = RedisUtil.LockOps.getLockUntilTimeout(opKey, requestId, TRY_LOCK_TIMEOUT_MILLS);
AssertUtil.isTrue(lockResult, ServiceErrorCodeEnum.SYSTEM_BUSY.getDesc());
if (isTemplateExist(templateCode)) {
return false;
}
template.setCode(templateCode);
messageBaseTemplateDao.save(template);
return true;
} finally {
RedisUtil.LockOps.releaseLock(SAVE_TEMPLATE_MUTEX_KEY, requestId);
}
}
private boolean isTemplateExist(String templateCode) {
return messageBaseTemplateDao.lambdaQuery()
.eq(MessageBaseTemplate::getCode, templateCode)
.eq(MessageBaseTemplate::getIsDelete, 0)
.count() > 0;
}
private MessageBaseTemplate convert(MessageTemplateCreateParam param) {
MessageBaseTemplate template = BeanConverter.convert(param, MessageBaseTemplate.class);
MessageBaseTemplate template = new MessageBaseTemplate();
template.setName(param.getTemplateName());
template.setCode(param.getTemplateCode());
template.setMsgCategory(param.getMsgCategory());
template.setTitle(param.getTitle());
template.setContent(param.getContent());
template.setCardContent(JSONObjectUtil.checkAndReturn(param.getCardContent()));
template.setIcon(param.getIcon());
template.setPushTerminal(JSONObjectUtil.toJSONString(param.getPushTerminals()));
template.setCreatorId(param.getOperatorId());
template.setUpdaterId(param.getOperatorId());
return template;
}
private void saveTemplateRouters(MessageTemplateCreateParam param, String templateCode) {
if (CollectionUtils.isEmpty(param.getRouters())) {
return;
}
List<RawMessageRouterDTO> routers = param.getRouters().stream()
.map(e -> RawMessageRouterDTO.from(e, templateCode))
.collect(Collectors.toList());
messageTemplateRouterService.batchInsert(routers);
}
}

View File

@ -5,16 +5,17 @@ import cn.axzo.msg.center.common.exception.ServiceException;
import cn.axzo.msg.center.common.utils.PlaceholderResolver;
import cn.axzo.msg.center.dal.PendingMessageRecordDao;
import cn.axzo.msg.center.domain.entity.PendingMessageRecord;
import cn.axzo.msg.center.message.domain.dto.MessageGroupNodeDTO;
import cn.axzo.msg.center.message.domain.dto.MessageGroupNodeStatisticDTO;
import cn.axzo.msg.center.message.domain.dto.MessageTemplateDTO;
import cn.axzo.msg.center.message.domain.dto.PendingMessageDTO;
import cn.axzo.msg.center.message.domain.dto.RawMessageRouterDTO;
import cn.axzo.msg.center.message.domain.param.MessageGroupNodeStatisticParam;
import cn.axzo.msg.center.message.domain.param.PendingMessagePushParam;
import cn.axzo.msg.center.message.service.MessageGroupService;
import cn.axzo.msg.center.message.service.MessageGroupNodeService;
import cn.axzo.msg.center.message.service.MessageTemplateGroupService;
import cn.axzo.msg.center.message.service.MessageTemplateNewService;
import cn.axzo.msg.center.message.service.PendingMessageNewService;
import cn.axzo.msg.center.service.dto.GroupTreeNodeDTO;
import cn.axzo.msg.center.service.dto.IdentityDTO;
import cn.axzo.msg.center.service.dto.MessageRouterDTO;
import cn.axzo.msg.center.service.dto.PersonDTO;
@ -29,6 +30,7 @@ import cn.axzo.msg.center.service.pending.response.PendingMessageResponse;
import cn.axzo.msg.center.utils.JSONObjectUtil;
import cn.axzo.msg.center.utils.MessageRouterUtil;
import cn.axzo.msg.center.utils.OrderFieldParseUtil;
import cn.axzo.msg.center.utils.TreeHelperUtil;
import cn.axzo.msg.center.utils.UUIDUtil;
import cn.azxo.framework.common.model.Page;
import com.alibaba.fastjson.JSON;
@ -45,10 +47,10 @@ import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Stack;
import java.util.stream.Collectors;
/**
@ -63,13 +65,13 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor
public class PendingMessageNewServiceImpl implements PendingMessageNewService {
private final MessageGroupService messageGroupService;
private final PendingMessageRecordDao pendingMessageRecordDao;
private final MessageGroupNodeService messageGroupNodeService;
private final MessageTemplateNewService messageTemplateNewService;
private final MessageTemplateGroupService messageTemplateGroupService;
@Override
public List<MessageGroupNodeDTO> groupStatistic(MessageGroupNodeStatisticParam param) {
public List<MessageGroupNodeStatisticDTO> groupStatistic(MessageGroupNodeStatisticParam param) {
return param.getGroupNodeCodes().stream()
.flatMap(e -> statistic(e, param).stream())
.collect(Collectors.toList());
@ -193,26 +195,23 @@ public class PendingMessageNewServiceImpl implements PendingMessageNewService {
return pendingMessage;
}
private List<MessageGroupNodeDTO> statistic(String rootNodeCode, MessageGroupNodeStatisticParam param) {
MessageGroupNodeDTO groupNode = messageGroupService.queryByNodeCode(rootNodeCode)
private List<MessageGroupNodeStatisticDTO> statistic(String rootNodeCode, MessageGroupNodeStatisticParam param) {
GroupTreeNodeDTO rootNode = messageGroupNodeService.queryRootNode(rootNodeCode)
.orElseThrow(() -> new ServiceException("groupNodeCode is invalid."));
MessageGroupNodeDTO root = groupNode;
Stack<MessageGroupNodeDTO> stack = new Stack<>();
stack.push(groupNode);
List<MessageGroupNodeDTO> children;
MessageGroupNodeStatisticDTO rootWrapper = TreeHelperUtil.wrapper(rootNode, MessageGroupNodeStatisticDTO::of);
LinkedList<MessageGroupNodeStatisticDTO> stack = new LinkedList<>();
stack.push(rootWrapper);
MessageGroupNodeStatisticDTO wrapper;
while (!stack.isEmpty()) {
// TODO: [cold_blade] [P3] 优化树形分类结点的统计效率
groupNode = stack.pop();
statistic(groupNode, param);
children = messageGroupService.listChildren(groupNode.getNodeCode());
groupNode.setChildren(children);
children.forEach(stack::push);
wrapper = stack.pop();
statistic(wrapper, param);
stack.addAll(wrapper.getNodeChildren());
}
// 外部传的是根节点但是前端希望只返回根节点的字节的
return root.getChildren();
return rootWrapper.getNodeChildren();
}
private void statistic(MessageGroupNodeDTO groupNode, MessageGroupNodeStatisticParam param) {
private void statistic(MessageGroupNodeStatisticDTO groupNode, MessageGroupNodeStatisticParam param) {
List<String> templateCodes = messageTemplateGroupService.listMessageTemplateCodes(groupNode.getNodeCode());
if (CollectionUtils.isEmpty(templateCodes)) {
groupNode.setPendingCount(0);
@ -298,6 +297,7 @@ public class PendingMessageNewServiceImpl implements PendingMessageNewService {
}
private void buildTemplateInfo(PendingMessageRecord record, MessageTemplateDTO msgTemplate, String routeParam) {
// TODO:[cold_blade] [P3] 后续其它业务对接的时候需要明确业务扩展字段和路由参数的分界
JSONObject routerParamObj = JSONObjectUtil.parseObject(routeParam);
String title = PlaceholderResolver
.getDefaultResolver().resolveByMap(msgTemplate.getTitle(), routerParamObj);
@ -316,4 +316,9 @@ public class PendingMessageNewServiceImpl implements PendingMessageNewService {
record.setBizCategory(param.getBizCategory());
record.setBizExtParam(JSONObjectUtil.checkAndReturn(param.getBizExtParams()));
}
private static class GroupTreeNodeWrapper {
private GroupTreeNodeDTO treeNode;
private int pendingCnt;
}
}

View File

@ -4,8 +4,12 @@ import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.Collection;
import java.util.Objects;
/**
* @author cold_blade
* @date 2023/10/13
@ -14,6 +18,8 @@ import org.apache.commons.lang3.StringUtils;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class JSONObjectUtil {
private static final JSONObject EMPTY_JSON_OBJ = new JSONObject();
private static final String EMPTY_JSON_OBJ_STR = "{}";
private static final String EMPTY_JSON_ARR_STR = "[]";
/**
* 解析JSON字符串若字符串格式不正确抛异常
@ -28,6 +34,20 @@ public final class JSONObjectUtil {
return JSON.parseObject(str);
}
public static String toJSONString(Object obj) {
if (Objects.isNull(obj)) {
return EMPTY_JSON_OBJ_STR;
}
return JSON.toJSONString(obj);
}
public static String toJSONString(Collection<?> objCollection) {
if (CollectionUtils.isEmpty(objCollection)) {
return EMPTY_JSON_ARR_STR;
}
return JSON.toJSONString(objCollection);
}
/**
* 校验字符串是否为有效的JSON字符串若字符串格式不正确抛异常
*

View File

@ -0,0 +1,44 @@
package cn.axzo.msg.center.utils;
import cn.axzo.basics.common.model.IBaseTree;
import cn.axzo.basics.common.util.AssertUtil;
import cn.axzo.msg.center.service.dto.GroupTreeNodeDTO;
import com.google.common.collect.Maps;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* @author cold_blade
* @date 2023/10/16
* @version 1.0
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class TreeHelperUtil {
public static <T extends IBaseTree<T, O>, O> T wrapper(GroupTreeNodeDTO treeNode,
Function<GroupTreeNodeDTO, T> wrapperFunc) {
AssertUtil.notNull(treeNode, "treeNode is null");
AssertUtil.notNull(wrapperFunc, "wrapperFunc is null");
final Map<GroupTreeNodeDTO, T> convertMap = Maps.newHashMap();
T wrapper = wrapperFunc.apply(treeNode);
convertMap.put(treeNode, wrapper);
LinkedList<GroupTreeNodeDTO> treeNodeStack = new LinkedList<>();
treeNodeStack.push(treeNode);
while (!treeNodeStack.isEmpty()) {
treeNode = treeNodeStack.pop();
List<GroupTreeNodeDTO> children = treeNode.getNodeChildren();
convertMap.get(treeNode).setNodeChildren(children.stream()
.map(e -> convertMap.put(e, wrapperFunc.apply(e)))
.collect(Collectors.toList()));
treeNodeStack.addAll(children);
}
return wrapper;
}
}

View File

@ -1,53 +0,0 @@
package cn.axzo.msg.center.service.dto;
import cn.axzo.msg.center.service.enums.MessageGroupNodeCategoryEnum;
import cn.axzo.msg.center.service.enums.StatusEnum;
import com.alibaba.fastjson.JSON;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
/**
* @description
*
* @author cold_blade
* @date 2023/9/23
* @version 1.0
*/
@Setter
@Getter
public class GroupNodeDTO implements Serializable {
private static final long serialVersionUID = -5935469947222698608L;
/**
* 模板分组结点名称
*/
private String name;
/**
* 模板分组结点名称code
*/
private String code;
/**
* 结点类型
*/
private MessageGroupNodeCategoryEnum category;
/**
* 父节点
*/
private String parentCode;
/**
* 是否为叶节点
*/
private Integer isLeaf;
/**
* 状态
*/
private StatusEnum status;
@Override
public String toString() {
return JSON.toJSONString(this);
}
}

View File

@ -0,0 +1,71 @@
package cn.axzo.msg.center.service.dto;
import cn.axzo.basics.common.model.IBaseTree;
import cn.axzo.msg.center.service.enums.MessageGroupNodeCategoryEnum;
import cn.axzo.msg.center.service.enums.StatusEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import java.io.Serializable;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
/**
* @author cold_blade
* @date 2023/10/14
* @version 1.0
*/
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class GroupTreeNodeDTO implements IBaseTree<GroupTreeNodeDTO, String>, Serializable {
private static final long serialVersionUID = -3244632155934087302L;
/**
* 模板分组结点名称
*/
private String nodeName;
/**
* 模板分组结点名称code
*/
private String nodeCode;
/**
* 结点类型
*/
private MessageGroupNodeCategoryEnum category;
/**
* 父节点
*/
private String parentNodeCode;
/**
* 是否为叶节点
*/
private Integer isLeaf;
/**
* 状态
*/
private StatusEnum status;
/**
* 子节点列表
*/
@Builder.Default
private List<GroupTreeNodeDTO> nodeChildren = Collections.emptyList();
public Optional<GroupTreeNodeDTO> getChild(String treeNodeCode) {
if (StringUtils.isBlank(treeNodeCode)) {
return Optional.empty();
}
return getNodeChildren().stream()
.filter(e -> Objects.equals(e.getNodeCode(), treeNodeCode))
.findFirst();
}
}

View File

@ -0,0 +1,23 @@
package cn.axzo.msg.center.common.enums;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author cold_blade
* @date 2023/10/14
* @version 1.0
*/
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum ServiceErrorCodeEnum {
SYSTEM_BUSY("E0002", "系统繁忙,请稍后重试"),
PARAM_IS_INVALID("E0020", "参数异常"),
;
private final String code;
private final String desc;
}