Skip to content

以下是针对动态菜单权限控制的详细实施方案,包含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_bindingsGSI1: tenant_id(投影resource_ids
model_states分区键: tenant_id#model_id新增tenant_owner无需新增索引
tenants分区键: tenant_idname, status, quotaGSI1: status(用于筛选活跃租户)

设计说明

  1. 复合主键:所有表的主键均包含tenant_id,通过#符号与业务ID拼接,实现天然隔离。例如查询租户A的资源:tenant_id = 'tenantA#resource1'
  2. 全局二级索引(GSI):所有表均创建以tenant_id为分区键的GSI,支持跨租户管理操作(如管理员批量查询)(https://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/data-modeling-foundations.html)

二、AWS控制台详细操作步骤

1. DynamoDB表创建(分步截图级指引)

步骤1:创建resources

  1. 进入DynamoDB控制台 → 创建表
  2. 基础配置:
    • 表名resources
    • 分区键tenant_id#resource_id(类型:String)
    • 排序键:留空
    • 设置:选择 自定义设置
  3. 添加GSI:
    • 索引名称tenant_id-index
    • 分区键tenant_id(类型:String)
    • 投影属性:选择 全部
  4. 容量模式:按需模式(适合初期测试)
  5. 加密:启用AWS托管密钥(KMS)

步骤2:创建role_resources

  1. 同上进入创建表界面
  2. 配置:
    • 表名role_resources
    • 分区键tenant_id#role_id
    • 排序键:留空
  3. GSI配置(同resources表)

步骤3:创建tenants表(新增)

  1. 表名tenants
  2. 分区键tenant_id(类型:String)
  3. GSI配置:
    • 索引名称status-index
    • 分区键status(类型:String)
    • 排序键tenant_id(类型:String)

(2) Cognito配置

  1. 添加自定义属性
    • 用户池 → 属性 → 添加custom:rolecustom:active_model,新增custom:tenant_id不可变属性
    • 设置custom:role为必需属性,custom:active_model为可选属性
  2. 应用客户端设置
    • 启用ALLOW_USER_PASSWORD_AUTHALLOW_REFRESH_TOKEN_AUTH流程
    • 回调URL设置为前端域名(如https://your-app.com/callback

(3) Lambda部署

  1. 创建函数resource-api
    • 运行时:Python 3.12
    • 执行角色:自定义角色(需包含dynamodb:Querydynamodb:BatchGetItem权限)
    • 环境变量:RESOURCES_TABLE=resources, ROLE_RESOURCES_TABLE=role_resources
  2. 代码部署
    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_idString复合主键,前缀为租户ID(如tenantA#building1:floor2
typeString资源类型(如menubuttonbuildingregionglobal_config
hierarchy_pathString层级路径(如tenantA/project1/building1/region2),支持模糊查询
model_bindingsList绑定的模型ID列表(如["modelA"],非建筑物资源可留空)
ui_configMap前端渲染配置(如图标、样式)
visibility_ruleMap条件规则(如{"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_pathtype分别创建GSI,支持按路径前缀或类型快速过滤(如查询租户A下所有建筑物资源)。
  • 稀疏索引:对model_bindings字段创建GSI,仅投影非空项,优化模型相关查询性能。

权限关联逻辑

  • 角色权限表(role_resources:存储角色可访问的resource_id列表,通过hierarchy_path实现继承(如总部角色可访问tenantA/project1/*)。
  • 动态过滤:前端请求资源时,Lambda根据用户角色和当前项目/建筑物上下文,联合查询role_resourcesresources表,返回匹配项。

关键实现要点

  1. 多租户隔离:所有表的主键均包含tenant_id,通过#拼接业务ID(如tenantA#resource1)。
  2. 层级查询:利用hierarchy_path的路径格式(如/分隔),结合DynamoDB的begins_with运算符实现高效层级遍历。
  3. 混合资源存储:通过type字段区分建筑物与非建筑物资源,确保权限逻辑一致但存储结构灵活。

  • 后续为代码层面改动设计

3. 前后端代码改动

(1) 前端(Vue3)动态渲染

  1. 资源加载逻辑(通常在布局组件中):
    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 }
      }
    }
  2. 条件渲染组件
    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)增强

  1. 模型状态更新接口
    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"
      ]
    }
  ]
}