1. system 提供新的 Resource 相关接口

2. admin-web 接入新的 Resource 相关接口
This commit is contained in:
YunaiV
2020-04-27 19:48:58 +08:00
parent caf605063c
commit f7157d283c
34 changed files with 684 additions and 368 deletions

View File

@@ -0,0 +1,24 @@
package cn.iocoder.mall.system.biz.bo.authorization;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
/**
* 授权模块 - 资源信息树节点 BO
*/
@Data
@Accessors(chain = true)
public class ResourceTreeNodeBO {
/**
* 当前节点
*/
private ResourceBO node;
/**
* 子节点们
*/
private List<ResourceTreeNodeBO> children;
}

View File

@@ -1,8 +1,12 @@
package cn.iocoder.mall.system.biz.convert.authorization;
import cn.iocoder.mall.system.biz.bo.authorization.ResourceBO;
import cn.iocoder.mall.system.biz.bo.authorization.ResourceTreeNodeBO;
import cn.iocoder.mall.system.biz.dataobject.authorization.ResourceDO;
import cn.iocoder.mall.system.biz.dto.authorization.ResourceAddDTO;
import cn.iocoder.mall.system.biz.dto.authorization.ResourceUpdateDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
import java.util.List;
@@ -14,6 +18,13 @@ public interface ResourceConvert {
ResourceBO convert(ResourceDO bean);
@Mapping(source = "bean", target = "node")
ResourceTreeNodeBO convertTreeNode(ResourceDO bean);
List<ResourceBO> convertList(List<ResourceDO> beans);
ResourceDO convert(ResourceAddDTO bean);
ResourceDO convert(ResourceUpdateDTO bean);
}

View File

@@ -29,8 +29,6 @@ public class RoleDO extends DeletableDO {
private String code;
/**
* 角色类型
*
* TODO 需要补充
*/
private Integer type;

View File

@@ -0,0 +1,22 @@
package cn.iocoder.mall.system.biz.dto.authorization;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
/**
* 授权模块 - 获得账号所拥有的资源树 DTO
*/
@Data
@Accessors(chain = true)
public class AuthorizationGetResourceTreeByAccountIdDTO {
@NotNull(message = "账号编号不能为空")
private Integer accountId;
/**
* 资源类型
*/
private Integer type;
}

View File

@@ -6,7 +6,7 @@ import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
/**
* 授权模块 - 获得账号所拥有的资源 DTO
* 授权模块 - 获得账号所拥有的资源列表 DTO
*/
@Data
@Accessors(chain = true)

View File

@@ -0,0 +1,47 @@
package cn.iocoder.mall.system.biz.dto.authorization;
import cn.iocoder.common.framework.validator.InEnum;
import cn.iocoder.mall.system.biz.enums.authorization.ResourceTypeEnum;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
/**
* 资源模块 - 添加资源 DTO
*/
@Data
@Accessors(chain = true)
public class ResourceAddDTO {
@NotNull(message = "管理员编号不能为空")
private Integer adminId;
@NotNull(message = "类型不能为空")
@InEnum(value = ResourceTypeEnum.class, message = "资源类型必须是 {value}")
private Integer type;
@NotNull(message = "类型不能为空")
private Integer sort;
@NotEmpty(message = "菜单名不能为空")
private String name;
@NotNull(message = "父级资源编号不能为空")
private Integer pid;
/**
* 前端路由
*/
private String route;
/**
* 图标
*/
private String icon;
/**
* 权限标识
*/
private String permission;
}

View File

@@ -0,0 +1,23 @@
package cn.iocoder.mall.system.biz.dto.authorization;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
/**
* 资源模块 - 删除资源 DTO
*/
@Data
@Accessors(chain = true)
public class ResourceDeleteDTO {
@NotNull(message = "管理员编号不能为空")
private Integer adminId;
@ApiModelProperty(value = "资源编号", required = true, example = "1")
@NotNull(message = "资源编号不能为空")
private Integer id;
}

View File

@@ -0,0 +1,25 @@
package cn.iocoder.mall.system.biz.dto.authorization;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Collection;
/**
* 资源模块 - 获得资源树 DTO
*/
@Data
@Accessors(chain = true)
public class ResourceGetTreeDTO {
/**
* 资源编号数组
*/
private Collection<Integer> ids;
/**
* 资源类型
*/
private Integer type;
}

View File

@@ -0,0 +1,53 @@
package cn.iocoder.mall.system.biz.dto.authorization;
import cn.iocoder.common.framework.validator.InEnum;
import cn.iocoder.mall.system.biz.enums.authorization.ResourceTypeEnum;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* 资源模块 - 更新资源 DTO
*/
@Data
@Accessors(chain = true)
public class ResourceUpdateDTO {
@NotNull(message = "管理员编号不能为空")
private Integer adminId;
@ApiModelProperty(value = "资源编号", required = true, example = "1")
@NotNull(message = "资源编号不能为空")
private Integer id;
@ApiModelProperty(value = "资源类型。1 代表【菜单】2 代表【按钮】", required = true, example = "1")
@NotNull(message = "类型不能为空")
@InEnum(value = ResourceTypeEnum.class, message = "资源类型必须是 {value}")
private Integer type;
@ApiModelProperty(value = "排序", required = true, example = "1")
@NotNull(message = "类型不能为空")
private Integer sort;
@ApiModelProperty(value = "菜单展示名", required = true, example = "商品管理")
@NotEmpty(message = "资源名字不能为空")
private String displayName;
@ApiModelProperty(value = "父级资源编号", required = true, example = "1")
@NotNull(message = "父级资源编号不能为空")
private Integer pid;
@ApiModelProperty(value = "操作", example = "/order/list")
private String handler;
@ApiModelProperty(value = "图标", example = "add")
private String icon;
@ApiModelProperty(value = "权限标识数组", example = "system.order.add,system.order.update")
private List<String> permissions;
}

View File

@@ -0,0 +1,29 @@
package cn.iocoder.mall.system.biz.event.authorization;
import cn.iocoder.mall.system.biz.dataobject.authorization.ResourceDO;
import org.springframework.context.ApplicationEvent;
/**
* {@link ResourceDO} 删除事件
*/
public class ResourceDeleteEvent extends ApplicationEvent {
/**
* 资源编号
*/
private Integer id;
public ResourceDeleteEvent(Object source) {
super(source);
}
public ResourceDeleteEvent(Object source, Integer id) {
super(source);
this.id = id;
}
public Integer getId() {
return id;
}
}

View File

@@ -0,0 +1,6 @@
/**
* Spring 事件机制
*
* 不了解的胖友,可以阅读 http://www.iocoder.cn/Spring-Boot/Event/?onemall 文章
*/
package cn.iocoder.mall.system.biz.event;

View File

@@ -2,6 +2,7 @@ package cn.iocoder.mall.system.biz.service.authorization;
import cn.iocoder.common.framework.exception.ServiceException;
import cn.iocoder.mall.system.biz.bo.authorization.ResourceBO;
import cn.iocoder.mall.system.biz.bo.authorization.ResourceTreeNodeBO;
import cn.iocoder.mall.system.biz.dto.authorization.AuthorizationCheckPermissionsDTO;
import cn.iocoder.mall.system.biz.dto.authorization.AuthorizationGetResourcesByAccountIdDTO;
@@ -25,8 +26,18 @@ public interface AuthorizationService {
* 如果该账号为超级管理员,则返回所有资源
*
* @param getResourcesByAccountIdDTO 查询条件 DTO
* @return 列表
* @return 资源列表
*/
List<ResourceBO> getResourcesByAccountId(AuthorizationGetResourcesByAccountIdDTO getResourcesByAccountIdDTO);
/**
* 获得指定账号的资源树
*
* 如果该账号为超级管理员,则返回所有资源
*
* @param getResourceTreeByAccountIdDTO 查询条件 DTO
* @return 资源树
*/
List<ResourceTreeNodeBO> getResourceTreeByAccountId(AuthorizationGetResourcesByAccountIdDTO getResourceTreeByAccountIdDTO);
}

View File

@@ -3,6 +3,7 @@ package cn.iocoder.mall.system.biz.service.authorization;
import cn.iocoder.common.framework.util.CollectionUtil;
import cn.iocoder.common.framework.util.ServiceExceptionUtil;
import cn.iocoder.mall.system.biz.bo.authorization.ResourceBO;
import cn.iocoder.mall.system.biz.bo.authorization.ResourceTreeNodeBO;
import cn.iocoder.mall.system.biz.dao.authorization.AccountRoleMapper;
import cn.iocoder.mall.system.biz.dao.authorization.RoleResourceMapper;
import cn.iocoder.mall.system.biz.dataobject.authorization.AccountRoleDO;
@@ -10,8 +11,11 @@ import cn.iocoder.mall.system.biz.dataobject.authorization.RoleResourceDO;
import cn.iocoder.mall.system.biz.dto.authorization.AuthorizationCheckPermissionsDTO;
import cn.iocoder.mall.system.biz.dto.authorization.AuthorizationGetResourcesByAccountIdDTO;
import cn.iocoder.mall.system.biz.dto.authorization.ResourceGetListDTO;
import cn.iocoder.mall.system.biz.dto.authorization.ResourceGetTreeDTO;
import cn.iocoder.mall.system.biz.event.authorization.ResourceDeleteEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import java.util.Collections;
@@ -89,8 +93,35 @@ public class AuthorizationServiceImpl implements AuthorizationService {
return Collections.emptyList();
}
Set<Integer> resourceIds = CollectionUtil.convertSet(roleResourceDOs, RoleResourceDO::getResourceId);
// 查询对应资源
// 查询对应资源列表
return resourceService.getResources(new ResourceGetListDTO().setIds(resourceIds).setType(getResourcesByAccountIdDTO.getType()));
}
@Override
public List<ResourceTreeNodeBO> getResourceTreeByAccountId(AuthorizationGetResourcesByAccountIdDTO getResourcesByAccountIdDTO) {
// 查询管理员拥有的角色关联数据
List<AccountRoleDO> accountRoleDOs = accountRoleMapper.selectByAccountId(getResourcesByAccountIdDTO.getAccountId());
if (CollectionUtil.isEmpty(accountRoleDOs)) {
return Collections.emptyList();
}
Set<Integer> roleIds = CollectionUtil.convertSet(accountRoleDOs, AccountRoleDO::getRoleId);
// 判断是否为超管。若是超管,默认有所有权限
if (roleService.hasSuperAdmin(roleIds)) {
return resourceService.getResourceTree(new ResourceGetTreeDTO().setType(getResourcesByAccountIdDTO.getType()));
}
// 查询角色拥有的资源关联数据
List<RoleResourceDO> roleResourceDOs = roleResourceMapper.selectListByRoleIds(roleIds);
if (CollectionUtil.isEmpty(roleResourceDOs)) {
return Collections.emptyList();
}
Set<Integer> resourceIds = CollectionUtil.convertSet(roleResourceDOs, RoleResourceDO::getResourceId);
// 查询对应资源树
return resourceService.getResourceTree(new ResourceGetTreeDTO().setIds(resourceIds).setType(getResourcesByAccountIdDTO.getType()));
}
@EventListener
public void handleResourceDeleteEvent(ResourceDeleteEvent event) {
roleResourceMapper.deleteByResourceId(event.getId());
}
}

View File

@@ -1,15 +1,38 @@
package cn.iocoder.mall.system.biz.service.authorization;
import cn.iocoder.common.framework.exception.ServiceException;
import cn.iocoder.mall.system.biz.bo.authorization.ResourceBO;
import cn.iocoder.mall.system.biz.dto.authorization.ResourceGetListDTO;
import cn.iocoder.mall.system.biz.bo.authorization.ResourceTreeNodeBO;
import cn.iocoder.mall.system.biz.dto.authorization.*;
import java.util.Collection;
import java.util.List;
/**
* 资源模块 - Service 接口
*/
public interface ResourceService {
List<ResourceBO> getResourcesByPermissions(Collection<String> permissions);
List<ResourceBO> getResources(ResourceGetListDTO getListDTO);
List<ResourceTreeNodeBO> getResourceTree(ResourceGetTreeDTO getTreeDTO);
Integer addResource(ResourceAddDTO addDTO);
/**
* 更新资源。如果更新失败,则抛出 {@link ServiceException} 异常
*
* @param updateDTO 更新资源
*/
void updateResource(ResourceUpdateDTO updateDTO);
/**
* 删除资源。如果删除失败,则抛出 {@link ServiceException} 异常
*
* @param deleteDTO 删除资源
*/
void deleteResource(ResourceDeleteDTO deleteDTO);
}

View File

@@ -1,19 +1,32 @@
package cn.iocoder.mall.system.biz.service.authorization;
import cn.iocoder.common.framework.constant.DeletedStatusEnum;
import cn.iocoder.common.framework.util.ServiceExceptionUtil;
import cn.iocoder.mall.system.biz.bo.authorization.ResourceBO;
import cn.iocoder.mall.system.biz.bo.authorization.ResourceTreeNodeBO;
import cn.iocoder.mall.system.biz.convert.authorization.ResourceConvert;
import cn.iocoder.mall.system.biz.dao.authorization.ResourceMapper;
import cn.iocoder.mall.system.biz.dataobject.authorization.ResourceDO;
import cn.iocoder.mall.system.biz.dto.authorization.ResourceGetListDTO;
import cn.iocoder.mall.system.biz.dto.authorization.*;
import cn.iocoder.mall.system.biz.enums.SystemErrorCodeEnum;
import cn.iocoder.mall.system.biz.enums.authorization.ResourceIdEnum;
import cn.iocoder.mall.system.biz.enums.authorization.ResourceTypeEnum;
import cn.iocoder.mall.system.biz.event.authorization.ResourceDeleteEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;
@Service
@Slf4j
public class ResourceServiceImpl implements ResourceService {
@Autowired
private ApplicationEventPublisher eventPublisher;
@Autowired
private ResourceMapper resourceMapper;
@@ -29,4 +42,122 @@ public class ResourceServiceImpl implements ResourceService {
return ResourceConvert.INSTANCE.convertList(resourceDOs);
}
@Override
public List<ResourceTreeNodeBO> getResourceTree(ResourceGetTreeDTO getTreeDTO) {
// 获得对应的资源列表
List<ResourceDO> resourceDOs = resourceMapper.selectListByIdsAndType(getTreeDTO.getIds(), getTreeDTO.getType());
// 拼装成树
// 使用 LinkedHashMap 的原因,是为了排序 。实际也可以用 Stream API ,就是太丑了。
Map<Integer, ResourceTreeNodeBO> treeNodeMap = new LinkedHashMap<>();
resourceDOs.stream().sorted(Comparator.comparing(ResourceDO::getSort))
.forEach(resourceDO -> treeNodeMap.put(resourceDO.getId(), ResourceConvert.INSTANCE.convertTreeNode(resourceDO)));
// 处理父子关系
treeNodeMap.values().stream()
.filter(node -> !node.getNode().getPid().equals(ResourceIdEnum.ROOT.getId()))
.forEach((childNode) -> {
// 获得父节点
ResourceTreeNodeBO parentNode = treeNodeMap.get(childNode.getNode().getPid());
if (parentNode == null) {
log.error("[getResourceTree][resource({}) 找不到父资源({})]", childNode.getNode().getId(), childNode.getNode().getPid());
return;
}
if (parentNode.getChildren() == null) { // 初始化 children 数组
parentNode.setChildren(new ArrayList<>());
}
// 将自己添加到父节点中
parentNode.getChildren().add(childNode);
});
// 获得到所有的根节点
return treeNodeMap.values().stream()
.filter(node -> node.getNode().getPid().equals(ResourceIdEnum.ROOT.getId()))
.collect(Collectors.toList());
}
@Override
public Integer addResource(ResourceAddDTO addDTO) {
// 校验父资源存在
checkParentResource(addDTO.getPid(), null);
// 存储到数据库
ResourceDO resource = ResourceConvert.INSTANCE.convert(addDTO);
initResourceProperty(resource);
resource.setCreateTime(new Date());
resource.setDeleted(DeletedStatusEnum.DELETED_NO.getValue());
resourceMapper.insert(resource);
// TODO 操作日志
// 返回成功
return resource.getId();
}
@Override
public void updateResource(ResourceUpdateDTO updateDTO) {
// 校验更新的资源是否存在
if (resourceMapper.selectById(updateDTO.getId()) == null) {
throw ServiceExceptionUtil.exception(SystemErrorCodeEnum.RESOURCE_NOT_EXISTS.getCode());
}
// 校验父资源存在
checkParentResource(updateDTO.getPid(), updateDTO.getId());
// 更新到数据库
ResourceDO resource = ResourceConvert.INSTANCE.convert(updateDTO);
initResourceProperty(resource);
resourceMapper.updateById(resource);
// TODO 操作日志
}
@Override
public void deleteResource(ResourceDeleteDTO deleteDTO) {
// 校验更新的资源是否存在
if (resourceMapper.selectById(deleteDTO.getId()) == null) {
throw ServiceExceptionUtil.exception(SystemErrorCodeEnum.RESOURCE_NOT_EXISTS.getCode());
}
// 校验是否还有子资源
if (resourceMapper.selectCountByPid(deleteDTO.getId()) > 0) {
throw ServiceExceptionUtil.exception(SystemErrorCodeEnum.RESOURCE_EXISTS_CHILDREN.getCode());
}
// 更新到数据库
resourceMapper.deleteById(deleteDTO.getId());
// 删除资源关联表
eventPublisher.publishEvent(new ResourceDeleteEvent(this, deleteDTO.getId()));
}
/**
* 校验父资源是否合法
*
* @param pid 父资源编号
* @param childId 当前资源编号
*/
private void checkParentResource(Integer pid, Integer childId) {
if (pid == null || ResourceIdEnum.ROOT.getId().equals(pid)) {
return;
}
if (pid.equals(childId)) { // 不能设置自己为父资源
throw ServiceExceptionUtil.exception(SystemErrorCodeEnum.RESOURCE_PARENT_ERROR.getCode());
}
ResourceDO resource = resourceMapper.selectById(pid);
if (resource == null) { // 父资源不存在
throw ServiceExceptionUtil.exception(SystemErrorCodeEnum.RESOURCE_PARENT_NOT_EXISTS.getCode());
}
if (!ResourceTypeEnum.MENU.getType().equals(resource.getType())) { // 父资源必须是菜单类型
throw ServiceExceptionUtil.exception(SystemErrorCodeEnum.RESOURCE_PARENT_NOT_MENU.getCode());
}
}
/**
* 初始化资源的通用属性。
*
* 例如说,只有菜单类型的资源,才设置 icon
*
* @param resource 资源
*/
private void initResourceProperty(ResourceDO resource) {
// 初始化根节点的情况
if (resource.getPid() == null) {
resource.setPid(ResourceIdEnum.ROOT.getId());
}
// 初始化资源为按钮类型时,无需 route 和 icon 属性
if (ResourceTypeEnum.BUTTON.getType().equals(resource.getType())) {
resource.setRoute(null);
resource.setIcon(null);
}
}
}