feat(REQ-1465): 消息分类相关模块接口实现

背景:
  https://jira.axzo.cn/browse/REQ-1465?goToView=1

修改:
  1、消息分类相关模块接口实现

影响:
  无
This commit is contained in:
luofu 2023-10-17 18:24:43 +08:00
parent 874676b897
commit 458846ca15
12 changed files with 318 additions and 29 deletions

View File

@ -0,0 +1,56 @@
package cn.axzo.msg.center.message.controller;
import cn.axzo.msg.center.message.domain.param.MessageGroupNodeSaveOrUpdateParam;
import cn.axzo.msg.center.message.service.MessageGroupNodeService;
import cn.axzo.msg.center.service.dto.GroupTreeNodeDTO;
import cn.axzo.msg.center.service.enums.MessageCategoryEnum;
import cn.axzo.msg.center.service.group.client.MessageGroupClient;
import cn.axzo.msg.center.service.group.request.MessageGroupNodeAddRequest;
import cn.axzo.msg.center.service.group.request.MessageGroupNodeUpdateRequest;
import cn.axzo.msg.center.service.group.response.MessageGroupTreeNodeResponse;
import cn.azxo.framework.common.model.CommonResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.stream.Collectors;
/**
* 消息分类管理
*
* @author cold_blade
* @date 2023/10/17
* @version 1.0
*/
@RestController
@RequiredArgsConstructor
public class MessageGroupController implements MessageGroupClient {
private final MessageGroupNodeService messageGroupNodeService;
@Override
public CommonResponse<Void> addNode(MessageGroupNodeAddRequest request) {
messageGroupNodeService.addGroupNode(MessageGroupNodeSaveOrUpdateParam.from(request));
return CommonResponse.success();
}
@Override
public CommonResponse<Void> updateNode(MessageGroupNodeUpdateRequest request) {
messageGroupNodeService.updateGroupNode(MessageGroupNodeSaveOrUpdateParam.from(request));
return CommonResponse.success();
}
@Override
public CommonResponse<Void> deleteNode(String nodeCode, Long operatorId) {
messageGroupNodeService.deleteGroupNode(operatorId, nodeCode);
return CommonResponse.success();
}
@Override
public CommonResponse<List<MessageGroupTreeNodeResponse>> list(MessageCategoryEnum category) {
List<MessageGroupTreeNodeResponse> groupTreeNodes = messageGroupNodeService.listGroupTree(category).stream()
.map(GroupTreeNodeDTO::toMessageGroupTreeNodeResponse)
.collect(Collectors.toList());
return CommonResponse.success(groupTreeNodes);
}
}

View File

@ -0,0 +1,69 @@
package cn.axzo.msg.center.message.domain.param;
import cn.axzo.core.utils.converter.BeanConverter;
import cn.axzo.msg.center.service.enums.MessageGroupNodeCategoryEnum;
import cn.axzo.msg.center.service.group.request.MessageGroupNodeAddRequest;
import cn.axzo.msg.center.service.group.request.MessageGroupNodeUpdateRequest;
import com.alibaba.fastjson.JSON;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* @author cold_blade
* @date 2023/10/17
* @version 1.0
*/
@Setter
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MessageGroupNodeSaveOrUpdateParam {
/**
* 操作者的自然人id
*/
private Long operatorId;
/**
* 父节点编码
*/
private String parentNodeCode;
/**
* 待添加结点名称
*/
private String nodeName;
/**
* 待修改的结点编码
*/
private String nodeCode;
/**
* 待添加结点类型
* GENERAL_MESSAGE_CENTER: 通知的业务中心
* GENERAL_MESSAGE_MODULE: 消息模块
* GENERAL_MESSAGE_CATEGORY: 消息分类
* PENDING_MESSAGE_CENTER: 待办的业务中心
* PENDING_MESSAGE_MODULE: 待办模块
* PENDING_MESSAGE_CATEGORY: 待办分类
*/
private MessageGroupNodeCategoryEnum category;
/**
* 待添加结点图标
*/
private String icon;
public static MessageGroupNodeSaveOrUpdateParam from(MessageGroupNodeAddRequest request) {
return BeanConverter.convert(request, MessageGroupNodeSaveOrUpdateParam.class);
}
public static MessageGroupNodeSaveOrUpdateParam from(MessageGroupNodeUpdateRequest request) {
return BeanConverter.convert(request, MessageGroupNodeSaveOrUpdateParam.class);
}
@Override
public String toString() {
return JSON.toJSONString(this);
}
}

View File

@ -1,8 +1,11 @@
package cn.axzo.msg.center.message.service;
import cn.axzo.msg.center.message.domain.param.MessageGroupNodeSaveOrUpdateParam;
import cn.axzo.msg.center.service.dto.GroupTreeNodeDTO;
import cn.axzo.msg.center.service.enums.MessageCategoryEnum;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@ -38,4 +41,33 @@ public interface MessageGroupNodeService {
* @return 结点信息
*/
Optional<GroupTreeNodeDTO> queryRootNode(String rootNodeCode);
/**
* 新增结点
*
* @param param 结点内容
*/
void addGroupNode(MessageGroupNodeSaveOrUpdateParam param);
/**
* 编辑结点
*
* @param param 结点内容
*/
void updateGroupNode(MessageGroupNodeSaveOrUpdateParam param);
/**
* 删除结点
*
* @param operatorId 操作人id
* @param nodeCode 待删除结点的编码
*/
void deleteGroupNode(Long operatorId, String nodeCode);
/**
* 查询消息/待办 或者两者的分类树
* @param category 消息/待办
* @return 分类树列表
*/
List<GroupTreeNodeDTO> listGroupTree(MessageCategoryEnum category);
}

View File

@ -16,11 +16,11 @@ import java.util.Optional;
public interface MessageGroupTreeNodeCacheService {
/**
* 获取所有有效的分类结点树的根节点
* 获取所有有效的分类结点树
*
* @return 根节点列表
* @return 列表
*/
List<GroupTreeNodeDTO> listAllRootNodes();
List<GroupTreeNodeDTO> listAllGroupTree();
/**
* 获取指定结点所在的树的根节点

View File

@ -1,17 +1,31 @@
package cn.axzo.msg.center.message.service.impl;
import cn.axzo.basics.common.util.AssertUtil;
import cn.axzo.msg.center.common.enums.TableIsDeleteEnum;
import cn.axzo.msg.center.common.exception.ServiceException;
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.GroupTreeNodePathDTO;
import cn.axzo.msg.center.message.domain.param.MessageGroupNodeSaveOrUpdateParam;
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 cn.axzo.msg.center.service.enums.MessageCategoryEnum;
import cn.axzo.msg.center.utils.TreeHelperUtil;
import cn.axzo.msg.center.utils.UUIDUtil;
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.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
@ -25,6 +39,8 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor
public class MessageGroupNodeServiceImpl implements MessageGroupNodeService {
private final MessageGroupNodeDao messageGroupNodeDao;
private final MessageTemplateGroupDao messageTemplateGroupDao;
private final MessageGroupTreeNodeCacheService messageGroupTreeNodeCacheService;
@Override
@ -60,4 +76,91 @@ public class MessageGroupNodeServiceImpl implements MessageGroupNodeService {
public Optional<GroupTreeNodeDTO> queryRootNode(String rootNodeCode) {
return messageGroupTreeNodeCacheService.queryRootNode(rootNodeCode);
}
@Override
public void addGroupNode(MessageGroupNodeSaveOrUpdateParam param) {
GroupTreeNodeDTO parent = messageGroupTreeNodeCacheService.queryNode(param.getParentNodeCode())
.orElseThrow(() -> new ServiceException("parentNodeCode is invalid"));
// 合法性校验
checkCreateRule(parent, param);
// 存储
messageGroupNodeDao.save(convert(param));
// 刷新缓存
messageGroupTreeNodeCacheService.refreshCache();
}
@Override
public void updateGroupNode(MessageGroupNodeSaveOrUpdateParam param) {
boolean updateResult = messageGroupNodeDao.lambdaUpdate()
.eq(MessageGroupNode::getCode, param.getNodeCode())
.eq(MessageGroupNode::getIsDelete, TableIsDeleteEnum.NORMAL.value)
.set(StringUtils.isNotBlank(param.getNodeName()), MessageGroupNode::getName, param.getNodeName())
.set(StringUtils.isNotBlank(param.getIcon()), MessageGroupNode::getIcon, param.getIcon())
.set(MessageGroupNode::getUpdaterId, param.getOperatorId())
.update();
if (updateResult) {
// 刷新缓存
messageGroupTreeNodeCacheService.refreshCache();
}
}
@Override
public void deleteGroupNode(Long operatorId, String nodeCode) {
if (Objects.isNull(operatorId) || StringUtils.isBlank(nodeCode)) {
log.info("the param is invalid. operatorId:[{}], nodeCode:[{}]", operatorId, nodeCode);
return;
}
// 合法性校验
checkDeleteRule(nodeCode);
// 删除结点
boolean removeResult = messageGroupNodeDao.lambdaUpdate()
.eq(MessageGroupNode::getCode, nodeCode)
.eq(MessageGroupNode::getIsDelete, TableIsDeleteEnum.NORMAL.value)
.remove();
if (removeResult) {
// 刷新缓存
messageGroupTreeNodeCacheService.refreshCache();
}
}
@Override
public List<GroupTreeNodeDTO> listGroupTree(MessageCategoryEnum category) {
return messageGroupTreeNodeCacheService.listAllGroupTree().stream()
// 查询消息/待办 OR 两者的分类树
.filter(e -> Objects.isNull(category)
|| Objects.equals(e.getCategory().getMsgCategory(), category))
.collect(Collectors.toList());
}
private MessageGroupNode convert(MessageGroupNodeSaveOrUpdateParam param) {
MessageGroupNode groupNode = new MessageGroupNode();
groupNode.setCode(UUIDUtil.uuidString());
groupNode.setName(param.getNodeName());
groupNode.setIcon(param.getIcon());
groupNode.setCategory(param.getCategory());
groupNode.setParentCode(param.getParentNodeCode());
groupNode.setCreatorId(param.getOperatorId());
groupNode.setUpdaterId(param.getOperatorId());
if (TreeHelperUtil.isLeafNodeCategory(param.getCategory())) {
groupNode.setIsLeaf(1);
}
return groupNode;
}
private void checkCreateRule(GroupTreeNodeDTO parent, MessageGroupNodeSaveOrUpdateParam param) {
AssertUtil.isTrue(parent.getCategory().getLevel() < param.getCategory().getLevel(), "父节点的类型非法");
}
private void checkDeleteRule(String nodeCode) {
GroupTreeNodeDTO node = messageGroupTreeNodeCacheService.queryNode(nodeCode)
.orElseThrow(() -> new ServiceException(String.format("未找到指定的结点[%s]", nodeCode)));
// TODO: [cold_blade] [P2] 异常处理需要检查
AssertUtil.isEmpty(node.getNodeChildren(), "删除失败!删除前请删除子级!");
// 校验其结点是否被模板关联
int cnt = messageTemplateGroupDao.lambdaQuery()
.like(MessageTemplateGroup::getPath, nodeCode)
.eq(MessageTemplateGroup::getIsDelete, TableIsDeleteEnum.NORMAL.value)
.count();
AssertUtil.isTrue(cnt == 0, "删除失败!删除前请先转移消息模版!");
}
}

View File

@ -40,12 +40,12 @@ public class MessageGroupTreeNodeCacheServiceImpl implements MessageGroupTreeNod
private final RedisUtil redisUtil;
private final MessageGroupNodeDao messageGroupNodeDao;
private List<GroupTreeNodeDTO> allGroupTreeRootNodesCache = Collections.emptyList();
private List<GroupTreeNodeDTO> allGroupTreesCache = Collections.emptyList();
private List<GroupTreeNodePathDTO> leafTreeNodePathsCache = Collections.emptyList();
@Override
public List<GroupTreeNodeDTO> listAllRootNodes() {
return getAllGroupTreeRootNodesCache();
public List<GroupTreeNodeDTO> listAllGroupTree() {
return getAllGroupTreesCache();
}
@Override
@ -54,7 +54,7 @@ public class MessageGroupTreeNodeCacheServiceImpl implements MessageGroupTreeNod
log.info("rootNodeCode is blank.");
return Optional.empty();
}
return getAllGroupTreeRootNodesCache().stream()
return getAllGroupTreesCache().stream()
.filter(e -> Objects.equals(e.getNodeCode(), rootNodeCode))
.findFirst();
}
@ -65,7 +65,7 @@ public class MessageGroupTreeNodeCacheServiceImpl implements MessageGroupTreeNod
log.info("groupNodeCode is blank.");
return Optional.empty();
}
return getAllGroupTreeRootNodesCache().stream()
return getAllGroupTreesCache().stream()
.map(e -> findTreeNode(e, groupNodeCode))
.filter(Objects::nonNull)
.findFirst();
@ -83,12 +83,11 @@ public class MessageGroupTreeNodeCacheServiceImpl implements MessageGroupTreeNod
}
@Override
public void refreshCache() {
public synchronized void refreshCache() {
// 清除redis中的缓存标识
redisUtil.getKeyOps().delete(CACHE_KEY);
// 清除本地缓存
allGroupTreeRootNodesCache = Collections.emptyList();
leafTreeNodePathsCache = Collections.emptyList();
// 本地缓存初始化并更新redis中的缓存标识
initialize(true);
}
private GroupTreeNodeDTO findTreeNode(GroupTreeNodeDTO root, String treeNodeCode) {
@ -107,10 +106,10 @@ public class MessageGroupTreeNodeCacheServiceImpl implements MessageGroupTreeNod
return null;
}
private List<GroupTreeNodeDTO> getAllGroupTreeRootNodesCache() {
private List<GroupTreeNodeDTO> getAllGroupTreesCache() {
if (redisUtil.getKeyOps().hasKey(CACHE_KEY)) {
// 其它结点进行了本地缓存但是当前服务进程还未进行缓存
if (CollectionUtils.isEmpty(allGroupTreeRootNodesCache)) {
if (CollectionUtils.isEmpty(allGroupTreesCache)) {
// 本地缓存初始化不更新redis中的缓存标识
initialize(false);
}
@ -118,7 +117,7 @@ public class MessageGroupTreeNodeCacheServiceImpl implements MessageGroupTreeNod
// 本地缓存初始化并更新redis中的缓存标识
initialize(true);
}
return this.allGroupTreeRootNodesCache;
return this.allGroupTreesCache;
}
private List<GroupTreeNodePathDTO> getLeafTreeNodePathsCache() {
@ -137,8 +136,8 @@ public class MessageGroupTreeNodeCacheServiceImpl implements MessageGroupTreeNod
private synchronized void initialize(boolean refreshCache) {
List<GroupTreeNodeDTO> groupNodes = listAllValidNodesFromDB();
allGroupTreeRootNodesCache = TreeUtil.buildTree(groupNodes);
leafTreeNodePathsCache = allGroupTreeRootNodesCache.stream()
allGroupTreesCache = TreeUtil.buildTree(groupNodes);
leafTreeNodePathsCache = allGroupTreesCache.stream()
.flatMap(e -> parseRootNode(e).stream())
.collect(Collectors.toList());
if (refreshCache) {

View File

@ -3,6 +3,7 @@ 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 cn.axzo.msg.center.service.enums.MessageGroupNodeCategoryEnum;
import com.google.common.collect.Maps;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
@ -41,4 +42,9 @@ public final class TreeHelperUtil {
}
return wrapper;
}
public static boolean isLeafNodeCategory(MessageGroupNodeCategoryEnum category) {
return MessageGroupNodeCategoryEnum.GENERAL_MESSAGE_CATEGORY.equals(category)
|| MessageGroupNodeCategoryEnum.PENDING_MESSAGE_CATEGORY.equals(category);
}
}

View File

@ -3,6 +3,7 @@ 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 cn.axzo.msg.center.service.group.response.MessageGroupTreeNodeResponse;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
@ -15,6 +16,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* @author cold_blade
@ -60,6 +62,18 @@ public class GroupTreeNodeDTO implements IBaseTree<GroupTreeNodeDTO, String>, Se
@Builder.Default
private List<GroupTreeNodeDTO> nodeChildren = Collections.emptyList();
public MessageGroupTreeNodeResponse toMessageGroupTreeNodeResponse() {
return MessageGroupTreeNodeResponse.builder()
.category(category)
.nodeName(nodeName)
.nodeCode(nodeCode)
.parentNodeCode(parentNodeCode)
.children(nodeChildren.stream()
.map(GroupTreeNodeDTO::toMessageGroupTreeNodeResponse)
.collect(Collectors.toList()))
.build();
}
public Optional<GroupTreeNodeDTO> getChild(String treeNodeCode) {
if (StringUtils.isBlank(treeNodeCode)) {
return Optional.empty();

View File

@ -15,13 +15,15 @@ import lombok.Getter;
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum MessageGroupNodeCategoryEnum {
GENERAL_MESSAGE_CENTER("消息中心"),
GENERAL_MESSAGE_MODULE("消息模块"),
GENERAL_MESSAGE_CATEGORY("消息分类"),
PENDING_MESSAGE_CENTER("待办中心"),
PENDING_MESSAGE_MODULE("待办模块"),
PENDING_MESSAGE_CATEGORY("待办分类"),
GENERAL_MESSAGE_CENTER("消息中心", 1, MessageCategoryEnum.GENERAL_MESSAGE),
GENERAL_MESSAGE_MODULE("消息模块", 2, MessageCategoryEnum.GENERAL_MESSAGE),
GENERAL_MESSAGE_CATEGORY("消息分类", 3, MessageCategoryEnum.GENERAL_MESSAGE),
PENDING_MESSAGE_CENTER("待办中心", 1, MessageCategoryEnum.PENDING_MESSAGE),
PENDING_MESSAGE_MODULE("待办模块", 2, MessageCategoryEnum.PENDING_MESSAGE),
PENDING_MESSAGE_CATEGORY("待办分类", 3, MessageCategoryEnum.PENDING_MESSAGE),
;
private final String desc;
private final int level;
private final MessageCategoryEnum msgCategory;
}

View File

@ -60,5 +60,6 @@ public interface MessageGroupClient {
* @param category 消息分类
*/
@PostMapping(value = "/message/group/node/list", produces = {MediaType.APPLICATION_JSON_VALUE})
CommonResponse<List<MessageGroupTreeNodeResponse>> list(@RequestParam("category") MessageCategoryEnum category);
CommonResponse<List<MessageGroupTreeNodeResponse>> list(@RequestParam(value = "category", required = false)
MessageCategoryEnum category);
}

View File

@ -36,11 +36,6 @@ public class MessageGroupNodeAddRequest implements Serializable {
*/
@NotEmpty(message = "nodeName is required")
private String nodeName;
/**
* 待添加结点编码
*/
@NotEmpty(message = "nodeCode is required")
private String nodeCode;
/**
* 待添加结点类型
* GENERAL_MESSAGE_CENTER: 通知的业务中心

View File

@ -1,7 +1,11 @@
package cn.axzo.msg.center.service.group.response;
import cn.axzo.msg.center.service.enums.MessageGroupNodeCategoryEnum;
import com.alibaba.fastjson.JSON;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.io.Serializable;
@ -14,6 +18,9 @@ import java.util.List;
*/
@Setter
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MessageGroupTreeNodeResponse implements Serializable {
private static final long serialVersionUID = -6741888813327778598L;
@ -44,4 +51,9 @@ public class MessageGroupTreeNodeResponse implements Serializable {
* 子节点列表
*/
private List<MessageGroupTreeNodeResponse> children;
@Override
public String toString() {
return JSON.toJSONString(this);
}
}