feat(permission): 重构权限系统实现对象级别权限控制

- 在PermissionService中新增getScopedMenusByRoleId和getScopedPermissionsByRoleId方法
- 实现getScopedMenusByRoleId方法用于获取角色的对象范围菜单列表
- 实现getScopedPermissionsByRoleId方法用于获取角色的对象范围权限集合
- 添加getEnabledScopedRole私有方法确保只处理启用状态的角色对象
- 在ProductMemberServiceImpl中替换SystemRoleMapper为ObjectPermissionApi调用
- 将验证产品角色的方法改为调用远程权限接口验证
- 更新ProductObjectPermissionService使用远程权限接口替代本地查询
- 修改ProductServiceImpl中权限获取逻辑使用新的对象权限API
- 移除原有的系统菜单和角色相关的数据对象依赖
- 在测试类中更新模拟对象和断言逻辑适配新的权限接口调用
This commit is contained in:
2026-04-23 09:22:43 +08:00
parent 2943a6255b
commit 156728b1b9
16 changed files with 217 additions and 376 deletions

View File

@@ -1,42 +0,0 @@
package com.njcn.rdms.module.project.dal.dataobject.permission;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 对象菜单表
*/
@TableName("system_menu")
@Data
@EqualsAndHashCode(callSuper = true)
public class SystemMenuDO extends BaseDO {
@TableId
private Long id;
private String name;
private String permission;
private String scopeType;
private String objectType;
private Integer type;
private Integer sort;
private Long parentId;
private String path;
private String icon;
private Integer status;
private Boolean visible;
}

View File

@@ -1,55 +0,0 @@
package com.njcn.rdms.module.project.dal.dataobject.permission;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 角色信息表
*/
@TableName("system_role")
@Data
@EqualsAndHashCode(callSuper = true)
public class SystemRoleDO extends BaseDO {
/**
* 角色ID
*/
@TableId
private Long id;
/**
* 角色名称
*/
private String name;
/**
* 角色编码
*/
private String code;
/**
* 作用域类型
*/
private String scopeType;
/**
* 对象类型
*/
private String objectType;
/**
* 显示顺序
*/
private Integer sort;
/**
* 角色状态
*/
private Integer status;
/**
* 角色类型
*/
private Integer type;
/**
* 备注
*/
private String remark;
}

View File

@@ -1,24 +0,0 @@
package com.njcn.rdms.module.project.dal.dataobject.permission;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 对象角色菜单关联表
*/
@TableName("system_role_menu")
@Data
@EqualsAndHashCode(callSuper = true)
public class SystemRoleMenuDO extends BaseDO {
@TableId
private Long id;
private Long roleId;
private Long menuId;
}

View File

@@ -1,23 +0,0 @@
package com.njcn.rdms.module.project.dal.mysql.permission;
import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX;
import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.njcn.rdms.module.project.dal.dataobject.permission.SystemMenuDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
@Mapper
public interface SystemMenuMapper extends BaseMapperX<SystemMenuDO> {
default List<SystemMenuDO> selectListByIdsAndScopeAndObjectType(Collection<Long> ids,
String scopeType,
String objectType) {
return selectList(new LambdaQueryWrapperX<SystemMenuDO>()
.inIfPresent(SystemMenuDO::getId, ids)
.eq(SystemMenuDO::getScopeType, scopeType)
.eq(SystemMenuDO::getObjectType, objectType));
}
}

View File

@@ -1,48 +0,0 @@
package com.njcn.rdms.module.project.dal.mysql.permission;
import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX;
import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.njcn.rdms.module.project.dal.dataobject.permission.SystemRoleDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
@Mapper
public interface SystemRoleMapper extends BaseMapperX<SystemRoleDO> {
default SystemRoleDO selectByIdAndScopeAndObjectType(Long id, String scopeType, String objectType) {
return selectOne(new LambdaQueryWrapperX<SystemRoleDO>()
.eq(SystemRoleDO::getId, id)
.eq(SystemRoleDO::getScopeType, scopeType)
.eq(SystemRoleDO::getObjectType, objectType)
.eq(SystemRoleDO::getStatus, 0));
}
default List<SystemRoleDO> selectListByIdsAndScopeAndObjectType(Collection<Long> ids,
String scopeType,
String objectType) {
return selectList(new LambdaQueryWrapperX<SystemRoleDO>()
.inIfPresent(SystemRoleDO::getId, ids)
.eq(SystemRoleDO::getScopeType, scopeType)
.eq(SystemRoleDO::getObjectType, objectType)
.eq(SystemRoleDO::getStatus, 0));
}
default SystemRoleDO selectByScopeAndObjectTypeAndCode(String scopeType, String objectType, String code) {
return selectOne(new LambdaQueryWrapperX<SystemRoleDO>()
.eq(SystemRoleDO::getScopeType, scopeType)
.eq(SystemRoleDO::getObjectType, objectType)
.eq(SystemRoleDO::getCode, code)
.eq(SystemRoleDO::getStatus, 0));
}
default SystemRoleDO selectByScopeAndObjectTypeAndName(String scopeType, String objectType, String name) {
return selectOne(new LambdaQueryWrapperX<SystemRoleDO>()
.eq(SystemRoleDO::getScopeType, scopeType)
.eq(SystemRoleDO::getObjectType, objectType)
.eq(SystemRoleDO::getName, name)
.eq(SystemRoleDO::getStatus, 0));
}
}

View File

@@ -1,16 +0,0 @@
package com.njcn.rdms.module.project.dal.mysql.permission;
import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX;
import com.njcn.rdms.module.project.dal.dataobject.permission.SystemRoleMenuDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface SystemRoleMenuMapper extends BaseMapperX<SystemRoleMenuDO> {
default List<SystemRoleMenuDO> selectListByRoleId(Long roleId) {
return selectList(SystemRoleMenuDO::getRoleId, roleId);
}
}

View File

@@ -1,5 +1,6 @@
package com.njcn.rdms.module.project.framework.rpc.config;
import com.njcn.rdms.module.system.api.permission.ObjectPermissionApi;
import com.njcn.rdms.module.system.api.user.AdminUserApi;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Configuration;
@@ -8,6 +9,6 @@ import org.springframework.context.annotation.Configuration;
* Project 模块的 RPC 配置
*/
@Configuration(value = "projectRpcConfiguration", proxyBeanMethods = false)
@EnableFeignClients(clients = {AdminUserApi.class})
@EnableFeignClients(clients = {AdminUserApi.class, ObjectPermissionApi.class})
public class RpcConfiguration {
}

View File

@@ -2,12 +2,9 @@ package com.njcn.rdms.module.project.framework.security.service;
import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils;
import com.njcn.rdms.module.project.dal.dataobject.member.UserObjectRoleDO;
import com.njcn.rdms.module.project.dal.dataobject.permission.SystemMenuDO;
import com.njcn.rdms.module.project.dal.dataobject.permission.SystemRoleMenuDO;
import com.njcn.rdms.module.project.dal.mysql.member.UserObjectRoleMapper;
import com.njcn.rdms.module.project.dal.mysql.permission.SystemMenuMapper;
import com.njcn.rdms.module.project.dal.mysql.permission.SystemRoleMenuMapper;
import com.njcn.rdms.module.project.enums.ErrorCodeConstants;
import com.njcn.rdms.module.system.api.permission.ObjectPermissionApi;
import com.njcn.rdms.module.system.enums.permission.PermissionScopeTypeEnum;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
@@ -15,7 +12,6 @@ import org.springframework.util.StringUtils;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@@ -34,9 +30,7 @@ public class ProductObjectPermissionService implements ObjectPermissionService {
@Resource
private UserObjectRoleMapper userObjectRoleMapper;
@Resource
private SystemRoleMenuMapper systemRoleMenuMapper;
@Resource
private SystemMenuMapper systemMenuMapper;
private ObjectPermissionApi objectPermissionApi;
@Override
public String getObjectType() {
@@ -65,26 +59,13 @@ public class ProductObjectPermissionService implements ObjectPermissionService {
}
private Set<String> getRolePermissions(Long roleId) {
List<SystemRoleMenuDO> roleMenus = systemRoleMenuMapper.selectListByRoleId(roleId);
if (roleMenus == null || roleMenus.isEmpty()) {
Set<String> permissions = objectPermissionApi
.getObjectRolePermissions(roleId, ROLE_SCOPE_OBJECT, PRODUCT_OBJECT_TYPE)
.getCheckedData();
if (permissions == null || permissions.isEmpty()) {
return Collections.emptySet();
}
Set<Long> menuIds = roleMenus.stream()
.map(SystemRoleMenuDO::getMenuId)
.collect(Collectors.toCollection(LinkedHashSet::new));
if (menuIds.isEmpty()) {
return Collections.emptySet();
}
List<SystemMenuDO> menus = systemMenuMapper.selectListByIdsAndScopeAndObjectType(
menuIds, ROLE_SCOPE_OBJECT, PRODUCT_OBJECT_TYPE);
if (menus == null || menus.isEmpty()) {
return Collections.emptySet();
}
return menus.stream()
.filter(menu -> ROLE_SCOPE_OBJECT.equals(menu.getScopeType()))
.filter(menu -> PRODUCT_OBJECT_TYPE.equals(menu.getObjectType()))
.filter(menu -> Integer.valueOf(0).equals(menu.getStatus()))
.map(SystemMenuDO::getPermission)
return permissions.stream()
.filter(StringUtils::hasText)
.map(String::trim)
.collect(Collectors.toCollection(LinkedHashSet::new));

View File

@@ -9,13 +9,13 @@ import com.njcn.rdms.module.project.controller.admin.product.vo.member.ProductMe
import com.njcn.rdms.module.project.framework.security.annotation.CheckObjectPermission;
import com.njcn.rdms.module.project.dal.dataobject.audit.BizAuditLogDO;
import com.njcn.rdms.module.project.dal.dataobject.member.UserObjectRoleDO;
import com.njcn.rdms.module.project.dal.dataobject.permission.SystemRoleDO;
import com.njcn.rdms.module.project.dal.dataobject.product.ProductDO;
import com.njcn.rdms.module.project.dal.mysql.audit.BizAuditLogMapper;
import com.njcn.rdms.module.project.dal.mysql.member.UserObjectRoleMapper;
import com.njcn.rdms.module.project.dal.mysql.permission.SystemRoleMapper;
import com.njcn.rdms.module.project.dal.mysql.product.ProductMapper;
import com.njcn.rdms.module.project.enums.ErrorCodeConstants;
import com.njcn.rdms.module.system.api.permission.ObjectPermissionApi;
import com.njcn.rdms.module.system.api.permission.dto.ObjectRoleRespDTO;
import com.njcn.rdms.module.system.api.user.AdminUserApi;
import com.njcn.rdms.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.Resource;
@@ -63,7 +63,7 @@ public class ProductMemberServiceImpl implements ProductMemberService {
@Resource
private UserObjectRoleMapper userObjectRoleMapper;
@Resource
private SystemRoleMapper systemRoleMapper;
private ObjectPermissionApi objectPermissionApi;
@Resource
private BizAuditLogMapper bizAuditLogMapper;
@Resource
@@ -75,7 +75,7 @@ public class ProductMemberServiceImpl implements ProductMemberService {
public List<ProductMemberRespVO> getProductMemberList(Long productId) {
ProductDO product = validateProductExists(productId);
List<UserObjectRoleDO> members = userObjectRoleMapper.selectListByObject(PRODUCT_OBJECT_TYPE, productId);
Map<Long, SystemRoleDO> roleMap = getRoleMap(members.stream().map(UserObjectRoleDO::getRoleId).collect(Collectors.toSet()));
Map<Long, ObjectRoleRespDTO> roleMap = getRoleMap(members.stream().map(UserObjectRoleDO::getRoleId).collect(Collectors.toSet()));
Map<Long, AdminUserRespDTO> userMap = getUserMap(members.stream().map(UserObjectRoleDO::getUserId).collect(Collectors.toSet()));
return members.stream().map(member -> {
ProductMemberRespVO respVO = new ProductMemberRespVO();
@@ -84,7 +84,7 @@ public class ProductMemberServiceImpl implements ProductMemberService {
AdminUserRespDTO user = userMap.get(member.getUserId());
respVO.setUserNickname(user == null ? null : user.getNickname());
respVO.setRoleId(member.getRoleId());
SystemRoleDO role = roleMap.get(member.getRoleId());
ObjectRoleRespDTO role = roleMap.get(member.getRoleId());
respVO.setRoleName(role == null ? null : role.getName());
respVO.setRoleCode(role == null ? null : role.getCode());
respVO.setManagerFlag(Objects.equals(member.getUserId(), product.getManagerUserId())
@@ -103,7 +103,7 @@ public class ProductMemberServiceImpl implements ProductMemberService {
permission = PRODUCT_UPDATE_PERMISSION)
public Long createProductMember(Long productId, ProductMemberSaveReqVO reqVO) {
ProductDO product = validateProductExists(productId);
SystemRoleDO targetRole = validateProductRole(reqVO.getRoleId());
ObjectRoleRespDTO targetRole = validateProductRole(reqVO.getRoleId());
UserObjectRoleDO existingMember = userObjectRoleMapper
.selectByObjectAndUserId(PRODUCT_OBJECT_TYPE, productId, reqVO.getUserId());
if (existingMember != null && Objects.equals(existingMember.getStatus(), MEMBER_STATUS_ACTIVE)) {
@@ -153,7 +153,7 @@ public class ProductMemberServiceImpl implements ProductMemberService {
throw exception(ErrorCodeConstants.PRODUCT_MEMBER_NOT_ACTIVE);
}
SystemRoleDO targetRole = validateProductRole(reqVO.getRoleId());
ObjectRoleRespDTO targetRole = validateProductRole(reqVO.getRoleId());
UserObjectRoleDO before = cloneMember(member);
member.setRemark(normalizeNullableText(reqVO.getRemark()));
@@ -215,8 +215,10 @@ public class ProductMemberServiceImpl implements ProductMemberService {
return member;
}
private SystemRoleDO validateProductRole(Long roleId) {
SystemRoleDO role = systemRoleMapper.selectByIdAndScopeAndObjectType(roleId, ROLE_SCOPE_OBJECT, PRODUCT_OBJECT_TYPE);
private ObjectRoleRespDTO validateProductRole(Long roleId) {
ObjectRoleRespDTO role = objectPermissionApi
.getObjectRoleById(roleId, ROLE_SCOPE_OBJECT, PRODUCT_OBJECT_TYPE)
.getCheckedData();
if (role == null) {
throw exception(ErrorCodeConstants.PRODUCT_MEMBER_ROLE_INVALID);
}
@@ -236,7 +238,7 @@ public class ProductMemberServiceImpl implements ProductMemberService {
return;
}
SystemRoleDO previousManagerRole = validatePreviousManagerTransfer(currentManagerUserId,
ObjectRoleRespDTO previousManagerRole = validatePreviousManagerTransfer(currentManagerUserId,
previousManagerUserId, previousManagerRoleId);
transferPreviousManager(product.getId(), previousManagerUserId, previousManagerRole.getId(), reason);
@@ -245,9 +247,9 @@ public class ProductMemberServiceImpl implements ProductMemberService {
writeManagerChangeAuditLog(product.getId(), currentManagerUserId, targetManagerUserId, reason);
}
private SystemRoleDO validatePreviousManagerTransfer(Long currentManagerUserId,
Long previousManagerUserId,
Long previousManagerRoleId) {
private ObjectRoleRespDTO validatePreviousManagerTransfer(Long currentManagerUserId,
Long previousManagerUserId,
Long previousManagerRoleId) {
if (currentManagerUserId == null
|| previousManagerUserId == null
|| previousManagerRoleId == null) {
@@ -256,7 +258,7 @@ public class ProductMemberServiceImpl implements ProductMemberService {
if (!Objects.equals(currentManagerUserId, previousManagerUserId)) {
throw exception(ErrorCodeConstants.PRODUCT_MANAGER_TRANSFER_SOURCE_INVALID);
}
SystemRoleDO previousManagerRole = validateProductRole(previousManagerRoleId);
ObjectRoleRespDTO previousManagerRole = validateProductRole(previousManagerRoleId);
if (isManagerRole(previousManagerRole)) {
throw exception(ErrorCodeConstants.PRODUCT_MANAGER_TRANSFER_ROLE_INVALID);
}
@@ -298,17 +300,21 @@ public class ProductMemberServiceImpl implements ProductMemberService {
writeMemberAuditLog(member, actionType, before, member, reason);
}
private boolean isManagerRole(SystemRoleDO role) {
private boolean isManagerRole(ObjectRoleRespDTO role) {
return Objects.equals(PRODUCT_MANAGER_ROLE_CODE, role.getCode());
}
private Map<Long, SystemRoleDO> getRoleMap(Set<Long> roleIds) {
private Map<Long, ObjectRoleRespDTO> getRoleMap(Set<Long> roleIds) {
if (roleIds.isEmpty()) {
return Collections.emptyMap();
}
List<SystemRoleDO> roles = systemRoleMapper
.selectListByIdsAndScopeAndObjectType(roleIds, ROLE_SCOPE_OBJECT, PRODUCT_OBJECT_TYPE);
return roles.stream().collect(Collectors.toMap(SystemRoleDO::getId, Function.identity()));
List<ObjectRoleRespDTO> roles = objectPermissionApi
.getObjectRoleList(roleIds, ROLE_SCOPE_OBJECT, PRODUCT_OBJECT_TYPE)
.getCheckedData();
if (roles == null || roles.isEmpty()) {
return Collections.emptyMap();
}
return roles.stream().collect(Collectors.toMap(ObjectRoleRespDTO::getId, Function.identity()));
}
private Map<Long, AdminUserRespDTO> getUserMap(Set<Long> userIds) {

View File

@@ -17,21 +17,19 @@ import com.njcn.rdms.module.project.controller.admin.product.vo.setting.ProductS
import com.njcn.rdms.module.project.framework.security.annotation.CheckObjectPermission;
import com.njcn.rdms.module.project.dal.dataobject.audit.BizAuditLogDO;
import com.njcn.rdms.module.project.dal.dataobject.member.UserObjectRoleDO;
import com.njcn.rdms.module.project.dal.dataobject.permission.SystemMenuDO;
import com.njcn.rdms.module.project.dal.dataobject.permission.SystemRoleDO;
import com.njcn.rdms.module.project.dal.dataobject.permission.SystemRoleMenuDO;
import com.njcn.rdms.module.project.dal.dataobject.product.ProductDO;
import com.njcn.rdms.module.project.dal.dataobject.product.ProductStatusLogDO;
import com.njcn.rdms.module.project.dal.dataobject.status.ObjectStatusTransitionDO;
import com.njcn.rdms.module.project.dal.mysql.audit.BizAuditLogMapper;
import com.njcn.rdms.module.project.dal.mysql.member.UserObjectRoleMapper;
import com.njcn.rdms.module.project.dal.mysql.permission.SystemMenuMapper;
import com.njcn.rdms.module.project.dal.mysql.permission.SystemRoleMapper;
import com.njcn.rdms.module.project.dal.mysql.permission.SystemRoleMenuMapper;
import com.njcn.rdms.module.project.dal.mysql.product.ProductMapper;
import com.njcn.rdms.module.project.dal.mysql.product.ProductStatusLogMapper;
import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusTransitionMapper;
import com.njcn.rdms.module.project.enums.ErrorCodeConstants;
import com.njcn.rdms.module.system.api.permission.ObjectPermissionApi;
import com.njcn.rdms.module.system.api.permission.dto.ObjectMenuRespDTO;
import com.njcn.rdms.module.system.api.permission.dto.ObjectRolePermissionRespDTO;
import com.njcn.rdms.module.system.api.permission.dto.ObjectRoleRespDTO;
import com.njcn.rdms.module.system.api.user.AdminUserApi;
import com.njcn.rdms.module.system.enums.permission.MenuTypeEnum;
import com.njcn.rdms.module.system.enums.permission.PermissionScopeTypeEnum;
@@ -45,7 +43,6 @@ import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -99,11 +96,7 @@ public class ProductServiceImpl implements ProductService {
@Resource
private UserObjectRoleMapper userObjectRoleMapper;
@Resource
private SystemRoleMapper systemRoleMapper;
@Resource
private SystemRoleMenuMapper systemRoleMenuMapper;
@Resource
private SystemMenuMapper systemMenuMapper;
private ObjectPermissionApi objectPermissionApi;
@Resource
private AdminUserApi adminUserApi;
@@ -174,23 +167,14 @@ public class ProductServiceImpl implements ProductService {
return respVO;
}
SystemRoleDO currentRole = systemRoleMapper
.selectByIdAndScopeAndObjectType(currentMember.getRoleId(), ROLE_SCOPE_OBJECT, PRODUCT_OBJECT_TYPE);
ObjectRolePermissionRespDTO permissionDetail = objectPermissionApi
.getObjectRolePermissionDetail(currentMember.getRoleId(), ROLE_SCOPE_OBJECT, PRODUCT_OBJECT_TYPE)
.getCheckedData();
ObjectRoleRespDTO currentRole = permissionDetail == null ? null : permissionDetail.getCurrentRole();
List<ObjectMenuRespDTO> menus = permissionDetail == null || permissionDetail.getMenus() == null
? Collections.emptyList()
: permissionDetail.getMenus();
respVO.setCurrentRole(buildCurrentRole(currentMember, currentRole));
List<SystemRoleMenuDO> roleMenus = systemRoleMenuMapper.selectListByRoleId(currentMember.getRoleId());
if (roleMenus.isEmpty()) {
respVO.setNavs(Collections.emptyList());
respVO.setButtons(Collections.emptyList());
return respVO;
}
Set<Long> menuIds = roleMenus.stream()
.map(SystemRoleMenuDO::getMenuId)
.collect(Collectors.toCollection(LinkedHashSet::new));
List<SystemMenuDO> menus = filterEnableProductObjectMenus(
systemMenuMapper.selectListByIdsAndScopeAndObjectType(menuIds, ROLE_SCOPE_OBJECT, PRODUCT_OBJECT_TYPE));
respVO.setNavs(buildContextNavs(menus));
respVO.setButtons(buildContextButtons(menus));
return respVO;
@@ -386,8 +370,9 @@ public class ProductServiceImpl implements ProductService {
}
private void initManagerMemberRelation(ProductDO product) {
SystemRoleDO managerRole = systemRoleMapper
.selectByScopeAndObjectTypeAndCode(ROLE_SCOPE_OBJECT, PRODUCT_OBJECT_TYPE, PRODUCT_MANAGER_ROLE_CODE);
ObjectRoleRespDTO managerRole = objectPermissionApi
.getObjectRoleByCode(PRODUCT_MANAGER_ROLE_CODE, ROLE_SCOPE_OBJECT, PRODUCT_OBJECT_TYPE)
.getCheckedData();
if (managerRole == null) {
throw invalidParamException("未找到产品经理对象角色配置:{}", PRODUCT_MANAGER_ROLE_CODE);
}
@@ -406,22 +391,11 @@ public class ProductServiceImpl implements ProductService {
writeManagerInitAuditLog(product.getId(), product.getManagerUserId());
}
private List<SystemMenuDO> filterEnableProductObjectMenus(List<SystemMenuDO> menus) {
if (menus == null || menus.isEmpty()) {
return Collections.emptyList();
}
return menus.stream()
.filter(menu -> Objects.equals(ROLE_SCOPE_OBJECT, menu.getScopeType()))
.filter(menu -> Objects.equals(PRODUCT_OBJECT_TYPE, menu.getObjectType()))
.filter(menu -> Objects.equals(0, menu.getStatus()))
.collect(Collectors.toList());
}
private ProductContextProductRespVO buildCurrentProduct(ProductDO product) {
return BeanUtils.toBean(product, ProductContextProductRespVO.class);
}
private ProductContextRoleRespVO buildCurrentRole(UserObjectRoleDO currentMember, SystemRoleDO currentRole) {
private ProductContextRoleRespVO buildCurrentRole(UserObjectRoleDO currentMember, ObjectRoleRespDTO currentRole) {
ProductContextRoleRespVO roleRespVO = new ProductContextRoleRespVO();
roleRespVO.setRoleId(currentMember.getRoleId());
if (currentRole != null) {
@@ -431,7 +405,7 @@ public class ProductServiceImpl implements ProductService {
return roleRespVO;
}
private List<ProductContextNavRespVO> buildContextNavs(List<SystemMenuDO> menus) {
private List<ProductContextNavRespVO> buildContextNavs(List<ObjectMenuRespDTO> menus) {
if (menus.isEmpty()) {
return Collections.emptyList();
}
@@ -455,13 +429,13 @@ public class ProductServiceImpl implements ProductService {
return navs;
}
private List<String> buildContextButtons(List<SystemMenuDO> menus) {
private List<String> buildContextButtons(List<ObjectMenuRespDTO> menus) {
if (menus.isEmpty()) {
return Collections.emptyList();
}
return menus.stream()
.filter(menu -> MenuTypeEnum.BUTTON.getType().equals(menu.getType()))
.map(SystemMenuDO::getPermission)
.map(ObjectMenuRespDTO::getPermission)
.filter(StringUtils::hasText)
.map(String::trim)
.distinct()

View File

@@ -4,26 +4,21 @@ import com.njcn.rdms.framework.common.exception.ServiceException;
import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils;
import com.njcn.rdms.framework.test.core.ut.BaseMockitoUnitTest;
import com.njcn.rdms.module.project.dal.dataobject.member.UserObjectRoleDO;
import com.njcn.rdms.module.project.dal.dataobject.permission.SystemMenuDO;
import com.njcn.rdms.module.project.dal.dataobject.permission.SystemRoleMenuDO;
import com.njcn.rdms.module.project.dal.mysql.member.UserObjectRoleMapper;
import com.njcn.rdms.module.project.dal.mysql.permission.SystemMenuMapper;
import com.njcn.rdms.module.project.dal.mysql.permission.SystemRoleMenuMapper;
import com.njcn.rdms.module.project.enums.ErrorCodeConstants;
import com.njcn.rdms.module.system.enums.permission.MenuTypeEnum;
import com.njcn.rdms.module.system.api.permission.ObjectPermissionApi;
import com.njcn.rdms.module.system.enums.permission.PermissionScopeTypeEnum;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import java.util.List;
import java.util.Set;
import static com.njcn.rdms.framework.common.pojo.CommonResult.success;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
@@ -35,9 +30,7 @@ class ProductObjectPermissionServiceTest extends BaseMockitoUnitTest {
@Mock
private UserObjectRoleMapper userObjectRoleMapper;
@Mock
private SystemRoleMenuMapper systemRoleMenuMapper;
@Mock
private SystemMenuMapper systemMenuMapper;
private ObjectPermissionApi objectPermissionApi;
@Test
void checkPermission_whenMemberOnlyAndCurrentUserIsMember_shouldPass() {
@@ -50,20 +43,18 @@ class ProductObjectPermissionServiceTest extends BaseMockitoUnitTest {
assertDoesNotThrow(() -> permissionService.checkPermission(productId, null, true));
}
verifyNoInteractions(systemRoleMenuMapper, systemMenuMapper);
verifyNoInteractions(objectPermissionApi);
}
@Test
void checkPermission_whenQueryPermissionConfiguredOnMenuNode_shouldPass() {
void checkPermission_whenCurrentRolePermissionsContainTarget_shouldPass() {
Long productId = 1002L;
Long loginUserId = 2002L;
when(userObjectRoleMapper.selectActiveByObjectAndUserId("product", productId, loginUserId))
.thenReturn(createMember(productId, loginUserId, 3002L));
when(systemRoleMenuMapper.selectListByRoleId(3002L))
.thenReturn(List.of(createRoleMenu(3002L, 4002L)));
when(systemMenuMapper.selectListByIdsAndScopeAndObjectType(any(),
eq(PermissionScopeTypeEnum.OBJECT.getScopeType()), eq("product")))
.thenReturn(List.of(createMenu(4002L, MenuTypeEnum.MENU.getType(), "project:product:query")));
when(objectPermissionApi.getObjectRolePermissions(3002L,
PermissionScopeTypeEnum.OBJECT.getScopeType(), "product"))
.thenReturn(success(Set.of("project:product:query")));
try (MockedStatic<SecurityFrameworkUtils> mockedStatic = mockLoginUser(loginUserId)) {
assertDoesNotThrow(() -> permissionService.checkPermission(productId, "project:product:query", false));
@@ -76,11 +67,9 @@ class ProductObjectPermissionServiceTest extends BaseMockitoUnitTest {
Long loginUserId = 2003L;
when(userObjectRoleMapper.selectActiveByObjectAndUserId("product", productId, loginUserId))
.thenReturn(createMember(productId, loginUserId, 3003L));
when(systemRoleMenuMapper.selectListByRoleId(3003L))
.thenReturn(List.of(createRoleMenu(3003L, 4003L)));
when(systemMenuMapper.selectListByIdsAndScopeAndObjectType(any(),
eq(PermissionScopeTypeEnum.OBJECT.getScopeType()), eq("product")))
.thenReturn(List.of(createMenu(4003L, MenuTypeEnum.BUTTON.getType(), "project:product:update")));
when(objectPermissionApi.getObjectRolePermissions(3003L,
PermissionScopeTypeEnum.OBJECT.getScopeType(), "product"))
.thenReturn(success(Set.of("project:product:update")));
try (MockedStatic<SecurityFrameworkUtils> mockedStatic = mockLoginUser(loginUserId)) {
ServiceException ex = assertThrows(ServiceException.class,
@@ -89,6 +78,22 @@ class ProductObjectPermissionServiceTest extends BaseMockitoUnitTest {
}
}
@Test
void checkPermission_whenCurrentUserIsNotMember_shouldThrowException() {
Long productId = 1004L;
Long loginUserId = 2004L;
when(userObjectRoleMapper.selectActiveByObjectAndUserId("product", productId, loginUserId))
.thenReturn(null);
try (MockedStatic<SecurityFrameworkUtils> mockedStatic = mockLoginUser(loginUserId)) {
ServiceException ex = assertThrows(ServiceException.class,
() -> permissionService.checkPermission(productId, "project:product:query", false));
assertEquals(ErrorCodeConstants.PRODUCT_OBJECT_PERMISSION_DENIED.getCode(), ex.getCode());
}
verifyNoInteractions(objectPermissionApi);
}
private UserObjectRoleDO createMember(Long productId, Long loginUserId, Long roleId) {
UserObjectRoleDO member = new UserObjectRoleDO();
member.setId(9001L);
@@ -100,25 +105,6 @@ class ProductObjectPermissionServiceTest extends BaseMockitoUnitTest {
return member;
}
private SystemRoleMenuDO createRoleMenu(Long roleId, Long menuId) {
SystemRoleMenuDO roleMenu = new SystemRoleMenuDO();
roleMenu.setId(9101L);
roleMenu.setRoleId(roleId);
roleMenu.setMenuId(menuId);
return roleMenu;
}
private SystemMenuDO createMenu(Long menuId, Integer type, String permission) {
SystemMenuDO menu = new SystemMenuDO();
menu.setId(menuId);
menu.setType(type);
menu.setPermission(permission);
menu.setScopeType(PermissionScopeTypeEnum.OBJECT.getScopeType());
menu.setObjectType("product");
menu.setStatus(0);
return menu;
}
private MockedStatic<SecurityFrameworkUtils> mockLoginUser(Long loginUserId) {
MockedStatic<SecurityFrameworkUtils> mockedStatic = mockStatic(SecurityFrameworkUtils.class);
mockedStatic.when(SecurityFrameworkUtils::getLoginUserId).thenReturn(loginUserId);

View File

@@ -5,29 +5,27 @@ import com.njcn.rdms.framework.common.exception.ServiceException;
import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils;
import com.njcn.rdms.framework.common.util.json.JsonUtils;
import com.njcn.rdms.framework.test.core.ut.BaseMockitoUnitTest;
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductContextRespVO;
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductDeleteReqVO;
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductSaveReqVO;
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductStatusActionReqVO;
import com.njcn.rdms.module.project.controller.admin.product.vo.setting.ProductSettingBaseInfoUpdateReqVO;
import com.njcn.rdms.module.project.dal.dataobject.audit.BizAuditLogDO;
import com.njcn.rdms.module.project.dal.dataobject.member.UserObjectRoleDO;
import com.njcn.rdms.module.project.dal.dataobject.permission.SystemMenuDO;
import com.njcn.rdms.module.project.dal.dataobject.permission.SystemRoleMenuDO;
import com.njcn.rdms.module.project.dal.dataobject.product.ProductDO;
import com.njcn.rdms.module.project.dal.dataobject.product.ProductStatusLogDO;
import com.njcn.rdms.module.project.dal.dataobject.status.ObjectStatusTransitionDO;
import com.njcn.rdms.module.project.dal.mysql.audit.BizAuditLogMapper;
import com.njcn.rdms.module.project.dal.mysql.member.UserObjectRoleMapper;
import com.njcn.rdms.module.project.dal.mysql.permission.SystemMenuMapper;
import com.njcn.rdms.module.project.dal.mysql.permission.SystemRoleMapper;
import com.njcn.rdms.module.project.dal.mysql.permission.SystemRoleMenuMapper;
import com.njcn.rdms.module.project.dal.mysql.product.ProductMapper;
import com.njcn.rdms.module.project.dal.mysql.product.ProductStatusLogMapper;
import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusTransitionMapper;
import com.njcn.rdms.module.project.enums.ErrorCodeConstants;
import com.njcn.rdms.module.system.api.permission.ObjectPermissionApi;
import com.njcn.rdms.module.system.api.permission.dto.ObjectMenuRespDTO;
import com.njcn.rdms.module.system.api.permission.dto.ObjectRolePermissionRespDTO;
import com.njcn.rdms.module.system.api.permission.dto.ObjectRoleRespDTO;
import com.njcn.rdms.module.system.api.user.AdminUserApi;
import com.njcn.rdms.module.system.enums.permission.MenuTypeEnum;
import com.njcn.rdms.module.system.enums.permission.PermissionScopeTypeEnum;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
@@ -35,13 +33,15 @@ import org.mockito.MockedStatic;
import org.mockito.Mock;
import java.util.List;
import java.util.Set;
import static com.njcn.rdms.framework.common.pojo.CommonResult.success;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -63,11 +63,7 @@ class ProductServiceImplTest extends BaseMockitoUnitTest {
@Mock
private UserObjectRoleMapper userObjectRoleMapper;
@Mock
private SystemRoleMapper systemRoleMapper;
@Mock
private SystemRoleMenuMapper systemRoleMenuMapper;
@Mock
private SystemMenuMapper systemMenuMapper;
private ObjectPermissionApi objectPermissionApi;
@Mock
private AdminUserApi adminUserApi;
@@ -343,6 +339,42 @@ class ProductServiceImplTest extends BaseMockitoUnitTest {
assertEquals(ErrorCodeConstants.PRODUCT_STATUS_NOT_ALLOW_EDIT.getCode(), ex.getCode());
}
@Test
void getProductContext_shouldAssembleCurrentRoleNavsAndButtonsFromObjectPermissionApi() {
Long productId = 1014L;
Long loginUserId = 3014L;
Long roleId = 9101L;
ProductDO product = createProduct(productId, "direction_value", "产品上下文", 2017L, "旧描述", "active");
UserObjectRoleDO currentMember = createMember(productId, loginUserId, roleId);
ObjectRolePermissionRespDTO detail = new ObjectRolePermissionRespDTO();
detail.setCurrentRole(createRole(roleId, "product_manager", "产品经理"));
detail.setMenus(List.of(
createMenu(9303L, "保存", "project:product:update", 3, 30, null, null, true),
createMenu(9302L, "隐藏导航", null, 2, 20, "/product/hidden", "mdi:hidden", false),
createMenu(9301L, "概览", null, 2, 10, "/product/overview", "mdi:view-dashboard-outline", true)
));
detail.setPermissions(Set.of("project:product:update"));
when(productMapper.selectById(productId)).thenReturn(product);
when(userObjectRoleMapper.selectActiveByObjectAndUserId("product", productId, loginUserId))
.thenReturn(currentMember);
when(objectPermissionApi.getObjectRolePermissionDetail(roleId, "object", "product"))
.thenReturn(success(detail));
ProductContextRespVO respVO;
try (MockedStatic<SecurityFrameworkUtils> mockedStatic = mockLoginUser(loginUserId, "测试人")) {
respVO = productService.getProductContext(productId);
}
assertNotNull(respVO.getCurrentProduct());
assertEquals(roleId, respVO.getCurrentRole().getRoleId());
assertEquals("product_manager", respVO.getCurrentRole().getRoleCode());
assertEquals("产品经理", respVO.getCurrentRole().getRoleName());
assertEquals(1, respVO.getNavs().size());
assertEquals(9301L, respVO.getNavs().get(0).getId());
assertEquals(List.of("project:product:update"), respVO.getButtons());
}
private ProductDO createProduct(Long id, String directionCode, String name, Long managerUserId,
String description, String statusCode) {
ProductDO product = new ProductDO();
@@ -356,35 +388,39 @@ class ProductServiceImplTest extends BaseMockitoUnitTest {
return product;
}
private void mockObjectPermission(Long productId, Long loginUserId, String permission) {
private UserObjectRoleDO createMember(Long productId, Long userId, Long roleId) {
UserObjectRoleDO currentMember = new UserObjectRoleDO();
currentMember.setId(9001L);
currentMember.setUserId(loginUserId);
currentMember.setUserId(userId);
currentMember.setObjectType("product");
currentMember.setObjectId(productId);
currentMember.setRoleId(9101L);
currentMember.setRoleId(roleId);
currentMember.setStatus(0);
return currentMember;
}
SystemRoleMenuDO roleMenu = new SystemRoleMenuDO();
roleMenu.setId(9201L);
roleMenu.setRoleId(9101L);
roleMenu.setMenuId(9301L);
private ObjectRoleRespDTO createRole(Long roleId, String roleCode, String roleName) {
ObjectRoleRespDTO role = new ObjectRoleRespDTO();
role.setId(roleId);
role.setCode(roleCode);
role.setName(roleName);
role.setScopeType("object");
role.setObjectType("product");
return role;
}
SystemMenuDO menu = new SystemMenuDO();
menu.setId(9301L);
private ObjectMenuRespDTO createMenu(Long menuId, String name, String permission, Integer type, Integer sort,
String path, String icon, Boolean visible) {
ObjectMenuRespDTO menu = new ObjectMenuRespDTO();
menu.setId(menuId);
menu.setName(name);
menu.setPermission(permission);
menu.setScopeType(PermissionScopeTypeEnum.OBJECT.getScopeType());
menu.setObjectType("product");
menu.setType(MenuTypeEnum.BUTTON.getType());
menu.setStatus(0);
menu.setVisible(true);
when(userObjectRoleMapper.selectActiveByObjectAndUserId("product", productId, loginUserId))
.thenReturn(currentMember);
when(systemRoleMenuMapper.selectListByRoleId(9101L)).thenReturn(List.of(roleMenu));
when(systemMenuMapper.selectListByIdsAndScopeAndObjectType(
any(), eq(PermissionScopeTypeEnum.OBJECT.getScopeType()), eq("product")))
.thenReturn(List.of(menu));
menu.setType(type);
menu.setSort(sort);
menu.setPath(path);
menu.setIcon(icon);
menu.setVisible(visible);
return menu;
}
private ObjectStatusTransitionDO createTransition(String actionCode, String toStatus, boolean needReason) {

View File

@@ -1,6 +1,9 @@
package com.njcn.rdms.module.system.service.permission;
import com.njcn.rdms.module.system.dal.dataobject.permission.MenuDO;
import java.util.Collection;
import java.util.List;
import java.util.Set;
/**
@@ -81,6 +84,10 @@ public interface PermissionService {
return getRoleMenuListByRoleId(roleIds);
}
List<MenuDO> getScopedMenusByRoleId(Long roleId, String scopeType, String objectType);
Set<String> getScopedPermissionsByRoleId(Long roleId, String scopeType, String objectType);
/**
* 获得拥有指定菜单的角色编号数组,从缓存中获取
*

View File

@@ -3,6 +3,7 @@ package com.njcn.rdms.module.system.service.permission;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.njcn.rdms.framework.common.enums.CommonStatusEnum;
import com.njcn.rdms.framework.common.util.collection.CollectionUtils;
@@ -248,7 +249,9 @@ public class PermissionServiceImpl implements PermissionService {
}
// 统一按角色实际授权返回当前仍然有效的菜单,并补齐其父链
Set<Long> scopedRoleIds = convertSet(roleService.getRoleList(roleIds, scopeType, objectType), RoleDO::getId);
Set<Long> scopedRoleIds = convertSet(roleService.getRoleList(roleIds, scopeType, objectType).stream()
.filter(role -> CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus()))
.toList(), RoleDO::getId);
if (CollUtil.isEmpty(scopedRoleIds)) {
return Collections.emptySet();
}
@@ -257,6 +260,34 @@ public class PermissionServiceImpl implements PermissionService {
return expandMenuIdsWithAncestors(convertSet(menus, MenuDO::getId), scopeType, objectType);
}
@Override
public List<MenuDO> getScopedMenusByRoleId(Long roleId, String scopeType, String objectType) {
RoleDO role = getEnabledScopedRole(roleId, scopeType, objectType);
if (role == null) {
return Collections.emptyList();
}
Set<Long> menuIds = getRoleMenuListByRoleId(Collections.singleton(roleId), scopeType, objectType);
if (CollUtil.isEmpty(menuIds)) {
return Collections.emptyList();
}
return menuService.filterDisableMenus(menuService.getMenuList(menuIds, scopeType, objectType));
}
@Override
public Set<String> getScopedPermissionsByRoleId(Long roleId, String scopeType, String objectType) {
List<MenuDO> menus = getScopedMenusByRoleId(roleId, scopeType, objectType);
if (CollUtil.isEmpty(menus)) {
return Collections.emptySet();
}
Set<String> permissions = new LinkedHashSet<>();
menus.forEach(menu -> {
if (StrUtil.isNotBlank(menu.getPermission())) {
permissions.add(menu.getPermission());
}
});
return permissions;
}
@Override
@Cacheable(value = RedisKeyConstants.MENU_ROLE_ID_LIST, key = "#menuId")
public Set<Long> getMenuRoleIdListByMenuIdFromCache(Long menuId) {
@@ -357,6 +388,18 @@ public class PermissionServiceImpl implements PermissionService {
return role;
}
/**
* scoped 权限查询只消费当前仍然有效的对象角色;缺失、作用域不匹配或已禁用时都返回 null
* 由上层统一收口为空菜单/空权限结果。
*/
private RoleDO getEnabledScopedRole(Long roleId, String scopeType, String objectType) {
RoleDO role = roleService.getRole(roleId, scopeType, objectType);
if (role == null || !CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus())) {
return null;
}
return role;
}
private PermissionServiceImpl getSelf() {
return SpringUtil.getBean(getClass());
}

View File

@@ -54,6 +54,10 @@ public interface RoleService {
*/
RoleDO getRole(Long id);
RoleDO getRole(Long roleId, String scopeType, String objectType);
RoleDO getRoleByCode(String code, String scopeType, String objectType);
/**
* 获得角色,从缓存中
*

View File

@@ -208,6 +208,17 @@ public class RoleServiceImpl implements RoleService {
return roleMapper.selectById(id);
}
@Override
public RoleDO getRole(Long roleId, String scopeType, String objectType) {
RoleDO role = roleMapper.selectById(roleId);
return matchesScope(role, scopeType, objectType) ? role : null;
}
@Override
public RoleDO getRoleByCode(String code, String scopeType, String objectType) {
return roleMapper.selectByCode(code, scopeType, objectType);
}
@Override
@Cacheable(value = RedisKeyConstants.ROLE, key = "#id", unless = "#result == null")
public RoleDO getRoleFromCache(Long id) {