主数据管理平台 主数据管理平台
产品介绍
产品安装指南
快速入门手册
用户操作手册
接口文档
开发文档
  • 数据授权
  • 给角色授予某个数据模型权限
  • 行列权限
  • 菜单功能权限
  • 子模型权限
  • 分类模型权限
  • 撤销授权
  • 平台角色管理
  • 其他

# 数据授权

把各个数据模型权限(包括数据模型行列权限,菜单权限,页面功能操作权限等)授予AFCenter平台角色(编码以mdm-开头且名称以MDM开头的平台角色)。通过给用户授予这些AFCenter平台角色,使其获得各个数据模型运行期菜单和业务数据访问权限。

image-20250725164514455

应当合理管理MDM数据模型业务角色,避免用户继承大量业务角色,个别情况下可能影响数据检索性能(所有角色对应同一模型都有行权限约束情况下,数据检索前会合并行权限,这会导致增加SQL过滤复杂性)。

访问路径【主数据管理门户】->【授权管理】->【数据授权】,进入角色数据模型授权列表页面。

image-20250725165126560

# 给角色授予某个数据模型权限

新增或编辑(检索后找到对应的权限)打开数据模型权限(包括行列权限,菜单权限,页面功能按钮权限等)配置对话框进行配置。

# 行列权限

列权限必须配置,行权限可选(约束可以访问的数据范围,e.g. 限制只能访问一年级2班的学生 class_no = '250102')

image-20250725165517226

# 菜单功能权限

菜单权限:业务门户二级菜单,选择该模型需要授予角色的菜单项。如果不授予任何菜单权限,则该模型不会挂载到业务门户上。

功能权限:影响业务门户二级菜单下页面功能按钮和后端功能API,按需授予;

低开扩展:允许在开发中心开发自定义表单挂载到业务门户二级菜单上,对需要的角色授予即可。

image-20250725170536772

此角色最终授予用户后,在业务门户中的显示效果如下图所示:

image-20250728101830400

# 子模型权限

子模型(内部从模型,One2Many)只有行列权限,配置方式与主模型一样。

image-20250728101956229

# 分类模型权限

分类模型(开启继承模式情况下)可以授予根模型通用字段的读权限(主要是为了支持大类检索能力,即跨模型检索,e.g. 检索各种类别的生产物料),此外还可以配置菜单权限以挂载到业务门户上(与普通模型不同的是,其二级菜单内容部分都会自带一分类树);而非继承模式情况下,则只需要配置菜单权限即可。

image-20250728100638509

image-20250728100659591

分类模型-根配置完成后,点击提交按钮保存(会关闭配置窗口),如果还需要继续配置子模型,则直接点击左侧树上的叶子节点模型即可触发保存提示。

一般来说,子模型(这里指的是分类模型的叶子模型,即具体业务模型,e.g. 生产物料模型中的螺丝螺母模型)是按需配置的,子模型的权限配置与普通模型一样,只是子模型的菜单权限是可以不配置的,不需要把子模型单独发布成业务门户的菜单的情况下就不用配置子模型的菜单权限(统一通过其根模型进行数据维护等操作)但功能权限还是有必要配置的(除非此角色访问该模型数据权限设置为只读模式)。

image-20250728101351409

# 撤销授权

从授权列表中检索到指定角色和模型的权限记录,点击授权角色右侧的【取消授权】链接,弹出取消权限确认框,确认后即可取消授权。

# 平台角色管理

对于用户授权和增加AFCenter平台角色(遵循命名规则:编码以mdm-开头且名称以MDM开头)请切换到应用中心->权限管理进行配置。此处不再赘述。

image-20250725171959671

# 其他

字段授权信息采用字节码的编码形式进行存储(每个字段占用一个字节1B,目前只用了其中3位,剩余5位可用于未来功能扩展),采用这个方案的好处是占用存储空间极小,且多路权限继承时需要进行权限合并,可以采用二进制数学运算进行字段权限合并————高效且运算过程占用内存空间极小。如果遇到授权信息不准确,可以尝试使用如下办法对数据库表MDM_DATA_MODEL_AUTH中字段授权信息进行解码操作(javac Test.java && java Test "${COPY_YOUR_FIELD_AUTH_STRING_TO_HERE}"),作为定位问题的参考依据。

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.util.stream.Collectors;
import java.util.zip.GZIPInputStream;
 
public class Test {
    public static void main(String[] args) {
        byte[] bytes = Base64.getDecoder().decode(args.length == 0 ? "H4sIAAAAAAAAAGNnRwIAATzjgA0AAAA=" : args[0]);
        bytes = unzip(bytes);
        for (int i = bytes.length - 1, j = 1; i >= 0; i--, j++) {
            // @see 字段发布后会获得一个终身不变的数字序号UID,这个序号就决定了其在字段授权完整字节码中的位置
            // @see 数据模型模块配置信息表MDM_MODULE_CONFIGURATION#CONFIGURATION (MODULE=2&VERSION=${MAX})
            System.out.print( "[字段" + j + "]  " + Integer.toBinaryString(Byte.toUnsignedInt(bytes[i])) + "   =>  " + FieldPermission.decode(bytes[i]) + "\n");
        }
        BigInteger bigInteger = new BigInteger(bytes);  // bigInteger.testBit(n) 用这个方式解析权限也可以
        System.out.println(bigInteger.toString(2));
    }
 
 
    public static byte[] unzip(byte[] data) {
        try (ByteArrayInputStream input = new ByteArrayInputStream(data);
             GZIPInputStream gzip = new GZIPInputStream(input);
             ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            byte[] buf = new byte[1024];
            int num;
            while ((num = gzip.read(buf, 0, buf.length)) != -1) {
                out.write(buf, 0, num);
            }
            out.flush();
            return out.toByteArray();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
 
 
    /**
     * byte[] 每个字段的各种权限合使用一个字节进行存储(每个字节有八个位置1byte=8bit,足够存储一个字段的所有权限),所有字段按顺序组成字节码进行存储。
     * 经典用法场景:Unix文件系统权限。
     */
    public enum FieldPermission {
 
        READABLE, WRITEABLE, SEARCHABLE;
 
        private final int value;
 
        FieldPermission() {
            this.value = 1 << ordinal();
        }
 
        public int value() {
            return value;
        }
 
        /**
         * 目前暂定value的值限制在一个字节内。即枚举值格式不超过8个
         */
        public boolean included(int value) {
            return (this.value & value) != 0;
        }
 
        public static boolean include(int value, FieldPermission permission) {
            return permission.included(value);
        }
 
        public static int encode(List<FieldPermission> permissions) {
            if (null == permissions || permissions.isEmpty()) {
                return 0;
            }
            int value = 0;
            for (FieldPermission permission : permissions) {
                value = value | permission.value(); // 不重复的情况下也可以使用加法运算
            }
            return value;
        }
 
        public static int encode(FieldPermission... permissions) {
            if (null == permissions || permissions.length == 0) {
                return 0;
            }
            return encode(Arrays.asList(permissions));
        }
 
        public static List<FieldPermission> decode(int value) {
            return Arrays.stream(values()).filter(e -> e.included(value)).collect(Collectors.toList());
        }
 
    }
 
}

数据推送配置和客户端授权配置都采用了这个字段授权信息存储方式,遇到授权问题需要辅助信息定位时都可以采用这个办法还原当前存储的字段权限明细。

← 服务授权 授权查询 →