目录
一、需求
二、逻辑
三、实现
(一)代码
(二)解释
1. 获取权限对照数组 (queryReferencePermissionsInfo)
2. 获取处理对照数组 (queryDisposePermissionsInfo)
3. 获取权限映射表信息并处理 (queryPermissionsInfo)
4. 处理权限信息并获取指定模块和目标字典信息 (handlePermissionsInfo)
5. 获取相应app的权限信息 (queryAppPermissionsInfo)
6. 递归获取权限数组 (renderTreeNodes)
7. 处理权限信息并获取指定模块及目标权限的具体信息 (queryPermissionsChildren)
四、使用
五、总结
一、需求
今年的4月份,我们的产品被甲方采购,并根据甲方需求更改了一下权限系统,由于原来的老旧权限系统过于臃肿且不够灵活,最重要的是没有注释看着很头大,于是我决定将权限系统重构。
二、逻辑
其实逻辑很简单,就是将获取的权限数据进行处理然后通过传入不同的参数返回不同的处理后的数据。再用这些返回的数据进行页面上的权限控制。
三、实现
(一)代码
import globalUtil from '../utils/global';
import { formatMessage, FormattedMessage } from 'umi-plugin-locale';
import Exception from '../components/Exception'const actionMaps = {admin: '管理员',developer: '开发者',viewer: '观察者',access: '访问者',owner: '拥有者'
};
const En_actionMaps = {admin: 'Administrators',developer: 'Developer',viewer: 'Observer',access: 'Visitor',owner: 'Owner'
};
const roleMaps = {admin: formatMessage({ id: 'utils.role.roleMaps_admin' }),app_store: formatMessage({ id: 'utils.role.roleMaps_app_store' })
};
const AccessText = {team_overview: '应用管理',team_app_create: '新建应用',team_app_manage: '应用控制',team_gateway_manage: '网关管理',team_plugin_manage: '插件管理',team_manage: '团队管理',app_overview: '应用总览',app_release: '应用发布',app_gateway_manage: '应用网关',app_upgrade: '应用升级',app_resources: '应用资源',app_config_group: '应用配置',route_manage: '路由管理',target_services: '目标服务',certificate: '证书管理',team_member: '成员管理',team_region: '集群管理',team_role: '角色管理',team_registry_auth: '镜像仓库授权管理',app_gateway_monitor: '网关监控',app_route_manage: '路由管理',app_target_services: '目标服务',app_certificate: '证书管理',team_gateway_monitor: '网关监控',team_route_manage: '路由管理',team_target_services: '目标服务',team_certificate: '证书管理',team_dynamic: '动态',team_overview_perms: '团队信息',app_backup: '应用备份',listed_manage: '上架管理',application_records: '申请记录',team_library_manage: '组件库',
};
const En_AccessText = {team_overview: 'Team overview',team_app_create: 'Create application',team_app_manage: 'Application management',team_gateway_manage: 'Gateway management',team_plugin_manage: 'Plug-in management',team_manage: 'Team management',app_overview: 'Application overview',app_release: 'Application publishing',app_gateway_manage: 'Application gateway',app_upgrade: 'Application upgrade',app_resources: 'Application resource',app_config_group: 'Application management',route_manage: 'Route management',target_services: 'Target service',certificate: 'Certificate management',team_member: 'Member management',team_region: 'Cluster management',team_role: 'Role management',team_registry_auth: 'Image warehouse authorization management',app_gateway_monitor: 'Gateway monitoring',app_route_manage: 'Route management',app_target_services: 'Target service',app_certificate: 'Certificate management',team_gateway_monitor: 'Gateway monitoring',team_route_manage: 'Route management',team_target_services: 'Target service',team_certificate: 'Certificate management',team_dynamic: 'Dynamic',team_overview_perms: 'Team information',app_backup: 'Application backup',listed_manage: 'Listed management',application_records: 'Application records',team_library_manage: 'Component library',
};// 定义菜单与权限属性的映射结构
const teamMenuPermissionsMap = {team_overview_perms: 'isTeamOverview',//团队总览app_create: 'isTeamAppCreate',//新建应用team_gateway_monitor: 'isTeamGatewayMonitor',//网关监控team_route_manage: 'isTeamRouteManage',//路由管理team_target_services: 'isTeamTargetServices',//目标服务team_certificate: 'isTeamCertificate',//证书管理team_dynamic: 'isTeamDynamic',//动态team_region: 'isTeamRegion',//集群管理team_role: 'isTeamRole',//角色管理team_registry_auth: 'isTeamRegistryAuth',//镜像仓库授权管理team_plugin_manage: 'isTeamPluginManage',//插件管理application_records: 'isAudit',//申请记录listed_manage: 'isTeamPutaway',//上架管理team_library_manage: 'isTeamLibraryManage',//组件库
};const appMenuPermissionsMap = {app_overview: 'isAppOverview', //应用总览app_release: 'isAppRelease',//应用发布app_upgrade: 'isAppUpgrade',//应用升级app_gateway_monitor: 'isAppGatewayMonitor',//网关监控app_route_manage: 'isAppRouteManage',//路由管理app_target_services: 'isAppTargetServices',//目标服务app_certificate: 'isAppCertificate',//证书管理app_resources: 'isAppResources',//应用资源app_config_group: 'isAppConfigGroup',//应用管理app_backup: 'isAppBackup',//应用备份
};
export default {// 身份actionMap(name, bool) {var keys = ''Object.keys(actionMaps).map(item => {if (actionMaps[item] == name) {return keys = item}})if (bool) {return actionMaps[keys] || name;} else {return En_actionMaps[keys] || name;}},// 角色roleMap(name) {return roleMaps[name] || name;},// 权限翻译fetchAccessText(text, bool) {if (bool) {return AccessText[text] || text;} else {return En_AccessText[text] || text;}},// 获取权限对照数组queryReferencePermissionsInfo(type) {const defaultTargetArr = ['describe','create','edit','delete','install','uninstall'];const defaultTargetOperationArr = ['start', 'stop', 'update', 'construct', 'copy', 'restart'];switch (type) {// 团队级别权限特殊处理case 'team_overview_perms':return ['describe', 'resource_limit', 'app_list']break;case 'team_app_create':case 'team_gateway_monitor':case 'app_gateway_monitor':case 'team_dynamic':return ['describe']break;// 应用级别特殊权限case 'app_release':return ['share', 'export', 'delete', 'describe']break;case 'app_upgrade':return ['app_model_list', 'upgrade_record', 'upgrade', 'rollback']break;// 组件与应用级别特殊权限case 'app_overview':return [...defaultTargetArr,...defaultTargetOperationArr,...['visit_web_terminal','service_monitor','telescopic','env','rely','storage','port','plugin','source','safety','other_setting']];break;case 'app_backup':return ['describe','add','import','recover','move','expor','delete']break;default:return defaultTargetArr;break;}},// 获取处理对照数组queryDisposePermissionsInfo(type) {const defaultArr = ['isAccess','isCreate','isEdit','isDelete','isInstall','isUninstall'];const defaultOperationArr = ['isStart', 'isStop', 'isUpdate', 'isConstruct', 'isCopy', 'isRestart' ];switch (type) {// 团队级别权限特殊处理case 'team_overview_perms':return ['isAccess', 'isResourceLimit', 'isAppList']break;case 'team_app_create':case 'team_gateway_monitor':case 'app_gateway_monitor':case 'team_dynamic':return ['isAccess']break;// 应用级别特殊权限case 'app_release':return ['isShare', 'isExport', 'isDelete', 'isAccess']break;case 'app_upgrade':return ['isAppModelList', 'isUpgradeRecord', 'isUpgrade', 'isRollback']break;// 组件与应用级别特殊权限case 'app_overview':return [...defaultArr,...defaultOperationArr,...['isVisitWebTerminal','isServiceMonitor','isTelescopic','isEnv','isRely','isStorage','isPort','isPlugin','isSource','isSafety','isOtherSetting',]];break;case 'app_backup':return ['isAccess','isAddBackup','isImportBackup','isRecoverBackup','isMoveBackup','isExportBackup','isDeleteBackup']break;default:return defaultArr;break;}},/*** 获取权限映射表信息并进行处理** @param {Object} data - 权限数据对象,包含团队或应用级别的权限信息* @param {string} module - 需要处理的目标模块名称* @param {string=} appid - (可选)应用ID,若提供,则查找对应应用的权限信息,否则查找团队级别的权限信息** @returns {Object} - 返回一个对象,键为处理后的权限标识,值为对应的权限字典信息* 此函数首先调用queryDisposePermissionsInfo方法获取处理权限的动作列表(disposeActions),* 然后调用queryReferencePermissionsInfo方法获取权限对照数组(referencePermissions)。* 接着,遍历处理权限的动作列表,对每个动作调用handlePermissionsInfo方法,根据提供的module和appid获取对应权限字典信息,并将结果存入一个新的对象(permissionsObj)中,其中对象的键为处理权限的动作,值为该动作在目标模块下的权限字典详情。*/queryPermissionsInfo(data, module, appid = '') {const disposeActions = this.queryDisposePermissionsInfo(module);const referencePermissions = this.queryReferencePermissionsInfo(module);const permissionsObj = {};disposeActions.forEach((item, index) => {permissionsObj[item] = this.handlePermissionsInfo(data, referencePermissions[index], module, appid);});return permissionsObj;},/*** 处理权限信息并获取指定模块和目标字典信息** @param {Object} data - 权限数据对象,包含团队或应用级别的权限信息* @param {string} target - 需要查找的目标权限字典名称* @param {string} module - 需要查找的目标模块名称* @param {string=} appid - (可选)应用ID,若提供,则查找对应应用的权限信息,否则查找团队级别的权限信息** @returns {Object|boolean} - 返回查找到的第一个匹配的权限字典信息,若未找到则返回false** 此函数根据提供的appid参数决定查找团队还是应用级别的权限信息。* 如果appid存在,则先通过queryAppPermissionsInfo函数获取应用权限信息,再利用renderTreeNodes递归获取目标模块的权限信息,* 最后通过queryPermissionsChildren获取指定模块和目标字典的具体信息,并存入results数组。* 若appid不存在,则直接对团队权限数据进行类似操作。* 结束处理后,返回results数组中第一个(或唯一)找到的权限字典信息,若未找到任何匹配项,则返回false。*/handlePermissionsInfo(data, target, module, appid = '') {const results = [];if (appid) {if (data && target && module) {const appInfo = this.queryAppPermissionsInfo(data, appid)const newAppInfo = this.renderTreeNodes(appInfo, module)this.queryPermissionsChildren(newAppInfo, module, target, results);}} else {if (data && target && module) {const teamInfo = this.renderTreeNodes(data, module)this.queryPermissionsChildren(teamInfo, module, target, results);}}return results.length > 0 ? results[0] : false;},/*** 获取相应app的权限信息** @param {Object} data - 整体权限数据对象,通常包含一系列子模型* @param {string} appid - 需要查找的目标应用ID** @returns {Object} - 返回与目标应用ID相对应的应用权限信息对象,若未找到则返回一个空对象 {}** 此函数首先在整体权限数据对象的子模型中查找名为'team_app_manage'的部分,* 找到后,在'team_app_manage'的子模型中搜索与给定appid相匹配的应用权限信息,* 若找到匹配项,则返回该应用的权限信息对象;否则返回一个空对象。*/queryAppPermissionsInfo(data, appid) {const appManagement = this.renderTreeNodes(data, 'team_app_manage');if (appManagement) {const appSubModels = appManagement.find((item) => Object.keys(item)[0] === 'team_app_manage').team_app_manage.sub_models || [];const targetApp = appSubModels.find((app) => Object.keys(app)[0] === appid);return targetApp ? targetApp[appid] : {};} else {return {};}},/*** 递归获取权限数组** @param {Object[]} data - 权限数据结构,通常为一棵包含子模型(sub_models)的树形结构* @param {string} moduleName - 需要查找的目标权限模块名称** @returns {Object[]|null} 返回与指定模块名称相符的权限数组,如果未找到,则返回null** 此函数遍历给定的数据结构,逐层深入子模型直至找到与 moduleName 匹配的权限模块。* 当找到匹配项时,返回该模块所在层级的权限数组。若递归遍历完整棵树仍未找到匹配项,则返回null。*/renderTreeNodes(data, moduleName) {const permissionsArr = data.sub_models || [];// 遍历当前层级的所有子模型for (let i = 0; i < permissionsArr.length; i++) {const item = permissionsArr[i];const keys = Object.keys(item)[0];// 如果当前节点的主键名称与目标模块名称相匹配,则直接返回当前层级的权限数组if (keys === moduleName) {return permissionsArr;}// 如果当前节点还包含子模型,并且子模型数量大于0,则递归查找if (item[keys] && item[keys].sub_models && item[keys].sub_models.length > 0) {const foundNode = this.renderTreeNodes(item[keys], moduleName);// 如果递归过程中找到了匹配的权限模块,则返回该模块的权限数组if (foundNode) {return foundNode;}}}// 如果遍历完成仍没找到匹配的权限模块,则返回nullreturn null;},/*** 处理权限信息并获取指定模块及目标权限的具体信息** @param {Array<Object>} data - 权限数据列表,每个元素为一个包含模块名称及权限详细信息的对象* @param {string} moduleName - 需要检索的目标模块名称* @param {string} targets - 需要在目标模块中检索的具体权限名称* @param {Array<*>} results - 用于存储匹配到的权限详细信息的数组,函数执行结束后会填充相关数据** @returns {void} 无返回值,而是通过修改传入的results参数来存储查找到的权限信息** 此函数遍历给定的权限数据列表,查找与moduleName相匹配的模块,并在其中寻找与targets相匹配的权限详细信息。* 找到匹配项时,将该权限的详细信息推入results数组中。*/queryPermissionsChildren(data, moduleName, targets, results) {return (data || []).map((item) => {const keys = Object.keys(item)[0];if (keys === moduleName) {item[keys].perms.map((item2) => {const name = Object.keys(item2)[0];if (targets === name) {results.push(item2[targets]);}});}});},/*** 通用权限获取方法** @param {Object} menuMap - 菜单与权限属性的映射结构,键为菜单类型,值为权限对象上对应的属性名* @param {Object} data - 权限数据对象,包含各种菜单类型的权限信息* @param {string} appid - (可选)应用ID,默认为空字符串。在某些情况下可能需要根据应用ID获取特定应用的权限信息* @param {Function} permissionCheckStrategy - (可选)权限检查策略函数,默认为检查`.isAccess`属性是否存在。* 该函数接收一个菜单权限对象作为参数,并返回一个布尔值表示用户是否有该菜单的权限。* 对于特殊情况(如'app_upgrade'),可以通过传入自定义策略函数进行处理。** @returns {Object} 返回一个对象,键为菜单类型对应的属性名,值为用户是否拥有该菜单的权限(布尔值)*/queryMenuPermissionsInfo(menuMap, data, appid = '') {const obj = {};for (const [menuKey, propertyKey] of Object.entries(menuMap)) {const menuPermissions = this.queryPermissionsInfo(data, menuKey, appid);if (menuKey == 'app_upgrade') {obj[propertyKey] = menuPermissions?.isAppModelList || menuPermissions?.isUpgradeRecord || false;} else {obj[propertyKey] = menuPermissions?.isAccess || false;}}return obj;},/*** 根据团队或应用类型调用通用方法获取菜单权限** @param {Object} data - 权限数据对象,包含各种菜单类型的权限信息* @param {'team'|'app'} type - 指定获取团队或应用的权限,取值只能为'team'或'app'* @param {string} appid - (可选)应用ID,默认为空字符串。当type为'app'时,可能需要根据应用ID获取特定应用的权限信息** @returns {Object} 返回一个对象,键为菜单类型对应的属性名,值为用户是否拥有该菜单的权限(布尔值)** 此方法根据传入的'type'参数自动选择合适的菜单权限映射表(teamMenuPermissionsMap或appMenuPermissionsMap),* 然后调用通用权限获取方法queryMenuPermissionsInfo,从而获取团队或应用的菜单权限信息。*/queryTeamOrAppPermissionsInfo(data, type, appid = '') {return this.queryMenuPermissionsInfo(type == 'team' ? teamMenuPermissionsMap : appMenuPermissionsMap, data, appid);},// 没有权限noPermission() {return <Exception type={403} style={{ minHeight: 600, height: '80%' }} actions />}
};
(二)解释
1. 获取权限对照数组 (queryReferencePermissionsInfo
)
此函数根据输入的type
参数,返回不同场景下的一组基础权限列表。这些列表定义了不同模块或操作下所涉及的基本权限集,如“描述”、“创建”、“编辑”等。对于特定类型的权限(如团队级别、应用级别等),有专门的处理逻辑,返回特定的权限集合。这个函数是权限对照的基础,定义了系统支持的各种权限类型。
2. 获取处理对照数组 (queryDisposePermissionsInfo
)
类似于queryReferencePermissionsInfo
,但这里的数组是针对权限操作的处理逻辑,如“是否允许访问”、“是否允许创建”等。每个操作对应一个布尔型的检查方法,用于实际权限控制逻辑中的判断依据。这个函数进一步细化了权限的实际运用方式。
3. 获取权限映射表信息并处理 (queryPermissionsInfo
)
这是整个权限处理流程的核心函数。它首先调用上述两个函数获取基础权限列表和处理逻辑列表,然后遍历处理逻辑列表,对每个逻辑调用handlePermissionsInfo
函数。handlePermissionsInfo
负责根据当前模块、目标权限字典以及是否针对特定应用来获取具体的权限字典信息。最终,queryPermissionsInfo
会返回一个对象,其键为处理逻辑名称,值为对应的权限字典信息,实现了权限到操作的映射。
4. 处理权限信息并获取指定模块和目标字典信息 (handlePermissionsInfo
)
该函数根据appid是否存在来区分处理团队或应用级别的权限。它首先调用queryAppPermissionsInfo
或直接从团队权限数据开始处理,然后通过递归函数renderTreeNodes
定位到目标模块,并通过queryPermissionsChildren
在该模块下查找特定的权限信息。最后,它返回查找到的第一个匹配的权限字典信息,或在找不到时返回false
。
5. 获取相应app的权限信息 (queryAppPermissionsInfo
)
此函数从整体权限数据中提取指定appid的应用权限信息。它首先找到“team_app_manage”部分,然后在其中查找与appid匹配的应用权限数据。
6. 递归获取权限数组 (renderTreeNodes
)
这个递归函数用于在树形结构的权限数据中查找指定模块的权限数组。它遍历每一层的子模型,直到找到匹配的模块名或遍历完整棵树。
7. 处理权限信息并获取指定模块及目标权限的具体信息 (queryPermissionsChildren
)
该函数遍历权限数据列表,定位到指定模块,并从中找出特定权限名称的详细信息,将其加入到结果数组中。
四、使用
import roleUtil from '../../utils/newRole';roleUtil.queryPermissionsInfo(this.props.currentTeamPermissionsInfo && this.props.currentTeamPermissionsInfo.team, 'team_app_create'),
这是我的权限树结构,传进去的参数一个是树结构,一个是节点名称,这样返回出来的就是该节点下的所有权限。因为我们这里做了动态权限,所以也有可能传入第三个参数,用于辨认是否是查询某个app的权限列表,这里我就不再赘述,具体可以看上面的代码实现逻辑。
五、总结
整个代码段构建了一套复杂的权限管理系统,能够根据不同的业务场景(如团队、应用级别等)动态地获取并处理权限信息。它通过分层次的函数调用,实现了权限数据的解析、筛选和具体化,以支持系统在运行时进行细粒度的权限控制。这套逻辑不仅灵活地适应了多种权限配置需求,而且保持了代码的模块化和可维护性。
以上只是个人的实现思路,针对业务的不同不能生搬硬套,具体详细的使用请参照开源项目
https://github.com/goodrain/rainbond-ui/tree/V6.0https://github.com/goodrain/rainbond-ui/tree/V6.0