Appearance
以下是针对动态菜单权限控制的详细实施方案,包含AWS控制台操作步骤、DynamoDB表设计、前后端代码改动点以及资源初始化脚本。本方案完全替换原有菜单级权限设计,改用统一资源表模型,支持模型驱动的动态UI渲染。
1. 架构概览
核心组件
| 组件 | 用途 |
|---|---|
resources表 | 存储所有UI资源(菜单/模型区域/按钮),含动态显示规则和前端配置 |
role_resources表 | 定义角色可访问的资源ID列表及模型绑定关系 |
model_states表 | 记录数字孪生模型实时状态(如加载进度),用于条件渲染 |
tenants | 记录租户信息 |
| Cognito自定义属性 | 存储用户角色(custom:role)和当前激活模型(custom:active_model) |
| Lambda授权层 | 动态过滤资源列表,结合模型状态和角色权限返回前端 |
数据流
2. AWS控制台操作步骤
1. DynamoDB表结构调整(关键变更)
| 表名 | 主键设计 | 新增多租户字段 | 索引配置(GSI) |
|---|---|---|---|
resources | 分区键: tenant_id#resource_id | 原字段保留 | GSI1: tenant_id(投影所有属性) |
role_resources | 分区键: tenant_id#role_id | 移除model_bindings | GSI1: tenant_id(投影resource_ids) |
model_states | 分区键: tenant_id#model_id | 新增tenant_owner | 无需新增索引 |
tenants | 分区键: tenant_id | name, status, quota | GSI1: status(用于筛选活跃租户) |
设计说明:
- 复合主键:所有表的主键均包含
tenant_id,通过#符号与业务ID拼接,实现天然隔离。例如查询租户A的资源:tenant_id = 'tenantA#resource1' - 全局二级索引(GSI):所有表均创建以
tenant_id为分区键的GSI,支持跨租户管理操作(如管理员批量查询)(https://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/data-modeling-foundations.html)
二、AWS控制台详细操作步骤
1. DynamoDB表创建(分步截图级指引)
步骤1:创建resources表
- 进入DynamoDB控制台 → 创建表
- 基础配置:
- 表名:
resources - 分区键:
tenant_id#resource_id(类型:String) - 排序键:留空
- 设置:选择 自定义设置
- 表名:
- 添加GSI:
- 索引名称:
tenant_id-index - 分区键:
tenant_id(类型:String) - 投影属性:选择 全部
- 索引名称:
- 容量模式:按需模式(适合初期测试)
- 加密:启用AWS托管密钥(KMS)
步骤2:创建role_resources表
- 同上进入创建表界面
- 配置:
- 表名:
role_resources - 分区键:
tenant_id#role_id - 排序键:留空
- 表名:
- GSI配置(同
resources表)
步骤3:创建tenants表(新增)
- 表名:
tenants - 分区键:
tenant_id(类型:String) - GSI配置:
- 索引名称:
status-index - 分区键:
status(类型:String) - 排序键:
tenant_id(类型:String)
- 索引名称:
(2) Cognito配置
- 添加自定义属性:
- 用户池 → 属性 → 添加
custom:role和custom:active_model,新增custom:tenant_id(不可变属性) - 设置
custom:role为必需属性,custom:active_model为可选属性
- 用户池 → 属性 → 添加
- 应用客户端设置:
- 启用
ALLOW_USER_PASSWORD_AUTH和ALLOW_REFRESH_TOKEN_AUTH流程 - 回调URL设置为前端域名(如
https://your-app.com/callback)
- 启用
(3) Lambda部署
- 创建函数
resource-api:- 运行时:Python 3.12
- 执行角色:自定义角色(需包含
dynamodb:Query、dynamodb:BatchGetItem权限) - 环境变量:
RESOURCES_TABLE=resources,ROLE_RESOURCES_TABLE=role_resources
- 代码部署:python
# resource-api/lambda_function.py import os import json from boto3 import client('dynamodb') def lambda_handler(event, context): role = event['requestContext']['authorizer']['jwt']['claims']['custom:role'] model = event.get('queryStringParameters', {}).get('model') # 获取角色权限资源 role_resources = dynamodb.get_item( TableName=os.environ['ROLE_RESOURCES_TABLE'], Key={'role_id': {'S': role}} ).get('Item', {}).get('resource_ids', []) # 批量查询资源详情 resources = dynamodb.batch_get_item( RequestItems={ os.environ['RESOURCES_TABLE']: { 'Keys': [{'resource_id': {'S': id}} for id in role_resources] } } )['Responses'][os.environ['RESOURCES_TABLE']] # 过滤模型相关资源 if model: resources = [r for r in resources if model in r.get('model_bindings', [])] return {'statusCode': 200, 'body': json.dumps(resources)}
2. 多层级权限业务下的resource表结构设计
根据租户>用户>项目>建筑物>区域的层级关系,结合非建筑物资源(如全局配置页、报表等),建议采用以下结构:
核心字段设计
| 字段名 | 类型 | 说明 |
|---|---|---|
tenant_id#resource_id | String | 复合主键,前缀为租户ID(如tenantA#building1:floor2) |
type | String | 资源类型(如menu、button、building、region、global_config) |
hierarchy_path | String | 层级路径(如tenantA/project1/building1/region2),支持模糊查询 |
model_bindings | List | 绑定的模型ID列表(如["modelA"],非建筑物资源可留空) |
ui_config | Map | 前端渲染配置(如图标、样式) |
visibility_rule | Map | 条件规则(如{"requires": ["model.loaded"]}) |
分区键设计示例
- 建筑物相关资源:
tenant_id#project_id:building_id:region_id
(如tenantA#proj1:bld1:floor2) - 非建筑物资源:
tenant_id#global:resource_type
(如tenantA#global:report_dashboard)
查询优化建议
- GSI:为
hierarchy_path和type分别创建GSI,支持按路径前缀或类型快速过滤(如查询租户A下所有建筑物资源)。 - 稀疏索引:对
model_bindings字段创建GSI,仅投影非空项,优化模型相关查询性能。
权限关联逻辑
- 角色权限表(
role_resources):存储角色可访问的resource_id列表,通过hierarchy_path实现继承(如总部角色可访问tenantA/project1/*)。 - 动态过滤:前端请求资源时,Lambda根据用户角色和当前项目/建筑物上下文,联合查询
role_resources和resources表,返回匹配项。
关键实现要点
- 多租户隔离:所有表的主键均包含
tenant_id,通过#拼接业务ID(如tenantA#resource1)。 - 层级查询:利用
hierarchy_path的路径格式(如/分隔),结合DynamoDB的begins_with运算符实现高效层级遍历。 - 混合资源存储:通过
type字段区分建筑物与非建筑物资源,确保权限逻辑一致但存储结构灵活。
- 后续为代码层面改动设计
3. 前后端代码改动
(1) 前端(Vue3)动态渲染
- 资源加载逻辑(通常在布局组件中):javascript
// src/layouts/MainLayout.vue import { ref, watch } from 'vue' import { Auth } from 'aws-amplify' export default { setup() { const resources = ref([]) const activeModel = ref(null) // 从JWT解析角色和模型 const loadAuth = async () => { const user = await Auth.currentAuthenticatedUser() activeModel.value = user.attributes['custom:active_model'] fetchResources(user.attributes['custom:role']) } // 获取可访问资源 const fetchResources = async (role) => { const res = await fetch(`/resources?model=${activeModel.value}`) resources.value = await res.json() } return { resources, activeModel } } } - 条件渲染组件:vue
<!-- src/components/DynamicUI.vue --> <template> <div v-for="res in filteredResources" :key="res.resource_id"> <component :is="getComponent(res.type)" :config="res.ui_config"/> </div> </template> <script> import MenuItem from './MenuItem.vue' import ModelRegion from './ModelRegion.vue' export default { props: ['resources', 'modelState'], computed: { filteredResources() { return this.resources.filter(res => { if (!res.visibility_rule) return true return res.visibility_rule.requires.every( cond => this.modelState[cond] ) }) } }, methods: { getComponent(type) { return { menu: MenuItem, region: ModelRegion }[type] } } } </script>
(2) 后端(Python)增强
- 模型状态更新接口:python
# digital_twin_backend/lambda_function.py def update_model_state(event): model_id = event['pathParameters']['model_id'] dynamodb.update_item( TableName='model_states', Key={'model_id': {'S': model_id}}, UpdateExpression='SET is_loaded = :val', ExpressionAttributeValues={':val': {'BOOL': True}} ) return {'statusCode': 200}
4. 数据初始化脚本
(1) DynamoDB初始数据
python
# scripts/init_resources.py
import boto3
dynamodb = boto3.client('dynamodb')
# 创建资源记录
resources = [
{
'resource_id': {'S': 'modelA:toolbar'},
'type': {'S': 'button'},
'ui_config': {'M': {'icon': {'S': 'save'}}},
'model_bindings': {'L': [{'S': 'modelA'}]},
'visibility_rule': {'M': {'requires': {'L': [{'S': 'model.loaded'}]}}}
}
]
for item in resources:
dynamodb.put_item(TableName='resources', Item=item)
# 创建角色权限映射
role_resources = {
'role_id': {'S': 'admin'},
'resource_ids': {'L': [{'S': 'modelA:toolbar'}]},
'model_bindings': {'M': {'modelA': {'L': [{'S': 'toolbar'}]}}}
}
dynamodb.put_item(TableName='role_resources', Item=role_resources)(2) Cognito用户初始化
python
# scripts/create_user.py
import boto3
cognito = boto3.client('cognito-idp')
response = cognito.admin_create_user(
UserPoolId='your-user-pool-id',
Username='admin',
UserAttributes=[
{'Name': 'custom:role', 'Value': 'admin'},
{'Name': 'custom:active_model', 'Value': 'modelA'}
]
)5. 权限与监控
IAM角色策略
json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:Query",
"dynamodb:BatchGetItem",
"dynamodb:UpdateItem"
],
"Resource": [
"arn:aws:dynamodb:*:*:table/resources",
"arn:aws:dynamodb:*:*:table/role_resources"
]
}
]
}